463 lines
30 KiB
Markdown
463 lines
30 KiB
Markdown
---
|
||
date: 2026-07-01
|
||
type: feat
|
||
origin: docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md
|
||
---
|
||
|
||
# feat: AgentKit UI/UE 增强 — 聊天流式 + Sticky 头部 + 日历重设计
|
||
|
||
## Summary
|
||
|
||
强化 AgentKit 聊天与日历模块的 UI/UE:让 agent 思考过程与团队/私董会结果真正流式可见,让 @team/@board 模式在对话顶部持久显式,去除常驻路由噪声,给用户消息补齐复制/删除/回填操作,并让日历回到 Notion 风格的统一设计语言。覆盖 22 个需求(R1-R22),含后端 `_phase_executor.py` 流式切换以打通 expert_result/team_synthesis 的流式链路。
|
||
|
||
---
|
||
|
||
## Problem Frame
|
||
|
||
当前聊天主界面的思考展示默认折叠且为纯文本,看不到 agent 推理过程;@team/@board 模式的顶部 banner 为静态文案且不可点击,无法回答"这次任务目标是什么、由哪些专家参与";专家团与私董会的最终结果以完整事件块一次性到达前端,与 `final_answer` 的流式体验割裂;路由元信息 tag 常驻显示对终端用户是无意义噪声;用户消息气泡无任何悬停操作;日历模块硬编码颜色与 `tokens.css` 的 Notion 调色板完全脱节。
|
||
|
||
来源:`docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md`(经 ce-doc-review 审查强化,含 9 个 Open Questions)。
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
### 需求追溯
|
||
|
||
| 需求组 | R-IDs | 实施单元 |
|
||
|--------|-------|----------|
|
||
| 思考展示 | R1-R3 | U1 |
|
||
| @team/@board 头部 | R4-R7 | U2 |
|
||
| 团队/私董会流式 | R8-R11 | U3, U4 |
|
||
| 路由标签 | R12-R13 | U5 |
|
||
| 用户消息悬停 | R14-R17 | U6 |
|
||
| 日历重设计 | R18-R22 | U7 |
|
||
|
||
完整需求清单见 origin 文档。关键决策(思考混合方案、sticky 持久条、FC+自定义外壳、流式方案待定)已由 brainstorm 确认。
|
||
|
||
### 成功标准
|
||
|
||
- 思考进行中时 `ThinkingBlock` 展开并显示流式光标,完成后收起为摘要条
|
||
- @team/@board 模式进入对话后顶部渲染 sticky 持久条,专家头像可点击查看详情
|
||
- `expert_result` 与 `team_synthesis` 事件按 token/文本块流式累加渲染,与 `final_answer` 体验对齐
|
||
- 路由元信息 tag 默认隐藏,悬停时淡入显示
|
||
- 用户消息气泡悬停时显示复制/删除/回填操作工具条
|
||
- 日历模块统一消费 `tokens.css`,无硬编码颜色值
|
||
|
||
---
|
||
|
||
## Key Technical Decisions
|
||
|
||
1. **ThinkingBlock 流式光标采用 CSS 伪元素 + watch content.length 截断展示**。后端已将 thinking chunks 累积到 `message.thinking`(`chatStream.ts:512-521`),前端无需改事件流,只需在 `ThinkingBlock.vue` 内 watch `content.length` 做截断展示 + 闪烁光标。完成后收起为摘要条显示 token 数。
|
||
|
||
2. **StickyModeHeader 为新组件,替换 `ExpertTeamView` + `BoardStatusView`**。从 `useTeamStore()` 和 `useChatStore().boardState` 读取状态,合并为单一 sticky 条(`position: sticky; top: 0; z-index: var(--z-sticky)`)。`PhaseIndicator` 独立保留在 sticky 头部下方,不合并。
|
||
|
||
3. **expert_result/team_synthesis 流式采用 `execute_stream()` + token 转发 + 前端累积式 updateMessage**。后端 `_phase_executor.py` 从 `agent.execute()` 切换为 `agent.execute_stream()`,在 `async for event in` 循环中转发 `token`/`final_answer` 事件到 WS。前端 `chatStream.ts` 的 `expert_result`/`team_synthesis` 分支从一次性 `appendMessage` 改为累积式 `updateMessage`(复用 `final_answer` 的 526-532 模式)。
|
||
|
||
4. **路由 tag 隐藏采用 `v-show` + opacity transition**(与 U5 Approach 对齐)。`AssistantText.vue` 的 `showRouting` computed 增加 hover 状态依赖,CSS `opacity: 0` → `1` + `transition: opacity 0.2s ease` 淡入淡出。`v-show` 保留 DOM 避免首次悬停的重挂载闪烁。
|
||
|
||
5. **UserBubble 回填采用 chatStore 共享状态**。新增 `chatStore.refillText: string` ref,`UserBubble` 的回填操作设置该值,`ChatInput` watch 该值回填到 `inputText`。删除操作新增 `chatStore.deleteMessage(convId, msgId)` 从 `currentMessages` 移除(仅前端隐藏,不删服务端副本)。
|
||
|
||
6. **日历 .fc-* 覆盖采用最小范围 CSS 覆盖**。仅覆盖字体、边框、表头背景、今日高亮四类样式,选择器限定为 `.fc-toolbar` / `.fc-col-header` / `.fc-day-today` 等稳定类名,接受 FC 升级时可能需要维护的成本。
|
||
|
||
---
|
||
|
||
## High-Level Technical Design
|
||
|
||
### 流式事件链路(R8-R11)
|
||
|
||
```
|
||
Backend: _phase_executor.py
|
||
agent.execute_stream() ← async generator yields ReActEvent
|
||
├─ event_type="token" → broadcast "expert_result_chunk" { expert_id, content }
|
||
├─ event_type="thinking" → broadcast "expert_step" { expert_id, thinking }
|
||
├─ event_type="final_answer" → broadcast "expert_result_chunk" { expert_id, content }
|
||
└─ 循环结束 → broadcast "expert_result" { expert_id, content: full, status: "completed" }
|
||
|
||
Frontend: chatStream.ts
|
||
case "expert_result_chunk":
|
||
→ updateMessage(convId, lastExpertMsg.id, { content: accumulated + chunk })
|
||
case "expert_result":
|
||
→ updateMessage(convId, lastExpertMsg.id, { status: "completed" }) ← 仅标记完成,不再 append
|
||
```
|
||
|
||
### 组件依赖关系
|
||
|
||
```
|
||
U1 ThinkingBlock (独立)
|
||
U2 StickyModeHeader (独立,替换两个旧组件)
|
||
U3 后端流式切换 (独立,无前端依赖)
|
||
U4 前端流式消费 (依赖 U3 联调,可 mock 开发)
|
||
U5 路由 tag 隐藏 (独立)
|
||
U6 UserBubble hover (独立,需 chatStore 新方法)
|
||
U7 CalendarGrid 重设计 (独立)
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Units
|
||
|
||
### U1. ThinkingBlock 流式展示重设计
|
||
|
||
**Goal:** 让 agent 思考过程在执行时展开流式显示,完成后收起为摘要条,切换不丢失内容。
|
||
|
||
**Requirements:** R1, R2, R3
|
||
|
||
**Dependencies:** 无
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/components/chat/ThinkingBlock.vue`(修改)
|
||
- `src/agentkit/server/frontend/tests/unit/components/ThinkingBlock.test.ts`(新建)
|
||
|
||
**Approach:**
|
||
- 默认展开(`expanded = ref(true)`),`isStreaming` 时显示流式光标(CSS `::after` 伪元素闪烁动画)
|
||
- watch `content.length` 截断展示:流式中仅显示已到达的字符,模拟 token-by-token 效果
|
||
- 完成后(`isStreaming` 由 true→false)自动收起为摘要条,显示思考开始时间与字符数
|
||
- 摘要条点击切换展开/收起,展开时定位到上一次滚动位置(记录 `scrollTop` 到 ref)
|
||
- 状态保持:收起时内容不丢失,`content` prop 始终持有完整文本
|
||
|
||
**Patterns to follow:**
|
||
- `ChatView.vue:47-76` 的 streamingSteps 渲染模式(字符数 counter)
|
||
- `final_answer` 在 `chatStream.ts:526-532` 的累积模式
|
||
|
||
**Test scenarios:**
|
||
- 流式中展开态显示光标 + 已到达字符,完成后自动收起为摘要条
|
||
- 摘要条点击展开,显示完整思考内容
|
||
- 展开→收起→再展开,滚动位置恢复到上次位置
|
||
- 空思考内容(`content=""`)时不显示 ThinkingBlock
|
||
- `isStreaming=true` 但 `content=""` 时显示加载占位
|
||
|
||
**Verification:** 思考流式中可见光标闪烁与字符累加,完成后收起为一行摘要,点击可重新展开查看完整内容。
|
||
|
||
---
|
||
|
||
### U2. StickyModeHeader + PhaseIndicator token 化
|
||
|
||
**Goal:** 替换静态 banner 为 sticky 持久头部条,显示模式 badge + 任务目标 + 专家头像组;PhaseIndicator 独立保留并 token 化。
|
||
|
||
**Requirements:** R4, R5, R6, R7
|
||
|
||
**Dependencies:** 无
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue`(新建)
|
||
- `src/agentkit/server/frontend/src/components/chat/ExpertTeamView.vue`(废弃/删除引用)
|
||
- `src/agentkit/server/frontend/src/components/chat/BoardStatusView.vue`(废弃/删除引用)
|
||
- `src/agentkit/server/frontend/src/components/chat/PhaseIndicator.vue`(修改 — token 化)
|
||
- `src/agentkit/server/frontend/src/views/ChatView.vue`(修改 — 替换 banner 引用)
|
||
- `src/agentkit/server/frontend/tests/unit/components/StickyModeHeader.test.ts`(新建)
|
||
|
||
**Approach:**
|
||
- `StickyModeHeader` 从 `useTeamStore()` 读取 `isTeamMode` / `teamState`(含 `task_description` / `experts`),从 `useChatStore().boardState` 读取私董会状态。注:实际 store 暴露 `teamState` / `activeExperts` / `leadExpert`,无 `currentPlan` / `experts` 顶层 ref(FYI F8 提示)— 实施时通过 `teamState.value?.task_description` 与 `activeExperts` / `teamState.value?.experts` 访问
|
||
- 左侧:模式 badge("专家团" / "私董会"),使用 `--accent-team` / `--accent-board`
|
||
- 中间:任务目标(@team 取 `teamState.value?.task_description`)或主题(@board 取 `boardState.topic`)
|
||
- 右侧:专家头像组(`v-for` 渲染 `activeExperts`,每个显示 `avatar` emoji + `color` 边框),溢出时显示 `+N`
|
||
- 头像点击弹出 `a-popover` 详情面板,显示 `name` / `description` / `persona`(现有字段,不假设 role/bio)。**Popover 关闭**:Esc 键 + 外击关闭;关闭时焦点回归触发头像(focus management)。**移动端**:viewport<768px 隐藏任务主题文本,仅显示 mode badge + avatar group;`+N` 溢出点击打开完整专家列表 popover
|
||
- `PhaseIndicator` 硬编码颜色(`#722ed1` / `#52c41a` / `#cf1322`)替换为 token(`--accent-board` / `--color-success` / `--color-error`)
|
||
- ChatView.vue 中 `<ExpertTeamView />` + `<BoardStatusView />` 替换为 `<StickyModeHeader />`,`<PhaseIndicator />` 保留在其下方
|
||
|
||
**Patterns to follow:**
|
||
- `MessageShell.vue:5-11` 的 custom-avatar 渲染逻辑
|
||
- `tokens.css` 的 `--z-sticky: 1020` / `--accent-team` / `--accent-board`
|
||
|
||
**Test scenarios:**
|
||
- @team 模式进入对话后渲染 sticky 条,显示"专家团" badge + 任务目标 + 专家头像
|
||
- @board 模式进入对话后渲染 sticky 条,显示"私董会" badge + 主题 + 专家头像
|
||
- 非 team/board 模式不渲染 sticky 条
|
||
- 专家头像点击弹出 popover 显示 name/description/persona
|
||
- Popover 通过 Esc 键关闭,关闭后焦点回归触发头像
|
||
- Popover 通过外击关闭
|
||
- 头像超过 5 个时显示 `+N` 溢出标识,`+N` 点击打开完整专家列表 popover
|
||
- viewport<768px 时隐藏任务主题文本,仅显示 mode badge + avatar group
|
||
- PhaseIndicator 颜色使用 token 变量而非硬编码
|
||
|
||
**Verification:** @team/@board 模式下顶部持久显示 sticky 头部条,专家头像可点击查看详情,PhaseIndicator 颜色与 tokens.css 一致。
|
||
|
||
---
|
||
|
||
### U3. 后端 _phase_executor.py 流式切换
|
||
|
||
**Goal:** 将专家阶段执行从同步 `agent.execute()` 切换为流式 `agent.execute_stream()`,转发 token/thinking/final_answer 事件到 WS。
|
||
|
||
**Requirements:** R8, R9(后端依赖)
|
||
|
||
**Dependencies:** ConfigDrivenAgent.execute_stream() 暴露 — 需先在 BaseAgent/ConfigDrivenAgent 暴露 stream dispatch(react/rewoo/plan_exec/reflexion 模式委派至底层引擎;direct/llm_generate/tool_call/custom 模式定义显式 fallback)
|
||
|
||
**Files:**
|
||
- `src/agentkit/core/config_driven.py`(修改 — 暴露 `execute_stream()` + `handle_task_stream()` dispatch,镜像 `execute()` → `handle_task()` 模式)
|
||
- `src/agentkit/core/base.py`(修改 — 在 BaseAgent 加 `execute_stream()` 抽象方法签名)
|
||
- `src/agentkit/experts/_phase_executor.py`(修改 — `_run_agent_steps` 方法)
|
||
- `src/agentkit/experts/_synthesizer.py`(修改 — `_synthesize_results` 注入 `broadcast_callback` 参数,保留 dict 返回类型,方法内每次 LLM chunk 调用 callback 广播 `team_synthesis_chunk`)
|
||
- `src/agentkit/experts/orchestrator.py`(修改 — `_synthesize_results` 调用处传入 `_broadcast_event` 的部分应用作为 callback;`final_result = await self._synthesize_results(...)` 与 `final_result.get("content")` 消费保持不变)
|
||
- `tests/unit/experts/test_phase_executor_streaming.py`(新建)
|
||
|
||
**Approach:**
|
||
- `_run_agent_steps`(line 222)从 `await agent.execute(task_msg)` 改为 `async for event in agent.execute_stream(task_msg, ...)` 循环
|
||
- 循环内按 `event.event_type` 转发(载荷在 `event.data: dict[str, object]`,无 `event.content` / `event.output` 属性 — 见 `react.py:131-137` ReActEvent 定义):
|
||
- `"token"` → `_broadcast_event("expert_result_chunk", { expert_id, content: event.data.get("content") })`
|
||
- `"thinking"` → `_broadcast_event("expert_step", { expert_id, thinking: event.data.get("content") })`
|
||
- `"final_answer"` → `_broadcast_event("expert_result_chunk", { expert_id, content: event.data.get("output") })`
|
||
- `"tool_call"` / `"tool_result"` → 可选转发(保持现有 `expert_step` 语义)
|
||
- 循环结束后广播 `expert_result` 完整事件(content = 累积的完整结果,status: "completed")
|
||
- `TeamOrchestrator._synthesize_results` 接口策略:注入 `broadcast_callback` 参数,保留 `dict[str, object]` 返回类型(对 `orchestrator.py:289` 消费者零破坏)。方法内在每次 LLM 输出 chunk 时调用 `broadcast_callback({"chunk": text})` 广播 `team_synthesis_chunk`,最终返回完整 dict;`orchestrator.py:294-301` 的最终 `team_synthesis` 广播路径不变
|
||
- **重试 + 流式合约**:`_run_agent_steps` 的 retry 循环(`_phase_executor.py:220-241`)与流式交互须遵循:(a) 重试前广播 `expert_result_chunk_reset` 事件,前端清空已累积内容;(b) 最终 `expert_result` 事件携带完整内容覆盖(前端在 `expert_result` 到达时替换而非累加);(c) 重试不广播新 chunks 直到本次流式完成 — 避免失败 attempt 1 的部分 chunks 与 attempt 2 的 chunks 在前端累积成乱码
|
||
- 异步生成器安全:遵循 AGENTS.md 约定,`async def` 中第一个 `yield` 前禁止 `return`(用 `return; yield` 模式)
|
||
- 异常分类:遵循 `docs/solutions/conventions/any-and-except-exception-governance.md`,WS 广播用 `except (ConnectionError, RuntimeError, asyncio.TimeoutError)`
|
||
|
||
**Patterns to follow:**
|
||
- `react.py:1443` 的 `execute_stream` 签名与 ReActEvent 结构
|
||
- `chatStream.ts:526-532` 的 final_answer 累积模式(后端镜像)
|
||
- `docs/solutions/logic-errors/long-horizon-reliability-code-review-fixes.md` 的 `execute_stream` 入口 `self.reset()` 前置条件
|
||
|
||
**Test scenarios:**
|
||
- `execute_stream` 产出 token 事件时,`_broadcast_event` 被调用转发 `expert_result_chunk`
|
||
- `execute_stream` 产出 final_answer 事件时,`_broadcast_event` 被调用转发 `expert_result_chunk`
|
||
- 循环结束后广播完整 `expert_result` 事件
|
||
- `execute_stream` 抛出异常时,phase 状态标记为 failed 且不阻塞其他阶段
|
||
- `execute_stream` 中途抛出异常时,广播 `expert_result` 事件 status="error" 携带已累积内容,前端可识别错误态并保留部分内容
|
||
- 流式会话必须以 `expert_result(completed)` 或 `expert_result(error)` 终结,不允许静默挂起
|
||
- `execute_stream` 在 2 chunks 后抛异常 → retry loop 触发 → 广播 `expert_result_chunk_reset` → 重试成功 → 前端消息仅含 attempt 2 内容(无 attempt 1 残留)
|
||
- TeamOrchestrator 综合阶段流式广播 `team_synthesis_chunk`
|
||
|
||
**Verification:** 后端日志可见 `expert_result_chunk` 事件按 token 推送,最终 `expert_result` 完整事件作为结束标记。
|
||
|
||
---
|
||
|
||
### U4. 前端 expert_result/team_synthesis 流式消费 + 身份标识
|
||
|
||
**Goal:** 前端将 expert_result/team_synthesis 从一次性 append 改为累积式 update,流式过程中显示专家身份标识。
|
||
|
||
**Requirements:** R8, R9, R10, R11
|
||
|
||
**Dependencies:** U3(联调,可 mock 开发)
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/stores/chatStream.ts`(修改 — expert_result/team_synthesis 分支)
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue`(修改 — 渲染 expert_name/expert_color badge)
|
||
- `src/agentkit/server/frontend/tests/unit/stores/chatStream.test.ts`(修改 — 新增流式测试)
|
||
|
||
**Approach:**
|
||
- 新增 `expert_result_chunk` 事件分支:若当前无该专家的流式消息则 `appendMessage` 创建占位(`status: "streaming"`),否则 `updateMessage` 累加 content(复用 final_answer 526-532 模式)
|
||
- `expert_result` 事件分支改为:仅 `updateMessage` 标记 `status: "completed"`(不再 appendMessage);`status="error"` 时标记为错误态并保留部分内容
|
||
- `expert_result_chunk_reset` 事件分支(U3 重试合约):清空已累积 content,重置 streaming 状态
|
||
- `team_synthesis_chunk` 同理:首次 append 占位,后续 update 累加
|
||
- `team_synthesis` 事件改为:仅 update 标记完成
|
||
- **并发专家流式顺序**:每个 `expert_id` 维护独立 streaming message slot(keyed by `expert_id`),同 expert 的 chunks 累积到该 expert 的消息;并发专家渲染为独立并行 streaming 消息,按 first-chunk 到达时间排序
|
||
- 流式消息渲染时显示身份标识(`expert_name` + `expert_color` badge),流式结束后标识保留
|
||
- R10 身份标识为用户可见的专家 badge(区别于 R12 的内部路由 tag),通过 `message.expert_name` / `message.expert_color` 在 MessageShell 中渲染
|
||
|
||
**Patterns to follow:**
|
||
- `chatStream.ts:526-532` 的 `final_answer` 累积模式
|
||
- `dispatchWsEvent` 纯函数设计(入参 `ChatStreamState`,无模块级状态)
|
||
|
||
**Test scenarios:**
|
||
- `expert_result_chunk` 首次到达时创建 streaming 占位消息
|
||
- `expert_result_chunk` 后续到达时累加 content 到已有消息
|
||
- `expert_result` 到达时标记消息 status 为 completed
|
||
- `expert_result` 到达时 status="error" 时,标记 streaming 消息为错误态并保留部分内容
|
||
- `expert_result_chunk_reset` 到达时清空已累积 content 并重置 streaming 状态
|
||
- `team_synthesis_chunk` 首次到达时创建 streaming 占位
|
||
- `team_synthesis` 到达时标记完成
|
||
- 流式消息显示 expert_name + expert_color badge
|
||
- 流式会话异常终止时,UI 显示错误指示器不静默挂起
|
||
- 两个专家并发流式 → 两条独立 streaming 消息,各自累积自己的 chunks,按 first-chunk 到达时间排序
|
||
|
||
**Verification:** 专家结果按 token 流式累加显示,完成后标记为 completed,身份标识保留在最终消息上。
|
||
|
||
---
|
||
|
||
### U5. AssistantText 路由标签悬停显示
|
||
|
||
**Goal:** 路由元信息 tag 默认隐藏,悬停助手消息时淡入显示。
|
||
|
||
**Requirements:** R12, R13
|
||
|
||
**Dependencies:** 无
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue`(修改)
|
||
- `src/agentkit/server/frontend/tests/unit/components/AssistantText.test.ts`(新建)
|
||
|
||
**Approach:**
|
||
- `showRouting` computed 增加 hover 状态依赖:`return props.message.role === 'assistant' && props.message.matched_skill && isHovered.value`
|
||
- 根容器加 `@mouseenter` / `@mouseleave` 事件设置 `isHovered = ref(false)`
|
||
- CSS `.assistant-text__routing` 加 `transition: opacity 0.2s ease`,默认 `opacity: 0`,hover 时 `opacity: 1`
|
||
- 采用 `v-show` + opacity transition(保留 DOM 避免首次悬停的重挂载闪烁,`opacity:0→1` + `transition:opacity 0.2s ease` 满足 R13 淡入要求)
|
||
|
||
**Patterns to follow:**
|
||
- Ant Design Vue 的 `<a-tag>` 组件用法
|
||
- `tokens.css` 的 `--transition-base` 变量(若存在)
|
||
|
||
**Test scenarios:**
|
||
- 默认状态路由 tag 不可见
|
||
- 鼠标悬停助手消息时 tag 淡入显示
|
||
- 鼠标移开时 tag 淡出
|
||
- 无 `matched_skill` 的消息悬停时也不显示 tag
|
||
- 淡入淡出过渡平滑无闪烁
|
||
|
||
**Verification:** 路由 tag 默认不可见,悬停时淡入,移开时淡出。
|
||
|
||
---
|
||
|
||
### U6. UserBubble 悬停操作(复制/删除/回填)
|
||
|
||
**Goal:** 用户消息气泡悬停时显示操作工具条,支持复制、删除(前端隐藏)、回填输入框重发。
|
||
|
||
**Requirements:** R14, R15, R16, R17
|
||
|
||
**Dependencies:** 无(chatStore 新方法独立于其他单元)
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue`(修改)
|
||
- `src/agentkit/server/frontend/src/stores/chatStore.ts`(修改 — 新增 deleteMessage / refillText)
|
||
- `src/agentkit/server/frontend/src/components/chat/ChatInput.vue`(修改 — watch refillText)
|
||
- `src/agentkit/server/frontend/tests/unit/components/UserBubble.test.ts`(新建)
|
||
|
||
**Approach:**
|
||
- `UserBubble.vue` 根容器加 `@mouseenter` / `@mouseleave` 控制 `showActions = ref(false)`
|
||
- **focus-visible parity**:根容器加 `@focus` / `@blur`(或 `:focus-within`)使 Tab 聚焦时也显示工具条(键盘用户可达);Touch 设备 tap on bubble 切换显示,tap elsewhere 隐藏
|
||
- 悬停时显示工具条(`v-if="showActions"`):复制按钮(`CopyOutlined`)、删除按钮(`DeleteOutlined`)、回填按钮(`EditOutlined`)
|
||
- 复制:`navigator.clipboard.writeText(content)`,成功后按钮短暂变色反馈
|
||
- 删除:弹出 `a-popconfirm` 二次确认,确认后调 `chatStore.deleteMessage(convId, msgId)` 从 `currentMessages` 移除(仅前端隐藏,不删服务端副本);**若有助手回复跟随**(thread order 中下一条为 assistant 消息),删除按钮 disabled 并显示 tooltip "该消息已有回复,无法删除"
|
||
- 回填:调 `chatStore.setRefillText(content)`,`ChatInput` watch `refillText` 回填到 `inputText`,不自动发送
|
||
- chatStore 新增:`refillText = ref('')`、`deleteMessage(convId, msgId)`、`setRefillText(text)`
|
||
|
||
**Patterns to follow:**
|
||
- `chatStore.resendLastUserMessage()`(chatStore.ts:449-462)的消息定位模式
|
||
- `chatStore.deleteConversation` 的 pending 状态清理模式(见 `calendar-capability-and-ui-fixes.md` 学习)
|
||
|
||
**Test scenarios:**
|
||
- 悬停时显示三个操作按钮
|
||
- Tab 聚焦用户消息时显示工具条;Enter 触发默认动作(复制)
|
||
- Touch 设备 tap on bubble 显示工具条,tap elsewhere 隐藏
|
||
- 复制按钮点击后内容写入剪贴板,按钮变色反馈
|
||
- 删除按钮点击后弹出二次确认,确认后消息从列表消失
|
||
- 删除取消后消息不消失
|
||
- 删除有助手回复跟随的用户消息 → 删除按钮 disabled + tooltip "该消息已有回复,无法删除"
|
||
- 回填按钮点击后 ChatInput 输入框显示消息文本,不自动发送
|
||
- 离开悬停后操作工具条消失
|
||
|
||
**Verification:** 悬停用户消息可见三个操作,复制/删除/回填功能正常,删除有二次确认。
|
||
|
||
---
|
||
|
||
### U7. CalendarGrid token 重设计
|
||
|
||
**Goal:** 日历模块统一消费 `tokens.css`,消除硬编码颜色,`.fc-*` 元素对齐 Notion 风格。
|
||
|
||
**Requirements:** R18, R19, R20, R21, R22
|
||
|
||
**Dependencies:** 无(tokens.css 已含全部变量;本期不修改 tokens.css)
|
||
|
||
**Files:**
|
||
- `src/agentkit/server/frontend/src/components/calendar/CalendarGrid.vue`(修改)
|
||
- `src/agentkit/server/frontend/src/styles/calendar-overrides.css`(新建 — .fc-* 覆盖)
|
||
- `src/agentkit/server/frontend/tests/unit/components/CalendarGrid.test.ts`(新建)
|
||
|
||
**Approach:**
|
||
- `CalendarGrid.vue:38` 的 `#1677ff` 替换为按 event_type 映射到 token(显式映射表):
|
||
- `event_type=team` → `var(--accent-team)`
|
||
- `event_type=board` → `var(--accent-board)`
|
||
- `event_type=task` → `var(--color-primary)`
|
||
- `event_type=reminder` → `var(--color-warning)`
|
||
- `event_type=system` → `var(--text-tertiary)`
|
||
- 默认 → `var(--color-primary)`
|
||
- 实施前需从 `CalendarGrid.vue` 的事件源枚举实际 `event_type` 值
|
||
- 事件卡片 `backgroundColor` / `borderColor` 改为从 token 映射函数返回
|
||
- 新建 `calendar-overrides.css` 覆盖 `.fc-toolbar`(按钮风格)、`.fc-col-header`(表头背景)、`.fc-day-today`(今日高亮)、`.fc-event`(事件卡片字体/边框)
|
||
- 覆盖范围最小化:仅覆盖 4 类稳定类名,接受 FC 升级时可能需要维护的成本
|
||
- 侧栏与头部工具栏组件使用 token 重绘
|
||
|
||
**Patterns to follow:**
|
||
- `tokens.css` 的 Notion 调色板(`--color-primary: #1a1a1a`、`--accent-team`、`--accent-board`)
|
||
- `CalendarGrid.vue` 现有的 `firstDay: 1` 配置(见 `jwt-secret-dev-mode-user-id-mismatch.md` 学习)
|
||
|
||
**Test scenarios:**
|
||
- 日历事件颜色按 event_type 映射到 token,无硬编码颜色值
|
||
- `.fc-toolbar` 按钮风格与主界面按钮一致
|
||
- `.fc-day-today` 今日高亮使用 token 色
|
||
- 暗色模式(`[data-theme="dark"]`)下日历正确显示
|
||
- FullCalendar 视图切换/日期导航/事件拖拽功能不受影响
|
||
- Calendar 零事件 → 空态 placeholder 文本使用 `--text-tertiary` token
|
||
- 事件获取失败 → 错误态带 retry CTA(使用 `--color-error` token)
|
||
- 事件加载中 → skeleton loader 匹配 token 间距(使用 `--color-border` / `--color-fill-quaternary`)
|
||
|
||
**Verification:** 日历视觉与主界面 Notion 风格一致,无硬编码颜色,FC 既有功能正常。
|
||
|
||
---
|
||
|
||
## Scope Boundaries
|
||
|
||
### Deferred for later
|
||
|
||
- 思考内容的 markdown 渲染:本期 ThinkingBlock 仍以纯文本呈现(origin 文档 Deferred)
|
||
- 日历事件的拖拽创建、resize 行为变更:仅重绘视觉外壳(origin 文档 Deferred)
|
||
- 专家头像组在线状态指示:仅展示静态头像(origin 文档 Deferred)
|
||
- 路由元信息 tag 的可配置显示规则:固定为"默认隐藏 + 悬停显示"(origin 文档 Deferred)
|
||
- 后端流式推送协议的具体方案(事件变体 vs token 分块):U3 采用 `execute_stream()` + token 转发,具体事件命名(`expert_result_chunk` 等)可在实施时调整
|
||
|
||
### Outside this product's identity
|
||
|
||
- 不引入新的 UI 组件库或设计系统(继续基于 Ant Design Vue + token 覆盖)
|
||
- 不替换 FullCalendar 为自建日历组件(保留 FC 核心,仅覆盖外壳与 `.fc-*` 样式)
|
||
- 不改变 `PhaseIndicator` 的现有阶段进度语义与展示形式(仅独立保留 + token 化)
|
||
|
||
### Deferred to Follow-Up Work
|
||
|
||
- R16 服务端删除语义:本期仅前端隐藏,服务端删除留待后续迭代(origin OQ4)
|
||
- 交互状态全覆盖(错误态/空态/加载态规范):origin OQ1,本期在相关单元测试中覆盖核心场景,全面规范留待后续
|
||
- 响应式与无障碍全面策略:origin OQ2,本期在组件中遵循基本 a11y,全面策略留待后续
|
||
- @team/@board 模式可发现性引导:origin OQ9(FYI 级别)
|
||
|
||
---
|
||
|
||
## Risks & Dependencies
|
||
|
||
| 风险 | 影响 | 缓解 |
|
||
|------|------|------|
|
||
| `_phase_executor.py` 流式切换可能影响阶段执行稳定性 | expert_result 流式中断或丢失;retry loop 与流式交互导致前端累积乱码 | U3 单独测试;遵循 `execute_stream` 入口 `self.reset()` 前置条件;retry+流式合约:重试前广播 `expert_result_chunk_reset` 让前端清空,最终 `expert_result` 覆盖累积(详见 U3 Approach) |
|
||
| FullCalendar `.fc-*` 类名升级时可能破坏 | 日历样式失效 | 覆盖范围最小化(4 类稳定类名);在 `calendar-overrides.css` 顶部注释标注风险 |
|
||
| chatStore 新增 `deleteMessage` 可能影响会话状态一致性 | 消息索引错乱 | 参考 `deleteConversation` 的 pending 清理模式;单元测试覆盖删除后消息索引 |
|
||
| U3/U4 联调依赖 | 前端流式无法独立验证 | U4 可用 mock `expert_result_chunk` 事件开发;U3 完成后联调 |
|
||
|
||
---
|
||
|
||
## System-Wide Impact
|
||
|
||
- **前端**:ChatView 组件树结构调整(banner 替换为 sticky header);chatStore 新增方法与状态;chatStream 事件分支改造
|
||
- **后端**:`_phase_executor.py` 执行模式变更(execute → execute_stream);WS 事件新增 `expert_result_chunk` / `team_synthesis_chunk`
|
||
- **测试**:新增组件测试文件(ThinkingBlock / StickyModeHeader / AssistantText / UserBubble);chatStream.test.ts 扩展流式测试
|
||
- **样式**:新增 `calendar-overrides.css`;PhaseIndicator token 化;CalendarGrid token 化
|
||
|
||
---
|
||
|
||
## Open Questions
|
||
|
||
来自 origin 文档的 9 个 Open Questions 处理状态:
|
||
|
||
| OQ | 状态 | 处理方式 |
|
||
|----|------|----------|
|
||
| OQ1 交互状态覆盖 | Deferred | 本期测试覆盖核心场景,全面规范留待后续 |
|
||
| OQ2 响应式与无障碍 | Deferred | 本期遵循基本 a11y,全面策略留待后续 |
|
||
| OQ3 流式结构 | Resolved | U3 采用 `execute_stream()` + token 转发 |
|
||
| OQ4 R16 删除作用域 | Resolved | 前端隐藏,服务端删除留待后续 |
|
||
| OQ5 模式切换用户流 | Addressed | U2 Approach 中覆盖 |
|
||
| OQ6 关键交互细节 | Addressed | U2/U6 Approach 中指定(Popover/Popconfirm) |
|
||
| OQ7 .fc-* 兼容性 | Noted as Risk | Risks 表中标注 |
|
||
| OQ8 Sticky 头部替代 | Resolved | brainstorm 已确认 sticky 方案 |
|
||
| OQ9 模式可发现性 | FYI | Deferred to Follow-Up |
|
||
| OQ10 (NEW) U3 后端协议 scope 声明 | Deferred | origin 行 88 显式排除后端流式协议("不属本文档 scope"),但 U3 已纳入本计划作为实施单元。是否重新分类 U3 为 "Cross-cutting dependency unit"(与 UI/UE 单元分开)或显式标注为 scope 扩展?建议实施前确认。 |
|
||
| OQ11 (NEW) PhaseIndicator token 化 R-id 追溯 | Deferred | U2 中 PhaseIndicator 颜色 token 化(`#722ed1`/`#52c41a`/`#cf1322` → `--accent-board`/`--color-success`/`--color-error`)无对应 R-id 追溯(origin R7 仅要求"独立保留 + token 化")。是否新增 R-id 显式覆盖此 token 化工作,或延后到独立 token 化 pass 处理其他组件同类硬编码? |
|
||
|
||
---
|
||
|
||
## Sources & Research
|
||
|
||
- Origin: `docs/brainstorms/2026-07-01-ui-ue-enhancement-requirements.md`(ce-brainstorm 产出 + ce-doc-review 审查强化)
|
||
- `docs/solutions/logic-errors/long-horizon-reliability-code-review-fixes.md` — `execute_stream` 入口 reset 前置条件、team_synthesis 综合阶段完整内容读取
|
||
- `docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md` — WS 事件链端到端追踪模式、notify_callback 注入
|
||
- `docs/solutions/logic-errors/calendar-capability-and-ui-fixes.md` — SystemMonitorPanel flex 方向、deleteConversation pending 清理
|
||
- `docs/solutions/conventions/any-and-except-exception-governance.md` — WebSocket 异常分类、CancelledError 守卫
|
||
- `docs/solutions/integration-issues/jwt-secret-dev-mode-user-id-mismatch.md` — CalendarGrid firstDay 配置
|
||
- 代码库研究:`react.py:1443`(execute_stream)、`_phase_executor.py:222`(流式缺口)、`chatStream.ts:526-532`(final_answer 累积模式)、`tokens.css`(Notion 调色板)、`configs/experts/*.yaml`(专家元数据字段)
|