"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Switch } from "@/components/ui/switch"; import { useTranslationStore, webllmModels, openaiModels } from "@/lib/store"; import { providers, testOpenAIConnection, testOllamaConnection, getOllamaModels, type OllamaModel } from "@/lib/api"; import { useWebLLM } from "@/lib/webllm"; import { Save, Loader2, Cloud, Check, ExternalLink, Wifi, CheckCircle, XCircle, Download, Trash2, Cpu, Server, RefreshCw } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Progress } from "@/components/ui/progress"; export default function TranslationServicesPage() { const { settings, updateSettings } = useTranslationStore(); const [isSaving, setIsSaving] = useState(false); const [selectedProvider, setSelectedProvider] = useState(settings.defaultProvider); const [translateImages, setTranslateImages] = useState(settings.translateImages); // Provider-specific states const [deeplApiKey, setDeeplApiKey] = useState(settings.deeplApiKey); const [openaiApiKey, setOpenaiApiKey] = useState(settings.openaiApiKey); const [openaiModel, setOpenaiModel] = useState(settings.openaiModel); const [libreUrl, setLibreUrl] = useState(settings.libreTranslateUrl); const [webllmModel, setWebllmModel] = useState(settings.webllmModel); // Ollama states const [ollamaUrl, setOllamaUrl] = useState(settings.ollamaUrl); const [ollamaModel, setOllamaModel] = useState(settings.ollamaModel); const [ollamaModels, setOllamaModels] = useState([]); const [loadingOllamaModels, setLoadingOllamaModels] = useState(false); const [ollamaTestStatus, setOllamaTestStatus] = useState<"idle" | "testing" | "success" | "error">("idle"); const [ollamaTestMessage, setOllamaTestMessage] = useState(""); // OpenAI connection test state const [openaiTestStatus, setOpenaiTestStatus] = useState<"idle" | "testing" | "success" | "error">("idle"); const [openaiTestMessage, setOpenaiTestMessage] = useState(""); // WebLLM hook const webllm = useWebLLM(); useEffect(() => { setSelectedProvider(settings.defaultProvider); setTranslateImages(settings.translateImages); setDeeplApiKey(settings.deeplApiKey); setOpenaiApiKey(settings.openaiApiKey); setOpenaiModel(settings.openaiModel); setLibreUrl(settings.libreTranslateUrl); setWebllmModel(settings.webllmModel); setOllamaUrl(settings.ollamaUrl); setOllamaModel(settings.ollamaModel); }, [settings]); // Load Ollama models when provider is selected const loadOllamaModels = async () => { setLoadingOllamaModels(true); try { const models = await getOllamaModels(ollamaUrl); setOllamaModels(models); } catch (error) { console.error("Failed to load Ollama models:", error); } finally { setLoadingOllamaModels(false); } }; useEffect(() => { if (selectedProvider === "ollama") { loadOllamaModels(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedProvider]); const handleTestOllama = async () => { setOllamaTestStatus("testing"); setOllamaTestMessage(""); try { const result = await testOllamaConnection(ollamaUrl); setOllamaTestStatus(result.success ? "success" : "error"); setOllamaTestMessage(result.message); if (result.success) { await loadOllamaModels(); updateSettings({ ollamaUrl, ollamaModel }); setOllamaTestMessage(result.message + " - Settings saved!"); } } catch { setOllamaTestStatus("error"); setOllamaTestMessage("Connection test failed"); } }; const handleTestOpenAI = async () => { if (!openaiApiKey.trim()) { setOpenaiTestStatus("error"); setOpenaiTestMessage("Please enter an API key first"); return; } setOpenaiTestStatus("testing"); setOpenaiTestMessage(""); try { const result = await testOpenAIConnection(openaiApiKey); setOpenaiTestStatus(result.success ? "success" : "error"); setOpenaiTestMessage(result.message); if (result.success) { updateSettings({ openaiApiKey, openaiModel }); setOpenaiTestMessage(result.message + " - Settings saved!"); } } catch { setOpenaiTestStatus("error"); setOpenaiTestMessage("Connection test failed"); } }; const handleSave = async () => { setIsSaving(true); try { updateSettings({ defaultProvider: selectedProvider, translateImages, deeplApiKey, openaiApiKey, openaiModel, libreTranslateUrl: libreUrl, webllmModel, ollamaUrl, ollamaModel, }); await new Promise((resolve) => setTimeout(resolve, 500)); } finally { setIsSaving(false); } }; return (

