From 7e777fd661f346d76e971f4c969efbb08b118e51 Mon Sep 17 00:00:00 2001 From: zksnet Date: Sat, 18 Apr 2026 00:00:24 +0800 Subject: [PATCH] feat(chat): improve resilience and collapsible sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题描述:\n- 刷新页面、切后台或手机锁屏后,进行中的对话容易丢失,SSE 断开时前端还会插入假的错误气泡\n- 移动端首屏会话列表会短暂遮住聊天区\n- 桌面端侧栏无法折叠,在窄窗口和缩放场景占用过多横向空间\n\n复现路径:\n- 发起一轮对话,在模型仍在输出时刷新页面或锁屏后再回到页面\n- 在窄屏设备首次打开聊天页,观察会话列表首帧覆盖聊天内容\n- 在桌面端缩窄浏览器窗口,观察侧栏始终保持完整宽度\n\n修复思路:\n- 为 chat store 增加本地缓存、水合、in-flight 标记和轮询恢复,SSE 断开后静默从服务端回补真实结果\n- 将运行中指示统一到 isRunActive,让实时流式与恢复轮询共享同一状态\n- 在 ChatPanel 首帧同步读取媒体查询,避免移动端会话列表闪烁覆盖\n- 为侧栏增加可持久化的桌面折叠状态,并补充对应文案与回归测试 --- .../src/components/hermes/chat/ChatPanel.vue | 10 +- .../components/hermes/chat/MessageList.vue | 4 +- .../src/components/layout/AppSidebar.vue | 179 ++++++- packages/client/src/i18n/locales/en.ts | 2 + packages/client/src/i18n/locales/zh.ts | 2 + packages/client/src/stores/hermes/app.ts | 15 + packages/client/src/stores/hermes/chat.ts | 462 ++++++++++++++++-- tests/client/app-store.test.ts | 36 ++ tests/client/chat-store.test.ts | 226 +++++++++ tests/setup.ts | 2 + 10 files changed, 890 insertions(+), 48 deletions(-) create mode 100644 tests/client/app-store.test.ts create mode 100644 tests/client/chat-store.test.ts diff --git a/packages/client/src/components/hermes/chat/ChatPanel.vue b/packages/client/src/components/hermes/chat/ChatPanel.vue index 3279070f..0c14274a 100644 --- a/packages/client/src/components/hermes/chat/ChatPanel.vue +++ b/packages/client/src/components/hermes/chat/ChatPanel.vue @@ -11,7 +11,15 @@ const chatStore = useChatStore() const message = useMessage() const { t } = useI18n() -const showSessions = ref(true) +// Initialize synchronously from the media query so first paint is correct. +// On narrow viewports the session list is an absolute-positioned overlay +// (z-index 10) on top of the chat area; if we default to `true`, onMounted +// only flips it to `false` AFTER the first render, causing a visible flash +// where the session list covers the chat content ("auto-fixes after a +// moment" — that was the race). +const showSessions = ref( + typeof window === 'undefined' || !window.matchMedia('(max-width: 768px)').matches, +) let mobileQuery: MediaQueryList | null = null function handleSessionClick(sessionId: string) { diff --git a/packages/client/src/components/hermes/chat/MessageList.vue b/packages/client/src/components/hermes/chat/MessageList.vue index b1311f8e..03a177e9 100644 --- a/packages/client/src/components/hermes/chat/MessageList.vue +++ b/packages/client/src/components/hermes/chat/MessageList.vue @@ -45,7 +45,7 @@ watch( scrollToBottom, ); watch( - () => chatStore.isStreaming, + () => chatStore.isRunActive, (v) => { if (v) scrollToBottom(); }, @@ -61,7 +61,7 @@ watch(currentToolCalls, scrollToBottom); -
+