DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

aliensweety /

aliensweety/dsh-usage-dock

Verified

DSH web plugin: per-session CNY cost pill that renders into the composer's shipped statistics row

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@fdc63429

dsh-usage-dock

在输入框下方那排统计药丸(官方 Session 信息 + Token 用量)后面并排追加一颗只显示金额的药丸,样式与官方药丸一致、三粒作为一组居中:

[ Session 信息 ]  [ Token 用量 ]  [¥ 0.47]

点开是明细:未缓存输入 / 缓存读取 / 缓存写入 / 输出 / 总计 / 高峰价与空闲价两档费用 / 缓存命中率。

官方那排已经在显示 Session 与 Token 用量了,所以这颗药丸不再重复这些数字:收起态只有一个金额,其余信息放在 tooltip 与展开面板里。

费用估算口径

  • 价格来源:DeepSeek 官方定价页(人民币/百万 tokens,高峰价)——deepseek-flash 输入未命中 ¥2、缓存命中 ¥0.04、输出 ¥8;deepseek-v4-pro 未命中 ¥9、缓存命中 ¥0.30、输出 ¥27。空闲档一律为高峰价的一半。
  • 模型匹配:读 modelSelection 投影的 lastUsed.model,id 含 pro 按 v4-pro 计价,其余按 flash 计价。
  • 只对 deepseek-official 路由计价:lastUsed.provider 是别的路由时整颗药丸不渲染。用 DeepSeek 价目表去算 Claude/GPT 的 token 会得出一个"很确定的错数字",宁可沉默。
  • 药丸上的金额:按查看时刻所处时段自动取高峰或空闲档;面板里两档始终并列展示,所以跨时段的会话其真实费用介于两者之间。
  • 缓存写入不计价:官方价格表没有单独的缓存写入价(本机数据里 cacheWrite 也恒为 0),字段保留、默认 0。

价格与时段(已核对,截至 2026-09-12)

lib/client.js 里的 PRICING / isPeakNow() 与官方口径一致:

模型 档位 缓存命中 未命中 输出
deepseek-v4-pro 高峰 0.30 9 27
deepseek-v4-pro 空闲 0.15 4.5 13.5
deepseek-v4.1-flash(含 deepseek-flash) 高峰 0.04 2 8
deepseek-v4.1-flash 空闲 0.02 1 4

单位:元/百万 tokens。峰谷时段:北京时间工作日 9:00–12:00、14:00–18:00 为高峰,其余(含夜间与周末)为空闲、按高峰价的一半计。

关键点:V4 Pro 独立计费——它继续调用 V4 Pro 本身,不会路由到 V4.1 Flash(原定 2026-09-14 12:00 的路由计划已于 09-11 取消),所以两者的价目表不会互相套用。这也是本插件按 modelSelection.lastUsed.model 取价、而不是按"当前默认模型"取价的原因。

价格调整后无需改代码,在 Web 控制台执行(对所有已计价模型生效,均填高峰价):

localStorage.setItem('dsh-usage-dock.pricing', JSON.stringify({ miss: 2, hit: 0.04, out: 8, write: 0 }))

删除该 key 即恢复内置价格。

数据来源

只读 DSH 内置的会话投影 tokenUsage 与 modelSelection,不读会话日志文件、不发网络请求、不碰任何凭证。 tokenUsage 由 provider 上报的用量累加而成,整段会话一个值,带 seq 单调保护,所以它是权威值而非本地估算;它的线上视图是扁平四桶:

{ uncachedInputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }

样式、位置与"明细放哪"

样式:与官方药丸逐条对齐——padding:1px 8px、border-radius:24px、gap:6px、--dsw-alias-label-tertiary 文字色与 --dsw-alias-interactive-bg-hover 悬停底色、font:inherit / line-height:inherit;宽屏带一颗 14px 图标,窄屏(≤640px)图标让位给一个 ¥ 字符,把宽度留给三个药丸。

位置:composer 下方那个 dock 是一个 column flex 容器(flex-direction:column; align-items:center),slot outlet 自己带 display:contents,所以第二个条目天然会另起一行;slot 契约里也没有"加入官方那一行"的入口(官方那颗 StatsPills 是自带排版的完整条目,也不声明子 slot)。

所以本插件用 ReactDOM.createPortal 把药丸直接渲染进官方那一排的 DOM 里(官方药丸的浮层也是用同一个 API):

  • 官方那排是 display:flex; gap:12px; justify-content:center,药丸进去后自动拿到同样的 12px 间距、并和它们一起居中——不需要平移、不需要改官方任何元素、不需要测量像素;
  • 靠一个 display:none 的宿主节点留在 outlet 里,只用来找那一排(data-composer-stats 标记);每次提交都重新解析,composer 重挂载换了节点、或那一排消失时会自动跟上;
  • 退化行为:找不到那一排(或宿主没提供 react-dom)时,药丸退回"自己一行居中"的原状。
  • 药丸带 flex:none:窄屏上被压缩的是官方那两粒(它们本来就会 ellipsis),本插件这一粒金额始终完整可读。

