a249fb038c
- Add Word document parsing and translation export - Add database configuration management - Add AI/LLM translation configuration - Add translation vocabulary management - Add export settings (colors, page orientation, headers) - Separate database and AI configurations - Add debounced search to prevent database overload
176 lines
6.3 KiB
TypeScript
176 lines
6.3 KiB
TypeScript
"use client";
|
|
import {useEffect, useState} from "react";
|
|
import {Loader2} from "lucide-react";
|
|
import {Button} from "@/components/ui/button";
|
|
|
|
interface AIConfig {
|
|
apiKey: string;
|
|
baseUrl: string;
|
|
model: string;
|
|
}
|
|
|
|
const defaultAIConfig: AIConfig = {
|
|
apiKey: "",
|
|
baseUrl: "https://api.openai.com/v1",
|
|
model: "gpt-3.5-turbo",
|
|
};
|
|
|
|
export default function AISettingsPage() {
|
|
const [config, setConfig] = useState<AIConfig>(defaultAIConfig);
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [testing, setTesting] = useState(false);
|
|
const [testResult, setTestResult] = useState<"success" | "error" | null>(null);
|
|
|
|
// 加载配置
|
|
useEffect(() => {
|
|
fetch("/api/config")
|
|
.then((res) => res.json())
|
|
.then((result) => {
|
|
if (result.success && result.data) {
|
|
setConfig({
|
|
apiKey: result.data.ai?.apiKey || defaultAIConfig.apiKey,
|
|
baseUrl: result.data.ai?.baseUrl || defaultAIConfig.baseUrl,
|
|
model: result.data.ai?.model || defaultAIConfig.model,
|
|
});
|
|
}
|
|
})
|
|
.catch(console.error)
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
// 保存配置
|
|
const handleSave = async () => {
|
|
setSaving(true);
|
|
try {
|
|
const response = await fetch("/api/config", {
|
|
method: "POST",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: JSON.stringify({ai: config}),
|
|
});
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
setTestResult("success");
|
|
setTimeout(() => setTestResult(null), 3000);
|
|
} else {
|
|
setTestResult("error");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to save config:", error);
|
|
setTestResult("error");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
// 测试 AI 连接
|
|
const handleTestAI = async () => {
|
|
if (!config.apiKey) return;
|
|
|
|
// 先保存配置
|
|
await handleSave();
|
|
|
|
setTesting(true);
|
|
setTestResult(null);
|
|
|
|
try {
|
|
const response = await fetch(`/api/ai/translate?word=${encodeURIComponent("早餐")}`);
|
|
const result = await response.json();
|
|
|
|
if (result.english) {
|
|
setTestResult("success");
|
|
} else {
|
|
setTestResult("error");
|
|
}
|
|
} catch {
|
|
setTestResult("error");
|
|
} finally {
|
|
setTesting(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold">大模型设置</h2>
|
|
<p className="text-sm text-muted-foreground">配置 AI 翻译服务</p>
|
|
</div>
|
|
<div className="h-40 animate-pulse rounded-lg bg-muted"/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className={"flex items-center justify-between"}>
|
|
<div>
|
|
<h2 className="text-lg font-semibold">大模型设置</h2>
|
|
<p className="text-sm text-muted-foreground">配置 AI 翻译服务</p>
|
|
</div>
|
|
<div className={"flex items-center justify-between gap-2"}>
|
|
<div className="flex items-center gap-4">
|
|
<Button onClick={handleTestAI} disabled={testing || saving || !config.apiKey}>
|
|
{testing ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin"/>
|
|
测试中...
|
|
</>
|
|
) : (
|
|
"测试并保存"
|
|
)}
|
|
</Button>
|
|
{testResult === "success" && (
|
|
<span className="text-sm text-green-600">配置成功!</span>
|
|
)}
|
|
{testResult === "error" && (
|
|
<span className="text-sm text-destructive">配置失败,请检查 API Key</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<Button onClick={handleSave} disabled={saving}>
|
|
{saving ? "保存中..." : "保存配置"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<div className="space-y-2 md:col-span-2">
|
|
<label className="text-sm font-medium">API Key</label>
|
|
<input
|
|
type="password"
|
|
value={config.apiKey}
|
|
onChange={(e) => setConfig({...config, apiKey: e.target.value})}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
placeholder="sk-..."
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium">Base URL</label>
|
|
<input
|
|
type="text"
|
|
value={config.baseUrl}
|
|
onChange={(e) => setConfig({...config, baseUrl: e.target.value})}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
placeholder="https://api.openai.com/v1"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium">模型</label>
|
|
<input
|
|
type="text"
|
|
value={config.model}
|
|
onChange={(e) => setConfig({...config, model: e.target.value})}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
placeholder="gpt-3.5-turbo"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
);
|
|
}
|