import { useState, useRef, useCallback } from "react"; import { Link } from "wouter"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { motion, AnimatePresence } from "framer-motion"; import { Download, Sparkles, Loader2, RefreshCw, ChevronDown, ChevronUp, CheckCircle, XCircle, Clock, Server, Upload, X, ImagePlus, LogIn, AlertTriangle, Lock, Globe, } from "lucide-react"; import { useQueryClient } from "@tanstack/react-query"; import { useAuth } from "@/contexts/AuthContext"; import { useGenerateImage, getGetImageHistoryQueryKey } from "@workspace/api-client-react"; import type { ApiDebugInfo } from "@workspace/api-client-react"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Badge } from "@/components/ui/badge"; import { useToast } from "@/hooks/use-toast"; import { useLang } from "@/contexts/LanguageContext"; import { AuthModal } from "@/components/AuthModal"; const NANO_BANANA_MODELS = new Set(["nano-banana-pro", "nano-banana-2"]); const formSchema = z.object({ prompt: z.string().min(1), style: z.string().optional(), aspectRatio: z.string().optional(), model: z.string().optional(), resolution: z.string().optional(), }); type FormValues = z.infer; const MODEL_KEYS = ["grok", "meta", "imagen-pro", "imagen-4", "imagen-flash", "nano-banana-pro", "nano-banana-2"] as const; const STYLE_KEYS = ["none", "realistic", "anime", "artistic", "cartoon", "sketch", "oil_painting", "watercolor", "digital_art"] as const; const STYLE_EMOJIS: Record = { none: "🚫", realistic: "πŸ“·", anime: "🎌", artistic: "🎨", cartoon: "πŸ–ΌοΈ", sketch: "✏️", oil_painting: "πŸ–ŒοΈ", watercolor: "πŸ’§", digital_art: "πŸ’»", }; const ASPECT_RATIO_KEYS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"] as const; function JsonBlock({ data }: { data: unknown }) { return (
      {JSON.stringify(data, null, 2)}
    
); } function ApiDebugPanel({ debug }: { debug: ApiDebugInfo }) { const { t } = useLang(); const [openSection, setOpenSection] = useState("info"); const toggle = (key: string) => setOpenSection(prev => prev === key ? null : key); const statusOk = debug.responseStatus >= 200 && debug.responseStatus < 300; return (
{t.debugTitle}
{debug.usedFallback ? ( {t.debugFallback} ) : ( {t.debugReal} )}
{debug.durationMs} {t.debugMs}
{[ { key: "info", label: t.debugSectionInfo, content: (
{t.debugEndpoint} {debug.requestUrl}
{t.debugMethod} {debug.requestMethod}
{t.debugStatus} {statusOk ? : } {debug.responseStatus === 0 ? t.debugNoResponse : debug.responseStatus}
{t.debugDuration} {debug.durationMs} {t.debugMs}
{debug.usedFallback && debug.fallbackReason && (
{t.debugReason} {debug.fallbackReason}
)}
)}, { key: "request", label: t.debugSectionRequest, content: (

{t.debugHeaders}

{t.debugBody}

)}, { key: "response", label: t.debugSectionResponse, content: (
)}, ].map(({ key, label, content }, idx, arr) => (
{openSection === key && ( {content} )}
))}
); } export function Home() { const { toast } = useToast(); const { t } = useLang(); const queryClient = useQueryClient(); const { isSignedIn, isAdmin } = useAuth(); const [authModalOpen, setAuthModalOpen] = useState(false); const [generatedImageUrl, setGeneratedImageUrl] = useState(null); const [apiDebug, setApiDebug] = useState(null); const [tokenError, setTokenError] = useState<{ expired: boolean; message: string } | null>(null); const [referenceImage, setReferenceImage] = useState<{ base64: string; mime: string; preview: string } | null>(null); const [isPrivate, setIsPrivate] = useState(false); const fileInputRef = useRef(null); const { mutate: generateImage, isPending } = useGenerateImage(); const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { prompt: "", style: "realistic", aspectRatio: "1:1", model: "grok", }, }); const handleFileUpload = useCallback((file: File) => { if (!file.type.startsWith("image/")) { toast({ variant: "destructive", title: t.errorFormatTitle, description: t.errorFormatDesc }); return; } const reader = new FileReader(); reader.onload = (e) => { const dataUrl = e.target?.result as string; const base64 = dataUrl.split(",")[1]; setReferenceImage({ base64, mime: file.type, preview: dataUrl }); }; reader.readAsDataURL(file); }, [toast, t]); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); const file = e.dataTransfer.files[0]; if (file) handleFileUpload(file); }, [handleFileUpload]); function onSubmit(values: FormValues) { if (!isSignedIn) { setAuthModalOpen(true); return; } setGeneratedImageUrl(null); setApiDebug(null); setTokenError(null); const resolution = values.resolution === "none" || !values.resolution ? undefined : values.resolution; generateImage( { data: { ...values as any, resolution, referenceImageBase64: referenceImage?.base64, referenceImageMime: referenceImage?.mime, isPrivate, }, }, { onSuccess: (data: any) => { setGeneratedImageUrl(data.imageUrl); setApiDebug(data.apiDebug); queryClient.invalidateQueries({ queryKey: getGetImageHistoryQueryKey() }); if (data.tokenExpired) { setTokenError({ expired: true, message: data.error || t.tokenExpiredFallback }); } else if (data.error) { setTokenError({ expired: false, message: data.error }); } }, onError: () => { toast({ variant: "destructive", title: t.errorGenTitle, description: t.errorGenDesc }); }, } ); } const handleDownload = () => { if (!generatedImageUrl) return; const a = document.createElement("a"); a.href = generatedImageUrl; a.download = "generated-image.jpg"; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const selectedModel = form.watch("model") as string; const selectedModelInfo = t.models[selectedModel as keyof typeof t.models]; return (
{/* Left Form Panel */}

{t.canvasTitle}

{t.canvasSubtitle}

{/* Prompt */} ( {t.promptLabel}