# Agent 对话页面体验优化 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 优化 agent 对话页面的 6 个体验问题:Skill 视觉跳变、Token 不准、Token UI、缺少任务规划、思考不流畅、思考级别控制。 **Architecture:** 后端扩展 thinking.ts 适配层 + 新增 TodoStore/todo 工具 + WS 协议新增 4 个事件;前端用 skill-card 替代双组件、新增 todo-card/thinking-block/token-stats/thinking-level-selector 5 个组件,store 统一管理新状态。 **Tech Stack:** Midway.js 3.20 + TypeORM + Socket.IO (后端) / Vue 3.5 + Element Plus 2.9 + Pinia (前端) **设计文档:** `docs/superpowers/specs/2026-04-13-agent-chat-ux-overhaul-design.md` --- ## 文件结构 ### 后端 (`packages/backend/src/modules/netaclaw/`) | 文件 | 操作 | 职责 | |------|------|------| | `runtime/todo_store.ts` | 新建 | TodoStore 类:write/read/merge/hydrate/formatForInjection | | `tools/todo_tool.ts` | 新建 | todo 工具 Schema + handler | | `runtime/thinking.ts` | 修改 | 扩展:buildThinkingParams、getModelThinkingCapability、预算映射 | | `plugins/llm_providers/anthropic.ts` | 修改 | 传递 thinking 参数 + 流式 thinking_delta | | `plugins/llm_providers/openai.ts` | 修改 | 传递 reasoning_effort 参数 | | `runtime/agent.ts` | 修改 | 会话级 thinkLevel 优先级链 + todo 工具注册 | | `gateway/server.ts` | 修改 | 新增 WS 事件 + set_thinking_level 处理 | ### 前端 (`packages/frontend/src/modules/agent/`) | 文件 | 操作 | 职责 | |------|------|------| | `components/skill-card.vue` | 新建 | 统一 Skill 卡片(替代 skill-indicator + message-item 内嵌) | | `components/todo-card.vue` | 新建 | Todo 任务规划卡片 | | `components/token-stats.vue` | 新建 | 紧凑 Token 统计组件 | | `components/thinking-block.vue` | 新建 | 思考内容流式展示 | | `components/thinking-level-selector.vue` | 新建 | 思考级别下拉选择器 | | `types/index.d.ts` | 修改 | 新增 TodoItem/TodoSummary/ThinkLevel/ModelThinkingCapability 类型 | | `store/chat.ts` | 修改 | 新增 todo/thinking/thinkLevel 状态 + 事件处理 | | `views/chat.vue` | 修改 | 布局重构:插入新组件 + token 统计移位 | | `components/message-item.vue` | 修改 | 删除旧 skill 历史 + 旧 thinking 区块,改用新组件 | | `components/chat-sidebar.vue` | 修改 | 会话项增加 todo 进度 | | `hooks/websocket.ts` | 修改 | 类型增加新事件 | | `components/skill-indicator.vue` | 删除 | 被 skill-card.vue 替代 | --- ## 任务依赖顺序 ``` Task 1: 前端类型定义(无依赖) Task 2: 后端 TodoStore(无依赖) Task 3: 后端 todo 工具(依赖 Task 2) Task 4: 后端 thinking.ts 扩展(无依赖) Task 5: 后端 Anthropic 适配器(依赖 Task 4) Task 6: 后端 OpenAI 适配器(依赖 Task 4) Task 7: 后端 agent.ts 改造(依赖 Task 2, 3, 4) Task 8: 后端 gateway WS 协议(依赖 Task 2, 3, 7) Task 9: 前端 store/chat.ts 改造(依赖 Task 1) Task 10: 前端 skill-card.vue(依赖 Task 9) Task 11: 前端 todo-card.vue(依赖 Task 9) Task 12: 前端 thinking-block.vue(依赖 Task 9) Task 13: 前端 token-stats.vue(依赖 Task 9) Task 14: 前端 thinking-level-selector.vue(依赖 Task 9) Task 15: 前端 chat.vue 布局重构(依赖 Task 10-14) Task 16: 前端 message-item.vue 改造(依赖 Task 10, 12) Task 17: 前端 chat-sidebar.vue 适配(依赖 Task 9) Task 18: 删除 skill-indicator.vue + 清理(依赖 Task 15, 16) ``` --- ## Task 1: 前端类型定义 **Files:** - Modify: `packages/frontend/src/modules/agent/types/index.d.ts:119-169` - [ ] **Step 1: 在 types/index.d.ts 中新增类型定义** 在文件末尾追加: ```typescript // === Todo 系统类型 === export interface TodoItem { id: string; content: string; status: 'pending' | 'in_progress' | 'completed' | 'cancelled'; } export interface TodoSummary { total: number; pending: number; in_progress: number; completed: number; cancelled: number; } // === 思考级别类型 === export type ThinkLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'adaptive'; export interface ModelThinkingCapability { supported: boolean; adaptive: boolean; levels: ThinkLevel[]; defaultLevel: ThinkLevel; } // === Token 统计类型 === export interface TokenUpdateEvent { current: { inputTokens: number; outputTokens: number; totalTokens: number; apiCalls: number; }; context: { usedTokens: number; maxTokens: number; percent: number; }; } ``` - [ ] **Step 2: 更新 WSServerEvent 类型,增加新事件** 找到 `WSServerEvent` 类型定义(约第119行),在 type 联合类型中追加: ```typescript // 在现有 type 联合中追加: | 'todo_update' | 'thinking_delta' | 'thinking_done' ``` - [ ] **Step 3: 更新 hooks/websocket.ts 中的 WSClientMessage 类型** 在 `hooks/websocket.ts` 中找到客户端消息类型,追加: ```typescript | 'set_thinking_level' ``` - [ ] **Step 4: 提交** ```bash git add packages/frontend/src/modules/agent/types/index.d.ts packages/frontend/src/modules/agent/hooks/websocket.ts git commit -m "feat(agent): 新增 Todo/Thinking/Token 类型定义和 WS 事件类型" ``` --- ## Task 2: 后端 TodoStore **Files:** - Create: `packages/backend/src/modules/netaclaw/runtime/todo_store.ts` - [ ] **Step 1: 创建 todo_store.ts** ```typescript /** * TodoStore - 会话级任务列表管理 * 参考 hermes-agent-main 的 TodoStore 设计 */ export interface TodoItem { id: string; content: string; status: 'pending' | 'in_progress' | 'completed' | 'cancelled'; } export interface TodoSummary { total: number; pending: number; in_progress: number; completed: number; cancelled: number; } const VALID_STATUSES = ['pending', 'in_progress', 'completed', 'cancelled']; export class TodoStore { private items: TodoItem[] = []; /** * 写入 todo 列表 * @param todos 任务项数组 * @param merge false=全量替换,true=按 id 增量更新+追加新项 */ write(todos: TodoItem[], merge = false): TodoItem[] { if (!merge) { this.items = todos.map(t => this.validate(t)); } else { for (const todo of todos) { const validated = this.validate(todo); const existing = this.items.find(i => i.id === validated.id); if (existing) { Object.assign(existing, validated); } else { this.items.push(validated); } } } return this.read(); } read(): TodoItem[] { return JSON.parse(JSON.stringify(this.items)); } hasItems(): boolean { return this.items.length > 0; } getSummary(): TodoSummary { const s: TodoSummary = { total: 0, pending: 0, in_progress: 0, completed: 0, cancelled: 0 }; for (const item of this.items) { s.total++; s[item.status]++; } return s; } /** 上下文压缩后注入,只保留 pending + in_progress */ formatForInjection(): string | null { const active = this.items.filter(i => i.status === 'pending' || i.status === 'in_progress'); if (active.length === 0) return null; const lines = active.map(i => { const marker = i.status === 'in_progress' ? '[>]' : '[ ]'; return `- ${marker} ${i.id}. ${i.content} (${i.status})`; }); return '[你的活跃任务列表在上下文压缩后被保留]\n' + lines.join('\n'); } /** 从历史消息中恢复 todo 状态 */ static hydrateFromHistory(messages: any[]): TodoStore { const store = new TodoStore(); for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg.role === 'tool' && msg.toolName === 'todo') { try { const data = JSON.parse(typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)); if (data.todos) { store.write(data.todos, false); } } catch { /* 忽略解析失败 */ } break; } } return store; } private validate(item: Partial): TodoItem { return { id: item.id || '?', content: item.content || '(无描述)', status: VALID_STATUSES.includes(item.status!) ? item.status! as TodoItem['status'] : 'pending', }; } } ``` - [ ] **Step 2: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/todo_store.ts git commit -m "feat(netaclaw): 新增 TodoStore 类 - 支持 merge/replace/hydrate/压缩注入" ``` --- ## Task 3: 后端 todo 工具 **Files:** - Create: `packages/backend/src/modules/netaclaw/tools/todo_tool.ts` - [ ] **Step 1: 创建 todo_tool.ts** ```typescript import { TodoStore } from '../runtime/todo_store.js'; export const todoToolSchema = { name: 'todo', description: '管理当前会话的任务列表。用于3步以上的复杂任务或用户提供多个任务时。\n' + '不传参数=读取当前列表。\n' + '传 todos 数组=写入。merge=false(默认)全量替换整个计划,merge=true 按 id 增量更新。\n' + '每个项: { id: string, content: string, status: pending|in_progress|completed|cancelled }\n' + '列表顺序=优先级。同一时间只能有一个 in_progress。\n' + '完成立即标记 completed。失败则 cancel 并添加修正项。\n' + '始终返回完整列表。', parameters: { type: 'object' as const, properties: { todos: { type: 'array' as const, description: '任务项数组。省略则读取当前列表。', items: { type: 'object' as const, properties: { id: { type: 'string' as const, description: '唯一标识' }, content: { type: 'string' as const, description: '任务描述' }, status: { type: 'string' as const, enum: ['pending', 'in_progress', 'completed', 'cancelled'], description: '当前状态', }, }, required: ['id', 'content', 'status'], }, }, merge: { type: 'boolean' as const, description: 'true=按 id 增量更新+追加新项。false(默认)=全量替换整个列表。', default: false, }, }, required: [] as string[], }, }; /** 执行 todo 工具 */ export function executeTodo( store: TodoStore, args: { todos?: any[]; merge?: boolean } ): { todos: any[]; summary: any } { if (args.todos && args.todos.length > 0) { store.write(args.todos, args.merge ?? false); } return { todos: store.read(), summary: store.getSummary(), }; } ``` - [ ] **Step 2: 提交** ```bash git add packages/backend/src/modules/netaclaw/tools/todo_tool.ts git commit -m "feat(netaclaw): 新增 todo 工具 Schema 和 handler" ``` --- ## Task 4: 后端 thinking.ts 扩展 **Files:** - Modify: `packages/backend/src/modules/netaclaw/runtime/thinking.ts` (当前12行) - [ ] **Step 1: 扩展 thinking.ts,新增预算映射和多模型适配** 保留现有的 `resolveThinkingDefault` 和 `isThinkingSupported`,在文件末尾追加: ```typescript import type { ThinkLevel } from '../plugins/plugin_entry.js'; // Anthropic 思考预算映射(参考 hermes THINKING_BUDGET) export const ANTHROPIC_BUDGET_MAP: Record = { minimal: 2000, low: 4000, medium: 8000, high: 16000, }; // Anthropic adaptive effort 映射 export const ANTHROPIC_ADAPTIVE_MAP: Record = { minimal: 'low', low: 'low', medium: 'medium', high: 'high', adaptive: 'medium', }; // OpenAI reasoning effort 映射 export const OPENAI_EFFORT_MAP: Record = { minimal: 'low', low: 'low', medium: 'medium', high: 'high', }; /** 检测模型是否支持 adaptive thinking(Claude 4.6+) */ export function supportsAdaptiveThinking(model: string): boolean { return /4[-.]6/.test(model); } /** 模型思考能力描述 */ export interface ModelThinkingCapability { supported: boolean; adaptive: boolean; levels: ThinkLevel[]; defaultLevel: ThinkLevel; } /** 获取模型的思考能力 */ export function getModelThinkingCapability(supplier: string, model: string): ModelThinkingCapability { if (supplier === 'anthropic' && supportsAdaptiveThinking(model)) { return { supported: true, adaptive: true, levels: ['off', 'minimal', 'low', 'medium', 'high', 'adaptive'], defaultLevel: 'adaptive' }; } if (supplier === 'anthropic') { return { supported: true, adaptive: false, levels: ['off', 'low', 'medium', 'high'], defaultLevel: 'medium' }; } if (supplier === 'openai' && /^(o1|o3)/.test(model)) { return { supported: true, adaptive: false, levels: ['off', 'low', 'medium', 'high'], defaultLevel: 'medium' }; } if (supplier === 'deepseek' && model.includes('r1')) { return { supported: true, adaptive: false, levels: ['off', 'low', 'medium', 'high'], defaultLevel: 'medium' }; } return { supported: false, adaptive: false, levels: ['off'], defaultLevel: 'off' }; } /** 构建 Anthropic 思考参数 */ export function buildAnthropicThinkingParams(model: string, level: ThinkLevel) { if (level === 'off') return null; if (supportsAdaptiveThinking(model)) { return { thinking: { type: 'adaptive' as const }, betas: ['interleaved-thinking-2025-05-14'], }; } const budget = ANTHROPIC_BUDGET_MAP[level] || 8000; return { thinking: { type: 'enabled' as const, budget_tokens: budget }, temperature: 1.0, maxTokensAdjust: budget + 4096, betas: ['interleaved-thinking-2025-05-14'], }; } /** 构建 OpenAI/DeepSeek 思考参数 */ export function buildOpenAIThinkingParams(level: ThinkLevel) { if (level === 'off') return null; return { reasoning_effort: OPENAI_EFFORT_MAP[level] || 'medium', }; } ``` - [ ] **Step 2: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/thinking.ts git commit -m "feat(netaclaw): 扩展 thinking.ts - 多模型预算映射和能力检测" ``` --- ## Task 5: 后端 Anthropic 适配器改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/plugins/llm_providers/anthropic.ts:23-30,66` - [ ] **Step 1: 导入 buildAnthropicThinkingParams 并传递思考参数** 在文件顶部导入: ```typescript import { buildAnthropicThinkingParams } from '../../runtime/thinking.js'; ``` 在 `chat()` 方法中 `client.messages.create()` 调用处(约第23-30行),在构建请求参数时加入思考参数: ```typescript const thinkParams = buildAnthropicThinkingParams(config.model, config.thinkLevel || 'off'); const createParams: any = { model: config.model, max_tokens: config.maxTokens || 4096, messages, tools, // ...现有参数 }; if (thinkParams) { createParams.thinking = thinkParams.thinking; if (thinkParams.temperature !== undefined) { createParams.temperature = thinkParams.temperature; } if (thinkParams.maxTokensAdjust) { createParams.max_tokens = Math.max(createParams.max_tokens, thinkParams.maxTokensAdjust); } } // 添加 beta headers const headers: Record = {}; if (thinkParams?.betas?.length) { headers['anthropic-beta'] = thinkParams.betas.join(','); } ``` - [ ] **Step 2: 提交** ```bash git add packages/backend/src/modules/netaclaw/plugins/llm_providers/anthropic.ts git commit -m "feat(netaclaw): Anthropic 适配器传递 thinking 参数和 beta headers" ``` --- ## Task 6: 后端 OpenAI 适配器改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/plugins/llm_providers/openai.ts:18-24` - [ ] **Step 1: 导入 buildOpenAIThinkingParams 并传递参数** 在文件顶部导入: ```typescript import { buildOpenAIThinkingParams } from '../../runtime/thinking.js'; ``` 在 `chat()` 方法中 `client.chat.completions.create()` 调用处(约第18-24行),加入: ```typescript const thinkParams = buildOpenAIThinkingParams(config.thinkLevel || 'off'); const createParams: any = { model: config.model, messages, tools, // ...现有参数 }; if (thinkParams) { createParams.reasoning_effort = thinkParams.reasoning_effort; } ``` - [ ] **Step 2: 提交** ```bash git add packages/backend/src/modules/netaclaw/plugins/llm_providers/openai.ts git commit -m "feat(netaclaw): OpenAI 适配器传递 reasoning_effort 参数" ``` --- ## Task 7: 后端 agent.ts 改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/runtime/agent.ts:36-45,57-66` - [ ] **Step 1: 导入 TodoStore 和 todo 工具,修改 thinkLevel 优先级链** 在文件顶部导入: ```typescript import { TodoStore } from './todo_store.js'; import { todoToolSchema, executeTodo } from '../tools/todo_tool.js'; ``` 修改 `runAgent()` 函数中 thinkLevel 的获取逻辑(约第36行): ```typescript // 优先级链:会话级 > agent 配置 > 模型默认 const thinkLevel = params.sessionThinkLevel ?? config.defaultThinkLevel ?? resolveThinkingDefault(config.model); ``` 在工具列表构建处,将 todo 工具加入: ```typescript // 在现有工具列表中追加 todo 工具 const todoStore = params.todoStore ?? new TodoStore(); const todoTool = { ...todoToolSchema, execute: async (args: any) => { const result = executeTodo(todoStore, args); // 通过回调通知前端 params.onTodoUpdate?.(result); return JSON.stringify(result); }, }; ``` - [ ] **Step 2: 在 AgentConfig 接口中新增 defaultThinkLevel 字段** ```typescript interface AgentConfig { // ...现有字段(name, systemPrompt, model, apiKey, baseUrl, temperature, maxTokens, maxToolRounds, skills) defaultThinkLevel?: ThinkLevel; // agent 配置的默认思考级别 } ``` - [ ] **Step 3: 在 AgentRunParams 接口中新增字段** ```typescript interface AgentRunParams { // ...现有字段 sessionThinkLevel?: ThinkLevel; // 会话级思考级别 todoStore?: TodoStore; // TodoStore 实例 onTodoUpdate?: (data: { todos: any[]; summary: any }) => void; // todo 更新回调 onThinkingDelta?: (text: string) => void; // 思考流式回调(替代 onThinking) } ``` - [ ] **Step 4: 提交** ```bash git add packages/backend/src/modules/netaclaw/runtime/agent.ts git commit -m "feat(netaclaw): agent.ts 集成 TodoStore + 会话级思考级别" ``` --- ## Task 8: 后端 gateway WS 协议改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/gateway/server.ts:77-251` - [ ] **Step 1: 处理 set_thinking_level 客户端消息** 在 `@OnWSMessage('message')` 处理函数中(约第77行),增加对 `set_thinking_level` 消息的处理: ```typescript case 'set_thinking_level': { const { level } = parsed.data; // 更新当前 session 的思考级别 session.thinkLevel = level; break; } ``` - [ ] **Step 2: 修改 handleChat 中的 runAgent 调用,新增回调** 在 `handleChat()` 方法中(约第193-213行),修改 `runAgent()` 调用: ```typescript const todoStore = session.todoStore ?? new TodoStore(); session.todoStore = todoStore; await runAgent({ // ...现有参数 sessionThinkLevel: session.thinkLevel, todoStore, onTodoUpdate: (data) => { this.send({ type: 'todo_update', sessionId: sid, data }); }, onThinkingDelta: (text) => { this.send({ type: 'thinking_delta', sessionId: sid, data: { text } }); }, onThinking: (text) => { // 保留完整思考文本用于存储 thinkingText += text; // 不再发送旧的 'thinking' 事件 }, }); // 思考结束标记 if (thinkingText) { this.send({ type: 'thinking_done', sessionId: sid, data: {} }); } ``` - [ ] **Step 3: 新增 token_update 推送逻辑(含 context 字段)** 当前后端没有中间 `token_update` 推送,只在 `done` 事件的 `usage` 字段里带。需要在 `runAgent` 回调中新增推送。 首先在 `thinking.ts` 中新增上下文估算工具函数: ```typescript /** 模型上下文窗口上限映射 */ const MODEL_MAX_TOKENS: Record = { 'claude-opus-4-6': 200000, 'claude-sonnet-4-6': 200000, 'gpt-4o': 128000, 'gpt-4o-mini': 128000, 'o1': 200000, 'o3': 200000, 'deepseek-r1': 64000, 'deepseek-chat': 64000, }; export function getModelMaxTokens(model: string): number { for (const [key, val] of Object.entries(MODEL_MAX_TOKENS)) { if (model.includes(key)) return val; } return 128000; // 默认 } ``` 然后在 gateway `handleChat()` 中,在 `runAgent` 调用后、`done` 事件发送前,推送 `token_update`: ```typescript // runAgent 完成后,推送最终 token_update if (result.usage) { const inp = result.usage.inputTokens || 0; const out = result.usage.outputTokens || 0; // 粗估上下文:历史消息 token + 本轮输入 const historyTokens = history.reduce((sum, m) => sum + (m.content?.length || 0) / 3, 0); const usedTokens = Math.round(historyTokens + inp); const maxTokens = getModelMaxTokens(agentConfig.model); this.send({ type: 'token_update', sessionId: sid, data: { current: { inputTokens: inp, outputTokens: out, totalTokens: inp + out, apiCalls: result.usage.apiCalls || 1 }, context: { usedTokens, maxTokens, percent: Math.min(100, Math.round((usedTokens / maxTokens) * 100)), }, }, }); } ``` - [ ] **Step 4: 提交** ```bash git add packages/backend/src/modules/netaclaw/gateway/server.ts git commit -m "feat(netaclaw): gateway 新增 todo_update/thinking_delta/thinking_done WS 事件" ``` --- ## Task 9: 前端 store/chat.ts 改造 **Files:** - Modify: `packages/frontend/src/modules/agent/store/chat.ts:142-337` - [ ] **Step 1: 新增状态变量** 在 store 的状态定义区域新增: ```typescript // Todo 状态 const todoItems = ref([]); const todoSummary = ref(null); // 思考流式状态 const isThinking = ref(false); const thinkingStream = ref(''); // 思考级别 const thinkLevel = ref('medium'); const modelThinkingCapability = ref({ supported: true, adaptive: false, levels: ['off', 'low', 'medium', 'high'], defaultLevel: 'medium', }); // Token(简化,删除本地估算) const tokenUsage = ref(null); ``` - [ ] **Step 2: 删除旧的 `case 'thinking'` 和旧的 `case 'token_update'`** 删除 store/chat.ts 中现有的 `case 'thinking':` 区块(约第153-157行)和现有的 `case 'token_update':` 区块(约第224-230行),替换为新事件处理。 - [ ] **Step 3: 在 handleWSEvent 中新增事件处理** 在 `handleWSEvent()` 函数的 switch 中追加: ```typescript case 'todo_update': { todoItems.value = event.data.todos; todoSummary.value = event.data.summary; break; } case 'thinking_delta': { if (!isThinking.value) { isThinking.value = true; thinkingStream.value = ''; } thinkingStream.value += event.data.text; break; } case 'thinking_done': { isThinking.value = false; if (assistantMsg) { assistantMsg.thinking = thinkingStream.value; } break; } case 'token_update': { tokenUsage.value = event.data; break; } ``` - [ ] **Step 3: 修改 'token' 事件处理,自动结束思考状态** 在现有的 `case 'token'` 处理中,开头加入: ```typescript case 'token': { if (isThinking.value) { isThinking.value = false; if (assistantMsg) { assistantMsg.thinking = thinkingStream.value; } } // ...原有追加 content 逻辑 } ``` - [ ] **Step 4: 修改 'done' 事件处理** 修改约第237-269行的 `case 'done'` 处理: ```typescript case 'done': { if (event.sessionId) { sessionId.value = event.sessionId; } if (assistantMsg) { if (!assistantMsg.metadata) assistantMsg.metadata = {}; // 将 skillProgress 写入 metadata(用于历史恢复),但不清空 assistantMsg.metadata.skillExecutions = [...skillProgress.value]; // 写入最终 token 数据 if (tokenUsage.value) { assistantMsg.metadata.tokenUsage = tokenUsage.value.current; } else if (event.usage) { // 兼容:如果没有 token_update 推送,从 done 事件中取 const inp = event.usage.inputTokens || 0; const out = event.usage.outputTokens || 0; assistantMsg.metadata.tokenUsage = { input: inp, output: out, total: inp + out, apiCalls: event.usage.apiCalls }; sessionTotalTokens.value += inp + out; } } tokenUsage.value = null; loading.value = false; loadSessions(); // 保留:刷新会话列表 // 注意:不再执行 setTimeout(() => { skillProgress.value = []; }, 3000) // skillProgress 在切换会话时才清空 break; } ``` - [ ] **Step 5: 简化 recalcSessionTokens,删除本地 token 估算逻辑** 修改 `recalcSessionTokens()` 函数(约第317-337行),删除中文字符估算代码(`chineseChars`/`otherChars`/`ctxEstimate` 相关行),只保留从 metadata.tokenUsage 累计 `sessionTotalTokens` 的逻辑。同时删除 `contextTokens` ref(上下文数据改由后端 `token_update.context` 推送)。 ```typescript function recalcSessionTokens() { let total = 0; for (const msg of messages.value) { const meta = typeof msg.metadata === 'string' ? (() => { try { return JSON.parse(msg.metadata); } catch { return null; } })() : msg.metadata; if (meta?.tokenUsage?.total) { total += typeof meta.tokenUsage.total === 'number' ? meta.tokenUsage.total : (meta.tokenUsage.total.total || 0); } } sessionTotalTokens.value = total; } ``` - [ ] **Step 6: 新增会话切换恢复函数** ```typescript function restoreTodoFromHistory(messages: ChatMessage[]) { for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg.role === 'tool' && msg.metadata?.toolName === 'todo') { try { const data = JSON.parse(msg.content); todoItems.value = data.todos; todoSummary.value = data.summary; } catch {} return; } } todoItems.value = []; todoSummary.value = null; } function restoreSkillProgress(messages: ChatMessage[]) { skillProgress.value = []; for (const msg of messages) { if (msg.metadata?.skillExecutions) { skillProgress.value.push(...msg.metadata.skillExecutions); } } } function setThinkLevel(level: ThinkLevel) { thinkLevel.value = level; sendWSMessage({ type: 'set_thinking_level', data: { level } }); } ``` - [ ] **Step 7: 在 switchSession 中调用恢复函数** 在现有的会话切换逻辑中加入: ```typescript function switchSession(newSessionId: string) { skillProgress.value = []; todoItems.value = []; todoSummary.value = null; thinkingStream.value = ''; isThinking.value = false; tokenUsage.value = null; // ...加载新会话消息后 restoreTodoFromHistory(messages); restoreSkillProgress(messages); thinkLevel.value = session.thinkLevel ?? 'medium'; } ``` - [ ] **Step 8: 导出新状态和函数** 在 store 的 return 中追加导出: ```typescript return { // ...现有导出 todoItems, todoSummary, isThinking, thinkingStream, thinkLevel, modelThinkingCapability, tokenUsage, restoreTodoFromHistory, restoreSkillProgress, setThinkLevel, }; ``` - [ ] **Step 9: 提交** ```bash git add packages/frontend/src/modules/agent/store/chat.ts git commit -m "feat(agent): store 新增 todo/thinking/token 状态管理和 WS 事件处理" ``` --- ## Task 10: 前端 skill-card.vue **Files:** - Create: `packages/frontend/src/modules/agent/components/skill-card.vue` - [ ] **Step 1: 创建 skill-card.vue** 完整组件代码见设计文档第 4.2 节。关键点: - Props: 接收 `SkillProgress` 的所有字段(name, label, status, percent, step, detail, result, tokens) - 状态过渡:`` 包裹进度条和结果区域 - CSS:`.skill-card--running/done/error` 通过 CSS 变量控制边框颜色 - 内部复用现有的 `skill-result-viewer.vue` 展示结果详情 - [ ] **Step 2: 提交** ```bash git add packages/frontend/src/modules/agent/components/skill-card.vue git commit -m "feat(agent): 新增 skill-card.vue 统一 Skill 卡片组件" ``` --- ## Task 11: 前端 todo-card.vue **Files:** - Create: `packages/frontend/src/modules/agent/components/todo-card.vue` - [ ] **Step 1: 创建 todo-card.vue** 完整组件代码见设计文档第 6.2 节。关键点: - Props: `items: TodoItem[]`, `summary: TodoSummary` - 可折叠头部:📋 图标 + 标题 + 进度摘要 + 微型进度条 - `` 包裹列表项实现增删动画 - 状态图标:✅ completed / spinner in_progress / ❌ cancelled / ⬚ pending - CSS:`.todo-card__item--in_progress` 高亮背景,`.is-done` 删除线 - [ ] **Step 2: 提交** ```bash git add packages/frontend/src/modules/agent/components/todo-card.vue git commit -m "feat(agent): 新增 todo-card.vue 任务规划卡片组件" ``` --- ## Task 12: 前端 thinking-block.vue **Files:** - Create: `packages/frontend/src/modules/agent/components/thinking-block.vue` - [ ] **Step 1: 创建 thinking-block.vue** 完整组件代码见设计文档第 7.4 节。关键点: - Props: `content: string`, `isStreaming: boolean` - 流式时默认展开 + 呼吸动画(`thinking-pulse` 1.5s)+ 光标闪烁(`blink` 1s) - 完成后自动折叠(watch isStreaming 变化触发) - 内容渲染为 Markdown(使用项目现有的 markdown 渲染工具) - CSS:虚线边框 + 半透明背景 + 12px 小字号 + 柔和色 - [ ] **Step 2: 提交** ```bash git add packages/frontend/src/modules/agent/components/thinking-block.vue git commit -m "feat(agent): 新增 thinking-block.vue 思考内容流式展示组件" ``` --- ## Task 13: 前端 token-stats.vue **Files:** - Create: `packages/frontend/src/modules/agent/components/token-stats.vue` - [ ] **Step 1: 创建 token-stats.vue** 完整组件代码见设计文档第 5.3 节。关键点: - Props: `tokenUsage: TokenUpdateEvent | null`, `isRunning: boolean` - 执行中显示:`↑1.2K ↓0.8K · 3次` - 始终显示:`上下文 32%` + 颜色圆点(≤50%绿/50-80%黄/>80%红) - `formatTokens()` 工具函数:1234→1.2K, 12345→12.3K - hover tooltip 显示详细数据 - [ ] **Step 2: 提交** ```bash git add packages/frontend/src/modules/agent/components/token-stats.vue git commit -m "feat(agent): 新增 token-stats.vue 紧凑 Token 统计组件" ``` --- ## Task 14: 前端 thinking-level-selector.vue **Files:** - Create: `packages/frontend/src/modules/agent/components/thinking-level-selector.vue` - [ ] **Step 1: 创建 thinking-level-selector.vue** 完整组件代码见设计文档第 9.2 节。关键点: - Props: `modelValue: ThinkLevel`, `disabled: boolean`, `capability: ModelThinkingCapability` - `el-select` 下拉,选项根据 `capability.levels` 动态生成 - label 映射:off→关闭, minimal→极简, low→低, medium→中等, high→高, adaptive→自适应 - emit `update:modelValue` 和 `change` 事件 - [ ] **Step 2: 提交** ```bash git add packages/frontend/src/modules/agent/components/thinking-level-selector.vue git commit -m "feat(agent): 新增 thinking-level-selector.vue 思考级别选择器" ``` --- ## Task 15: 前端 chat.vue 布局重构 **Files:** - Modify: `packages/frontend/src/modules/agent/views/chat.vue:16-218` - [ ] **Step 1: 删除顶部 token 统计条** 删除约第16-37行的 `
` 整个区块。 - [ ] **Step 2: 重构消息列表区域** 修改消息列表区域(约第40-89行),插入新组件: ```vue
``` - [ ] **Step 3: 在输入区域添加思考级别选择器和 token 统计** 在输入区域(约第92-218行)的工具栏中加入: ```vue
``` - [ ] **Step 4: 导入新组件** 在 `