diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 6d8dc26..f249c22 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -81,10 +81,16 @@ async function createWindow(): Promise { } }) + // ready-to-show 在 Electron 中可能不止一次触发(实测:回合中途首次挂载受管浏览器 + // WebContentsView 后会再次发出)。会话初始化只能做一次——二次触发会全量广播 + // session_switched,把渲染层进行中的回合状态清掉(过程区误折叠/反复横跳的根因)。 + let sessionInitialized = false win.on('ready-to-show', () => { win.show() + if (sessionInitialized) return + sessionInitialized = true // pi 会话初始化放在窗口就绪后,错误可以第一时间在 UI 上呈现 - agentService.ensureReady().catch((err) => { + agentService.ensureReady().catch((err: unknown) => { console.error('[main] pi session init failed:', err) }) }) diff --git a/apps/desktop/src/renderer/src/components/MessageItem.tsx b/apps/desktop/src/renderer/src/components/MessageItem.tsx index d1321c1..c9f5afb 100644 --- a/apps/desktop/src/renderer/src/components/MessageItem.tsx +++ b/apps/desktop/src/renderer/src/components/MessageItem.tsx @@ -252,7 +252,8 @@ function AssistantTurnBase({ // live 回合从一开始就可见,挂载内容;一旦挂载过就保持(再次折叠走 GSAP // 高度动画需要内容在场,且 Thinking 面板内部滚动状态不重置)。 const [contentMounted, setContentMounted] = useState(() => live) - const autoCollapsedRef = useRef(false) + // 用户手动收起过的回合不再自动展开;自动收起(结算/技术性重挂载)不受此限 + const userCollapsedRef = useRef(false) const prevLiveRef = useRef(live) useEffect(() => { const prev = prevLiveRef.current @@ -260,14 +261,14 @@ function AssistantTurnBase({ if (prev && !live) { const timer = window.setTimeout(() => { if (scrollPinnedRef?.current !== false) { - autoCollapsedRef.current = true setCollapsed(true) } }, 600) return () => window.clearTimeout(timer) } - if (!prev && live && autoCollapsedRef.current) { - autoCollapsedRef.current = false + // live 恢复时重新展开(含重挂载场景:初始收起的回合在回合续跑后自愈展开, + // 不依赖随重挂载丢失的 ref 状态);用户手动收起过的除外 + if (!prev && live && !userCollapsedRef.current) { setContentMounted(true) setCollapsed(false) } @@ -401,7 +402,7 @@ function AssistantTurnBase({ type="button" aria-expanded={!collapsed} onClick={() => { - autoCollapsedRef.current = false + userCollapsedRef.current = true setContentMounted(true) setCollapsed((v) => !v) }} diff --git a/apps/desktop/src/renderer/src/hooks/useAgent.ts b/apps/desktop/src/renderer/src/hooks/useAgent.ts index 48de44d..b4bae84 100644 --- a/apps/desktop/src/renderer/src/hooks/useAgent.ts +++ b/apps/desktop/src/renderer/src/hooks/useAgent.ts @@ -178,22 +178,27 @@ function reducer(state: AgentUiState, action: Action): AgentUiState { return { ...state, ready: true, sessionState: event.state } case 'session_error': return { ...state, sessionError: event.message } - case 'session_switched': + case 'session_switched': { + // 同一会话的技术性重发(初始化重放等)不得打断进行中的回合:保留回合 + // 状态,仅刷新会话数据与历史。真正的切换(新会话/切换/分叉)会话 id + // 不同,走完整重置。 + const sameSession = state.sessionState?.sessionId === event.state.sessionId return { ...state, ready: true, error: null, retry: null, - streaming: false, - turnActive: false, - queue: { steering: [], followUp: [] }, + streaming: sameSession ? state.streaming : false, + turnActive: sameSession ? state.turnActive : false, + queue: sameSession ? state.queue : { steering: [], followUp: [] }, stats: null, - compacting: false, - compactionNotice: null, + compacting: sameSession ? state.compacting : false, + compactionNotice: sameSession ? state.compactionNotice : null, sessionState: event.state, messages: historyToMessages(event.messages), tools: historyToTools(event.messages) } + } case 'agent_start': return { ...state, streaming: true, turnActive: true, error: null, retry: null } case 'agent_end':