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.

ai-gallery generate add generation timer

Luna 9d0a556c a65a3dc3

+78 -14
+26 -7
app/ai-gallery/generate/page.tsx
··· 9 9 import Notice from "@/components/notice"; 10 10 import UploadButton from "./upload.component"; 11 11 import { HiPrinter } from "react-icons/hi"; 12 + import Time from "./time.component"; 13 + import LinkTag from "@/components/link-tag"; 14 + import cn from "@/utils/cn"; 15 + import { useCookies } from "next-client-cookies"; 12 16 13 17 enum State { 14 18 Idle = 0, ··· 16 20 } 17 21 18 22 export default function Home() { 23 + const cookies = useCookies(); 19 24 const search = useSearchParams() 20 25 const router = useRouter(); 21 26 ··· 38 43 setGpu(res.gpu || null) 39 44 }) 40 45 .catch((err) => { 41 - setError(`No gpu instance found at ${baseUrl}: ${err.toString()}`) 46 + setError(`Could not fetch local GPU instance (${err.toString()})`) 42 47 }); 43 48 }, [baseUrl]); 44 49 45 50 async function generate() { 46 51 setState(State.Loading); 47 52 53 + let params = new URLSearchParams(); 54 + params.delete("image_url"); 55 + router.push(`?${params.toString()}`, { scroll: false }); 56 + 48 57 const reqparams = new URLSearchParams({ 49 58 prompt: prompt, 50 59 }); ··· 52 61 const res = await fetch(`${baseUrl}/generate/image/${model}?${reqparams.toString()}`) 53 62 .then((res) => res.json()) as { url: string, duration: number }; 54 63 55 - const params = new URLSearchParams(); 64 + params = new URLSearchParams(); 56 65 params.delete("image_url"); 57 66 params.append("image_url", res.url); 58 67 ··· 105 114 <div className="md:w-1/3 mb-8 md:mt-0 flex flex-col"> 106 115 <DumbTextInput name="Base URL" value={baseUrl} setValue={setBaseUrl} thin /> 107 116 <DumbTextInput name="Model" value={model} setValue={setModel} thin /> 108 - <DumbTextInput name="Prompt" value={prompt} setValue={setPrompt} thin /> 117 + <DumbTextInput name="Prompt" value={prompt} setValue={setPrompt} placeholder="bocchi the rock" thin /> 109 118 110 119 <Button 111 120 className="mt-4" 112 121 color="secondary" 113 122 onClick={generate} 114 - startContent={<HiPrinter />} 123 + startContent={ 124 + state === State.Loading 125 + ? <></> 126 + : <HiPrinter /> 127 + } 115 128 isLoading={state === State.Loading} 116 129 isDisabled={!gpu || !prompt || !model} 117 130 > 118 131 Generate 119 132 </Button> 120 133 121 - <span className="mt-2 font-medium"> 122 - {gpu || "unknown gpu"} 123 - </span> 134 + <div className="flex justify-between"> 135 + <span className="font-medium"> 136 + {gpu || "unknown gpu"} 137 + </span> 138 + 139 + <span className="italic"> 140 + (<Time loading={state === State.Loading} />) 141 + </span> 142 + </div> 124 143 125 144 <UploadButton 126 145 model={model}
+34
app/ai-gallery/generate/time.component.tsx
··· 1 + import { useEffect, useRef, useState } from "react"; 2 + 3 + export default function Time({ 4 + loading 5 + }: { 6 + loading: boolean; 7 + }) { 8 + const intervalRef = useRef<NodeJS.Timeout | null>(null); 9 + 10 + const [previousLoading, setPreviousLoading] = useState(false); 11 + const [estimatedTime, setEstimatedTime] = useState(0); 12 + 13 + useEffect(() => { 14 + if (loading === previousLoading) return; 15 + 16 + if (!previousLoading && loading) setEstimatedTime(0); 17 + setPreviousLoading(loading) 18 + 19 + if (!loading) { 20 + if (intervalRef.current) clearInterval(intervalRef.current); 21 + return; 22 + } 23 + 24 + intervalRef.current = setInterval(() => { 25 + setEstimatedTime((prev) => prev + 100); 26 + }, 100); 27 + }, [loading]); 28 + 29 + return ( 30 + <span className="inline"> 31 + {(estimatedTime / 1000).toFixed(1)}s 32 + </span> 33 + ) 34 + }
+18 -7
app/ai-gallery/generate/upload.component.tsx
··· 7 7 import { useSearchParams } from "next/navigation"; 8 8 import { ApiV1UploadGetResponse } from "@/typings"; 9 9 import { HiCloudUpload } from "react-icons/hi"; 10 + import cn from "@/utils/cn"; 10 11 11 12 enum State { 12 13 Idle = 0, ··· 30 31 const [error, setError] = useState<string | null>(null); 31 32 32 33 const [nsfw, setNsfw] = useState(false); 34 + 35 + const isDisabled = !cookies.get("hasSession") || !imageUrl; 33 36 34 37 useEffect(() => { 35 38 setState(State.Idle); ··· 61 64 setState(State.Success); 62 65 } 63 66 64 - if (!cookies.get("hasSession") || !imageUrl) return <></>; 65 - 66 67 return ( 67 - <div className="flex flex-col"> 68 + <div 69 + className={cn( 70 + "flex flex-col", 71 + isDisabled && "cursor-not-allowed select-none blur-sm opacity-75" 72 + )} 73 + > 68 74 69 - <div className="mt-4 flex items-center"> 75 + <div className="mt-6 flex items-center"> 70 76 <Checkbox 71 77 isSelected={nsfw} 72 78 onValueChange={(now) => setNsfw(now)} 73 79 color="secondary" 80 + isDisabled={isDisabled} 74 81 /> 75 - <span className="font-medium">Is NSFW?</span> 82 + <span className="font-medium">Is NSFW content?</span> 76 83 </div> 77 84 78 85 <Button ··· 83 90 : "secondary" 84 91 } 85 92 onClick={upload} 86 - startContent={<HiCloudUpload />} 93 + startContent={ 94 + state === State.Loading 95 + ? <></> 96 + : <HiCloudUpload /> 97 + } 87 98 isLoading={state === State.Loading} 88 - isDisabled={state === State.Success} 99 + isDisabled={state === State.Success || isDisabled} 89 100 > 90 101 { 91 102 state === State.Success