diff --git a/app/page.tsx b/app/page.tsx index c6ca04f..cb293ba 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,17 +1,21 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; +import useDebounce from "@/src/utils/useDebounce"; +import { useGenerateImage } from "@/src/useGetImage"; - const DEFAULT_INPUT = "super fast pink and gold jet is flying against a beautiful nebula, trail behind it" +const DEFAULT_INPUT = + "super fast pink and gold jet is flying against a beautiful nebula, trail behind it"; export default function Home() { - const [texts, setTexts] = useState([DEFAULT_INPUT]); + const [text, setText] = useState(DEFAULT_INPUT); - const onChange = (e: React.ChangeEvent) => { - setTexts([...texts, e.target.value]); - }; + const debouncedText = useDebounce(text, 200); + + const params = useMemo(() => ({ prompt: debouncedText }), [debouncedText]); + const { imageUrl, loading: imageLoading } = useGenerateImage(params); // Focus the input element when the page loads) const inputElement = useRef(null); @@ -21,36 +25,62 @@ export default function Home() { } }, []); + const loading = imageLoading || debouncedText !== text; + return (
-
+
setText(e.target.value)} placeholder="Type to generate images..." className="flex-grow p-2 bg-transparent text-stone-100 placeholder-stone-400 overflow-hidden text-xl resize-none h-10 outline-none mr-4" ref={inputElement} onFocus={(e) => e.target.setSelectionRange(0, e.target.value.length)} /> - -
+ + + + + + +
+ + +
- {texts.map((text, index) => ( - {text} - ))} + {debouncedText}
); diff --git a/src/useGetImage.ts b/src/useGetImage.ts new file mode 100644 index 0000000..d246606 --- /dev/null +++ b/src/useGetImage.ts @@ -0,0 +1,47 @@ +import { useState, useEffect } from "react"; + +interface UseGenerateImageParams { + prompt: string; +} + +export const useGenerateImage = (params: UseGenerateImageParams) => { + const [imageUrl, setImageUrl] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!params.prompt) return; + + const fetchImage = async () => { + setLoading(true); + setError(null); + + try { + const response = await fetch( + `/api/generate-image?text=${encodeURIComponent(params.prompt)}` + ); + if (!response.ok) { + throw new Error(`Error: ${response.status} ${response.statusText}`); + } + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + setImageUrl(url); + } catch (err) { + if (err instanceof Error) setError(err.message); + } finally { + setLoading(false); + } + }; + + fetchImage(); + + // Clean up the URL object when the component unmounts or text changes + return () => { + if (imageUrl) { + URL.revokeObjectURL(imageUrl); + } + }; + }, [params]); + + return { imageUrl, loading, error }; +}; diff --git a/src/utils/useDebounce.ts b/src/utils/useDebounce.ts new file mode 100644 index 0000000..9ab01d7 --- /dev/null +++ b/src/utils/useDebounce.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +const useDebounce = (value: T, delay: number = 500) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +}; + +export default useDebounce;