feat: add recipe translation tool with AI translation and settings management
- 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
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
"use client";
|
||||
|
||||
import {useState, useEffect} from "react";
|
||||
import {useToast} from "@/components/ui/toast";
|
||||
import {Loader2} from "lucide-react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
|
||||
interface DBConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
const defaultDBConfig: DBConfig = {
|
||||
host: "127.0.0.1",
|
||||
port: 3306,
|
||||
user: "root",
|
||||
password: "",
|
||||
database: "recipe_tools",
|
||||
};
|
||||
|
||||
export default function DatabasePage() {
|
||||
const [config, setConfig] = useState<DBConfig>(defaultDBConfig);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const {addToast} = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/config")
|
||||
.then((res) => res.json())
|
||||
.then((result) => {
|
||||
if (result.success && result.data) {
|
||||
setConfig({
|
||||
host: result.data.db?.host || defaultDBConfig.host,
|
||||
port: result.data.db?.port || defaultDBConfig.port,
|
||||
user: result.data.db?.user || defaultDBConfig.user,
|
||||
password: result.data.db?.password || defaultDBConfig.password,
|
||||
database: result.data.db?.database || defaultDBConfig.database,
|
||||
});
|
||||
}
|
||||
})
|
||||
.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({db: config}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
addToast({type: "success", title: "数据库配置已保存"});
|
||||
} else {
|
||||
addToast({type: "error", title: "保存失败", description: result.error});
|
||||
}
|
||||
} catch {
|
||||
addToast({type: "error", title: "保存失败"});
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">数据库配置</h2>
|
||||
<p className="text-sm text-muted-foreground">配置 MySQL 数据库连接信息</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">配置 MySQL 数据库连接信息</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin"/>}
|
||||
{saving ? "保存中..." : "保存配置"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">主机地址</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.host}
|
||||
onChange={(e) => setConfig({...config, host: e.target.value})}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
placeholder="127.0.0.1"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">端口</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.port}
|
||||
onChange={(e) => setConfig({...config, port: parseInt(e.target.value) || 3306})}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
placeholder="3306"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">用户名</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.user}
|
||||
onChange={(e) => setConfig({...config, user: e.target.value})}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
placeholder="root"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">密码</label>
|
||||
<input
|
||||
type="password"
|
||||
value={config.password}
|
||||
onChange={(e) => setConfig({...config, password: e.target.value})}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
placeholder="******"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-sm font-medium">数据库名称</label>
|
||||
<input
|
||||
type="text"
|
||||
value={config.database}
|
||||
onChange={(e) => setConfig({...config, database: e.target.value})}
|
||||
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
||||
placeholder="recipe_tools"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user