diff --git a/apps/web/app/components/agent/agent-drawer.tsx b/apps/web/app/components/agent/agent-drawer.tsx index ea124aac..6dee2a57 100644 --- a/apps/web/app/components/agent/agent-drawer.tsx +++ b/apps/web/app/components/agent/agent-drawer.tsx @@ -28,6 +28,7 @@ import { setAgentDrawerOpen, shortGoalTitle, showAgentRunInDrawer, + syncAgentContextToPath, takeReturnFocus, toggleAgentDrawer, useAgentDrawer, @@ -104,6 +105,7 @@ export function AgentDrawer() { const content = useRef(null); // 记下最近的非智能体页面:会话页「在侧边打开」回到这里 useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]); + useEffect(() => syncAgentContextToPath(pathname), [pathname]); const name = useContextName(context); const mock = agentMockEnabled(); // 目标前缀带名称和 ID:名称给人看,ID 给模型用 diff --git a/apps/web/app/components/agent/agent-nav.test.ts b/apps/web/app/components/agent/agent-nav.test.ts index 3cdc6db0..11e862fd 100644 --- a/apps/web/app/components/agent/agent-nav.test.ts +++ b/apps/web/app/components/agent/agent-nav.test.ts @@ -17,7 +17,19 @@ import { writeDraft, } from "./agent-nav.ts"; import { CompactPager } from "./agent-session-list.tsx"; -import { agentReturnPath, isAgentPage, noteAgentReturnPath, outletKey, toggleAction } from "./agent-store.ts"; +import { + agentReturnPath, + clearAgentContext, + isAgentPage, + noteAgentReturnPath, + openAgentDrawer, + outletKey, + setAgentDrawerOpen, + showAgentRunInDrawer, + syncAgentContextToPath, + toggleAction, + useAgentDrawer, +} from "./agent-store.ts"; import { cachedRunEvents, cacheRunEvents } from "./use-agent-stream.ts"; // 智能体页面切换的状态逻辑:整页重挂载 key、分栏与 URL、草稿、滚动策略、快捷键在智能体页面的行为。 @@ -176,3 +188,33 @@ describe("会话页「在侧边打开」回到之前的页面", () => { expect(agentReturnPath()).toBe("/console/sites"); }); }); + +describe("抽屉开着时路由变化", () => { + // useAgentDrawer 是 useSyncExternalStore,测试里直接读快照 + const snapshot = () => { + let out: ReturnType | null = null; + const Probe = () => { + out = useAgentDrawer(); + return null; + }; + renderToStaticMarkup(createElement(Probe)); + return out as unknown as ReturnType; + }; + it("新对话的路由上下文跟着页面换", () => { + openAgentDrawer({ label: "作品 aaaa", entityId: "aaaa", fromPath: true }); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toMatchObject({ siteId: "hdh", fromPath: true }); + syncAgentContextToPath("/console/home"); + expect(snapshot().context).toBeNull(); + }); + it("页面按钮带的上下文、正在看的会话、抽屉关着时不动", () => { + openAgentDrawer({ label: "作品:沙丘", entityId: "e1" }); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toMatchObject({ entityId: "e1" }); + clearAgentContext(); + showAgentRunInDrawer("r1"); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toBeNull(); + setAgentDrawerOpen(false); + }); +}); diff --git a/apps/web/app/components/agent/agent-store.ts b/apps/web/app/components/agent/agent-store.ts index 7d499987..3a21c58d 100644 --- a/apps/web/app/components/agent/agent-store.ts +++ b/apps/web/app/components/agent/agent-store.ts @@ -84,6 +84,18 @@ export function newAgentConversation() { emit(); } +/** + * 抽屉开着时路由变了(浏览器前进后退、会话里的链接):还在新对话、上下文是按路由推断的,就换成新页面的对象; + * 页面按钮带进来的上下文、已经在看的会话不动。 + */ +export function syncAgentContextToPath(pathname: string) { + if (!state.open || state.runId || (state.context && !state.context.fromPath)) return; + const next = contextFromPath(pathname); + if (JSON.stringify(next) === JSON.stringify(state.context)) return; + state = { ...state, context: next }; + emit(); +} + /** 去掉上下文(输入框上的名称标签 ×) */ export function clearAgentContext() { state = { ...state, context: null };