Skip to content

Latest commit

 

History

History
233 lines (148 loc) · 14.4 KB

File metadata and controls

233 lines (148 loc) · 14.4 KB

Chat Composer Redesign — Why(April 2026)

技术实现见 docs/handover/chat-composer-redesign.md

起点:Chat 页是工具集合,还是 Agent 工作入口

重构前的聊天页实际上是一个"按钮收纳柜":

  • 输入框上方:一行 代码 / 计划 tabs(segmented control,按钮形态)
  • 输入框上方:另一行 默认权限 / 完全访问(按钮形态)
  • 输入框上方:设计 Agent(按钮形态)
  • 输入框内部 footer:附加文件按钮 + 斜杠按钮 + CLI 工具按钮 + 模型选择 + Effort + 提交(六个按钮一字排开)
  • 顶部一行 RunCockpit:⚡ Claude Code · 🔌 Anthropic / claude-sonnet-4 · 📌 Pinned · ● 健康(每段都是 button 跳到不同设置页)
  • 底部右下角:上下文圆环图标 + HoverCard

每个组件单独看都"合理",但拼起来用户每次发消息前要不要挨个确认这些按钮?这种"工具集合"心智反而让 chat 变成"配置页 + 输入框",不像"把任务交给 Agent 去执行"。

用户的原话:"状态、模式、能力、命令都被平铺成按钮了,所以页面看起来干净,但心智上反而有点吵。"

设计原则的演化

第一原则:按钮越少,离 AGI 越近

不是说功能要砍,而是把"用户每次发消息前不需要决定的事"从视觉决定区移走:

  • 不要让用户主动选:模式 / 权限 / 模型几乎从来不动 → 默认态视觉权重压到最低(隐形 select,hover 才显形)
  • 不要让用户拼:本次会用什么 Runtime / 什么 Provider / 什么权限 → 合一个面板一次说清,不要散成多个 chip 让用户脑内拼装
  • 不要重复说:底部已经显示当前 Runtime → 模型选择器顶部就别再写一遍 Runtime 名

第二原则:默认无形 + 错误才上色

"出问题才提示,没出问题就不要显示" 是这一轮反复打磨出的核心规则。

最初的设计错误:底部 RunCockpit 常态显示 ⚡ Claude Code · Auto · ● 健康 三段,每段都用 Button 形态。三段都没问题的时候,"三个按钮"反而成了视觉噪声——用户被吸引去关注三个其实"一切正常"的状态指示。

正确的分级:

类别 视觉
P0 阻塞 / 严重故障 banner 醒目提示并阻断发送
温和警告(非阻塞) composer 右槽显示一个 status chip,点击跳 Health 页
常态 OK 完全不渲染

具体到这一轮:

  • "已固定" 是用户主动选过的状态,不是错误,所以默认态也是浅灰文字(不是 chip)
  • "Auto" 是默认态,根本不显示
  • 健康度绿色 dot 在常态完全消失(之前一直绿着,给用户造成"健康度是不是个我得管理的指标"的负担)
  • Runtime 标签常驻(这是真信息,告诉你"现在用什么引擎在跑")
  • 出问题才上色:runtime fallback / pinned-invalid / no compatible provider / health warn 上 status-pair chip

第三原则:解释 vs 修改要分开

Codex review 提出的 Run 状态聚合面板是这轮的关键改造。底层洞察:

  • 散落在 4 个独立 chip 里的状态信息,用户必须自己拼装 "这次会怎么跑"
  • 每个 chip 跳到不同设置页 → 用户得来回切换才能改/查
  • 解决方法:把 5 个状态维度(引擎 / 模型 / 默认 / 权限 / 上下文)汇总到一个 popover,面板只负责解释,不允许在面板里改——所有"设置 / 切换 / 修改"都跳到对应设置页

为什么面板不允许直接改?两个原因:

  1. 把它做成"能改"的 popover 就变成又一个设置弹层,跟主路径下的 ModeIndicator / PermissionSelector / 模型 dropdown 重复
  2. 解释和修改的视觉权重不一样——解释面板要密、要全、要静;修改要分散到操作触手可及的位置(toolbar / dropdown)

决策日志

关于"搜索框"

Codex review 建议给模型选择器加搜索框 + 顶部置顶 "当前默认 / 推荐 / 最近使用" 三栏。

我们最终的决定:不加搜索框,但加"最近使用"置顶(最多 3 项)。

理由:

  • 模型选择器不是"模型市场",不应该鼓励用户搜索海量模型
  • 我们的用户主要是切回刚用过的 1-2 个模型,最近使用列表覆盖 80% 场景
  • 搜索框加进来后必然要做高亮 / 模糊匹配 / 键盘导航 / 占位文案,引入设计复杂度
  • 如果用户真的要找某个特定模型,可以滚——20 个模型滚动找不是大问题

关于"健康度"是否常驻

设计初期我把 RunCockpit 的健康 dot(绿/黄/红)做成常驻显示。用户当场反馈:"这个设计目前感觉有点尴尬。健康度应该取决于长期的规划,如果你以后打算依托健康度做其他功能,那目前如果不健康或者出现警示,会给用户造成很大的心理负担。"

最终决定:常态不显示健康度(绿色 dot 完全消失);只在 severity=warn/error 时上 status chip。

延伸到所有"状态显示":默认极简,异常才扩展。这跟操作系统状态栏的设计哲学一致——平时只显示真正需要 user 关心的信息。

关于"已固定"的视觉权重

原始设计:Pinned chip 用 bg-primary text-primary-foreground 实色(项目主色)。

用户反馈过 3 轮,最终落到:"已固定" 跟其它工具栏组件 同色(muted-foreground),靠图钉图标 + 文字内容传达状态,不靠色彩。

教训:在低对比环境(浅灰打底的工具栏)里,任何"再轻量的色彩 chip" 都会成为视觉焦点。用户会下意识被吸引去关注那个非默认色的元素。

→ 沉淀为 memory 里的 "三层视觉规则"。

关于"用代码符号还是界面文字跟用户沟通"

中途用户当场指出:"我他妈看不见组件名,用户也看不见组件名。" 这位用户是产品/品牌策略方向,不是程序员,读不懂 RunCockpit.tsx / text-primary / bg-status-error-muted 这种符号。

所有跟 UI 相关的沟通都改成"屏幕上看到的文字 + 位置 + 感知颜色":

  • ✅ "已固定 这个标签"、"输入框右下角的百分比图标"
  • ❌ "RunCockpit 的 Pinned chip"、"text-primary"

→ 沉淀为 memory feedback_describe_in_user_terms.md

用户旅程上的视觉对比

之前

用户进入聊天页,眼睛被这些元素同时吸引:

  • 顶部 4 段独立 chip,3 种不同的色彩(运行中绿色、警告黄色、固定蓝色)
  • 输入框 footer 里 6 个按钮一字排开
  • 输入框上方 3 行操作(代码/计划 + 默认权限 + 设计 Agent + 上下文圆环)

视觉重量分布:每个元素都"在喊"。心智负担:发消息前要不要挨个确认?

现在

用户进入聊天页,第一眼看到的是:

  • 一个紧凑输入框(柔光阴影、24px 圆角)
  • 输入框下方两侧:左侧 代码 默认权限(浅灰文字,几乎隐形);右侧 Claude Code · 16%(一行状态文本)
  • 异常时左/右某处变成醒目 chip + 警告色

视觉重量集中在:输入框本身(用户当下要做的事)。其它一切是"待命"——我在那里、需要时点开就有。

Codex review 的价值

这一轮中后期引入 Codex(外部 AI)做整体 review 是关键校准点。Codex 一次性提了 10 条建议,其中:

  • 5 条是低风险高价值的小修补(占位文字、tooltip、aria-label、∞% 修复、模型下拉文案)→ 直接做
  • 3 条需要决策方向(搜索框、运行状态合面板、权限弱化的边界)→ 跟用户对齐再做
  • 1 条是大改动(context chips)→ 留下一阶段
  • 1 条是不冲突的小修补(菜单项加副标题)→ 直接做

外部 review 的最大价值是 防止过拟合:当我们已经在这个面板上来回打磨 8 轮,会陷入"再调一下应该就好了"的局部最优;Codex 没经历过那 8 轮,看到的是新用户视角,能直接指出"这个面板功能对了,但右侧链接默认 40% opacity 在浅色背景下看起来像 disabled"。

→ 沉淀为后续 review 节奏:每打磨完一段视觉收敛后做一次外部 review,避免在同一处过度打磨。

Chat 页设计原则(沉淀版)

经过两轮迭代(April 2026 视觉收敛 + Context chips Phase 1),用户明确这五条作为后续 Chat 页所有改动的判断基线。新增任何 UI 元素、交互、状态展示前,按这五条交叉检验。

1. 输入框是主入口,按钮越少越好

聊天页的核心动作是对 Agent 说话。一切让用户在发送前需要"决定要不要先点一下这个按钮"的元素都是噪声。新增控件前先问:

  • 99% 的会话里这个按钮会被用户动到吗?不会的话不要常驻
  • 这个状态用户能从其它地方推断出来吗?能的话不要单独显示
  • 这个能力是用户主动触发的吗?是的话只在意图触发时出现,不要常驻按钮化

反例:早期版本输入框上方有"代码 / 计划 / 默认权限 / 设计 Agent" 四个 outlined button 一字排开。每个单独"合理",拼起来像 settings 页。用户每次发送前都要扫一眼这些按钮的状态,心智负担超过收益。

2. 非 AI 的确定性控制要清楚

权限 / 模型 / 上下文 / 发送 这四件事是用户对系统的硬性指令——不该被 AI 推断、不该模糊化、必须可见可改。

  • 权限:决定 Agent 是否经过用户批准就能动文件 / 跑命令 — 不能隐藏
  • 模型:决定哪个 LLM 真正在响应 — 不能隐藏
  • 上下文:决定 Agent 看到了什么信息 — 必须可见可移除
  • 发送:用户对消息边界的决定权 — 永远显式(按 Enter 或点提交按钮,不是 AI 自动发)

