Composer 控制栏响应式设计
范围:聊天输入框上方的 environment chips,以及下方的 permission、more、active tools、chat model、execution model、effort 和 context 控件。
当前实现与缺口
.composer-bottom-row 同样不用固定断点:它需要的宽度取决于开了哪些工具开关、模型名多长。use-compact-controls-row.ts 在整行旁边放一份隐藏副本,按内容最大宽度排版,并关掉 flex 收缩和过渡(data-measuring)。它累加副本各组宽度和间距,在绘制前移除副本,超过整行宽度就设置 data-compact。测量从不切换真实的这一行:行里的按钮对所有属性做过渡,刚退出 compact 的行仍会报出压缩后的宽度。尺寸变化、内容或 class 变化、网页字体加载完成时都会重新测量,权限、模型和思考档位的文字随之收成各自的图标。展开回去需要留出 12px 余量。.envChips 不再使用固定断点,因为聊天页和 DAG 页的控件数量不同,同一宽度下需要的空间也不同。它选出第一个能放进自身 clientWidth 的级别,所需宽度取最后一个胶囊的右边界,测量时把靠右胶囊的自动外边距清零(不能用 scrollWidth:那个自动外边距让内容永远铺满整行,收缩后就再也展不开):先关掉过渡(data-measuring)把整行按每一级排一遍,再以动画切到选中的 data-squeeze 级别;第 3 级同时设置 data-compact,隐藏全部 label。回到更宽松的级别需要留出 12px 余量。单页和 split pane 运行完全相同的测量逻辑。
现有组件已经提供四类互异图标:Shield 表示权限,Message 表示 chat model,Terminal 表示 execution model,Biceps 表示 effort。所有触发器已经具备 Popover/Menu、tooltip 或 accessible name,因此不新增图标组件和菜单。effort 触发器点开思考力度选择卡;其标题行、点阵滑轨和"推荐"标注见 effort-picker.zh.html。
组件边界
Composer 按交互职责组织目录:输入编辑归入 input/,提交链路归入 submit/,模式分发归入 modes/,会话设置归入 state/,附件、粘贴、slash 与 controls 沿用各自现有目录。环境栏内部再区分整体布局与单个 chip;ProjectBadge、WorkingDirChips 和 GoalChip 保留在其共享业务模块。DAG HUD 由 DAG 模块负责,通过 EnvironmentRow 的通用尾部内容插槽接入,EnvironmentRow 不包含 DAG 名称、DOM id 或控制逻辑。
| 目录或文件 | 唯一职责 | 不负责 |
|---|---|---|
composer/environment-row/environment-row.tsx | 组合环境栏子组件并渲染调用方提供的尾部内容。 | 不包含 DAG 名称、portal id、聊天提交或项目状态。 |
composer/environment-row/use-compact-environment-row.ts | 测量环境栏实际宽度并维护 compact 属性。 | 不渲染组件、不定义视觉样式。 |
composer/environment-row/environment-row.module.css | 环境栏布局、pill surface、四级收缩动画和 DAG slot 样式。 | 不包含输入框、底部 controls 或其他 Composer 模式。 |
composer/environment-row/chips/connection-status-chip.tsx、web-surface-chip.tsx | 分别负责连接状态入口和当前网页 surface 入口。 | 不控制环境栏整体响应状态。 |
composer/input/ | textarea、键盘事件与历史回调。 | 不发送消息或切换 Composer mode。 |
composer/attach/、paste/、modes/question/ | 组件实现与其专用 CSS Module 放在同一职责目录。 | 不把组件专用选择器留在根 composer.module.css。 |
composer/submit/ | 构造并提交聊天消息,维护提交副作用。 | 不渲染输入区。 |
composer/modes/composer-body.tsx | 根据派生 mode 选择普通输入、函数表单或问题表单。 | 不拥有输入、附件或会话状态。 |
composer/state/use-composer-settings.ts | 读取和更新当前 SessionScope 的 Composer 设置。 | 不提供全局 focused-session fallback。 |
composer/controls/use-*.ts | 分别管理模型可用性、工具 profile、无人值守状态及既有控制项。 | 不在 Composer 根组件中直接请求或同步这些状态。 |
composer/index.tsx | 传入 session、tools 和 toggle 参数,渲染一个 EnvironmentRow。 | 不持有环境栏 ref、observer 或内部 JSX。 |
scripts/chat/check-composer-environment-row.mjs | 只检查环境栏组件边界和响应契约。 | 不累积其他聊天 UI 检查。 |
参考与选择
| 参考 | 适用能力 | 采用方式 |
|---|---|---|
| W3C CSS 包含第 3 级规范 | @container 根据组件容器尺寸控制后代样式,不依赖 viewport。 | 只保留给上下文圆环的文字和思考档位卡片的宽度;文字收起改为实测,因为这一栏需要的宽度不只取决于它自身的宽度。 |
| WAI 可访问名称 | 隐藏可见文字时,交互控件仍需保留可计算的名称。 | 沿用现有 tooltip、Popover trigger 和标签;视觉隐藏不删除 DOM 语义。 |
| 横向滚动 | 保留任意长度的文字,但会让部分设置入口离开当前可见范围。 | 保留为极窄宽度的后备;不作为主要响应方式。 |
| 换成两行 | 全部文字可见,但会改变 composer 高度并影响 transcript 底部预留。 | 拒绝;本任务保持单行。 |
目标显示
控制按钮 surface 层级
上方 environment chips 使用填充与边缘层级,底部 controls 则只有 20px 高。底部 permission、more、active tools、chat model、execution model、effort 与 context 只复用主题级 --chip-bg,形成始终可见的浅色填充;常态 surface 不新增 border、内描边或外部阴影,避免压缩小控件的内部视觉空间。既有 hover 前景色和键盘 focus-visible 指示保持不变。
| 状态 | 表面 | 保持不变 |
|---|---|---|
| 常态 | background:var(--chip-bg);computed border 为 0、shadow 为 none | 20px 高度、5px 圆角、图标与文字颜色 |
| hover / open | 在 --chip-bg 上混入 8% --text-bright | 原菜单、tooltip、动画、既有 hover 前景色与 effort 等级色 |
| 窄态 | 与常态同一 surface,不因文字视觉隐藏而改变 | 四个核心触发器仍为 20px 图标按钮 |
Context 统计弹层的数据时序
ContextBreakdownPanel 在关闭时会卸载。若每次挂载都把已有结果清空并进入 loading,用户重复打开同一会话、同一 DAG 分支时会先看到一张只有“Loading…”的卡片,再看到统计内容。网络请求即使很短,这次整卡替换仍然可见。
| 场景 | 立即显示 | 后台行为 |
|---|---|---|
| 同一会话、同一分支再次打开 | 上一次成功取得的完整统计 | 重新请求并原位更新,不进入整卡 loading |
| 首次打开或缓存不存在 | 既有 loading 空态 | 请求当前统计,成功后写入缓存 |
| 切换会话或 DAG 分支 | 只读取新 sessionId + headId 对应的缓存 | 取消旧请求;旧响应不得覆盖当前面板 |
| 请求失败且已有缓存 | 继续保留缓存内容 | 本次刷新失败不得用错误页替换可用统计 |
缓存只驻留当前前端进程,按最近使用顺序保留 32 个会话分支,不写入持久存储,不设置定时刷新,不新增依赖。同一 key 的并发刷新只允许最新请求更新缓存。服务端仍是统计数据的唯一来源;缓存只消除重复打开时的过渡画面。
收缩规则
- environment chips 的 label 不参与 flex 收缩;被截短的名字以省略号结尾,不出现半截文字。
- 内容溢出时整行逐级收紧直到放得下:截短名字、收起分支名、隐藏文字并显示改动圆点;空间恢复后逐级展开。
- 各图标触发器保持尺寸、原顺序和操作能力。
交互与无障碍
- 点击图标打开原菜单。
- chat 与 execution 使用不同图标。
- effort 图标颜色继续反映当前级别。
- focus、tooltip 和 accessible name 不随文字隐藏而消失。
边界
- 不改模型、权限和 effort 状态。
- 使用原生 ResizeObserver 与 MutationObserver,不新增依赖。
- 不换行、不隐藏整个控件。
- 不调整 composer 高度和 splitRatio。
实施 brief 与验收
上排实现集中在 apps/web/components/chat/composer/environment-row/:组件、测量 hook、样式和两个专用 chip 按职责分文件。composer/index.tsx 只渲染 EnvironmentRow。Context 缓存批次限定为 apps/web/components/chat/context-badge.tsx、context-breakdown-panel.tsx 和 apps/web/lib/chat/context-breakdown-cache.ts。独立检查由 apps/web/scripts/chat/check-composer-environment-row.mjs 提供,并纳入 npm --prefix apps/web run check:chat-ui。这些批次不修改 Popover、Menu、provider、session store 或提交逻辑。
- public-entry 检查必须证明 environment chips 由实际溢出逐级切换
data-squeeze,最后一级为data-compact,不存在固定宽度特例或 label 的flex-shrink:1。 - 状态、surface、项目、目录、Fit、缩放百分比和 Legend 必须共用同一动画规则;不得使用
display:none或只收起其中一部分。 - 默认安装版在 split pane 中把 divider 向左拖到触发阈值后,右侧四个设置入口和左侧 permission/more 均可见、无重叠。
- 分别点击 permission、chat model、execution model 和 effort 图标,原菜单或面板正常展开。
- 把 pane 拉宽到完整内容可容纳后,所有文字一起恢复;重新选择 split、切换聊天/DAG 和刷新不改变该行为。
- 底部所有可见控制按钮在未 hover 时均有非透明的
--chip-bgsurface,computed border 为 0 且 shadow 为 none;hover 或菜单展开时背景必须比常态更明显。键盘focus-visible指示不属于常态边框,继续保留。 - Context 统计第一次成功加载后,关闭并重新打开同一会话、同一分支必须立即复用缓存;后台刷新完成后更新缓存和圆环统计。
- Context 缓存必须按会话与分支隔离;快速切换时取消过期请求,失败刷新不得覆盖已有缓存。
实现状态
| 项目 | 状态 |
|---|---|
| 设计与任务边界 | 完成 |
| 公开入口 RED / GREEN | 完成:静态入口先因缺少窄态 effort 图标失败,实施后通过;真实 App E2E 覆盖 700px 与 360px 两种容器宽度。 |
| 规格审查 / 质量审查 | 完成:规格审查与修复后的独立质量审查均通过。 |
| 完整检查与默认安装版可见验收 | 完成:Web 全量检查、TypeScript、构建、desktop webtab/transfer、单元测试和默认安装版 CDP 验收均通过。 |
| 底部控制按钮常态 surface | 完成:常态仅使用 --chip-bg 填充,不增加边框或内描边;真实 App computed style 验证 border 为 0、shadow 为 none,hover/open 仅增强背景。 |
| Composer 职责目录 | 完成:输入、提交、mode、session state、附件和环境 chips 分别归入职责目录;输入、粘贴、图片附件与问题面板使用同目录 CSS Module。EnvironmentRow 只接受通用尾部内容,不包含 DAG id 或控制逻辑。 |
| Context 统计重复打开 | 已实现:缓存按会话与分支隔离;重复打开立即显示上次结果并后台刷新。check:chat-ui 可执行验证缓存隔离、迟到请求取消、同 key 反序响应、刷新失败保留、32 项容量淘汰,并约束面板按会话与分支 key 同步重建。 |
可见验收使用实际 /Applications/OpenProgram.app、默认 18100 页面和 9223 CDP。测试操作真实 permission、more、active tool、chat model、execution model、effort 与 context 触发器,确认常态无边框、hover/open 背景增强、窄态无重叠且菜单可打开,并在结束时关闭菜单、恢复焦点与原始页面样式。