dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
在 DSH 侧边栏底部、用户名正上方显示 DeepSeek 账号剩余额度(总余额 + 赠金余额)的 Web 插件
中文 | English
装在 DeepSeek Harness 侧边栏底部的小组件:在你的用户名正上方显示账号剩余额度,点一下即刷新。
平时金额贴住最右边;点整行立即刷新,转圈只在这时出现。
在 DSH 里用 plugin_manager 装(Creator 模式,或设置里的插件页):
plugin_manager install_bundle target = github:lbqcgza/dsh-quota-usage
也可以直接指向本仓库的本地克隆路径:
plugin_manager install_bundle target = <本仓库绝对路径>
装完要重启一次桌面 App。 客户端模块的启动图只在宿主进程启动时生成一次,之后刷新页面也拿不到新插件 ——
这一点和多数 DSH 插件不同,原因见实现要点。dsh web 浏览器模式则刷新页面即可。
装好后:侧边栏底部、设置 与头像/用户名那一行的上面会多出一行 额度。
需要 DSH 0.2.0-rc.2 或更新的 Web 版(桌面版或 dsh web)。它依赖 sidebar.footer.action 槽位与官方
remote.account 命名空间;headless / SDK / ACP 这类没有 Web 客户端的 profile 不适用。
normal_wallets)与赠金余额(bonus_wallets)在该币种下的合计;括号里是赠金部分,不足 1 分(含为 0)时整个括号连单元格一起不渲染,不会留下空位或 <0.01 这种噪声transform 平移让位 22px,曲线 cubic-bezier(.22,.61,.36,1);只动 transform/opacity,不触发布局、不 reflow展开侧边栏时占两行,位置在 sidebar.settings(设置 + 账号 launcher)上方:
额度 ¥13.34(赠¥1.00) ← 本组件(用户名上方)
本会话 约¥1.20–2.40 ← 用量副标题
⚙ 设置
(头像) 张三 ← 账号 launcher / 用户名
点击触发一次手动刷新时,金额向左让位,转圈出现在它的右侧:
额度 ¥13.34(赠¥1.00) ◌
本会话 约¥1.20–2.40
| 阶段 | 显示 | 含义 |
|---|---|---|
loading | 读取中… | 首次读取尚未返回 |
ready | ¥13.34(赠¥1.00) | 正常 |
failed | 读取失败 | 调用失败、返回未就绪、或返回成功但没有任何钱包(保留上一次有效数字) |
unavailable | 未连接 | 页面还没有 remote.account 服务,正在快速重试 |
signed-out | 未登录 | 宿主侧没有可用凭证 |
悬停提示给出拆分、用量与刷新节奏:
总余额 ¥13.34 · 充值余额 ¥12.34 · 赠金余额 ¥1.00 · 更新于 14:32 · 每 60 秒自动刷新 · 本会话用量 1.28M tokens · 估算 ¥1.20–2.40 · token 取自会话日志;金额按官方价目表推算,不是账单金额(…) · 点击立即刷新
收起成 56px 轨道时(macOS / 普通 Web)变成 36px 圆形按钮,数字居中、转圈变成包住它的外圈; Windows 原生标题栏下侧边栏收起时整个底部区域由 shell 隐藏,与用户名行一同隐藏。 轨道态不显示用量副标题(36px 放不下)。
额度下方那一行是当前会话的用量估算,与上面一行构成同一个"标签 + 金额"列表:
额度 ¥13.34(赠¥1.00)
本会话 约¥1.20–2.40
两行共用同一套布局:额度 / 本会话 是左侧标签列(flex:none,所以两行的标签左边缘对齐),
金额都用 margin-left:auto 贴住同一个右边缘。注意 <button> 的 UA 样式默认 text-align:center,
会把撑满宽度的子元素里的文字居中,所以插件在行上显式设了 text-align:left(有 CSS 契约测试锁住)。
deepseek-flash 空闲时段 输入命中 0.02 / 输入未命中 1 / 输出 4(元每百万 tokens),高峰时段翻倍tokenUsage 投影(整份持久日志的回放结果,分页与压缩不会改变它),口径与 DSH 自己一致:提示侧三个互斥桶 uncachedInputTokens + cacheReadTokens + cacheWriteTokens,加上 outputTokens。悬停可见精确 token 数,副标题只显示钱两个估算固有的误差来源,都来自数据而不是算术:
¥1.20–2.40。高峰时段是北京时间周一至周五 9:00–12:00、14:00–18:00(不含法定节假日)cacheWriteTokens 按未命中输入价计,与 DSH 自己的提示侧分组一致设置 → 通用 → 本会话用量显示 可以关掉这一行。开关状态记在浏览器本地(localStorage 的
dsh-quota-usage:show-usage,这是本插件唯一写入的键),关掉后副标题与悬停提示里的用量段一起消失。
没打开任何会话时这一行本来就不渲染,组件仍是单行。
这一行只覆盖当前会话。额度行所在的 sidebar.footer.action 是 root 作用域,
而 tokenUsage 这类投影只能从会话作用域读到 —— 所以插件额外在会话作用域的
conversation.composer.dock 挂了一个不渲染任何东西的桥接组件,把读数交给额度行。
要做到"整个工作区"(含没打开过的会话)必须加 host 半侧去汇总会话日志,那会引入一个本地路由,
与当前"零网络"的架构不符,所以没做。
account.watch 账号状态流,登录 / 登出后立刻重读apply,只为让包在 Loader 里占一行remote.account.getBalance,与官方「设置 → 账号」页同源;随请求的元数据只有 version(DSH 版本号)、locale(界面语言)、timezoneOffsetSeconds(UTC 偏移)三项,与官方账号页逐字一致,没有设备 ID / 用户 IDlocalStorage、sessionStorage、cookie、indexedDB 全无使用。唯一的例外是那一个显示偏好键 dsh-quota-usage:show-usage(即「本会话用量显示」开关的状态),它不出浏览器、不参与任何请求fetch / XMLHttpRequest / WebSocket / sendBeacon需要报告安全问题请用 私密漏洞报告, 不要开公开 issue;判断边界见 SECURITY.md。
ui-settings-account 的 shell.overlay 共享宿主条目独占,第三方插件不应另起一个<0.01。这只是展示格式化,原始余额字符串不被改写MIN_VISIBLE_AMOUNT):格式化后只会是 <0.01,那是噪声不是余额,所以连单元格一起不渲染;总余额仍按真实数值计算,不受显示阈值影响PRICE_CNY_PER_MILLION。token 数是精确的CLIENT_VERSION 目前硬编码为 0.2.0-rc.2(只作账号接口的客户端标识,不影响功能)npm test # 等价于 node test/smoke.mjs
npm run check # 语法检查 + 上面那步
test/smoke.mjs 用桩件跑真实的 lib/client.js(模拟模块加载器、React、DOM 与 Cordis 上下文),
无需依赖、无需构建。CI 在 Node 20 / 22 / 24 上跑同一套。
覆盖的内容:槽位与 props、每请求元数据、就绪/轨道/亚分/未登录/失败/USD 各状态渲染、
转圈的行为(平时只留空占位不画环、后台轮询不显示、点击后才出现四分之一弧并旋转、
读取 settle 后立即消失、onClick 确实走手动路径)、缓动契约(--dsh-quota-ease 必须是非线性
cubic-bezier,且显式禁止任何 transition 落到 linear)、自诊断(未就绪阶段镜像、挂载失败留痕、
重复挂载容忍)。
改完 lib/client.js 后,在插件页或 plugin_manager 里停用再启用该插件即可让页面重新加载,无需重启 App。
这一节写给 DSH 插件作者 —— 三个都是踩出来的,不是推测。
ctx.get("remote.account")API gateway 把每个 Remote 命名空间注册成独立的 Cordis 服务,key 是 `remote.${namespace}`
(RemoteNamespaceService extends Service)。所以规范查找是 ctx.get("remote.account");
ctx.remote.account 只是嵌套访问器,在没有注入该 dotted key 的上下文里可能拿不到值 ——
那正是"组件注册成功、但余额永远空白"的原因。
本插件按 ctx.get("remote.account") → ctx.remote.account → ctx["remote.account"] 依次回退,
并且故意不在 inject 里声明 "remote.account":声明一个在某些部署下不存在的服务会让 fiber
一直 pending,而 shell 的启动审计把 pending 也算作失败。
apply 抛异常 = 整个页面启动失败shell 的启动审计(web boot: N entry did not activate)在任一条目 fiber 不是 active 时中止整个启动。
桌面版随后报告 web-boot 崩溃(%APPDATA%\@deepseek-ai\dsh-desktop\logs\crash-*-web-boot.log),
而启动器的恢复流程会重写 profile,丢掉 dsh.profile.bundles 里的额外条目和
cordis.patch.yml 里的覆盖项。
所以本插件的 apply 永不向外抛异常:每个挂载步骤都被隔离,失败时记一笔 trace 并让插件保持惰性,
绝不让一个小组件拖垮整个 App。
桌面壳把 SPA 的 index.html 静态送出,启动图(window.__DSH_BOOT__ 里的客户端模块表)只在 host
启动时取一次:
const ready = await host.start();
injections = ready.injections; // 只在 App 启动时算一次
ipcMain.handle(DESKTOP_IPC.boot, () => ({ injections, streamBaseUrl }));
所以新加一行客户端模块后,刷新页面也拿不到它的 bundle(Loader 条目已是 active,但页面手里的模块表
是旧的)。dsh web 浏览器模式则刷新页面就会重新渲染启动图。但一旦这一行已存在,停用再启用会触发
页面重新同步并重新加载,不需要重启。
shell.overlay 的 occupant id 形式留在页面里,
形如 dsh-quota-usage-diag:bind=ok | dict=ok | … | seat=!<错误>,客户端 Slot 检查即可读到;
同时有一行 console.error。ready,插件会把自己镜像成 shell.overlay 的
dsh-quota-usage-state:<phase> 条目,拿到金额后自动撤下。所以"overlay 里没有 state 条目"
等于"这条路没有跑起来","有 state 条目"等于"跑起来了但卡在该阶段"。.gitattributes 与 SECURITY.md 的组织方式参考了它