import { useState, useEffect, FormEvent } from "react"; import { NextPage } from "next"; import NextLink from "next/link"; import { Box, VStack, HStack, Heading, Text, Input, Button, Switch, FormControl, FormLabel, FormHelperText, Alert, AlertIcon, InputGroup, InputRightElement, IconButton, Badge, Link, useColorModeValue, Spinner, Textarea, RadioGroup, Radio, Code } from "@chakra-ui/react"; import { FiEye, FiEyeOff, FiSave, FiLogOut, FiArrowLeft } from "react-icons/fi"; import { CustomHead } from "@components"; type ReplicateMode = "cloud" | "local"; type Settings = { replicateApiToken: string; jigsawApiKey: string; modelVersion: string; replicateEnabled: boolean; replicateMode: ReplicateMode; localEndpoint: string; }; const DEFAULT_MODEL = "jigsawstack/text-translate:454df4c49941c05dea05175bd37686d0872c73c1f9366d1c2505db32ade52a89"; const DEFAULT_LOCAL_ENDPOINT = "http://localhost:5030/predictions"; const AdminPage: NextPage = () => { const [authed, setAuthed] = useState(null); const [password, setPassword] = useState(""); const [loginError, setLoginError] = useState(""); const [loginLoading, setLoginLoading] = useState(false); const [settings, setSettings] = useState({ replicateApiToken: "", jigsawApiKey: "", modelVersion: DEFAULT_MODEL, replicateEnabled: false, replicateMode: "cloud", localEndpoint: DEFAULT_LOCAL_ENDPOINT }); const [newPassword, setNewPassword] = useState(""); const [saveMsg, setSaveMsg] = useState<{ type: "success" | "error"; text: string } | null>(null); const [saving, setSaving] = useState(false); const [showToken, setShowToken] = useState(false); const [showJigsawKey, setShowJigsawKey] = useState(false); const [testResult, setTestResult] = useState(null); const [testLoading, setTestLoading] = useState(false); const pageBg = useColorModeValue("gray.50", "gray.900"); const cardBg = useColorModeValue("white", "gray.800"); const borderCol = useColorModeValue("gray.200", "gray.600"); const codeBg = useColorModeValue("gray.100", "gray.700"); useEffect(() => { fetch("/api/admin/auth") .then(r => r.json()) .then(d => setAuthed(d.authenticated)) .catch(() => setAuthed(false)); }, []); useEffect(() => { if (!authed) return; fetch("/api/admin/settings") .then(r => r.json()) .then(d => setSettings(s => ({ ...s, ...d }))) .catch(() => {}); }, [authed]); const login = async (e: FormEvent) => { e.preventDefault(); setLoginLoading(true); setLoginError(""); try { const res = await fetch("/api/admin/auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password }) }); if (res.ok) setAuthed(true); else { const d = await res.json(); setLoginError(d.error ?? "Login failed"); } } catch { setLoginError("Network error"); } finally { setLoginLoading(false); } }; const logout = async () => { await fetch("/api/admin/auth", { method: "DELETE" }); setAuthed(false); setPassword(""); }; const save = async (e: FormEvent) => { e.preventDefault(); setSaving(true); setSaveMsg(null); try { const body: Record = { ...settings }; if (newPassword.length >= 6) body.newPassword = newPassword; const res = await fetch("/api/admin/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const d = await res.json(); if (res.ok) { setSettings(s => ({ ...s, ...d })); setSaveMsg({ type: "success", text: "Settings saved successfully" }); setNewPassword(""); } else { setSaveMsg({ type: "error", text: d.error ?? "Save failed" }); } } catch { setSaveMsg({ type: "error", text: "Network error" }); } finally { setSaving(false); } }; const testTranslation = async () => { setTestLoading(true); setTestResult(null); try { const res = await fetch("/api/translate/replicate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: "Hello, world!", targetLanguage: "es" }) }); const d = await res.json(); setTestResult(res.ok ? `✓ "${d.translation}"` : `✗ ${d.error}`); } catch { setTestResult("✗ Network error"); } finally { setTestLoading(false); } }; if (authed === null) { return ( ); } if (!authed) { return ( <> Admin Login {loginError && ( {loginError} )} Password setPassword(e.target.value)} autoFocus placeholder="Admin password" /> Default: admin (set ADMIN_PASSWORD env var) ← Back to translator ); } return ( <> Admin Settings {/* ── Replicate Section ── */} Replicate AI Translation {settings.replicateEnabled ? "Enabled" : "Disabled"} Enable Replicate Translation setSettings(s => ({ ...s, replicateEnabled: e.target.checked }))} /> {/* Mode selector */} Translation Backend setSettings(s => ({ ...s, replicateMode: v as ReplicateMode }))} > Replicate Cloud Uses replicate.com API Local Docker (Cog) Uses local container {/* Cloud fields */} {settings.replicateMode === "cloud" && ( <> Replicate API Token setSettings(s => ({ ...s, replicateApiToken: e.target.value }))} placeholder="r8_..." fontFamily="mono" fontSize="sm" /> : } onClick={() => setShowToken(v => !v)} /> Get your token at{" "} replicate.com/account/api-tokens Model Version