From f7ce0559b7187b2561e1ae3be49840a0b74a0de9 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 18 Nov 2025 10:12:16 +0800 Subject: [PATCH] feat(web): add reasoning mode toggle --- config/api.py | 10 +++- core/main_terminal.py | 5 ++ core/web_terminal.py | 8 +-- static/app.js | 90 +++++++++++++++++++++++++-------- static/index.html | 42 +++++++++++----- static/style.css | 43 ++++++++++++++++ utils/api_client.py | 104 +++++++++++++++++++++++---------------- utils/context_manager.py | 6 ++- web_server.py | 39 +++++---------- 9 files changed, 235 insertions(+), 112 deletions(-) diff --git a/config/api.py b/config/api.py index 1179f8a..246a1cf 100644 --- a/config/api.py +++ b/config/api.py @@ -4,6 +4,11 @@ API_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3" API_KEY = "3e96a682-919d-45c1-acb2-53bc4e9660d3" MODEL_ID = "kimi-k2-250905" +# 推理模型配置(智能思考模式使用) +THINKING_API_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3" +THINKING_API_KEY = "3e96a682-919d-45c1-acb2-53bc4e9660d3" +THINKING_MODEL_ID = "kimi-k2-250905" + # Tavily 搜索 TAVILY_API_KEY = "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" @@ -16,10 +21,13 @@ __all__ = [ "MODEL_ID", "TAVILY_API_KEY", "DEFAULT_RESPONSE_MAX_TOKENS", + "THINKING_API_BASE_URL", + "THINKING_API_KEY", + "THINKING_MODEL_ID", ] ''' -API_BASE_URL = "https://api.moonshot.cn/v1", +API_BASE_URL = "https://api.moonshot.cn/v1" API_KEY = "sk-xW0xjfQM6Mp9ZCWMLlnHiRJcpEOIZPTkXcN0dQ15xpZSuw2y", MODEL_ID = "kimi-k2-0905-preview" ''' diff --git a/core/main_terminal.py b/core/main_terminal.py index 4a3759c..3cd64e3 100644 --- a/core/main_terminal.py +++ b/core/main_terminal.py @@ -2066,6 +2066,11 @@ class MainTerminal: if sub_agent_prompt: messages.append({"role": "system", "content": sub_agent_prompt}) + if self.thinking_mode: + thinking_prompt = self.load_prompt("thinking_mode_guidelines").strip() + if thinking_prompt: + messages.append({"role": "system", "content": thinking_prompt}) + # 添加对话历史(保留完整结构,包括tool_calls和tool消息) for conv in context["conversation"]: metadata = conv.get("metadata") or {} diff --git a/core/web_terminal.py b/core/web_terminal.py index c5a22f9..8ee8781 100644 --- a/core/web_terminal.py +++ b/core/web_terminal.py @@ -284,13 +284,7 @@ class WebTerminal(MainTerminal): def get_thinking_mode_status(self) -> str: """获取思考模式状态描述""" - if not self.thinking_mode: - return "快速模式" - else: - if self.api_client.current_task_first_call: - return "思考模式(等待新任务)" - else: - return "思考模式(任务进行中)" + return "思考模式" if self.thinking_mode else "快速模式" def get_focused_files_info(self) -> Dict: """获取聚焦文件信息(用于WebSocket更新)- 使用与 /api/focused 一致的格式""" diff --git a/static/app.js b/static/app.js index 611c8f3..237c414 100644 --- a/static/app.js +++ b/static/app.js @@ -111,6 +111,7 @@ async function bootstrapApp() { projectPath: '', agentVersion: '', thinkingMode: '未知', + nextThinkingMode: null, // 消息相关 messages: [], @@ -161,6 +162,7 @@ async function bootstrapApp() { // 对话历史侧边栏 sidebarCollapsed: true, // 默认收起对话侧边栏 panelMode: 'files', // files | todo | subAgents + panelMenuOpen: false, subAgents: [], subAgentPollTimer: null, conversations: [], @@ -253,6 +255,7 @@ async function bootstrapApp() { document.addEventListener('click', this.handleClickOutsideSettings); document.addEventListener('click', this.handleClickOutsideToolMenu); + document.addEventListener('click', this.handleClickOutsidePanelMenu); window.addEventListener('popstate', this.handlePopState); this.onDocumentClick = (event) => { @@ -292,6 +295,7 @@ async function bootstrapApp() { beforeUnmount() { document.removeEventListener('click', this.handleClickOutsideSettings); document.removeEventListener('click', this.handleClickOutsideToolMenu); + document.removeEventListener('click', this.handleClickOutsidePanelMenu); window.removeEventListener('popstate', this.handlePopState); if (this.onDocumentClick) { document.removeEventListener('click', this.onDocumentClick); @@ -590,6 +594,7 @@ async function bootstrapApp() { this.projectPath = data.project_path || ''; this.agentVersion = data.version || this.agentVersion; this.thinkingMode = data.thinking_mode || '未知'; + this.nextThinkingMode = null; console.log('系统就绪:', data); // 系统就绪后立即加载对话列表 @@ -1318,7 +1323,13 @@ async function bootstrapApp() { const statusData = await statusResponse.json(); this.projectPath = statusData.project_path || ''; this.agentVersion = statusData.version || this.agentVersion; - this.thinkingMode = statusData.thinking_mode || '未知'; + if (statusData.thinking_mode) { + this.thinkingMode = statusData.thinking_mode.label || '未知'; + this.nextThinkingMode = statusData.thinking_mode.next ?? null; + } else { + this.thinkingMode = '未知'; + this.nextThinkingMode = null; + } // 获取当前对话信息 const statusConversationId = statusData.conversation && statusData.conversation.current_id; @@ -1629,30 +1640,34 @@ async function bootstrapApp() { }; } - // 处理思考内容 - 支持多种格式 const content = message.content || ''; - const thinkPatterns = [ - /([\s\S]*?)<\/think>/g, - /([\s\S]*?)<\/thinking>/g - ]; + let reasoningText = (message.reasoning_content || '').trim(); - let allThinkingContent = ''; - for (const pattern of thinkPatterns) { - let match; - while ((match = pattern.exec(content)) !== null) { - allThinkingContent += match[1].trim() + '\n'; + if (!reasoningText) { + const thinkPatterns = [ + /([\s\S]*?)<\/think>/g, + /([\s\S]*?)<\/thinking>/g + ]; + + let extracted = ''; + for (const pattern of thinkPatterns) { + let match; + while ((match = pattern.exec(content)) !== null) { + extracted += (match[1] || '').trim() + '\n'; + } } + reasoningText = extracted.trim(); } - if (allThinkingContent) { + if (reasoningText) { currentAssistantMessage.actions.push({ id: `history-think-${Date.now()}-${Math.random()}`, type: 'thinking', - content: allThinkingContent.trim(), + content: reasoningText, streaming: false, timestamp: Date.now() }); - console.log('添加思考内容:', allThinkingContent.substring(0, 50) + '...'); + console.log('添加思考内容:', reasoningText.substring(0, 50) + '...'); } // 处理普通文本内容(移除思考标签后的内容) @@ -1660,10 +1675,15 @@ async function bootstrapApp() { const appendPayloadMeta = metadata.append_payload; const modifyPayloadMeta = metadata.modify_payload; - let textContent = content - .replace(/[\s\S]*?<\/think>/g, '') - .replace(/[\s\S]*?<\/thinking>/g, '') - .trim(); + let textContent = content; + if (!message.reasoning_content) { + textContent = textContent + .replace(/[\s\S]*?<\/think>/g, '') + .replace(/[\s\S]*?<\/thinking>/g, '') + .trim(); + } else { + textContent = textContent.trim(); + } if (appendPayloadMeta) { currentAssistantMessage.actions.push({ @@ -2080,6 +2100,24 @@ async function bootstrapApp() { this.fetchSubAgents(); } }, + + togglePanelMenu() { + this.panelMenuOpen = !this.panelMenuOpen; + }, + + selectPanelMode(mode) { + if (this.panelMode === mode) { + this.panelMenuOpen = false; + return; + } + this.panelMode = mode; + this.panelMenuOpen = false; + if (mode === 'todo') { + this.fetchTodoList(); + } else if (mode === 'subAgents') { + this.fetchSubAgents(); + } + }, formatTaskStatus(task) { if (!task) { @@ -2114,10 +2152,10 @@ async function bootstrapApp() { return; } const { protocol, hostname } = window.location; + const base = `${protocol}//${hostname}:8092`; const parentConv = agent.conversation_id || this.currentConversationId || ''; const convSegment = this.stripConversationPrefix(parentConv); const agentLabel = agent.agent_id ? `sub_agent${agent.agent_id}` : agent.task_id; - const base = `${protocol}//${hostname}:8092`; const pathSuffix = convSegment ? `/${convSegment}+${agentLabel}` : `/sub_agent/${agent.task_id}`; @@ -2331,6 +2369,17 @@ async function bootstrapApp() { this.toolMenuOpen = false; } }, + + handleClickOutsidePanelMenu(event) { + if (!this.panelMenuOpen) { + return; + } + const wrapper = this.$refs.panelMenuWrapper; + if (wrapper && wrapper.contains(event.target)) { + return; + } + this.panelMenuOpen = false; + }, applyToolSettingsSnapshot(categories) { if (!Array.isArray(categories)) { @@ -2492,7 +2541,8 @@ async function bootstrapApp() { 'todo_finish': '🏁', 'todo_finish_confirm': '❗', 'create_sub_agent': '🤖', - 'wait_sub_agent': '⏳' + 'wait_sub_agent': '⏳', + 'close_sub_agent': '🛑' }; return icons[toolName] || '⚙️'; }, diff --git a/static/index.html b/static/index.html index c644a0a..8502957 100644 --- a/static/index.html +++ b/static/index.html @@ -128,13 +128,29 @@