import { useEffect, useMemo, useState } from "react"; import Header from "./components/Header"; import UploadZone from "./components/UploadZone"; import ImagePreview from "./components/ImagePreview"; import CaptionResult from "./components/CaptionResult"; import ErrorBanner from "./components/ErrorBanner"; import Spinner from "./components/Spinner"; import { captionImage } from "./services/api"; export default function App() { const [file, setFile] = useState(null); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const previewUrl = useMemo( () => (file ? URL.createObjectURL(file) : null), [file], ); useEffect(() => { if (!previewUrl) return; return () => URL.revokeObjectURL(previewUrl); }, [previewUrl]); const handleFileSelected = (nextFile, validationError) => { setResult(null); if (validationError) { setFile(null); setError(validationError); return; } setError(null); setFile(nextFile); }; const handleClear = () => { setFile(null); setResult(null); setError(null); }; const handleGenerate = async () => { if (!file || loading) return; setLoading(true); setError(null); setResult(null); try { const data = await captionImage(file); setResult(data); } catch (err) { setError(err?.message || "Caption generation failed."); } finally { setLoading(false); } }; return (

Describe any image{" "} in natural language

Upload a photo and let the model generate a concise caption. Powered by a vision-encoder/text-decoder pipeline served over FastAPI.

{file ? ( ) : ( )} setError(null)} />

Inference

{loading && ( running… )}

{file ? "Click generate to send the image to the inference endpoint." : "Upload an image to enable generation."}

{result && }
); }