fix: use custom event for training panel toggle, no more poll inference
SkillInsertDialog dispatches SKILL_TOGGLED_EVENT on user click. AssistantWorkspace listens for that event to set applied state. Completely eliminates poll-based state inference that caused flash. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,8 @@ import { ClawSessionReplayProvider } from "@/lib/claw-session-replay";
|
|||||||
import { pushSessionUrl } from "@/lib/claw-session-url";
|
import { pushSessionUrl } from "@/lib/claw-session-url";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
|
SKILL_TOGGLED_EVENT,
|
||||||
|
type SkillToggledDetail,
|
||||||
useAppliedTraining,
|
useAppliedTraining,
|
||||||
useModelIterationEnabled,
|
useModelIterationEnabled,
|
||||||
} from "@/lib/use-training-mode";
|
} from "@/lib/use-training-mode";
|
||||||
@@ -329,31 +331,17 @@ function AssistantWorkspace() {
|
|||||||
closeActivityPanel();
|
closeActivityPanel();
|
||||||
}, [showPipeline, closeActivityPanel]);
|
}, [showPipeline, closeActivityPanel]);
|
||||||
|
|
||||||
// Skill 开关联动 applied:只有用户在本次页面会话中「主动点击」开启 skill
|
// 面板只通过用户在 SkillInsertDialog 里显式点击开/关 model-iteration 来控制。
|
||||||
// 才展开面板。页面加载时 skill 已经是 enabled 的情况不自动展开(避免闪烁)。
|
// 监听 SkillInsertDialog 发出的自定义事件,不再从 poll 状态变化推断。
|
||||||
// 判断依据:skill.loading 从 true→false 是初始加载完成,之后 enabled 变化
|
|
||||||
// 才是用户主动操作。
|
|
||||||
const skillLoadedOnce = useRef(false);
|
|
||||||
const prevSkillEnabled = useRef<boolean | null>(null);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (skill.loading) return;
|
const handler = (e: Event) => {
|
||||||
if (!skillLoadedOnce.current) {
|
const detail = (e as CustomEvent<SkillToggledDetail>).detail;
|
||||||
skillLoadedOnce.current = true;
|
if (detail.skill !== "model-iteration") return;
|
||||||
prevSkillEnabled.current = skill.enabled;
|
setApplied(detail.enabled);
|
||||||
// 初始加载:无条件清除 applied(清 localStorage 残留),面板只从用户点击打开
|
};
|
||||||
if (applied) {
|
window.addEventListener(SKILL_TOGGLED_EVENT, handler);
|
||||||
setApplied(false);
|
return () => window.removeEventListener(SKILL_TOGGLED_EVENT, handler);
|
||||||
}
|
}, [setApplied]);
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 初始加载之后的变化 = 用户主动操作
|
|
||||||
if (skill.enabled && !prevSkillEnabled.current) {
|
|
||||||
setApplied(true);
|
|
||||||
} else if (!skill.enabled && prevSkillEnabled.current) {
|
|
||||||
setApplied(false);
|
|
||||||
}
|
|
||||||
prevSkillEnabled.current = skill.enabled;
|
|
||||||
}, [skill.enabled, skill.loading, applied, setApplied]);
|
|
||||||
|
|
||||||
// 注意:用同一棵树 + 条件渲染,保证 <Thread /> 和 <ActivityPanel /> 在
|
// 注意:用同一棵树 + 条件渲染,保证 <Thread /> 和 <ActivityPanel /> 在
|
||||||
// pipeline 切换前后都保持挂载,否则它们会 remount,
|
// pipeline 切换前后都保持挂载,否则它们会 remount,
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ import {
|
|||||||
} from "@/lib/claw-active-session";
|
} from "@/lib/claw-active-session";
|
||||||
import { useClawSessionReplay } from "@/lib/claw-session-replay";
|
import { useClawSessionReplay } from "@/lib/claw-session-replay";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { dispatchSkillToggled } from "@/lib/use-training-mode";
|
||||||
|
|
||||||
type ComposerInsertEvent = CustomEvent<{ text: string }>;
|
type ComposerInsertEvent = CustomEvent<{ text: string }>;
|
||||||
|
|
||||||
@@ -1800,6 +1801,7 @@ function SkillInsertDialog() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
setSkills(payload);
|
setSkills(payload);
|
||||||
|
dispatchSkillToggled({ skill: skill.name, enabled });
|
||||||
setStatus(enabled ? `已启用 ${skill.name}` : `已停用 ${skill.name}`);
|
setStatus(enabled ? `已启用 ${skill.name}` : `已停用 ${skill.name}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSkills(previous);
|
setSkills(previous);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
export const SESSION_UPDATED_EVENT = "claw-session-updated";
|
export const SESSION_UPDATED_EVENT = "claw-session-updated";
|
||||||
|
export const SKILL_TOGGLED_EVENT = "claw-skill-toggled";
|
||||||
const APPLIED_STORAGE_PREFIX = "claw.training.applied:";
|
const APPLIED_STORAGE_PREFIX = "claw.training.applied:";
|
||||||
|
|
||||||
const MODEL_ITERATION_SKILL_NAMES = new Set(["model-iteration"]);
|
const MODEL_ITERATION_SKILL_NAMES = new Set(["model-iteration"]);
|
||||||
@@ -158,6 +159,15 @@ export function dispatchSessionUpdated(detail: SessionUpdatedDetail) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SkillToggledDetail = { skill: string; enabled: boolean };
|
||||||
|
|
||||||
|
export function dispatchSkillToggled(detail: SkillToggledDetail) {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent<SkillToggledDetail>(SKILL_TOGGLED_EVENT, { detail }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function useTrainingMode(sessionId: string | null) {
|
export function useTrainingMode(sessionId: string | null) {
|
||||||
const [isTraining, setIsTraining] = useState(false);
|
const [isTraining, setIsTraining] = useState(false);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user