"use client"; import React, { useState, useEffect, useRef, useCallback } from "react"; import { AlertTriangle, Camera } from "lucide-react"; import { KioskFrame } from "@/components/KioskFrame"; import { StandbyScreen } from "@/components/screens/StandbyScreen"; import { CameraScreen } from "@/components/screens/CameraScreen"; import { ConfirmScreen } from "@/components/screens/ConfirmScreen"; import { StyleScreen } from "@/components/screens/StyleScreen"; import { LoadingScreen } from "@/components/screens/LoadingScreen"; import { ResultScreen } from "@/components/screens/ResultScreen"; import { RateLimitedScreen } from "@/components/screens/RateLimitedScreen"; import { BoothConfig, BoothStyle, ScreenState } from "@/types/booth"; import { I18nProvider, useI18n } from "@/lib/i18n"; function PhotoBoothApp() { const [config, setConfig] = useState(null); const [styles, setStyles] = useState([]); const [screen, setScreen] = useState("standby"); const [capturedPhoto, setCapturedPhoto] = useState(null); const [selectedStyle, setSelectedStyle] = useState(null); const [resultImage, setResultImage] = useState(null); const [providerInfo, setProviderInfo] = useState(null); const [rateLimitResetMinutes, setRateLimitResetMinutes] = useState(0); const [errorMessage, setErrorMessage] = useState(null); const [safetyWarning, setSafetyWarning] = useState(null); const [clientId, setClientId] = useState(""); const idleTimerRef = useRef(null); const { t } = useI18n(); useEffect(() => { let storedId = localStorage.getItem("booth_client_id"); if (!storedId) { storedId = "dev_" + Math.random().toString(36).substring(2, 12); localStorage.setItem("booth_client_id", storedId); } setClientId(storedId); fetch("/api/config") .then((res) => res.json()) .then((data) => { if (data.styles) { setStyles(data.styles); } setConfig(data); // URL parameter support for direct previewing, e.g. /?preview=result if (typeof window !== "undefined") { const params = new URLSearchParams(window.location.search); const previewScreen = params.get("preview") as ScreenState | null; if (previewScreen === "result") { const previewImg = params.get("image") || "/assets/styles/jack_o_lantern_sensei.jpg"; setResultImage(previewImg); setScreen("result"); } } }) .catch((err) => console.error("Failed to load booth config:", err)); }, []); const resetIdleTimer = useCallback(() => { if (idleTimerRef.current) { clearTimeout(idleTimerRef.current); } if (!config?.kiosk?.enableAutoReset || screen === "standby") { return; } const timeoutMs = (config.kiosk.idleTimeoutSeconds || 90) * 1000; idleTimerRef.current = setTimeout(() => { console.log("[Kiosk] Idle timeout reached. Resetting to standby..."); setScreen("standby"); setCapturedPhoto(null); setSelectedStyle(null); setResultImage(null); setErrorMessage(null); setSafetyWarning(null); }, timeoutMs); }, [config, screen]); useEffect(() => { const handleActivity = () => resetIdleTimer(); window.addEventListener("pointerdown", handleActivity); window.addEventListener("keydown", handleActivity); resetIdleTimer(); return () => { window.removeEventListener("pointerdown", handleActivity); window.removeEventListener("keydown", handleActivity); if (idleTimerRef.current) { clearTimeout(idleTimerRef.current); } }; }, [resetIdleTimer]); const handleStart = () => { setErrorMessage(null); setSafetyWarning(null); setScreen("camera"); }; const handlePhotoCaptured = (photoBase64: string) => { setCapturedPhoto(photoBase64); setScreen("confirm"); }; const handleRetake = () => { setCapturedPhoto(null); setScreen("camera"); }; const handleConfirmPhoto = () => { setScreen("style"); }; const handleStyleSelected = async (style: BoothStyle) => { setSelectedStyle(style); setScreen("processing"); setErrorMessage(null); setSafetyWarning(null); try { const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json", "x-client-id": clientId, }, body: JSON.stringify({ imageBase64: capturedPhoto, styleId: style.id, }), }); const data = await response.json(); if (response.status === 429) { setRateLimitResetMinutes(data.resetInMinutes || 60); setScreen("rate_limited"); return; } if (!response.ok) { if (data.isSafetyViolation) { setSafetyWarning(data.error); } else { setErrorMessage(data.error || "Generation failed. Please try again."); } setScreen("style"); return; } setResultImage(data.imageUrl); setProviderInfo({ provider: data.provider, model: data.model, durationMs: data.durationMs, fallbackTriggered: data.fallbackTriggered, }); setScreen("result"); } catch (err: any) { console.error("API call error:", err); setErrorMessage( t({ en: "Network error. Please check your internet connection and try again.", zh: "网络连接异常,请检查网络后重试。" }) ); setScreen("style"); } }; const handleStartOver = () => { setCapturedPhoto(null); setSelectedStyle(null); setResultImage(null); setErrorMessage(null); setSafetyWarning(null); setScreen("standby"); }; if (!config) { return (

🥋 加载万圣道馆中...

); } return ( {safetyWarning && (
{safetyWarning}
)} {errorMessage && (
{errorMessage}
)} {screen === "standby" && ( )} {screen === "camera" && ( )} {screen === "confirm" && capturedPhoto && ( )} {screen === "style" && ( setScreen("confirm")} /> )} {screen === "processing" && } {screen === "result" && resultImage && ( )} {screen === "rate_limited" && ( )}
); } export default function PhotoBoothPage() { return ( ); }