Feat: 新增自定义选择模型功能
This commit is contained in:
parent
b71de1f5c9
commit
83e826e034
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export type Project = {
|
||||||
updatedAt: number; // ms timestamp
|
updatedAt: number; // ms timestamp
|
||||||
preferences?: {
|
preferences?: {
|
||||||
aiPrompt?: string;
|
aiPrompt?: string;
|
||||||
|
aiModel?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue