Files
deepseek-harness/packages/client/ui-conversation/README.zh.md
Hypatia May 8a8c1965d7 feat(web): show durable token usage and context occupancy in the stats line
The chat stats line took its token totals from the loaded conversation nodes,
so paging changed them and compaction erased the billing behind replaced
content. It also had no way to show context occupancy: the numerator and
capacity never reached the browser.

Both now come from token-meter session projections read through the standard
useProjection seat. Window nodes keep supplying turn and step counts plus LLM
and tool wall times, which are correctly window-scoped facts about what is on
screen; accounting no longer comes from there.

`tokenUsage` supplies billing and cache hit. `contextPressure` supplies
occupancy, pairing the newest provider-reported prompt size with the newest
capacity recorded by `request/context`. Deployments without token-meter drop
the token groups; a route whose adapter advertises no capacity drops the
occupancy group rather than rendering a placeholder.

Occupancy is deliberately approximate: the numerator and capacity are
independent last-wins fields, not one atomic request observation, so switching
models pairs a fresh capacity with the prior route's pressure until the next
request reports usage. It is a user-facing reference figure that nothing in the
harness makes decisions from, and it matches how the TUI status line has always
computed occupancy. The Agent Note and token-meter README state this as a
decision, including why the atomic alternative was implemented and rejected, so
it is not re-litigated as a defect.

Snapshot delta is one added `Context N% of 128K` segment across eight web
goldens; the preceding commit absorbed master's pre-existing golden drift.
2026-07-30 14:48:19 +08:00

13 KiB
Raw Blame History

@deepseek-ai/dsh-client-ui-conversation

English | 中文

会话领域:骨架(标题栏/标签页/编辑器/空状态)、聊天视图(分组步骤摘要流、流式尾部隔离、逐工具行 slot 及一个 bash 示例注册方与 todo 行)、编辑器 dock与输入区一同 sticky 的会话统计行)、输入区 dock队列行加 todo 计划条)、最小详情面板、按 scope 寻址的 ConversationService。契约api-contracts v3 §7 加 slot 终端设计store seatprops share

常驻会话壳会跨无会话与会话状态切换而保留。没有当前会话时,它会渲染禁用输入栏;其根作用域的 conversation.hero.workspace slot 承载 Workspace 选择器。选择 Workspace 会连接或复用由 Host 拥有的空白会话并在不替换会话壳的情况下打开该会话。空白会话与活跃会话渲染相同的输入区主体InputHub 则在 Workspace 切换间携带草稿,并将草稿镜像到会话 store。活跃阶段会话标题栏以普通列 chrome 占据顶部;其下滚动容器(data-conversation-scroll)承载流动排版的各视图与 sticky 编辑器栈(统计 dock输入区 dock输入栏。textarea 上的滚轮会链式处理:限高草稿先在本地滚动,到达边缘后再转交给该宿主。

视图环本身就是 slot会话注册声明 'conversation.view' 列表 slotSession scope并将其列在 children 表中ConversationRoot 通过 renderSlot share 渲染活跃配置项(only: <active id>);视图标签页从环账本的注册选项(idorderlabel投影而来。聊天视图是该包自身的环配置项其他插件ui-trajectory通过普通的 ctx.slots.register 贡献标签页。先前包内的视图注册表(registerViewViewEntryConversationViewMap 及 chrome 附加表)已退役,逐视图 chrome 则被拆入视图组件自身。

通用工具行把内置的 bash、read、search、write、edit 和 run_code 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和路径摘要;该路径是悬停下划线链接,点击后通过宿主操作系统的默认应用打开文件(host.openPath,相对路径相对会话 cwd 解析)。工具行不再是整行点击目标,也不会打开 details 面板。code 变体以模型撰写的 description 作摘要,展开后显示程序本身;其已记录的子调用经由同一个键控 toolview 空位渲染为始终可见的嵌套行(自定义注册和 GenericToolCard fallback 原样适用于子行。Cordis 生命周期工具复用这些通用变体,同时以统一的 Cordis 强调色呈现 InspectMount temporary PluginUnmount temporary Pluginmount 行保留 code 变体的可展开源码渲染。

声明 terminal 渲染意图的工具调用,会在两个对话渲染点上都通过 ui-primitives 的 TerminalBlock 内联渲染其命令输出。contract/terminal-card-model.ts 是从快照的 callViewresultView 对推导的唯一位置因此两个渲染点不可能在命令、cwd 或退出状态上产生分歧;对任何其他 card 标签——包括当前客户端版本不认识的标签——它返回 null落回通用路径。因此两个渲染点也都显示卡片的运行状态点它与工具行行首图标承载同一套 StateDot 语义,所以一行与其自身的卡片对同一条命令的状态总是一致。多行命令的每一行各占一个提示行,状态点只在第一行为整次调用标记一次——退出状态属于整次调用,因此每行一枚就会声称一个 bash 并不报告的逐行结果。键控的 BashRow 把卡片常驻在摘要行下方;由于工具行已不再是详情面板的点击目标,卡片的复制与展开控件就是该行唯一的交互。渲染点兜底行则保持其既有的展开控件。行的上限是 CHAT_TERMINAL_MAX_LINES8面板为 16正是这一点让摘要面保持有界——面板仍是单次调用的阅读面。内联输出只对该意图开放通用工具的内容仍然只在面板中呈现决策)。

工具行同样是 slot独立工具环ToolViewRegistryctx.toolviewsoutlet已经退役。聊天配置项声明键控的 'conversation.chat.toolview' 空位Session scopekey 空间在运行时开放);其渲染点逐行通过 entryKey: toolName 分发,并以 GenericToolCard 作为调用点 fallback。owner 载荷是统一的 ToolRowOwnerPropscallIdtoolNameblockopenFileToolRowProps 则预先将其与 Session 标准工具包组合。注册方只是普通插件:ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row),以 inject: ['slots', 'conversation'] 作为加载顺序 seamapply 在聊天注册后挂载 ConversationService因此服务存在即可保证 slot 已声明Session 区分在组件内部完成(useSessions 读取 parentIdbash 示例是第三方姿态的范例。Trajectory/waterfall 工具视图 slot 共享此形状并随各自的渲染点落地RendersCheck 会拒绝没有任何渲染方的声明)。