Translation Services

Select and configure your preferred translation provider.

{/* Provider Selection */}
Choose Provider Select your default translation service
{providers.map((provider) => (
setSelectedProvider(provider.id as typeof selectedProvider)} tabIndex={-1} className={` relative p-4 rounded-lg border-2 cursor-pointer transition-all ${ selectedProvider === provider.id ? "border-teal-500 bg-teal-500/10" : "border-zinc-700 hover:border-zinc-600 bg-zinc-800/50" } `} > {selectedProvider === provider.id && (
)}
{provider.icon}

{provider.name}

{provider.description}

))}
{/* Google - No config needed */} {selectedProvider === "google" && (

Ready to use!

Google Translate works out of the box. No configuration needed.

)} {/* Ollama Settings */} {selectedProvider === "ollama" && (
Ollama Configuration Connect to your local Ollama server
{ollamaTestStatus !== "idle" && ollamaTestStatus !== "testing" && ( {ollamaTestStatus === "success" && } {ollamaTestStatus === "error" && } {ollamaTestStatus === "success" ? "Connected" : "Error"} )}
setOllamaUrl(e.target.value)} placeholder="http://localhost:11434" className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500" />
{ollamaTestMessage && (

{ollamaTestMessage}

)}

Don't have Ollama? Install it from{" "} ollama.ai {" "}then run: ollama pull llama3.2

)} {/* WebLLM Settings */} {selectedProvider === "webllm" && ( WebLLM Settings Run AI models directly in your browser using WebGPU - no server required! {/* WebGPU Support Check */} {!webllm.isWebGPUSupported() && (

⚠️ WebGPU is not supported in this browser. Please use Chrome 113+, Edge 113+, or another WebGPU-compatible browser.

)}
{/* Model Loading Status */} {webllm.isLoading && (
{webllm.loadStatus} {webllm.loadProgress}%
)} {webllm.isLoaded && (

Model loaded: {webllm.currentModel}

)} {webllm.error && (

{webllm.error}

)} {/* Action Buttons */}

💡 Models are downloaded once and cached in your browser (~1-5GB depending on model). Loading may take a minute on first use.

)} {/* DeepL Settings */} {selectedProvider === "deepl" && ( DeepL Settings Configure your DeepL API credentials
setDeeplApiKey(e.target.value)} onKeyDown={(e) => e.stopPropagation()} placeholder="Enter your DeepL API key" className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500" />

Get your API key from{" "} deepl.com/pro-api

)} {/* LibreTranslate Settings */} {selectedProvider === "libre" && ( LibreTranslate Settings Configure your LibreTranslate server (open-source, self-hosted)
setLibreUrl(e.target.value)} onKeyDown={(e) => e.stopPropagation()} placeholder="https://libretranslate.com" className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500" />

Public instances (free but rate-limited):

Or{" "} self-host your own instance

)} {/* OpenAI Settings */} {selectedProvider === "openai" && (
OpenAI Settings Configure your OpenAI API for GPT-4 Vision translations
{openaiTestStatus !== "idle" && openaiTestStatus !== "testing" && ( {openaiTestStatus === "success" && } {openaiTestStatus === "error" && } {openaiTestStatus === "success" ? "Connected" : "Error"} )}
setOpenaiApiKey(e.target.value)} onKeyDown={(e) => e.stopPropagation()} placeholder="sk-..." className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500" />
{openaiTestMessage && (

{openaiTestMessage}

)}

Get your API key from{" "} platform.openai.com

Models with Vision can translate text in images

)} {/* Image Translation - Only for Ollama and OpenAI */} {(selectedProvider === "ollama" || selectedProvider === "openai") && ( Advanced Options Additional translation features
Vision Models

Extract and translate text from embedded images using vision models

)} {/* Save Button */}
); }