"use client"; import { useState, useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import useStore from "@/lib/store"; import modelAdapter from "@/lib/services/adapter-instance"; import { Save, Key, Server, Eye, EyeOff, CheckCircle, XCircle, Loader2, RefreshCw } from "lucide-react"; import { translations } from "@/lib/i18n/translations"; export default function SettingsPanel() { const { language, apiKeys, setApiKey, selectedProvider, setSelectedProvider, qwenTokens, setQwenTokens, apiValidationStatus, setApiValidationStatus, } = useStore(); const t = translations[language].settings; const common = translations[language].common; const [showApiKey, setShowApiKey] = useState>({}); const [isAuthLoading, setIsAuthLoading] = useState(false); const [validating, setValidating] = useState>({}); const validationDebounceRef = useRef>({}); const handleSave = () => { if (typeof window !== "undefined") { localStorage.setItem("promptarch-api-keys", JSON.stringify(apiKeys)); alert(t.keysSaved); } }; const handleLoad = () => { if (typeof window !== "undefined") { const saved = localStorage.getItem("promptarch-api-keys"); if (saved) { try { const keys = JSON.parse(saved); if (keys.qwen) { setApiKey("qwen", keys.qwen); modelAdapter.updateQwenApiKey(keys.qwen); } if (keys.ollama) { setApiKey("ollama", keys.ollama); modelAdapter.updateOllamaApiKey(keys.ollama); } if (keys.zai) { setApiKey("zai", keys.zai); modelAdapter.updateZaiApiKey(keys.zai); } } catch (e) { console.error("Failed to load API keys:", e); } } const storedTokens = modelAdapter.getQwenTokenInfo(); if (storedTokens) { setQwenTokens(storedTokens); } // Load validation status const storedValidation = localStorage.getItem("promptarch-api-validation"); if (storedValidation) { try { const validation = JSON.parse(storedValidation); // Only use cached validation if it's less than 5 minutes old const now = Date.now(); const fiveMinutes = 5 * 60 * 1000; const entries = Object.entries(validation) as Array<[string, any]>; for (const [provider, status] of entries) { if (status.lastValidated && (now - status.lastValidated) < fiveMinutes) { setApiValidationStatus(provider as any, status); } } } catch (e) { console.error("Failed to load validation status:", e); } } } }; const validateApiKey = async (provider: "qwen" | "ollama" | "zai") => { const key = apiKeys[provider]; if (!key || key.trim().length === 0) { setApiValidationStatus(provider, { valid: false, error: "API key is required" }); return; } setValidating((prev) => ({ ...prev, [provider]: true })); try { const result = await modelAdapter.validateConnection(provider); if (result.success && result.data?.valid) { const status = { valid: true, lastValidated: Date.now(), models: result.data.models, }; setApiValidationStatus(provider, status); // Save to localStorage if (typeof window !== "undefined") { const storedValidation = localStorage.getItem("promptarch-api-validation"); const allValidation = storedValidation ? JSON.parse(storedValidation) : {}; allValidation[provider] = status; localStorage.setItem("promptarch-api-validation", JSON.stringify(allValidation)); } } else { const status = { valid: false, error: result.error || "Connection failed", lastValidated: Date.now(), }; setApiValidationStatus(provider, status); } } catch (error) { setApiValidationStatus(provider, { valid: false, error: error instanceof Error ? error.message : "Validation failed", lastValidated: Date.now(), }); } finally { setValidating((prev) => ({ ...prev, [provider]: false })); } }; const handleApiKeyChange = (provider: string, value: string) => { setApiKey(provider as "qwen" | "ollama" | "zai", value); // Clear existing timeout if (validationDebounceRef.current[provider]) { clearTimeout(validationDebounceRef.current[provider]); } // Update the service immediately switch (provider) { case "qwen": modelAdapter.updateQwenApiKey(value); break; case "ollama": modelAdapter.updateOllamaApiKey(value); break; case "zai": modelAdapter.updateZaiApiKey(value); break; } // Debounce validation (500ms) if (value.trim().length > 0) { validationDebounceRef.current[provider] = setTimeout(() => { validateApiKey(provider as "qwen" | "ollama" | "zai"); }, 500); } else { setApiValidationStatus(provider as any, { valid: false }); } }; const handleQwenAuth = async () => { if (qwenTokens) { setQwenTokens(null); modelAdapter.updateQwenTokens(); modelAdapter.updateQwenApiKey(apiKeys.qwen || ""); setApiValidationStatus("qwen", { valid: false }); return; } setIsAuthLoading(true); try { const token = await modelAdapter.startQwenOAuth(); setQwenTokens(token); modelAdapter.updateQwenTokens(token); // Validate after OAuth await validateApiKey("qwen"); } catch (error) { console.error("Qwen OAuth failed", error); window.alert( error instanceof Error ? error.message : t.qwenAuthFailed ); } finally { setIsAuthLoading(false); } }; const getStatusIndicator = (provider: "qwen" | "ollama" | "zai") => { const status = apiValidationStatus[provider]; if (validating[provider]) { return (
Validating...
); } if (status?.valid) { const timeAgo = status.lastValidated ? `Validated ${Math.round((Date.now() - status.lastValidated) / 60000)}m ago` : "Connected"; return (
{timeAgo}
); } if (status?.error && apiKeys[provider]?.trim().length > 0) { return (
{status.error}
); } return null; }; useEffect(() => { handleLoad(); return () => { // Clear all debounce timeouts on unmount Object.values(validationDebounceRef.current).forEach(timeout => { if (timeout) clearTimeout(timeout); }); }; }, []); return (
{t.apiKeys} {t.apiKeysDesc}
handleApiKeyChange("qwen", e.target.value)} className="font-mono text-xs lg:text-sm pr-24" />
{getStatusIndicator("qwen")}

{t.getApiKey}{" "} Alibaba DashScope

{apiKeys.qwen && ( )}
{qwenTokens && (

✓ {t.authenticated} ({t.expires}: {new Date(qwenTokens.expiresAt || 0).toLocaleString()})

)}
handleApiKeyChange("ollama", e.target.value)} className="font-mono text-xs lg:text-sm pr-24" />
{getStatusIndicator("ollama")}

{t.getApiKey}{" "} ollama.com/cloud

{apiKeys.ollama && ( )}
handleApiKeyChange("zai", e.target.value)} className="font-mono text-xs lg:text-sm pr-24" />
{getStatusIndicator("zai")}

{t.getApiKey}{" "} docs.z.ai

{apiKeys.zai && ( )}
{t.defaultProvider} {t.defaultProviderDesc}
{(["qwen", "ollama", "zai"] as const).map((provider) => ( ))}
{t.dataPrivacy} {t.dataPrivacyTitleDesc}

{t.dataPrivacyDesc}

); }