diff --git a/sundynix-desktop/frontend/src/lib/api.ts b/sundynix-desktop/frontend/src/lib/api.ts index a71f173..d8efab9 100644 --- a/sundynix-desktop/frontend/src/lib/api.ts +++ b/sundynix-desktop/frontend/src/lib/api.ts @@ -789,3 +789,38 @@ export async function runReplay(taskId: string): Promise<{ output: string; exec: const d = (await res.json()) as { output?: string; exec?: ExecEvent[] }; return { output: d.output ?? "", exec: d.exec ?? [] }; } + +// ---- 每用户 JARVIS 设置(名字 / 人设 / 自带豆包配置)---- +export interface JarvisConfig { + name: string; + persona: string; + asr_resource_id: string; + tts_resource_id: string; + tts_voice_type: string; + api_key: string; // 读取时为脱敏值;保存留空/掩码=沿用已存 + has_own_voice: boolean; +} + +export async function getMyJarvis(): Promise { + const res = guard401(await fetch(`${GATEWAY}/api/v1/me/jarvis`, { headers: bearer() })); + if (!res.ok) throw new Error("读取 JARVIS 设置失败"); + return res.json() as Promise; +} + +export async function saveMyJarvis(cfg: { + name: string; + persona: string; + api_key?: string; + asr_resource_id?: string; + tts_resource_id?: string; + tts_voice_type?: string; +}): Promise { + const res = guard401( + await fetch(`${GATEWAY}/api/v1/me/jarvis`, { + method: "PUT", + headers: { "Content-Type": "application/json", ...bearer() }, + body: JSON.stringify(cfg), + }), + ); + if (!res.ok) throw new Error(((await res.json().catch(() => ({}))) as { error?: string }).error || "保存失败"); +} diff --git a/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx b/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx new file mode 100644 index 0000000..82fb6a5 --- /dev/null +++ b/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx @@ -0,0 +1,139 @@ +import { useEffect, useState } from "react"; +import { Dialog } from "../ui/Dialog"; +import { Button } from "../ui/Button"; +import { useToast } from "../ui/Toast"; +import { getMyJarvis, saveMyJarvis, type JarvisConfig } from "../lib/api"; + +// 每用户 JARVIS 设置:名字 / 人设 / (高级)自带豆包配置。 +// 名字与人设归用户自己;豆包配置齐全则语音走用户的账号,否则走系统兜底。 +export function JarvisSettings({ open, onClose }: { open: boolean; onClose: () => void }) { + const toast = useToast(); + const [cfg, setCfg] = useState(null); + const [saving, setSaving] = useState(false); + const [advanced, setAdvanced] = useState(false); + + useEffect(() => { + if (!open) return; + setCfg(null); + getMyJarvis() + .then(setCfg) + .catch((e) => toast.push("error", (e as Error).message)); + }, [open, toast]); + + const set = (k: keyof JarvisConfig, v: string) => setCfg((c) => (c ? { ...c, [k]: v } : c)); + + const onSave = async () => { + if (!cfg) return; + setSaving(true); + try { + await saveMyJarvis({ + name: cfg.name, + persona: cfg.persona, + api_key: cfg.api_key.includes("•") ? "" : cfg.api_key, // 掩码=没改动,留空沿用已存 + asr_resource_id: cfg.asr_resource_id, + tts_resource_id: cfg.tts_resource_id, + tts_voice_type: cfg.tts_voice_type, + }); + toast.push("success", "已保存,下次说话即生效"); + onClose(); + } catch (e) { + toast.push("error", (e as Error).message); + } finally { + setSaving(false); + } + }; + + return ( + + + + + } + > + {!cfg ? ( +
加载中…
+ ) : ( +
+ + +