# dsh-ui-web

> 覆盖 DSH 官方会话统计行：完整展示轮/步/耗时/缓存/token，加运行状态指示点，并允许自定义思考中/工作中/完成时三种状态文本。

## Metadata

- Author: [@CAPTAIN1275](https://github.com/CAPTAIN1275)
- Repo: <https://github.com/CAPTAIN1275/dsh-ui-web.git>
- GitHub: [CAPTAIN1275/dsh-ui-web](https://github.com/CAPTAIN1275/dsh-ui-web)
- Stars: 34
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Topics: `dsh-plugin`, `dsh-plugin-market`, `dsh-plugins`
- Forks: 2
- Open Issues: 0
- Last push: 2026-08-16T18:08:27.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/dsh-full-stats
```

## Wiki

## 一句话定位
dsh-full-stats 覆盖 DSH Web GUI 官方那条会被截断的会话统计行，把它替换成一整行可换行展示的轮/步/耗时/首 token/速度/缓存命中/输入输出 token 数字串，并在行首加一颗琥珀色或绿色的运行状态点。它在「Web UI 插件」分组里提供一张可折叠配置卡，让你自定义「思考中 / 工作中 / 完成时」三种状态的提示文字，并替换官方硬编码的「Deep diving...」。

## 核心能力
- 完整展示会话统计：会话级插槽组件（id=stats，priority=-1）输出 `${turns} 轮 · ${steps} 步 | LLM ${llmMs} · 工具调用 ${toolMs} | 首 token 平均 ${ttftMs/steps} | ${decodeTokens/秒} tok/s | 缓存命中 % | 输入 tok · 输出 tok`，整行 `whiteSpace:normal` 不省略（src/client/index.ts:121-160）。
- 运行状态指示点：行首渲染 8px 圆点，会话运行中为琥珀色（#f59e0b 带 6px 阴影）、空闲为绿色（#4ade80），点击会话切换有 0.15s 过渡（src/client/index.ts:163-196）。
- 三种状态自定义文本：配置卡提供「思考中 / 工作中 / 完成时」三段输入框，留空即退回原始内容；填了之后按会话状态显示对应前缀，文本后仍接完整统计（src/client/index.ts:152-159、src/client/FullStatsSettingsCard.tsx:104-137）。
- 覆盖官方「Deep diving...」占位文本：MutationObserver 监听 `[class*="turnStatus"]` 节点，遇到官方硬编码的「Deep diving...」文本节点即原位替换为 `thinkingText`，保留时钟 span（src/client/index.ts:74-91）。
- 跨进程配置持久化：宿主页注册 `GET / PUT /api/full-stats/config`，写入 `~/.dsh/full-stats.json`（`$DSH_HOME` 优先，否则 `~/.dsh`），浏览器配置卡保存后即派发 `dshc-full-stats-config` 事件，统计行即时刷新（src/index.ts:69-92、src/client/FullStatsSettingsCard.tsx:73-85）。
- WebUI 设置卡接入：在 `web-ui.plugin.item` 插槽注册 id=`full-stats`（order=120），与任务看板、皮肤中心同级出现在 DSH Web 设置页（src/client/index.ts:218-226）。

## 技术实现
- **语言**: TypeScript（ESM，TSX + CSS Modules；tsdown 编译，target es2024、jsx react-jsx）
- **关键依赖**: `@deepseek-ai/cordis`（host 插件运行时，注册 webServer 路由）、`@deepseek-ai/dsh-client-runtime` 与 `@deepseek-ai/dsh-client-ui-conversation`（browser 半区被注入目标）、`react ^18.2.0`（memo 化 FullStatsLine 与 FullStatsSettingsCard 渲染）
- **架构模式**: Cordis 双半区插件 — `src/index.ts` 是 host 半区（`ctx.inject(['webServer'], ...)` 注册 `/api/full-stats/config` GET/PUT 路由 + 读写 `~/.dsh/full-stats.json`，无 webServer 服务时为空操作），`src/client/index.ts` 是 browser 半区（`ctx.slots.inject` 覆盖 `conversation.composer.dock#stats` 插槽 + 注册 `web-ui.plugin.item#full-stats` 配置卡 + 监听宿主路由与 dshc-full-stats-config 事件）；`cordis.patch.yml` 注册插件 id=`ui-full-stats`，`package.json#dsh.client` 声明 `platform: "web"`、`inject: [dsh-client-runtime, dsh-client-ui-conversation]`。
- **入口文件**: `src/index.ts`（host 半区入口，apply 注册配置路由）、`src/client/index.ts`（browser 半区入口，覆盖统计行 + MutationObserver 替换 Deep diving + 注册配置卡）、`src/client/FullStatsSettingsCard.tsx`（可折叠配置卡 UI）、`src/client/card.module.css`（卡片样式，复用官方 ui-plugin-config token）

## 适用场景
已经在 DSH Web GUI 里跑项目会话、想要一眼看清本轮「跑了多少步、LLM 与工具各花了多久、首 token 多快、缓存命中几成」的数字党；以及想把官方「Deep diving...」改成自己人格化文案（如「大肥鱼正在吃白饭」）的玩家。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 0.1.0-rc.6 | devDependencies 锁定 `@deepseek-ai/dsh-client-runtime` 与 `@deepseek-ai/dsh-client-ui-conversation` 为 `^0.1.0-rc.6`，`@deepseek-ai/cordis` 为 `^4.0.1`（package.json:33-42） |
| 客户端 profile | web | `cordis.patch.yml` 注册 `id: ui-full-stats`、`name: '@captain1275/dsh-full-stats'`；`package.json#dsh.client.platform` 为 `web`，`inject` 含 dsh-client-runtime + dsh-client-ui-conversation；headless / CLI profile 下浏览器半区不会加载（cordis.patch.yml:1-4、package.json:13-23） |
| React | ^18.2.0 | 浏览器半区 React 18 渲染（package.json:42） |
| Node.js | 未声明 | 仓库根 `package.json` 与本包 `package.json` 均无 `engines` 字段；host 半区仅用 `node:fs` / `node:path` / `node:os` / `node:http` 标准库（src/index.ts:11-13） |
| 平台 | 跨平台（macOS / Windows / Linux） | 仅依赖 Node 标准库，无原生绑定；host 半区路径处理由 `path.join` 自动适配 win32 |
| 原生模块 | 无 | 没有 koffi / node-pty / `node:sqlite` 等原生绑定；测试用 `jsdom@29.1.1`（package.json:34-42） |

## 安装方式
```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/dsh-full-stats
```

安装后重启 DSH Web，进入任意项目会话即可看到对话框下方的完整统计行与行首状态点；DSH Web 设置页 → Web UI 插件分组里也会出现「完整统计行（状态文本）」可折叠配置卡。

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| 思考中状态文本（替换 Deep diving...） | 字符串 | 会话思考阶段替换官方硬编码「Deep diving...」占位文本，留空则显示原文本 | 空 |
| 工作中状态文本 | 字符串 | 会话正在生成回复时显示在统计行前的自定义前缀，留空则只显示统计行 | 空 |
| 完成时状态文本 | 字符串 | 会话空闲且非空白时显示在统计行前的自定义前缀，留空则只显示统计行 | 空 |

以上三项文本在浏览器侧的「完整统计行（状态文本）」卡片填写后，PUT 到 `/api/full-stats/config` 持久化到 `~/.dsh/full-stats.json`，并发 `dshc-full-stats-config` 事件让统计行即时刷新。

## 常见问题

**Q: 安装后能在哪里看到它？**

A: 进入 DSH Web GUI 任意项目会话，对话框下方会多出一行不省略的统计行（轮/步/LLM 与工具耗时/首 token/速度/缓存命中/输入输出 token），行首带琥珀色或绿色状态点；DSH Web 设置页的「Web UI 插件」分组里也会出现一张「完整统计行（状态文本）」可折叠配置卡。

**Q: 配置保存到哪？卸载或换机器会丢吗？**

A: 三项文本字段写到宿主进程侧的 `~/.dsh/full-stats.json`（默认读取 `$DSH_HOME` 环境变量，回退 `~/.dsh`），PUT 接口在写入前会校验字段类型并覆盖式写回。换机器需要手动迁移这个 JSON 文件；卸载插件不会删除该文件。

**Q: 「思考中状态文本」是覆盖官方哪段话？**

A: 覆盖官方 ChatView 内联 JSX 硬编码的「Deep diving...」占位文本。客户端用 MutationObserver 监听 `[class*="turnStatus"]` 节点，匹配到该字符串后原位替换为用户配置；保留时钟 span，仅替换文本节点。

**Q: 三种状态文本要怎么触发显示？**

A: 会话运行中 + `workingText` 非空时显示「工作中」前缀；会话空闲且非空白 + `doneText` 非空时显示「完成时」前缀；任意一种配置为空串就退回原始统计行，不显示自定义前缀。

**Q: 它会替代 DSH 官方的统计行吗？其他类似插件冲突怎么办？**

A: 是覆盖而非并存。浏览器半区以同 id=stats、更低 priority=-1 在 `conversation.composer.dock` 插槽里重新注册；DSH 自带的官方组件与本插件组件的渲染顺序由 priority 决定。本插件源码中未对 dsh-live-stats 等同类插件做特殊互斥处理。

**Q: 配置接口有大小限制吗？**

A: 有。PUT `/api/full-stats/config` 的请求体超过 100,000 字节会被服务端直接拒绝并销毁请求（`reject(new Error('body too large'))`）；三个文本字段加起来远低于该阈值，常规输入不会触发。

**Q: 必须 DSH Web 才能用吗？**

A: 是。`package.json#dsh.client.platform` 字段为 `web`，浏览器半区依赖 DSH 客户端运行时；宿主路由 `/api/full-stats/config` 同样挂在 DSH Web 的 webServer 上，headless / CLI 模式不会加载。

**Q: 卸载后统计行会自动恢复成官方样式吗？**

A: 会。本插件以 priority=-1 顶替官方 id=stats，移除插件后该覆盖项随插件卸载消失，DSH Web 自带的统计行即恢复显示——但官方原始样式仍是会被截断的 `whiteSpace:nowrap` 行。

## 上手难度
入门 — 装好插件、重启 DSH Web 即可看到效果；想要自定义状态文本时进入设置页 → Web UI 插件分组展开「完整统计行（状态文本）」填写三项文本即可，无需手写配置或命令行。

## 已知问题与限制
- 配置接口请求体上限 100,000 字节：PUT `/api/full-stats/config` 在 `readBody` 中对超过该阈值的请求直接 `reject(new Error('body too large'))` 并 `req.destroy()`；当前三个文本字段不可能触发，但若未来扩展字段需注意（src/index.ts:54-67）。
- 自定义状态文本前缀只在「非空配置」下生效：`thinkingText` 为空时 `mountThinkingTextReplacer` 直接 return，`workingText` / `doneText` 为空时跳过对应分支退回原始统计行——三段文本必须都填才有完整效果（src/client/index.ts:76、src/client/index.ts:152-159）。
- `cachedConfig` 是模块级单例：`src/client/index.ts:46` 的 `let cachedConfig` 在多次实例化插件或 HMR 场景下可能残留旧值；保存配置后通过 `dshc-full-stats-config` 事件刷新，但事件未触达时仍可能读到陈旧数据（src/client/index.ts:46-63、src/client/index.ts:200-203）。
- 强依赖官方 DOM 选择器与硬编码文本：`mountThinkingTextReplacer` 用 `[class*="turnStatus"]` 与文本「Deep diving...」匹配官方节点；若官方 ChatView 重构（class 名变更或文本 i18n 化）将直接失效（src/client/index.ts:74-91）。
- 聚合包 `dsh-web-ui-all` 不会自动加载本插件：aggregate.yml 把 `dsh-full-stats` 放在 `deps:` 但未列入 `patchFrom:`，所以仅依赖安装不会把 `ui-full-stats` 注入 profile 名册——通过聚合包使用者需手动将 `@captain1275/dsh-full-stats` 加入 `dsh.profile.bundles`（packages/dsh-web-ui-all/aggregate.yml:21-31、README.md:97-110）。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-ui-web](https://deepseek-plugin.org/plugins/CAPTAIN1275/dsh-ui-web/packages/dsh-full-stats)
Wiki generated by AI (model: `MiniMax-M3`)
