Feat: 新增自定义选择模型功能

This commit is contained in:
奇趣保罗 2025-11-12 14:49:43 +08:00
parent b71de1f5c9
commit 83e826e034
4 changed files with 29 additions and 7 deletions

View File

@ -25,6 +25,7 @@ type Props = {
languages: string[]; languages: string[];
paths: string[]; paths: string[];
prompt: string | undefined; prompt: string | undefined;
model?: string | undefined;
initialSelectedLanguages?: string[]; initialSelectedLanguages?: string[];
getExistingByPath?: (path: string) => Record<string, string>; getExistingByPath?: (path: string) => Record<string, string>;
onConfirm: ( onConfirm: (
@ -33,7 +34,7 @@ type Props = {
) => Promise<void> | void; ) => Promise<void> | void;
}; };
function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, initialSelectedLanguages, getExistingByPath, onConfirm }: Props) { function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, model, initialSelectedLanguages, getExistingByPath, onConfirm }: Props) {
const [inputsByKey, setInputsByKey] = useState<Record<string, string>>({}); const [inputsByKey, setInputsByKey] = useState<Record<string, string>>({});
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -92,7 +93,7 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, in
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
const result = await requestTranslations({ items, languages: selectedLangs, prompt }); const result = await requestTranslations({ items, languages: selectedLangs, prompt, model });
await onConfirm(result, { overwrite, selectedLanguages: selectedLangs }); await onConfirm(result, { overwrite, selectedLanguages: selectedLangs });
setInputsByKey({}); setInputsByKey({});
onOpenChange(false); onOpenChange(false);

View File

@ -8,13 +8,14 @@ type Props = {
open: boolean; open: boolean;
onOpenChange: (v: boolean) => void; onOpenChange: (v: boolean) => void;
project: Project | null; project: Project | null;
onSave: (update: { name: string; preferences: { aiPrompt?: string } }) => Promise<void> | void; onSave: (update: { name: string; preferences: { aiPrompt?: string; aiModel?: string } }) => Promise<void> | void;
onDelete: () => Promise<void> | void; onDelete: () => Promise<void> | void;
}; };
function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelete }: Props) { function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelete }: Props) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [aiPrompt, setAiPrompt] = useState<string>(""); const [aiPrompt, setAiPrompt] = useState<string>("");
const [aiModel, setAiModel] = useState<string>("");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [err, setErr] = useState<string | null>(null); const [err, setErr] = useState<string | null>(null);
@ -22,6 +23,7 @@ function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelet
if (open) { if (open) {
setName(project?.name ?? ""); setName(project?.name ?? "");
setAiPrompt(project?.preferences?.aiPrompt ?? ""); setAiPrompt(project?.preferences?.aiPrompt ?? "");
setAiModel(project?.preferences?.aiModel ?? "");
setSaving(false); setSaving(false);
setErr(null); setErr(null);
} }
@ -35,7 +37,7 @@ function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelet
try { try {
await onSave({ await onSave({
name: trimmed, name: trimmed,
preferences: { aiPrompt }, preferences: { aiPrompt, aiModel },
}); });
onOpenChange(false); onOpenChange(false);
} catch (e) { } catch (e) {
@ -69,6 +71,22 @@ function ProjectSettingsModalImpl({ open, onOpenChange, project, onSave, onDelet
<label className="block text-sm text-muted-foreground"></label> <label className="block text-sm text-muted-foreground"></label>
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入项目名称" /> <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入项目名称" />
</div> </div>
<div>
<label className="block text-sm text-muted-foreground">AI </label>
<Input
list="ai-model-options"
value={aiModel}
onChange={(e) => setAiModel(e.target.value)}
placeholder="例如openai/gpt-5"
/>
<datalist id="ai-model-options">
<option value="openai/gpt-5" />
<option value="gpt-4o-mini" />
<option value="gpt-4o" />
<option value="gpt-4.1-mini" />
<option value="o4-mini" />
</datalist>
</div>
<div> <div>
<label className="block text-sm text-muted-foreground">AI Prompt</label> <label className="block text-sm text-muted-foreground">AI Prompt</label>
<textarea <textarea

View File

@ -5,6 +5,7 @@ export type Project = {
updatedAt: number; // ms timestamp updatedAt: number; // ms timestamp
preferences?: { preferences?: {
aiPrompt?: string; aiPrompt?: string;
aiModel?: string;
}; };
}; };

View File

@ -232,7 +232,7 @@ export default function Editor() {
/> />
</td> </td>
<td className="px-3 py-2 font-mono wrap-break-word"> <td className="px-3 py-2 font-mono wrap-break-word">
<button type="button" className="w-full text-left min-h-8 leading-8 px-2 rounded hover:bg-accent" onClick={handleCopy} title="点击复制"> <button type="button" className="w-full text-left min-h-8 leading-normal px-2 rounded hover:bg-accent" onClick={handleCopy} title="点击复制">
{entry.path} {entry.path}
</button> </button>
</td> </td>
@ -251,12 +251,12 @@ export default function Editor() {
onBlur={inline.saveEdit} onBlur={inline.saveEdit}
onKeyDown={inline.handleKeyDown} onKeyDown={inline.handleKeyDown}
disabled={isSaving} disabled={isSaving}
className="leading-8 px-2 py-0" className="leading-normal px-2 py-0"
/> />
) : ( ) : (
<button <button
type="button" type="button"
className="w-full text-left min-h-8 leading-8 px-2 rounded hover:bg-accent" className="w-full text-left min-h-8 leading-normal px-2 rounded hover:bg-accent"
onClick={() => inline.startEdit(entry.path, lang)} onClick={() => inline.startEdit(entry.path, lang)}
title="点击编辑" title="点击编辑"
> >
@ -517,6 +517,7 @@ export default function Editor() {
initialSelectedLanguages={aiModal?.path ? computeSuggestedLanguages([aiModal.path]) : []} initialSelectedLanguages={aiModal?.path ? computeSuggestedLanguages([aiModal.path]) : []}
getExistingByPath={getExistingByPath} getExistingByPath={getExistingByPath}
prompt={project?.preferences?.aiPrompt} prompt={project?.preferences?.aiPrompt}
model={project?.preferences?.aiModel}
onConfirm={async (translations, options) => { onConfirm={async (translations, options) => {
if (!projectId || !aiModal) return; if (!projectId || !aiModal) return;
const updates: Record<string, Record<string, string>> = {}; const updates: Record<string, Record<string, string>> = {};
@ -554,6 +555,7 @@ export default function Editor() {
initialSelectedLanguages={computeSuggestedLanguages(Array.from(selected))} initialSelectedLanguages={computeSuggestedLanguages(Array.from(selected))}
getExistingByPath={getExistingByPath} getExistingByPath={getExistingByPath}
prompt={project?.preferences?.aiPrompt} prompt={project?.preferences?.aiPrompt}
model={project?.preferences?.aiModel}
onConfirm={async (translations, options) => { onConfirm={async (translations, options) => {
if (!projectId || selected.size === 0) return; if (!projectId || selected.size === 0) return;
try { try {