这四件事在 Chat 页都有明确的 UI surface:底部工具栏左侧是权限 / 模型 toggle,输入框上方是 context chips,输入框右下角是发送按钮。它们的视觉权重可以低(隐形 select / 浅灰文字),但不能消失

3. AI 能理解 / 调度的东西不要常驻按钮化

跟上一条相反:Agent / MCP / CLI 工具 / Skills 这类"AI 能根据用户意图自己选择"的能力,不要做成常驻 toggle。它们应该是"按意图触发才出现"。

  • 具体到当前实现:+ 菜单 (添加文件上下文 / 插入命令 / 调用 CLI 工具) 全部收进一个入口
  • 用户输 / 才出 slash 命令选择器
  • 用户输 @ 才出 @ 文件 / mention 选择器
  • Agent 想用 MCP 工具时是它自己决定调用,不是用户每次发送前点一下"启用 MCP"

这条原则的反面是 Agent / MCP / CLI 各做一个 toggle 按钮放在工具栏 —— 早期版本就这样过,结果用户根本分不清"我要不要打开 MCP"。

4. Run 状态面板只解释"这次会怎么跑",不变成第二个设置页

Run 状态面板的职责是让用户在发送前理解 "this run":哪个引擎、哪个模型、什么权限、上下文用了多少。

不是

  • 一个能在里面修改设置的弹层(修改请去对应 Settings 页)
  • 一个 dashboard(分析 / 历史 / cost 都不属于这里)
  • 一个 health 页(详细的健康检查请去 /settings#health

每一行右边的 → 设置 链接是降权的(默认 40% opacity),鼓励"理解" 大过"操作"。如果未来发现某个修改动作高频被用户从面板里去找,那是一个独立设计任务(要不要把它升级成主操作),而不是"在面板里加更多按钮"。

5. Context chips 必须可见、可移除、可估算成本

用户加入 Chat 的每一份显式上下文(文件 / 目录 / @mention / 历史选区等)都必须满足三件事:

  • 可见:以 chip 形式出现在输入框上方。看不到 = 用户不信任 Agent 在用什么
  • 可移除:每个 chip 有移除按钮(带 aria-label="移除 X")。加错了能立刻撤
  • 可估算成本:chip 上显示 ~XK token 估算。Run 面板的上下文行汇总成 (+总和 待加)。让用户在发送前知道"我这次会消耗多少"

这三件事任何一件做不到,chip 系统就退化成"装饰"。未来扩展 chip 类型(如 git diff、选区、记忆片段)必须先过这三道门:能不能可见?能不能移除?能不能估算?

这五条之间的关系

1 是总纲(只关心输入框),2 + 3 是分类(什么常驻 / 什么意图触发),4 是 Run 面板的边界,5 是 Context chips 的边界。

每次设计新 Chat 页元素时按这个顺序检验:

  • 它属于"非 AI 的确定性控制"吗?→ 按 2 处理(清楚但克制)
  • 它属于"AI 能调度的能力"吗?→ 按 3 处理(意图触发,不常驻)
  • 它属于"运行状态解释"吗?→ 按 4 处理(进 Run 面板,不进操作流)
  • 它属于"用户给 Agent 的上下文"吗?→ 按 5 处理(chip 三要素)
  • 都不是?→ 按 1 处理(不要加)

已知局限 / 未来方向

当前局限

  1. 路由级 spinner 仍存在:刷新进 /chat/[id] 大约 1s 路由级 spinner,然后才进入 composer 加载态。我们做的"composer 级加载文案"只覆盖 composer 已挂载但子组件数据未到的窗口
  2. Run 面板链接的"次级"度可能太弱:在浅色背景上 40% opacity 接近 disabled 观感
  3. --user-bubble CSS 变量是死代码:用户气泡不再引用,留作未来灵活性

下一阶段:Context Chips

现在 Chat 页解释了"用什么 Runtime / Model / 权限",但还没解释"Agent 会看什么"。第 9 条留作下个 phase:

  • 文件树点 "Add to chat" → composer 上方出现 chip(文件名 / 类型 / 估算 tokens)
  • 发送消息时把 chip 作为显式上下文传入(不再只是 UI 状态)
  • Run 面板的"上下文"统计要把 chips 算进去

这一步做完,Chat 页才真正从"能发消息"变成"用户知道 Agent 带着什么去执行任务"。

更远

  • Run 面板从"配置快照"升级到"运行实况":本次用了哪些上下文、调了哪些工具、哪一步失败、为什么需要确认
  • 多 Agent / @agent:必须等单 Agent 上下文+运行解释稳定后再做,否则会变成"多 Agent 同时跑但用户更不懂发生了什么"

反向引用 / 相关 memory

  • feedback_composer_invisible_until_hover.md — 三层视觉规则
  • feedback_describe_in_user_terms.md — 沟通要用界面语言
  • feedback_pinned_default_hard_promise.md — Pinned + invalid 必须阻断
  • feedback_propose_dont_ask.md — 同期反馈:不要 A/B/C 让用户选,给推荐 + 备选