明细放在官方 token 用量卡里:点官方那粒 21.1M tok · Cache hit 99% 打开的卡片,本来就已经列出四个 token 桶;本插件把那两行费用(高峰价 / 空闲价)和一个估算说明追加进那张卡的同一个 <dl>:

  • 那张卡的行是 dt/dd 对,而它的样式是元素选择器(.xxx_details dt / .xxx_details dd{display:grid;…text-align:right}),所以追加进去的 <dt>/<dd> 自动拿到和官方行完全一致的排版与配色,不需要写任何上游类名;
  • 检测方式:那一排里最后一颗 button[aria-haspopup="dialog"] 就是官方 token 用量药丸,它 aria-expanded="true" 时出现的 div[role="dialog"] 就是它自己的卡(官方一次只开一张卡);MutationObserver 盯 document.body 的 childList,卡片一出现就写入,卡片还开着时数值变化就地更新;
  • 点本插件这颗药丸 = 直接打开那张官方卡片(没有自己的浮层,所以窄屏不会再溢出);
  • 写入失败(上游改了结构、宿主没给 react-dom、或那一排/那粒药丸不存在)时:药丸的金额照常显示,明细退回自带的兜底面板(该面板只在对齐到屏幕外时才翻转贴边,max-width 也限制在视口内)。

安装

# 从 GitHub 安装(推荐锁定 tag 或 commit)
dsh plugin --profile web add github:aliensweety/dsh-usage-dock#v0.4.0

# 本地开发(link 安装,改完即生效)
dsh plugin --profile web add D:\Ai\dsh\dsh-usage-dock

dsh plugin add 会把该包写进 profile 的 dependencies 与 dsh.profile.bundles(CLI 内含 bundles 与已安装依赖的对账逻辑),无需手工编辑 package.json。 profile 的 patchReload 是 live,通常热挂载;若没生效,重启 dsh web 并强刷浏览器。

验证

必须在上装的浏览器里验证:DSH Web 有信任围栏,从别的 shell 直接请求 /、/api/*、/plugins/* 会拿到 401(或鉴权后的 404),curl 只能确认服务活着、证不了插件对不对。

  1. 强刷界面(Ctrl+F5)。

  2. 看输入框下方那排统计药丸的右侧同一行是否出现 ¥0.xx(空白会话不渲染)。

  3. 点开明细,用 DSH 自己的投影缓存核对四个桶与费用:

    $s = Get-Content "$env:USERPROFILE\.dsh\storages\session_projcache\sessions\<session-id>.json" -Raw | ConvertFrom-Json
    $s.record.rows.tokenUsage.val.totals    # uncachedInputTokens / outputTokens / cacheReadTokens / cacheWriteTokens
    
  4. DevTools → Network:应看到 /plugins/dsh-usage-dock/client.js 为 200。 Console 若出现 [dsh-usage-dock] slots service unavailable,说明没拿到 slot 服务。

排查顺序:dsh --profile web --dump-config 看 dsh-usage-dock 是否在层栈里 → 不在就重跑 dsh plugin --profile web add。

卸载 / 回滚

dsh plugin --profile web remove dsh-usage-dock

设计要点与已知限制

  • 失败安全:useProjection 缺席、投影未到达、四桶全为 0(空白会话)、或路由不是 deepseek-official 时渲染 null,不留空壳、不报错。
  • 不改官方元素:药丸是 portal 进官方那排的,插件不移动、不重设官方那排的位置或样式,也不碰它的数据;找不到那排时退回自己一行,卸载即消失。
  • 它是估算值:会话中途换过模型、或跨高峰/空闲时段时,真实账单介于面板两档之间;药丸只按当前时段显示其中一档。
  • 档位不会自己刷新:金额在重渲染时计算(新用量到达即重算),没有定时器,所以闲置会话跨过时段边界后药丸会短暂停留在旧档位。
  • 金额不谎报零:fmtMoney 自动加小数位,费用非零时绝不显示 ¥0.00。
  • 药丸上的数字不带货币符号:¥ 只出现在那颗 14px 图标上(官方药丸也是"图标 + 数字"的排版);面板与 tooltip 里仍带 ¥,因为那里没有图标可依赖。
  • 命中率口径:cacheRead / (uncachedInput + cacheRead + cacheWrite),与官方 cacheHitPercent 同一口径(分母是提示侧计费输入,不含输出);整数四舍五入到 100% 时会自动加精度,绝不把未命中说成 100%。
  • 文案按浏览器语言(zh* → 中文,否则英文),没有走 DSH 的 locale seat。
  • 只报消耗,不报余额:本地不存在剩余额度数据,会话投影里也没有。
  • 数字口径:总计 = 四桶之和,含 cacheRead,所以它明显大于"计费输入"——看成本时别拿它乘输入价。
  • 无 React 依赖打包:客户端 bundle 走宿主注入的虚拟 react(require('react'))。

开发自检

.verify/render-check.cjs 是一个零依赖的自检脚本(自带最小 React 垫片、DOM 桩与内联 fixture——一串真实会话投影值),会加载真实客户端 bundle、走真实 slot 注册路径:

node .verify\render-check.cjs     # 期望 ALL GREEN (pass=86 fail=0)

覆盖:注册契约、纯金额收起态、展开面板、provider 守卫、价格覆盖(含坏 JSON)、按模型匹配价格、命中率取整护栏、空/缺字段/无 hook 的退化输入、portal 目标识别与退化(含官方那排消失时的回退)、中英双语文案。

—/ 5

No ratings yet

Verified DSH bundle

Commit fdc634291955

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout