Feat: 增加项目和 Prompt 偏好设置

This commit is contained in:
2025-11-04 22:22:33 +08:00
parent 61dee0b127
commit 85d197f149
6 changed files with 260 additions and 57 deletions
+3 -2
View File
@@ -15,10 +15,11 @@ type Props = {
onOpenChange: (next: boolean) => void;
languages: string[];
path: string;
prompt: string | undefined;
onConfirm: (result: Record<string, string>) => Promise<void> | void;
};
function AiTranslateModalImpl({ open, onOpenChange, languages, path, onConfirm }: Props) {
function AiTranslateModalImpl({ open, onOpenChange, languages, path, prompt, onConfirm }: Props) {
const [text, setText] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -37,7 +38,7 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, path, onConfirm }
setLoading(true);
setError(null);
try {
const result = await requestTranslations({ text: payload, languages });
const result = await requestTranslations({ text: payload, languages, prompt });
// 二次校验 keys 完整性
for (const l of languages) {
if (!Object.prototype.hasOwnProperty.call(result, l)) {
@@ -0,0 +1,95 @@
import { memo, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import type { Project } from "@/lib/db";
type Props = {
open: boolean;
onOpenChange: (v: boolean) => void;
project: Project | null;
onSave: (update: { name: string; preferences: { aiPrompt?: string } }) => Promise<void> | void;
onDelete: () => Promise<void> | void;
};
function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelete }: Props) {
const [name, setName] = useState("");
const [aiPrompt, setAiPrompt] = useState<string>("");
const [saving, setSaving] = useState(false);
const [err, setErr] = useState<string | null>(null);
useEffect(() => {
if (open) {
setName(project?.name ?? "");
setAiPrompt(project?.preferences?.aiPrompt ?? "");
setSaving(false);
setErr(null);
}
}, [open, project]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const trimmed = name.trim();
if (!trimmed) return setErr("项目名称不能为空");
setSaving(true);
try {
await onSave({
name: trimmed,
preferences: { aiPrompt },
});
onOpenChange(false);
} catch (e) {
setErr((e as Error)?.message ?? "保存失败");
} finally {
setSaving(false);
}
}
async function handleDelete() {
if (!confirm("确认删除整个项目?该操作不可恢复!")) return;
setSaving(true);
try {
await onDelete();
onOpenChange(false);
} catch (e) {
setErr((e as Error)?.message ?? "删除失败");
} finally {
setSaving(false);
}
}
return (
<Dialog open={open} onOpenChange={(v) => { if (!saving) onOpenChange(v); }}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>项目设置</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm text-muted-foreground">项目名称</label>
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入项目名称" />
</div>
<div>
<label className="block text-sm text-muted-foreground">翻译偏好设置(AI Prompt)</label>
<textarea
className="mt-1 w-full min-h-32 rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none font-mono"
placeholder="请输入提供给 AI 的提示词(Prompt),描述风格、用词偏好、命名规则等..."
value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)}
/>
</div>
{err && <div className="text-sm text-red-600">{err}</div>}
<DialogFooter>
<Button className="mr-auto" variant="destructive" onClick={handleDelete} disabled={saving}>删除项目</Button>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>取消</Button>
<Button type="submit" disabled={saving}>保存</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}
export const ProjectSettingsModal = memo(ProjectSettingsModalImpl);