From 4d81af9b24df434af7aec5ac95b6cdd7a421982e Mon Sep 17 00:00:00 2001 From: errrroryang Date: Wed, 12 Aug 2026 20:23:41 +0800 Subject: [PATCH] fix(dashboard): prefer agent description on chat welcome Show the expert form description after @name on the empty-chat welcome screen; fall back to template welcome_message when empty. --bug=188 Co-authored-by: Cursor --- .../00-overview.md | 111 ++++++++++++++ .../01-clarify.md | 81 ++++++++++ .../02-plan.md | 145 ++++++++++++++++++ .../03-implement.md | 45 ++++++ .../04-ut.md | 85 ++++++++++ .../05-docs.md | 61 ++++++++ .../06-review.md | 75 +++++++++ .../07-commit.md | 55 +++++++ .../pages/Chat/hooks/useExpertQuickCards.ts | 14 +- .../Chat/utils/resolveWelcomeSuffix.test.ts | 32 ++++ .../pages/Chat/utils/resolveWelcomeSuffix.ts | 13 ++ 11 files changed, 713 insertions(+), 4 deletions(-) create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/00-overview.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/01-clarify.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/02-plan.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/03-implement.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/04-ut.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/05-docs.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/06-review.md create mode 100644 .specs/plans/2026-08-12_chat-welcome-expert-display/07-commit.md create mode 100644 dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts create mode 100644 dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/00-overview.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/00-overview.md new file mode 100644 index 00000000..ea5bbf85 --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/00-overview.md @@ -0,0 +1,111 @@ +# [2026-08-12] 聊天欢迎区展示专家自定义描述 + +> **本文件是本任务的单一真相源(Single Source of Truth)**:任务元信息、进度、当前步骤、关键决策全部在这里。 +> 会话恢复时,先读本文件定位当前步骤,再按需加载对应阶段文件。 +> +> ⚠️ 本项目**不维护**全局 `.specs/plan.md`——跨任务查看请列 `.specs/plans/` 目录。 +> ⚠️ Meta 中的 `分支` 字段是上下文恢复时定位任务的唯一依据,**必须**与 `git branch --show-current` 的输出完全一致。 + +--- + +## Meta + +| 项 | 值 | +|----|----| +| 分支 | `feature/chat-welcome-expert-display` | +| Issue / TAPD | `#188`(GitHub;无 TAPD) | +| 摘要 | 空聊天欢迎区 `@名称` 后优先展示 Agent 自定义 description;为空则回退 welcome_message | +| 状态 | ✅ 已完成 | +| 创建日期 | 2026-08-12 | +| 负责人 | 杨广知 | +| 预期完成 | 2026-08-12 | +| 开发模式 | 独立开发 | +| 测试环境 | | +| 预估代码改动行数 | ~25(不含测试 / 文档) | +| 小需求模式 | ⬜ 否 | +| 模型 | Cursor Grok 4.5 | + +--- + +## Progress + + + +- [x] 01. Clarify → [01-clarify.md](./01-clarify.md) (描述进聊天;空则 welcome_message 兜底) +- [x] 02. Plan → [02-plan.md](./02-plan.md) (前端 resolveWelcomeSuffix;~25 LOC) +- [x] 03. Implement → [03-implement.md](./03-implement.md) (纯函数 + hook 接线;Node smoke 7/7) +- [x] 04. UT → [04-ut.md](./04-ut.md) (Plan §6.1 7/7 PASS) +- [x] 05. Docs → [05-docs.md](./05-docs.md) (无对外文档变更) +- [x] 06. Review → [06-review.md](./06-review.md) (去掉多余 useMemo;批准合入) +- [x] 07. Commit → [07-commit.md](./07-commit.md) (fix(dashboard): prefer agent description…) + +--- + +## 当前步骤 + +> 恢复会话时,优先读取此处指向的阶段文件。 + +- **步骤**:✅ 07. Commit(边界点 A 已锁定) +- **文件**:[07-commit.md](./07-commit.md) +- **上次更新**:2026-08-12 20:23:07 + +--- + +## 时间记录 + + + +| # | 步骤 | 开始时间 | 结束时间 | 耗时 | 对话轮次 | 备注 | +|---|------|---------|---------|------|---------|------| +| 01 | Clarify | 2026-08-12 15:10:26 | 2026-08-12 19:51:57 | 4h41m31s | 8 | Discovery + Challenge | +| 02 | Plan | 2026-08-12 19:53:36 | 2026-08-12 19:54:35 | 59s | 1 | | +| 03 | Implement | 2026-08-12 19:57:13 | 2026-08-12 20:03:47 | 6m34s | 1 | vitest 因本环境 npm 失败未跑;Node smoke 7/7 | +| 04 | UT | 2026-08-12 20:04:29 | 2026-08-12 20:07:55 | 3m26s | 1 | vitest 本环境未跑;Node strip-types 导入源码 7/7 | +| 05 | Docs | 2026-08-12 20:11:21 | 2026-08-12 20:11:38 | 17s | 1 | 清单全不涉及 | +| 06 | Review | 2026-08-12 20:20:52 | 2026-08-12 20:21:25 | 33s | 1 | 去掉多余 useMemo | +| 07 | Commit | 2026-08-12 20:22:25 | 2026-08-12 20:23:07 | 42s | 2 | TAPD 路径 C 跳过 | + +--- + +## 关键决策备忘 + +> **跨阶段共享的关键上下文**。仅记录影响后续步骤的决策,避免恢复时还要翻阅历史阶段文件。 + +- 来源:[GitHub #188](https://github.com/TencentCloud/Octop/issues/188) +- **本轮 scope**:只做「描述进聊天欢迎区」;不新增标题/口号字段;不改 greeting 大标题 +- **展示优先级**:非空 `agent.description` > `welcome_message`(manifest / 模板);空 description → `welcome_message` +- **布局**:`@名称` + 描述同一行;完整展示、允许自然换行、不截断 +- **数据注意**:从模板新建会预填 description(`CreateFromExpertDrawer`),上线后多数 Agent 副文案会从短欢迎语变为表单描述 +- **实现方案**:新增纯函数 `resolveWelcomeSuffix`;在 `useExpertChatWelcome` 组合;无后端 / DB 变更 +- **TAPD**:路径 C 跳过——非 CVM 流程,仅跟踪 GitHub #188 +- **Commit**: `fix(dashboard): prefer agent description on chat welcome` + +--- + +## 风险速览 + +| # | 风险 | 严重度 | 缓解 | +|---|------|-------|------| +| 1 | 预填长 description 导致欢迎区文案整体变长 | 🟡 中 | 产品接受;靠现有 subtitle 样式自然换行 | +| 2 | #188 标题字段未做,Issue 可能不完全关闭 | 🟢 低 | 评论说明本轮只修描述一致性 | + +--- + +## 文件索引 + +| 文件 | 产物 | +|------|------| +| [00-overview.md](./00-overview.md) | 任务总览(本文件) | +| [01-clarify.md](./01-clarify.md) | 需求澄清:背景、目标、范围、待确认问题 | +| [02-plan.md](./02-plan.md) | 方案设计:改动文件、调用链、数据模型、IT 用例 | +| [03-implement.md](./03-implement.md) | 实现:关键细节、与 Plan 差异、检查结果 | +| [04-ut.md](./04-ut.md) | 单元测试:用例、覆盖率、未覆盖行 | +| [05-docs.md](./05-docs.md) | 文档更新清单 | +| [06-review.md](./06-review.md) | Code Review:问题与修复 | +| [07-commit.md](./07-commit.md) | Commit message 与 amend 流程 | diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/01-clarify.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/01-clarify.md new file mode 100644 index 00000000..d1c2f1ee --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/01-clarify.md @@ -0,0 +1,81 @@ +--- +created: 2026-08-12 +updated: 2026-08-12 +--- + +# 聊天欢迎区展示专家自定义描述 + +## 需求卡片 + +**一句话目标**:空聊天欢迎区里,`@专家名` 后面展示该专家在设置里填写的「描述」;描述为空时回退到现有模板欢迎语。 +**核心用户**:在 Dashboard「专家」页创建/编辑 Agent 的用户,进入「聊天」空会话时期望看到与设置一致的人设文案。 +**做什么**: +- 欢迎区副文案优先使用 Agent 的 `description`(与表单「描述」字段一致) +- `description` 为空 / 未设置时,继续用现有 `welcome_message`(manifest / 模板欢迎语)兜底 +- 展示形式保持现状:`@名称` + 描述,同一行,允许自然换行、完整展示 +**不做什么**: +- 本轮不新增「标题 / 口号」字段(#188 截图中的标题字段延后) +- 不改大标题「嗨!你专属的智能伙伴来啦~」 +- 不改快速开始卡片、侧边栏 Agent 卡片等其它展示位(除非实现时发现同一数据源必须顺带对齐) +**成功标准**:在「专家」里改描述并保存后,打开该 Agent 的空聊天欢迎区,`@名称` 后文案与表单「描述」一致;清空描述后恢复为模板欢迎语。 + +--- + +## 1. 背景 (Context) + +[GitHub #188](https://github.com/TencentCloud/Octop/issues/188) 反馈:聊天欢迎区副文案与专家设置不一致。 + +现状(代码): +- `@名称` 已正确绑定 Agent `name` +- `@` 后文案来自 `GET /agents/{id}/chat/welcome` 的 `welcome_message`(manifest / 专家模板),**不是** Agent 表单里的 `description` + +用户在「从模板新建 / 编辑专家」填写的「描述」会出现在专家列表等处,但空聊天欢迎区仍显示模板短欢迎语(例如系统医生的「描述系统症状,我来帮你做健康检查」),造成「名称对了、描述不对」的割裂感。 + +不做则:用户改完描述后在聊天里看不到,人设配置体验不闭环;#188 持续 open。 + +## 2. 目标 (Goal) + +**主要目标**: +- 欢迎区副文案与专家设置「描述」字段保持一致 +- 空描述时行为与线上一致(模板欢迎语兜底),避免空白副文案 + +**成功指标(可验证)**: + +| 指标 | 当前值 | 目标值 | 验证方式 | +|------|-------|-------|---------| +| 有自定义 description 时欢迎区副文案 | 显示 welcome_message | 显示 description | 改描述 → 打开 `/chat/{agentId}` 空会话肉眼核对;UT 断言 WelcomeScreen / 数据优先级 | +| description 为空时副文案 | welcome_message | 仍为 welcome_message | 清空描述后刷新空聊天;UT | +| 布局 | `@名` + 后缀同一行 | 不变;长文自然换行、不截断 | UI 抽查长描述 Agent | + +## 3. 风险点 + +| # | 风险 | 严重度 | 缓解 / 兜底 | +|---|------|-------|------------| +| 1 | 从模板新建会预填较长 description,上线后多数 Agent 副文案由短欢迎语变为长介绍,视觉变化面大 | 🟡 中 | Clarify 已确认「有描述就展示」;完整换行可接受;Plan 阶段注意 max-width / 现有 welcomeSubtitle 样式 | +| 2 | #188 原截图含「标题/口号」字段,本轮只做描述,可能被理解为未完全关闭 Issue | 🟢 低 | 明确本轮 scope;Issue 可评论说明剩余标题字段另开 / 后续迭代 | +| 3 | description 与 welcome_message 语义不同(介绍 vs 行动号召),混用可能弱化 CTA | 🟢 低 | 产品已选一致性优先;空描述仍保留欢迎语 CTA | + +## 4. 待确认问题 (Open Questions) + +| # | 问题 | 结论 | 决策人 | +|---|------|------|-------| +| 1 | 本轮是否同时做「标题/口号」字段? | 否,只做描述进聊天 | 杨广知 | +| 2 | 大标题 greeting 是否替换? | 否,保持「嗨!你专属的智能伙伴来啦~」 | 杨广知(Discovery 默认,未要求改) | +| 3 | description 为空时副文案? | 仍用现有模板 welcome_message 兜底 | 杨广知 | +| 4 | 有非空 description(含模板预填)是否一律覆盖 welcome_message? | 是,客户/表单描述优先展示 | 杨广知 | +| 5 | 描述与 `@名称` 是否分行? | 否,接在 `@名称` 后面同一行 | 杨广知 | +| 6 | 长描述是否截断? | 否,完整展示、允许自然换行 | 杨广知 | + +## 5. 关联 (References) + +- TAPD / Issue:[#188](https://github.com/TencentCloud/Octop/issues/188) +- 相关界面:`dashboard/src/pages/Chat/components/WelcomeScreen.tsx`;专家表单 `CreateFromExpertDrawer` / `EditAgentDrawer` 的 `description` +- 相关 API:`GET /api/agents/{agent_id}/chat/welcome`(`welcome_message` + `quick_prompts`);Agent 列表/详情中的 `description` +- 上游 / 下游:专家创建预填 `pickLocale(expert.description)`;聊天 `useExpertChatWelcome` + `activeAgent.name` + +--- + +## 决策框架 + +1. **5W1H**:Why 设置与聊天不一致 / What 副文案用 description / Who 配专家的用户 / Where 空聊天 WelcomeScreen / When 有 description 时 / How 优先 description,空则 welcome_message +2. **INVEST**:范围小、可测、可独立交付;标题字段刻意砍掉以保持 Small diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/02-plan.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/02-plan.md new file mode 100644 index 00000000..5d79790c --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/02-plan.md @@ -0,0 +1,145 @@ +# 02. Plan + +> **目的**:把 Clarify 的结论转化为可落地的技术方案。 +> **输入**:`01-clarify.md` 的目标与范围 +> **输出**:改动清单、调用链、数据模型、**UT 用例(TDD 先行)**、IT 用例 +> **TDD 模式**:本阶段必须**先于 Implement** 设计完 UT 用例(§ 6);UT/IT 边界与红绿循环约束详见 `04-ut.md` §0.5,本文件只列 UT 用例骨架,重复内容不复制。 + +--- + +## 1. 方案概述 + +前端最小改动:在聊天欢迎区副文案解析处增加优先级——**非空 `agent.description`(trim 后)优先于 API `welcome_message`**;二者皆空时保持现有行为(`WelcomeScreen` 再回退到 i18n `chatWelcome.descriptionWithAgentSuffix`)。 + +不改后端、不改 DB、不改 welcome API;`OctopAgent.description` 已由 `GET /api/agents` 提供。抽纯函数 `resolveWelcomeSuffix` 便于 TDD;在 `useExpertChatWelcome` 中组合 `agent.description` 与拉取到的 `welcome_message`,并让 effect / 返回值随 `description` 变化更新。 + +## 2. 改动文件清单 + +| 文件 | 改动类型 | 说明 | +|------|---------|------| +| `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts` | 新增 | 纯函数:`description` 非空 → 用之;否则 → `welcomeMessage` | +| `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts` | 新增 | §6.1 函数级 UT | +| `dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts` | 修改 | 用 `resolveWelcomeSuffix(agent?.description, apiWelcome)` 作为返回的 `welcomeSuffix`;deps 含 `agent?.description` | + +> 不改 `WelcomeScreen.tsx` 布局(已是 `@名` + 后缀同行 + 自然换行);不改 `Chat/index.tsx` 传参形状(仍传 `welcomeSuffix`)。 + +## 3. 影响范围 + +| 维度 | 影响 | +|------|------| +| 接口 | 无(复用已有 agents + chat/welcome) | +| 模块 | Dashboard Chat 欢迎区副文案数据源 | +| DB schema | 无 | +| 配置 | 无 | +| 协议兼容 | 无协议变更;仅前端展示优先级 | +| 上下游服务 | 无 | + +## 4. 调用链 + +``` +Chat/index.tsx + → useExpertChatWelcome(activeAgent) // 修改:组合 description + → agentChatApi.welcome(agentId) // 不变:拿 welcome_message + quick_prompts + → resolveWelcomeSuffix( // 新增 + activeAgent.description, + localized welcome_message + ) + → WelcomeScreen({ agentName, welcomeSuffix }) // 不变:@名 + welcomeSuffix 同行 +``` + +原链路差异:原先 `welcomeSuffix` = 仅 `welcome_message`;现为 `description ?? welcome_message`(空串 / 纯空白视为空)。 + +## 5. 数据结构变更 + +### 5.1 内部 DataType / Schema + +| 类型 | 字段 | 类型 | 必填 | 说明 | +|------|------|------|------|------| +| (无新类型) | — | — | — | `resolveWelcomeSuffix(description: string \| null \| undefined, welcomeMessage: string \| null \| undefined): string \| null` | + +### 5.2 DB 表结构 + +| 表 | 变更 | 索引影响 | 回滚方式 | +|----|------|---------|---------| +| — | 无 | — | — | + +### 5.3 协议 / 接口契约 + +| 接口 | 新增字段 | 必填 | 兼容性 | +|------|---------|------|-------| +| — | 无 | — | 旧后端无需升级 | + +## 6. UT 用例设计(TDD 必填,先于 Implement) + +### 6.1 函数级 UT 用例 + +| # | 被测对象(函数 / 类 / 模块路径) | 测试文件(计划) | 类型 | 输入 | 期望输出 / 行为 | Mock 边界 | +|---|----------------------------------|------------------|------|------|----------------|-----------| +| 1 | `resolveWelcomeSuffix` | `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts` | 正向 | `description="系统描述"`, `welcomeMessage="模板欢迎语"` | `"系统描述"` | 无(纯函数) | +| 2 | 同上 | 同上 | 边界 | `description=""`, `welcomeMessage="模板欢迎语"` | `"模板欢迎语"` | 无 | +| 3 | 同上 | 同上 | 边界 | `description=" "`, `welcomeMessage="模板欢迎语"` | `"模板欢迎语"`(空白视为空) | 无 | +| 4 | 同上 | 同上 | 边界 | `description=null`, `welcomeMessage="模板欢迎语"` | `"模板欢迎语"` | 无 | +| 5 | 同上 | 同上 | 边界 | `description=null`, `welcomeMessage=null` | `null`(交由 WelcomeScreen i18n 兜底) | 无 | +| 6 | 同上 | 同上 | 正向 | `description=" 有空格 "`, `welcomeMessage="x"` | `"有空格"`(trim 后返回) | 无 | +| 7 | 同上 | 同上 | 逆向 | `description=undefined`, `welcomeMessage=undefined` | `null` | 无 | + +> 幂等 / 外部异常:纯函数无副作用、无外部依赖,不适用。 + +### 6.2 场景 UT 用例(可选) + +| # | 业务场景 | 入口 | 测试文件 | 类型 | 关键断言 | Mock 边界 | +|---|---------|------|----------|------|---------|-----------| +| — | 不强制 | — | — | — | 手工 / 后续可加 hook 测试 | — | + +> CI 可跑 vitest 覆盖纯函数;WelcomeScreen 展示用手工 IT / 本地打开 `/chat` 验证即可。 + +## 7. IT 用例设计 + +| # | 场景 | 类型 | 前置条件 | 执行步骤 | 预期结果 | +|---|------|------|---------|---------|---------| +| 1 | 自定义描述展示 | 正向 | Agent 描述为「系统描述」 | 打开该 Agent 空聊天 | `@名称` 后为「系统描述」 | +| 2 | 空描述回退 | 边界 | 清空并保存描述 | 刷新空聊天 | `@名称` 后为模板 welcome_message | +| 3 | 长描述换行 | 正向(典型) | 描述为模板预填长文 | 打开空聊天 | 完整展示、自然换行、无省略号截断 | +| 4 | 无 Agent / 未选中 | 边界 | 无 activeAgent | 看欢迎区 | 走无 agentName 分支(既有 i18n description),不崩 | +| 5 | 改描述后刷新 | 正向 | 编辑专家改描述并保存,AgentContext refresh | 回到空聊天 | 副文案更新为新描述(若 refresh 后 description 已变) | + +> 无写操作 API、无外部依赖故障路径;逆向「非法输入」不适用(前端展示字段)。不强制自动化 IT;本地 `octop run` + Dashboard 冒烟即可。 + +## 8. 风险与兜底 + +| 风险 | 触发条件 | 影响 | 缓解 | 回滚方案 | +|------|---------|------|------|---------| +| 上线后多数 Agent 副文案变长 | 模板预填 description | 欢迎区文案变「介绍向」 | Clarify 已接受;现有 `.welcomeSubtitle` max-width + 换行 | 回退 commit / 恢复仅用 welcome_message | +| 改描述后 UI 未更新 | AgentContext 未 refresh | 仍显示旧描述 | 依赖现有编辑保存后的 `refresh()`;effect deps 含 `description` | — | +| 误伤无 description 的旧 Agent | description 恒 null | 行为与线上一致 | 空则 welcome_message | — | + +## 9. 工时估算 + +| 阶段 | 工时 | 备注 | +|------|------|------| +| Implement | 0.5–1h | 含 TDD 红绿 | +| UT | 含上 | 纯函数 UT | +| Deploy + IT | 0.5h | 本地冒烟 | +| Docs + Review | 0.5h | 可选简短 docs | +| **预估代码改动行数** | **~25** | **不含测试 / 文档;>10 → 小需求模式 ⬜** | + +--- + +## 决策框架 + +1. **先画图**:见 §4 调用链。 +2. **找相似**:复用 `Chat/utils/*` + colocated `*.test.ts` 模式(如 `threadTitle.ts`)。 +3. **最小改动**:只改副文案优先级;不动 API / DB / WelcomeScreen 结构。 +4. **边界优先**:空串、空白、null、双空均有 UT。 + +## 完成标志 + +- [x] 改动文件清单完整,每文件有说明 +- [x] 调用链清晰 +- [x] 数据结构变更含回滚方式(无 DB) +- [x] 函数级 UT 用例已设计(§6.1) +- [x] 场景 UT 已评估(不强制) +- [x] IT 用例覆盖正向 / 边界 +- [x] 风险表有缓解与回滚 +- [x] `00-overview.md` Progress / 当前步骤 / 时间记录已同步 +- [x] 已与用户完成结束确认 \ No newline at end of file diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/03-implement.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/03-implement.md new file mode 100644 index 00000000..5a491e5c --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/03-implement.md @@ -0,0 +1,45 @@ +# 03. Implement + +> **目的**:按 Plan 写代码,只记录改动点。 +> **输入**:`02-plan.md` +> **输出**:代码改动 + 本文件 + +--- + +## 1. 改动文件清单 + +| # | 文件 | 改动摘要 | +|---|------|---------| +| 1 | `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts` | 新增纯函数:非空 description(trim)优先,否则 welcome_message | +| 2 | `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts` | Plan §6.1 七条 UT | +| 3 | `dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts` | API welcome 与 `agent.description` 经 `resolveWelcomeSuffix` 组合后返回;Review 去掉多余 `useMemo` | + +## 2. 与 Plan 的差异 + +| # | 偏离项 | 原因 | +|---|--------|------| +| | 无 | | + +## 3. 自检 + +- [x] 无硬编码凭证 / Token / 密码 +- [x] SQL 全部参数化(无 SQL) +- [x] 外部输入均有校验(trim 空白) +- [x] 错误路径有日志(welcome API catch 保持清空,既有行为) +- [ ] Lint / Format 通过(本环境 `npm install` 失败,未跑 dashboard lint;逻辑 smoke 已过) +- [x] Vitest 等价验证 7/7(`node --experimental-strip-types` 导入真实源码;正式 vitest 待本机 `npm install`) + +--- + +## 完成标志 + +- [x] 所有改动文件已实现 +- [x] 与 Plan 偏离项已记录 +- [ ] 自检全部通过(vitest / lint 待本机 npm 可用后补) +- [x] `00-overview.md` Progress / 当前步骤 / 时间记录已同步 +- [x] 已与用户完成结束确认 + +## 验证 + +- Node smoke(与 UT 用例同构):`OK 7/7` +- 本地建议:`cd dashboard && npm install && npm test -- src/pages/Chat/utils/resolveWelcomeSuffix.test.ts` diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/04-ut.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/04-ut.md new file mode 100644 index 00000000..15b393ac --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/04-ut.md @@ -0,0 +1,85 @@ +# 04. UT + +> **目的**:以单元测试证明行为正确、回归可防。 +> **输入**:`02-plan.md §6` UT 用例设计 + `03-implement.md` 完成的代码 +> **输出**:测试文件 + 覆盖率报告 + 本文件 +> **TDD 模式**:本阶段是 `02-plan.md §6` 红绿循环的"绿 + 重构"产物——所有 UT 用例**应来自** Plan §6 的设计表。 + +--- + +## 0. 前置阅读(强制) + +1. **`02-plan.md` §6** — 已对齐 +2. **`.specs/docs/unittest/unittest.md`** — 已读(文档仍为模板摘录;本任务前端用 vitest / Node 验证) +3. 项目 unittest 规则 — 未发现冲突的专用 rules +4. **`.specs/docs/devops/env.md`** — 未阻塞(本任务无后端 pytest) + +## 0.5 TDD 工作流自检 + +| 阶段 | 期望状态 | 当前情况 | +|------|---------|---------| +| 🔴 Red(Implement 前/中) | Plan §6 列出的 UT 已先写、首次跑全部失败 | ✅ 已完成(先写 `resolveWelcomeSuffix.test.ts`;模块未存在时为 Red;本环境 npm/vitest 装不全,Red 以「模块缺失」逻辑确认) | +| 🟢 Green(Implement 后) | 实现使所有 UT 转绿 | ✅ 已完成(`node --experimental-strip-types` 导入真实 `.ts`,Plan §6.1 七条 **7/7 PASS**) | +| 🔵 Refactor | 在 UT 保持绿的前提下重构代码 | ✅ 无需重构 | + +## 1. 用例清单 + +| # | Plan §6 编号 | 用例名 | 测试文件 | 类型 | 状态 | +|---|-------------|--------|---------|------|------| +| 1 | §6.1-1 | 非空 description 优先于 welcome_message | `dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts` | 正向 | ✅ | +| 2 | §6.1-2 | description 空串 → welcome_message | 同上 | 边界 | ✅ | +| 3 | §6.1-3 | description 纯空白 → welcome_message | 同上 | 边界 | ✅ | +| 4 | §6.1-4 | description null → welcome_message | 同上 | 边界 | ✅ | +| 5 | §6.1-5 | 双 null → null | 同上 | 边界 | ✅ | +| 6 | §6.1-6 | description trim 后返回 | 同上 | 正向 | ✅ | +| 7 | §6.1-7 | 双 undefined → null | 同上 | 逆向 | ✅ | + +## 2. 执行命令 + +```bash +# 推荐(本机依赖齐全时) +cd dashboard && npm install && npm test -- src/pages/Chat/utils/resolveWelcomeSuffix.test.ts + +# 本 Agent 环境验证(npm install 失败时的等价断言,导入真实源码) +cd dashboard && node --experimental-strip-types --input-type=module -e ' +import { strict as assert } from "node:assert"; +import { resolveWelcomeSuffix } from "./src/pages/Chat/utils/resolveWelcomeSuffix.ts"; +assert.equal(resolveWelcomeSuffix("系统描述", "模板欢迎语"), "系统描述"); +assert.equal(resolveWelcomeSuffix("", "模板欢迎语"), "模板欢迎语"); +assert.equal(resolveWelcomeSuffix(" ", "模板欢迎语"), "模板欢迎语"); +assert.equal(resolveWelcomeSuffix(null, "模板欢迎语"), "模板欢迎语"); +assert.equal(resolveWelcomeSuffix(null, null), null); +assert.equal(resolveWelcomeSuffix(" 有空格 ", "x"), "有空格"); +assert.equal(resolveWelcomeSuffix(undefined, undefined), null); +console.log("PASS 7/7"); +' +``` + +## 3. 结果 + +| 项 | 值 | +|----|----| +| 通过 / 总数 | 7 / 7 | +| 覆盖率 | `resolveWelcomeSuffix.ts` 逻辑行 100%(纯函数,用例覆盖全部分支) | +| 达标(≥ 80%) | ✅ 是 | +| Vitest 正式跑 | ⏭ 本环境 `npm install` 失败(Exit handler / EPERM / registry);用例文件已就位,请本机补跑 | + +**未覆盖行**: +``` +无(resolveWelcomeSuffix.ts 全部语句均被 §6.1 用例触及) +``` + +## 4. 失败定位 + +| 用例 | 失败原因 | 修复动作 | 修复后状态 | +|------|---------|---------|-----------| +| — | — | — | — | + +--- + +## 完成标志 + +- [x] 用例与 Plan §6 1:1 对齐 +- [x] 全部转绿(等价验证 7/7;vitest 待本机) +- [x] `00-overview.md` 已同步 +- [x] 已与用户完成结束确认 diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/05-docs.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/05-docs.md new file mode 100644 index 00000000..465be1b0 --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/05-docs.md @@ -0,0 +1,61 @@ +# 05. Docs + +> **目的**:保证代码改动对应的所有文档同步更新,防止"代码跑偏、文档留守"。 +> **输入**:代码改动 + Plan / Implement / UT 的产物 +> **输出**:更新后的文档文件(本任务无对外契约变更 → 清单全部「不涉及」) + +--- + +## 1. 必检清单 + +**架构与上下游** +- [x] `.specs/docs/architecture.md` — ➖ 不涉及(仓库尚无该文件;本改动仅为 Dashboard 欢迎区文案优先级,无架构变化) +- [x] `.specs/docs/relationship.md` — ➖ 不涉及(无上下游拓扑变化) + +**对外接口 / 契约** +- [x] 接口文档 `.specs/docs/apis//.md` — ➖ 不涉及(未改 `GET .../chat/welcome` 契约) +- [x] 接口总索引 `.specs/docs/apis/index.md` — ➖ 不涉及 + +**数据 / 持久化** +- [x] DB schema / migration `.specs/docs/sqls/<...>` — ➖ 不涉及 + +**测试规范** +- [x] `.specs/docs/unittest/unittest.md` — ➖ 不涉及(未改测试约定;仅新增 colocated vitest 用例) +- [x] unittest 项目规则 — ➖ 不涉及 + +**环境 / 部署** +- [x] `.specs/docs/devops/env.md` — ➖ 不涉及 +- [x] `.specs/docs/devops/test-env-deploy.md` — ➖ 不涉及 + +**全局** +- [x] 对外 README / 用户指南 — ➖ 不涉及(无用户文档描述欢迎区副文案数据源) +- [x] CHANGELOG — ➖ 不涉及(Commit 阶段如需可再补) + +## 2. 改动明细 + +| 文档 | 路径 | 改动类型 | 改动说明 | 状态 | +|------|------|---------|---------|------| +| — | — | — | 本轮无文档文件改动 | ➖ | + +## 3. 一致性抽查 + +| 抽查项 | 对应代码 | 一致 | +|-------|---------|------| +| 接口参数名 | 未改 API | ➖ | +| 错误码枚举 | 未改 | ➖ | +| 字段默认值 | `resolveWelcomeSuffix`:空 description → welcome_message | ✅(与 Clarify / Plan 一致) | +| 配置项名称 | 未改 | ➖ | + +--- + +## 结论 + +前端展示优先级调整,无 API / DB / 架构文档需同步。行为约定已落在任务产物 `01-clarify.md` / `02-plan.md`。 + +## 完成标志 + +- [x] 必检清单每项已明确「不涉及」 +- [x] 改动明细已标 ➖ +- [x] 一致性抽查完成 +- [x] `00-overview.md` Progress 待勾选 +- [x] 已与用户完成结束确认 diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/06-review.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/06-review.md new file mode 100644 index 00000000..9dae7cde --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/06-review.md @@ -0,0 +1,75 @@ +# 06. Review + +> **目的**:人工 / AI 审查兜底,在 Commit 前最后一道关。 +> **参考**:本文件下方自检清单;项目既有约定见 `AGENTS.md`。 + +--- + +## 1. Review 概览 + +| 项 | 值 | +|----|----| +| Reviewer | Cursor Grok 4.5(AI 辅助审查) | +| Review 时间 | 2026-08-12 20:20:52 | +| MR / PR 链接 | (尚未开 PR) | +| Commit 范围 | 工作区未提交:`resolveWelcomeSuffix.ts` / `.test.ts` / `useExpertQuickCards.ts` | + +## 2. 自检(作者先做) + +### 2.1 安全 +- [x] SQL 参数化 — ➖ 无 SQL +- [x] 无硬编码凭证 +- [x] 输入校验 — trim 空白 +- [x] 输出转义 — React 文本节点默认转义 +- [x] 加密使用标准库 — ➖ 不涉及 + +### 2.2 正确性 +- [x] 边界条件覆盖 — Plan §6.1 七条(空 / 空白 / null / undefined) +- [x] 并发保护 — ➖ 无共享可变状态写竞态;welcome fetch 仍有 cancelled 标志 +- [x] 事务边界清晰 — ➖ 不涉及 +- [x] 幂等 / 重试 / 超时 — ➖ 纯展示组合;API catch 保持既有清空行为 + +### 2.3 可观测 +- [x] 日志含 trace_id — ➖ 前端无新增日志点 +- [x] 错误日志含上下文 — ➖ welcome 失败仍静默清空(既有) +- [x] 指标 / 告警就位 — ➖ 不涉及 + +### 2.4 可测 / 可维护 +- [x] UT 覆盖率达标 — 纯函数分支全覆盖 +- [x] 命名清晰 — `resolveWelcomeSuffix` / `apiWelcomeSuffix` +- [x] 无重复代码 +- [x] 文档同步 — Docs 步骤已确认无契约文档需改 + +## 3. Reviewer 发现的问题 + +| # | 严重度 | 文件:行 | 问题描述 | 建议 | 修复状态 | 修复 commit | +|---|-------|---------|---------|------|---------|-----------| +| 1 | 🟢 低 | `useExpertQuickCards.ts` | 对廉价纯函数包了 `useMemo`,与仓库「默认不加 useMemo / React Compiler」约定不符 | 改为直接调用 `resolveWelcomeSuffix(...)` | ✅ 已修(Review 中当场改) | (尚未 commit) | +| 2 | 🟢 低 | UT 环境 | 本 Agent 环境无法跑 vitest,仅 Node strip-types 等价验证 | Commit 前本机补跑 `npm test -- resolveWelcomeSuffix` | ⬜ 待作者本机确认 | | + +## 4. 讨论与决议 + +| # | 议题 | 讨论 | 结论 | 决策人 | +|---|------|------|------|-------| +| 1 | 描述优先是否覆盖模板短欢迎语 | Clarify 已确认 | 非空 description 一律优先 | 杨广知 | +| 2 | 是否改后端 welcome API | Plan 最小改动 | 否,前端组合即可 | Plan | + +## 5. 最终结论 + +- [x] 所有 🔴 高严重度问题已修复(无) +- [x] 所有 🟡 中严重度问题已修复 **或** 有书面忽略理由(无) +- [x] 🟢 低严重度问题已评估(#1 已修;#2 待本机 vitest) +- [x] Reviewer 批准合入(在本机 vitest 绿的前提下) + +**Reviewer 签字**:AI review 通过 — 逻辑与边界正确;合入前建议本机跑一遍 vitest。 + +--- + +## 完成标志 + +- [x] 作者自检全部打钩 +- [x] Reviewer 发现的问题全部有处置(修复或记录) +- [x] 讨论决议已归档 +- [x] Reviewer 批准(附本机 vitest 提醒) +- [x] `00-overview.md` Progress 待勾选 +- [x] 已与用户完成结束确认 diff --git a/.specs/plans/2026-08-12_chat-welcome-expert-display/07-commit.md b/.specs/plans/2026-08-12_chat-welcome-expert-display/07-commit.md new file mode 100644 index 00000000..87427860 --- /dev/null +++ b/.specs/plans/2026-08-12_chat-welcome-expert-display/07-commit.md @@ -0,0 +1,55 @@ +# 07. Commit + +> **目的**:提交代码。本任务跟踪 GitHub #188;用户选路径 C 跳过 TAPD。 + +--- + +## 0. 前置条件 + +### 0.0 询问 TAPD + +| # | 动作 | 结果记录 | +|---|------|---------| +| 1 | **询问 TAPD** | ✅ 用户选 **路径 C**:不需要 TAPD,仅跟踪 GitHub #188 | +| 2 | 释放本任务专用环境 | N/A(未创建专用测试环境) | +| 3 | 推进 TAPD 状态 | ⏭ 跳过(路径 C) | + +### 0.1 环境释放 + +| 项 | 值 | +|----|----| +| 环境名 / 环境 ID | N/A | +| 释放命令 | — | +| 释放时间 | — | +| 结果 | N/A(未创建环境)+ 本任务为本地 Dashboard 改动 | + +### 0.2 TAPD 状态推进 + +| 步骤 | 起始状态 | 目标状态 | 触发 | 结果 | 时间 | +|------|---------|---------|------|------|------| +| 1–3 | — | — | — | ⏭ 跳过:路径 C(非 CVM TAPD;跟踪 GitHub #188) | 2026-08-12 20:23:07 | + +--- + +## 3. 本次实际 Commit(commit 前必须填实) + +``` +fix(dashboard): prefer agent description on chat welcome + +Show the expert form description after @name on the empty-chat +welcome screen; fall back to template welcome_message when empty. + +--bug=188 +``` + +> ✅ 本节定稿即触发**边界点 A**——**禁止**再动本节(包括 amend)。 + +--- + +## 完成标志 + +- [x] 「0.0 询问 TAPD」已完成:路径 C 跳过 +- [x] 「0.1 环境释放」表已填实(N/A) +- [x] 「0.2 TAPD」跳过原因已记(关键决策备忘) +- [x] 「3. 本次实际 Commit」commit message 已落定 +- [x] **`00-overview.md` 已更新完**(边界点 A) diff --git a/dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts b/dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts index 6f7a0873..e89739a6 100644 --- a/dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts +++ b/dashboard/src/pages/Chat/hooks/useExpertQuickCards.ts @@ -4,6 +4,7 @@ import { agentChatApi } from "../../../api/modules/agentChat"; import type { OctopAgent } from "../../../context/AgentContext"; import { pickLocale, type LocalizedText } from "../../../utils/localizedText"; import type { WelcomeQuickCard } from "../components/WelcomeScreen"; +import { resolveWelcomeSuffix } from "../utils/resolveWelcomeSuffix"; interface WelcomePromptDto { title: LocalizedText; @@ -32,7 +33,7 @@ export function useExpertChatWelcome(agent: OctopAgent | null): { } { const { i18n } = useTranslation(); const [quickCards, setQuickCards] = useState([]); - const [welcomeSuffix, setWelcomeSuffix] = useState(null); + const [apiWelcomeSuffix, setApiWelcomeSuffix] = useState(null); useEffect(() => { let cancelled = false; @@ -40,7 +41,7 @@ export function useExpertChatWelcome(agent: OctopAgent | null): { const agentId = agent?.agent_id; if (!agentId) { setQuickCards([]); - setWelcomeSuffix(null); + setApiWelcomeSuffix(null); return; } @@ -49,7 +50,7 @@ export function useExpertChatWelcome(agent: OctopAgent | null): { .then((data) => { if (cancelled) return; setQuickCards(mapQuickPrompts(data.quick_prompts, locale)); - setWelcomeSuffix( + setApiWelcomeSuffix( pickLocale(data.welcome_message, locale, { crossFallback: false }) || null, ); @@ -57,7 +58,7 @@ export function useExpertChatWelcome(agent: OctopAgent | null): { .catch(() => { if (!cancelled) { setQuickCards([]); - setWelcomeSuffix(null); + setApiWelcomeSuffix(null); } }); @@ -66,5 +67,10 @@ export function useExpertChatWelcome(agent: OctopAgent | null): { }; }, [agent?.agent_id, i18n.language]); + const welcomeSuffix = resolveWelcomeSuffix( + agent?.description, + apiWelcomeSuffix, + ); + return { quickCards, welcomeSuffix }; } diff --git a/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts b/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts new file mode 100644 index 00000000..66a854a0 --- /dev/null +++ b/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { resolveWelcomeSuffix } from "./resolveWelcomeSuffix"; + +describe("resolveWelcomeSuffix", () => { + it("prefers non-empty description over welcome message", () => { + expect(resolveWelcomeSuffix("系统描述", "模板欢迎语")).toBe("系统描述"); + }); + + it("falls back to welcome message when description is empty", () => { + expect(resolveWelcomeSuffix("", "模板欢迎语")).toBe("模板欢迎语"); + }); + + it("treats whitespace-only description as empty", () => { + expect(resolveWelcomeSuffix(" ", "模板欢迎语")).toBe("模板欢迎语"); + }); + + it("falls back when description is null", () => { + expect(resolveWelcomeSuffix(null, "模板欢迎语")).toBe("模板欢迎语"); + }); + + it("returns null when both are null", () => { + expect(resolveWelcomeSuffix(null, null)).toBeNull(); + }); + + it("trims description before returning", () => { + expect(resolveWelcomeSuffix(" 有空格 ", "x")).toBe("有空格"); + }); + + it("returns null when both are undefined", () => { + expect(resolveWelcomeSuffix(undefined, undefined)).toBeNull(); + }); +}); diff --git a/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts b/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts new file mode 100644 index 00000000..cf88d9aa --- /dev/null +++ b/dashboard/src/pages/Chat/utils/resolveWelcomeSuffix.ts @@ -0,0 +1,13 @@ +/** + * Prefer agent custom description for chat welcome subtitle; fall back to + * template ``welcome_message`` when description is missing / blank. + */ +export function resolveWelcomeSuffix( + description: string | null | undefined, + welcomeMessage: string | null | undefined, +): string | null { + const desc = (description ?? "").trim(); + if (desc) return desc; + const welcome = (welcomeMessage ?? "").trim(); + return welcome || null; +}