From 4e1d08f106e565a7434af5e5a139ebad617f0efe Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Wed, 15 Jul 2026 00:31:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E5=89=8D=E7=AB=AF=E4=BA=A4=E4=BA=92=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复运行时 intent Unicode 转义显示异常(后端 extract_intent_from_partial 解码) - 修复 ask_user 弹窗问题/说明不换行 - 修复刷新页面后等待回答提示不恢复(添加 fetchPendingUserQuestions 并在初始化路径调用) - 修复回答问题弹窗长问题撑满窗口(header/context 限高滚动) - 修复角色编辑器模型选择菜单定位与滚动 - 在 AGENTS.md 中再次强调前端调试日志必须统一筛选词 测试:/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/bin/python3.9 -m unittest test.test_server_refactor_smoke; npm run build --- AGENTS.md | 4 ++- server/chat_flow_runner_helpers.py | 12 ++++++- static/src/app/lifecycle.ts | 3 +- static/src/app/methods/conversation/load.ts | 2 ++ static/src/app/methods/ui/dialog.ts | 29 ++++++++++++++++ static/src/app/methods/ui/git.ts | 2 +- static/src/app/methods/ui/route.ts | 3 ++ static/src/app/methods/ui/socket.ts | 1 + static/src/components/input/InputComposer.vue | 34 ++++++++++++------- .../components/overlay/UserQuestionDialog.vue | 8 +++++ .../personalization/PersonalizationDrawer.vue | 26 +++++++++++++- test/test_server_refactor_smoke.py | 4 +++ 12 files changed, 111 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d8af071..59e5571 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -207,7 +207,9 @@ ### 前端调试日志 -- **统一筛选词**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。 +- **统一筛选词(强制)**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。 + - **同一次调试任务中严禁使用多个前缀**。如果涉及多个模块(如状态栏、Git 摘要、用户问题),应共用同一个前缀(如 `[status-bar-debug]`),通过日志对象里的字段区分模块,而不是发明多个前缀增加用户筛选成本。 + - 选择前缀时优先与本次排查的「用户可见现象」对齐,而不是与内部模块名对齐。 - **控制数量与时机**: - 禁止一次性输出上百上千条日志刷屏。 - 只在关键路径(入口、分支判断、状态变化、导航动作)打印,避免在循环、高频事件、每帧渲染中输出。 diff --git a/server/chat_flow_runner_helpers.py b/server/chat_flow_runner_helpers.py index 806ccbd..36e921d 100644 --- a/server/chat_flow_runner_helpers.py +++ b/server/chat_flow_runner_helpers.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import re from typing import Any, Dict, List, Optional @@ -10,7 +11,16 @@ def extract_intent_from_partial(arg_str: str) -> Optional[str]: return None match = re.search(r'"intent"\s*:\s*"([^"]{0,128})', arg_str, re.IGNORECASE | re.DOTALL) if match: - return match.group(1) + raw = match.group(1) + try: + # 某些模型会对非 ASCII 字符输出 JSON Unicode 转义(如 \u4e2d\u6587), + # 这里用 json.loads 安全解码,失败时回退原始值。 + decoded = json.loads('"{}"'.format(raw)) + if isinstance(decoded, str): + return decoded + except Exception: + pass + return raw return None diff --git a/static/src/app/lifecycle.ts b/static/src/app/lifecycle.ts index 6020a52..cfdc1a0 100644 --- a/static/src/app/lifecycle.ts +++ b/static/src/app/lifecycle.ts @@ -41,7 +41,6 @@ export async function mounted() { // 立即加载初始数据(并行获取状态,优先同步运行模式) const initialDataPromise = this.loadInitialData(); - this.refreshProjectGitSummary?.(); this.startProjectGitSummaryIdleRefresh?.(); this.startConnectionHeartbeat(); this.fetchTerminalCount(); @@ -50,6 +49,8 @@ export async function mounted() { if (initialDataPromise && typeof initialDataPromise.then === 'function') { initialDataPromise .then(() => { + // 初始数据加载完成后再刷新 git 摘要,确保 workspace/project_path 已就绪 + this.refreshProjectGitSummary?.(); // 避免在初始加载阶段被覆盖,加载完成后再兜底检查一次 this.checkTutorialPrompt(); }) diff --git a/static/src/app/methods/conversation/load.ts b/static/src/app/methods/conversation/load.ts index e65fcff..e07a6dc 100644 --- a/static/src/app/methods/conversation/load.ts +++ b/static/src/app/methods/conversation/load.ts @@ -232,6 +232,8 @@ export const loadMethods = { this.fetchNetworkPermission(); await this.fetchVersioningStatus(conversationId, { silent: true }); this.fetchPendingToolApprovals(); + this.fetchPendingUserQuestions(); + this.refreshProjectGitSummary(); this.fetchConversationTokenStatistics(); this.updateCurrentContextTokens(); traceLog('loadConversation:after-history', { diff --git a/static/src/app/methods/ui/dialog.ts b/static/src/app/methods/ui/dialog.ts index 25a33bb..ab7e526 100644 --- a/static/src/app/methods/ui/dialog.ts +++ b/static/src/app/methods/ui/dialog.ts @@ -58,6 +58,35 @@ export const dialogMethods = { this.userQuestionDialogVisible = true; this.userQuestionMinimized = false; }, + async fetchPendingUserQuestions() { + if (!this.currentConversationId) { + this.pendingUserQuestions = []; + return; + } + try { + const response = await fetch( + `/api/user-questions/pending?conversation_id=${encodeURIComponent(this.currentConversationId)}` + ); + const payload = await response.json().catch(() => ({})); + if (!response.ok || !payload?.success) { + return; + } + const items = Array.isArray(payload.items) ? payload.items : []; + this.pendingUserQuestions = items; + if (items.length > 0) { + this.userQuestionActiveIndex = Math.min( + Math.max(0, Number(this.userQuestionActiveIndex || 0)), + Math.max(0, items.length - 1) + ); + } else { + this.userQuestionDialogVisible = false; + this.userQuestionMinimized = false; + this.userQuestionActiveIndex = 0; + } + } catch (_error) { + // ignore + } + }, approveToolApproval(approvalId) { return this.decideToolApproval(approvalId, 'approved'); }, diff --git a/static/src/app/methods/ui/git.ts b/static/src/app/methods/ui/git.ts index cc4b90b..8566e80 100644 --- a/static/src/app/methods/ui/git.ts +++ b/static/src/app/methods/ui/git.ts @@ -70,7 +70,7 @@ export const gitMethods = { if (this.gitChangesPanelOpen) { this.loadGitChangesDiff?.(); } - } catch (_) { + } catch (_error) { this.projectGitSummary = null; this.gitChangesDiff = null; } finally { diff --git a/static/src/app/methods/ui/route.ts b/static/src/app/methods/ui/route.ts index 1fbfd23..f6773a6 100644 --- a/static/src/app/methods/ui/route.ts +++ b/static/src/app/methods/ui/route.ts @@ -151,6 +151,9 @@ export const routeMethods = { this.initialRouteResolved = true; } await this.restoreComposerDraftState('bootstrap-route:conversation'); + if (this.currentConversationId) { + this.fetchPendingUserQuestions?.(); + } }, handlePopState(event) { const state = event.state || {}; diff --git a/static/src/app/methods/ui/socket.ts b/static/src/app/methods/ui/socket.ts index dbc56d0..cd478a9 100644 --- a/static/src/app/methods/ui/socket.ts +++ b/static/src/app/methods/ui/socket.ts @@ -372,6 +372,7 @@ export const socketMethods = { this.fetchConversationTokenStatistics(); this.updateCurrentContextTokens(); await this.fetchVersioningStatus(this.currentConversationId, { silent: true }); + this.fetchPendingUserQuestions(); } catch (e) { console.warn('获取当前对话标题失败:', e); this.titleReady = true; diff --git a/static/src/components/input/InputComposer.vue b/static/src/components/input/InputComposer.vue index 19f0d27..7e7a360 100644 --- a/static/src/components/input/InputComposer.vue +++ b/static/src/components/input/InputComposer.vue @@ -170,7 +170,7 @@ >目标模式就绪