审批经由本包声明的链接管编辑器:ApprovalPanel 注册为按选择器路由的 'conversation.composer' 配置项ui-question 模式),在审批等待未决期间取代 InputBar 占据编辑器(琥珀色条、理由标题、来自运行中调用参数的配对命令行、一次性的拒绝/允许)。contract/slots.ts 中的 PendingApproval 领域面在运行时 PendingWait 载体之上拥有 wire 编码——带审计关联的 ApprovalResponsePayload 值;广播的 approval/resolved 帧使等待落定并恢复编辑器。侧边栏通过 manager 跟踪的 waitingApproval 列表位未实例化会话同样点亮镜像该阻塞状态其优先级高于运行中圆环直至问题解决。未决等待完全离开消息流问题ui-question与审批ApprovalPanel都经编辑器接管作答不再保留只读占位卡。编辑器底行的 Access 席位挂载 PermissionSelect,由 host 计算的 permissions 投影经标准工具包 useProjection 供数key 缺席即隐藏 chipchip 打开 Menu 原语下拉kebab-case 预设名渲染为 Title Case 标签(与 /permission popup 的显示变换孪生),选中会经由输入栏注入的 command 回调提交 /permission <preset> 命令行。

todo 两个面就是在该形状上的两个注册项,都是普通注册方插件,inject: ['slots', 'conversation']TodoRow 占用 'conversation.chat.toolview'todo_write key摘要该次调用「试图写入」的内容从其 args 解析出 <已完成>/<总数> 已完成 · <进行中条目>;模型 JSON 残缺或形状不对时回落到通用摘要;非 ok 执行状态保留通用状态点,使被取消的调用绝不读成一次已完成的更新)。TodoDockorder: -1 占用 'conversation.input.dock' 列表 slot位于队列行之上是计划条它经 useProjection 读取 host 计算的 todos 投影(站立计划:其后没有更晚 turn/start 的最近一次 todo/write)并渲染 TodoPanel,后者接收纯列表,在列表为空时自我隐藏;列表非空时面板初始折叠,表头显示标题加 "<已完成>/<总数> tasks · <n> in progress"(状态图标为 figma 的勾选/进行中/虚线未开始一组)。选取由 dock 适配器负责,因此面板保持为其 props 的纯函数;站立列表放在此处而非行内,行才能保持单行。输入区 composer 链隐藏的一切(例如 ui-question 对 conversation.composer 的接管)也会隐藏整个 dock包括这条计划条。

逐 Session UI 状态中的选择与活跃视图位于已声明的聊天 storestores.ts createChatStoreInputHub 拥有输入区状态机,并将草稿镜像到该 store 以便持久化。apply 将同一个 store handle 传给严格限定于会话的子树、聊天视图和详情注册,因此每个会话内共享一个实例,框架拥有其生命周期。组件保持纯粹:框架标准工具包提供 useSessionsessionId、全局 useSessionsuseWorkspaces,以及输入状态机的 useInputinputActionsstore 表层与 inject factory 提供其余状态和回调。

输入栏为 'conversation.input.plan'(位于本地 access 模式控件右侧)和 'conversation.input.model'(渲染在 pending 指示器与发送/停止按钮之前)声明会话作用域的单实例 seat并为 overlay、dock、left 和 right 输入扩展声明列表 slot。各功能包拥有相应控件及其状态ui-conversation 提供放置位置、locked owner prop 和标准 slot share。当 plan 投影的有效目标为 plan mode 时InputBar 将文本框 placeholder 切换为 plan 任务措辞,经本包注册的 command.hint locale 命名空间本地化,并与已认领 /plan 命令的提示逐字共用同一份文案(经标准套件 useProjection 读取的 host 折叠值owner 提供的 placeholder 优先)。另一个会话视图活跃时,待处理的 composer 接管仍保持挂载,使被阻塞的 agent智能体仍能收到回答没有待处理交互时活跃会话的 composer 归 Chat 所有。常驻无会话壳使用 DisabledInputBar,因此不会分发任何会话作用域的控件 seat。

聊天统计行的 token 账目来自经标准套件 useProjection 读取的两个通用 token-meter 投影:tokenUsage 提供完整日志计费用量(缓存命中率为 cacheRead / (uncachedInput + cacheRead),不计入缓存写入),contextPressure 提供上下文占用率。可见节点只提供轮次与步骤计数,以及 LLM 和工具的墙钟时间:这些是关于「屏幕上有什么」的窗口作用域事实,而非账目。未组合 token-meter 的部署会整组省略 token 分组;适配器未公布容量的路由会省略占用率分组,而不是渲染占位文案。占用率是刻意为之的近似值:它的分子与容量是两个相互独立的「后者胜」投影字段,并非同一次请求的原子观测(原理)。行内统计行仍是唯一的上下文 UI模型选择器不增加圆环或附属控件。

src/client/ 按未来的包拆分组织:contract/ 是唯一的跨领域共享表层(slots.ts slot 声明 + 组合后的 slot props包括工具行契约、views.ts 共享原语、tool-call-model.tsskeleton/chat/toolviews/(示例注册方)领域目录只导入 contract 文件,彼此绝不导入;apply.ts 是唯一允许导入全部三个领域的组装点。/client 导出表层只包含契约:applyinject、两个服务类和 contract/ 类型家族;实现组件(骨架、聊天行)与 store factory 保持内部状态,只能通过 apply 的 slot 注册到达页面(测试通过 ./src/* 子路径获取它们)。

模型体验

无。会话 UI 在浏览器中渲染会话历史与流;这里没有任何内容进入模型请求。

KV Cache 影响

无;该包既不组装也不发送提供方请求。

已知限制与暂缓事项

  • 统计行的耗时只覆盖窗口内消息流LLM 与工具墙钟时间由快照的 assistant timing 与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入。
  • 详情面板是最小形态,且当前没有入口以原始形式显示已选择调用的参数结果Input/Output/Metadata 切换、Prev/Next 步进与 See-in-trajectory 深链接暂缓实现。工具行已不再是详情面板的点击目标,且没有任何手势接替它,因此 ChatViewInjected.openDetails 虽已实现却无人调用,该面板(含其终端卡片)在组装后的应用中不可达;其渲染仍由直接以选中态挂载它来覆盖。
  • assistant 逐消息分页是预留 slot:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/分支/时钟)只挂在 text 输出下;分支仍是 chrome stub。
  • others 工具行的闪光图标是手绘近似版本:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
  • 审批面板的「始终允许此类」暂缓:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
  • TodoPanel 将过长条目截成单行省略号figma 条没有换行或展开入口,完整文本无法在行内读完。
  • Queue 编辑仅支持文本包含非文本块的行仍显示扁平化预览但由于内联编辑器无法保留这些块其编辑控件会被禁用。文本行进入编辑模式后删除会替换为保存和取消Enter 保存Escape 取消。QueueDock 不提供立即发送控件。
  • Web 仅暴露待处理 Queue:在 steering中途引导拥有专用交互之前Host 不会把待处理 steering 纳入 Queue 快照。已消费的 steering/message 仍会渲染到持久 transcript文本记录因此从外部提交的 steering 在回放时仍能如实呈现。