From 7e0393bf6264f609faed31334a74a3dd650402e0 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Sat, 25 Jul 2026 15:54:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E8=AE=BE=E7=BD=AE=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E7=8B=82=E9=97=AA+=E8=BE=93=E5=85=A5=E8=A2=AB=E6=B8=85?= =?UTF-8?q?=E7=A9=BA(toast=20context=20=E6=AF=8F=E6=AC=A1=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E9=80=A0=E6=96=B0=E5=AF=B9=E8=B1=A1)=20+=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=94=9F=E6=95=88=E5=8F=AF=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【弹窗闪/填着填着没了】根因在 Toast Provider:`value={{ push }}` 每次渲染都造 新对象,而全项目有 7 处把 useToast() 结果放进 useEffect 依赖。于是**任意一条 toast 弹出**都会让这些 effect 重跑——JarvisSettings 因此重新拉取配置、把用户 正在填的内容覆盖掉;若拉取本身失败又会 push 错误 toast → 无限循环狂闪。 修:Provider 的 context value 用 useMemo 稳定(一处修好,7 处受益); JarvisSettings 的加载 effect 只依赖 open,toast 走 ref 取用(纵深防御)。 【不确定配置是否生效】改完名字语音坞还挂着旧名(只在挂载时拉一次), 用户完全看不出改动生效——这本身就是 bug。 修:设置保存后回调 onSaved → 语音坞立刻重拉名字;弹窗顶部加「当前生效」摘要 (助手名/人设是否自定义/语音走自己的还是系统配置)+ 一句验证方法。 live 验证:①填入「星期五」后触发 toast,输入内容不再被清空 ②保存后落库正确 ③按语音提示词问「你是谁」→ 答「我是星期五,你的私人语音助手」 Co-Authored-By: Claude Opus 5 --- .../frontend/src/shell/JarvisSettings.tsx | 32 ++++++++++++++++--- .../frontend/src/shell/VoiceDock.tsx | 8 +++-- sundynix-desktop/frontend/src/ui/Toast.tsx | 9 ++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx b/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx index 59b28c3..76fdbab 100644 --- a/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx +++ b/sundynix-desktop/frontend/src/shell/JarvisSettings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Dialog } from "../ui/Dialog"; import { Button } from "../ui/Button"; import { useToast } from "../ui/Toast"; @@ -15,19 +15,31 @@ import { // 每用户 JARVIS 设置:名字 / 人设 / (高级)自带豆包配置。 // 名字与人设归用户自己;豆包配置齐全则语音走用户的账号,否则走系统兜底。 -export function JarvisSettings({ open, onClose }: { open: boolean; onClose: () => void }) { +export function JarvisSettings({ + open, + onClose, + onSaved, +}: { + open: boolean; + onClose: () => void; + onSaved?: () => void; // 保存成功后通知外层刷新(名字要立刻反映到语音坞/HUD,否则看不出生效) +}) { const toast = useToast(); const [cfg, setCfg] = useState(null); + const toastRef = useRef(toast); // effect 里用 ref 取最新 toast,避免它进依赖 + toastRef.current = toast; const [saving, setSaving] = useState(false); const [advanced, setAdvanced] = useState(false); + // 只在"打开"时拉一次。toast 不能进依赖——它一变就会重拉配置、把用户正在填的内容覆盖掉。 useEffect(() => { if (!open) return; setCfg(null); getMyJarvis() .then(setCfg) - .catch((e) => toast.push("error", (e as Error).message)); - }, [open, toast]); + .catch((e) => toastRef.current.push("error", (e as Error).message)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); const set = (k: keyof JarvisConfig, v: string) => setCfg((c) => (c ? { ...c, [k]: v } : c)); @@ -43,7 +55,8 @@ export function JarvisSettings({ open, onClose }: { open: boolean; onClose: () = tts_resource_id: cfg.tts_resource_id, tts_voice_type: cfg.tts_voice_type, }); - toast.push("success", "已保存,下次说话即生效"); + toast.push("success", `已保存:${cfg.name.trim() || "JARVIS"} · 下次说话即生效`); + onSaved?.(); onClose(); } catch (e) { toast.push("error", (e as Error).message); @@ -72,6 +85,15 @@ export function JarvisSettings({ open, onClose }: { open: boolean; onClose: () =
加载中…
) : (
+
+ 当前生效:助手名 {cfg.name.trim() || "JARVIS"} + {" · "}人设 {cfg.persona.trim() ? "已自定义" : "默认平和"} + {" · "}语音走 {cfg.has_own_voice ? "你自己的豆包配置" : "系统配置"} + + 验证是否生效:保存后按住空格问一句「你是谁」,它会用这个名字自称。 + +
+
- setSettingsOpen(false)} /> + setSettingsOpen(false)} onSaved={refreshName} /> {fullscreen && ( setToasts((t) => t.filter((x) => x.id !== id)); + // 必须 useMemo:`value={{ push }}` 每次渲染都造新对象,凡是把 useToast() 结果放进 + // useEffect 依赖的组件(有 7 处)都会在**任意一条 toast 弹出时**重跑 effect。 + // JarvisSettings 因此边填边被重新拉取覆盖;拉取失败还会 push 错误 toast → 无限循环狂闪。 + const value = useMemo(() => ({ push }), [push]); + return ( - + {children}
{toasts.map((t) => {