The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

add ai gallery generation page

Luna f710a674 2550fcd1

+246 -8
+2 -4
app/ai-gallery/[uploadId]/side.component.tsx
··· 11 11 import ImageReduceMotion from "@/components/image-reduce-motion"; 12 12 import { formatDate } from "@/components/time"; 13 13 import { AnalyticsError, AnalyticsResponse } from "@/lib/analytics"; 14 - import { ApiError, ApiV1GuildsGetResponse } from "@/typings"; 14 + import { ApiError, ApiV1GuildsGetResponse, ApiV1UploadGetResponse } from "@/typings"; 15 15 import { truncate } from "@/utils/truncate"; 16 16 import { getCanonicalUrl } from "@/utils/urls"; 17 - 18 - import { ExtendedUpload } from "../api"; 19 17 20 18 export default function Side({ 21 19 upload, 22 20 guild, 23 21 analytics 24 22 }: { 25 - upload: ExtendedUpload | ApiError; 23 + upload: ApiV1UploadGetResponse | ApiError; 26 24 guild: ApiV1GuildsGetResponse | ApiError | undefined; 27 25 analytics: { results: AnalyticsResponse[] } | AnalyticsError | undefined; 28 26 }) {
+134
app/ai-gallery/generate/page.tsx
··· 1 + "use client"; 2 + 3 + import { Button, Image, Spinner } from "@nextui-org/react"; 4 + import NextImage from "next/image"; 5 + 6 + import DumbTextInput from "@/components/inputs/dumb-text-input"; 7 + import { useEffect, useState } from "react"; 8 + import { useRouter, useSearchParams } from "next/navigation"; 9 + import Notice from "@/components/notice"; 10 + import UploadButton from "./upload.component"; 11 + import { HiPrinter } from "react-icons/hi"; 12 + 13 + enum State { 14 + Idle = 0, 15 + Loading = 1 16 + } 17 + 18 + export default function Home() { 19 + const search = useSearchParams() 20 + const router = useRouter(); 21 + 22 + const imageUrl = search.get("image_url"); 23 + 24 + const [state, setState] = useState<State>(State.Idle); 25 + const [error, setError] = useState<string | null>(null); 26 + 27 + const [gpu, setGpu] = useState<string | null>(null); 28 + 29 + const [baseUrl, setBaseUrl] = useState("https://ai.local.wamellow.com"); 30 + const [model, setModel] = useState("animagine-xl-v3"); 31 + const [prompt, setPrompt] = useState(""); 32 + 33 + useEffect(() => { 34 + fetch(baseUrl) 35 + .then((res) => res.json() as Promise<{ gpu: string }>) 36 + .then((res) => { 37 + setError(null); 38 + setGpu(res.gpu || null) 39 + }) 40 + .catch((err) => { 41 + setError(`No gpu instance found at ${baseUrl}: ${err.toString()}`) 42 + }); 43 + }, [baseUrl]); 44 + 45 + async function generate() { 46 + setState(State.Loading); 47 + 48 + const reqparams = new URLSearchParams({ 49 + prompt: prompt, 50 + }); 51 + 52 + const res = await fetch(`${baseUrl}/generate/image/${model}?${reqparams.toString()}`) 53 + .then((res) => res.json()) as { url: string, duration: number }; 54 + 55 + const params = new URLSearchParams(); 56 + params.delete("image_url"); 57 + params.append("image_url", res.url); 58 + 59 + router.push(`?${params.toString()}`, { scroll: false }); 60 + 61 + setState(State.Idle); 62 + } 63 + 64 + return ( 65 + <div className="w-full"> 66 + 67 + {error && 68 + <Notice message={error} /> 69 + } 70 + 71 + <div className="flex flex-col-reverse md:flex-row"> 72 + 73 + <div className="md:w-2/3 w-full md:mr-6"> 74 + {imageUrl && state !== State.Loading ? 75 + <Image 76 + as={NextImage} 77 + className="rounded-lg" 78 + height={1024} 79 + isBlurred 80 + isZoomed 81 + src={imageUrl || ""} 82 + width={1024} 83 + onError={() => { 84 + const params = new URLSearchParams(); 85 + params.delete("image_url"); 86 + router.push(`?${params.toString()}`, { scroll: false }); 87 + }} 88 + /> 89 + : 90 + <div className="w-full aspect-square border border-violet-400 bg-wamellow flex items-center justify-center rounded-lg shadow-xl"> 91 + {state === State.Loading 92 + ? 93 + <div> 94 + <Spinner color="secondary" /> 95 + </div> 96 + : 97 + <span className="text-2xl font-medium text-neutral-200"> 98 + No image generated yet 99 + </span> 100 + } 101 + </div> 102 + } 103 + </div> 104 + 105 + <div className="md:w-1/3 mb-8 md:mt-0 flex flex-col"> 106 + <DumbTextInput name="Base URL" value={baseUrl} setValue={setBaseUrl} thin /> 107 + <DumbTextInput name="Model" value={model} setValue={setModel} thin /> 108 + <DumbTextInput name="Prompt" value={prompt} setValue={setPrompt} thin /> 109 + 110 + <Button 111 + className="mt-4" 112 + color="secondary" 113 + onClick={generate} 114 + startContent={<HiPrinter />} 115 + isLoading={state === State.Loading} 116 + isDisabled={!gpu || !prompt || !model} 117 + > 118 + Generate 119 + </Button> 120 + 121 + <span className="mt-2 font-medium"> 122 + {gpu || "unknown gpu"} 123 + </span> 124 + 125 + <UploadButton 126 + model={model} 127 + prompt={prompt} 128 + /> 129 + 130 + </div> 131 + </div> 132 + </div> 133 + ); 134 + }
+106
app/ai-gallery/generate/upload.component.tsx
··· 1 + "use client"; 2 + 3 + import { Button, Checkbox } from "@nextui-org/react"; 4 + import { useCookies } from "next-client-cookies"; 5 + 6 + import { useEffect, useState } from "react"; 7 + import { useSearchParams } from "next/navigation"; 8 + import { ApiV1UploadGetResponse } from "@/typings"; 9 + import { HiCloudUpload } from "react-icons/hi"; 10 + 11 + enum State { 12 + Idle = 0, 13 + Loading = 1, 14 + Success = 2 15 + } 16 + 17 + export default function UploadButton({ 18 + model, 19 + prompt 20 + }: { 21 + model: string; 22 + prompt: string; 23 + }) { 24 + const search = useSearchParams() 25 + const cookies = useCookies(); 26 + 27 + const imageUrl = search.get("image_url"); 28 + 29 + const [state, setState] = useState<State>(State.Idle); 30 + const [error, setError] = useState<string | null>(null); 31 + 32 + const [nsfw, setNsfw] = useState(false); 33 + 34 + useEffect(() => { 35 + setState(State.Idle); 36 + }, [imageUrl]); 37 + 38 + async function upload() { 39 + setState(State.Loading); 40 + 41 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/ai`, { 42 + method: "POST", 43 + credentials: "include", 44 + headers: { 45 + "Content-Type": "application/json" 46 + }, 47 + body: JSON.stringify({ 48 + url: imageUrl, 49 + model: model, 50 + prompt: prompt, 51 + nsfw: nsfw 52 + }) 53 + }) 54 + .then((res) => res.json()) as ApiV1UploadGetResponse; 55 + 56 + if ("message" in res) { 57 + setError(res.message as string); 58 + return; 59 + } 60 + 61 + setState(State.Success); 62 + } 63 + 64 + if (!cookies.get("hasSession") || !imageUrl) return <></>; 65 + 66 + return ( 67 + <div className="flex flex-col"> 68 + 69 + <div className="mt-4 flex items-center"> 70 + <Checkbox 71 + isSelected={nsfw} 72 + onValueChange={(now) => setNsfw(now)} 73 + color="secondary" 74 + /> 75 + <span className="font-medium">Is NSFW?</span> 76 + </div> 77 + 78 + <Button 79 + className="mt-4" 80 + color={ 81 + state === State.Success 82 + ? "success" 83 + : "secondary" 84 + } 85 + onClick={upload} 86 + startContent={<HiCloudUpload />} 87 + isLoading={state === State.Loading} 88 + isDisabled={state === State.Success} 89 + > 90 + { 91 + state === State.Success 92 + ? "Upload Successful" 93 + : "Upload" 94 + } 95 + </Button> 96 + 97 + {error && 98 + <span className="dark:text-red-500 text-red-400 text-sm mt-1"> 99 + {error} 100 + </span> 101 + } 102 + 103 + </div> 104 + ); 105 + 106 + }
+4 -4
next.config.js
··· 26 26 }, 27 27 { 28 28 protocol: "https", 29 - hostname: "the-net.loves-genshin.lol", 29 + hostname: "r2.wamellow.com", 30 30 port: "", 31 - pathname: "/images/ai/**" 31 + pathname: "/ai-image/**" 32 32 }, 33 33 { 34 34 protocol: "https", 35 - hostname: "r2.wamellow.com", 35 + hostname: "ai.local.wamellow.com", 36 36 port: "", 37 - pathname: "/ai-image/**" 37 + pathname: "/static/**" 38 38 } 39 39 ] 40 40 }