TestFox000/dsh-deepseek-peak-whale ↗★ 0
dsh-deepseek-peak-whale
在侧栏显示 DeepSeek 峰谷计价状态与账户钱包:小鲸鱼娘 + 距半价倒计时 + 官方价格表 + 余额与当日费用。 适合需要实时查看DeepSeek余额、今日费用及计价倒计时的用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:TestFox000/dsh-deepseek-peak-whaleREADME
Read the full README ↗三、使用
- 状态条:侧栏底部、账号位上方(挂载在
sidebar.footer.action,order: -10),每秒刷新倒计时;侧栏收起成 56px 图标轨道时只显示鲸鱼头。 展开态是两行:上行「时段 · 倒计时」,下行「余额 · 今日费用」,中间一条细分隔线; 没拿到数据时第二行整行不渲染——宁可保持原样,也不在侧栏底部挂一句「账户数据不可用」。 - 点击状态条:弹出详情浮层(价格表、账户卡片、下次切换、依据与来源),点浮层外关闭;打开时会立刻刷新一次余额。
- 悬停:title 提示里有完整的一句话摘要(含余额与今日费用)。
- 命令:会话里输入
/price打印峰谷报告,/balance打印余额 + 今日费用的纯文本版(都由 Host 半身提供)。
账户钱包(余额 + 当日费用)
| 位置 | 显示 |
|---|---|
| 状态条第二行 | 余额 $12.34 · 今日 $0.0042(未登录 → 账户 未登录 · 今日 …;取不到 → 整行不显示,原因写在浮层里) |
| 详情浮层的账户卡片 | 账户状态、每个钱包的余额、赠送余额、今日费用(日期 + 金额,单独一段并放大)、按哪一档计价、请求次数与 token 分解、充值 / 官方用量入口、估算声明 |
/balance 命令 | 同上内容的纯文本版 |
余额由 Host 半身调 DSH 自己的 deepseekAccount.getBalance()(Platform 账户接口)取得,
带 45 秒服务端缓存——多个标签页共用一次查询;未登录只读状态、不打余额接口。
今日费用完全在本机算,不联网:
- Host 监听
session/event(会话的持久化事件流),从assistant/message、assistant/attempt里取 provider 报告的 token 用量(request/header负责记住当前路由,llm/retry-started负责重试语义); - 折算规则与 DSH 内置的
tokenUsage投影逐字对齐(同 turn/step 的重复样本不改账、 重试叠加、替换按 delta 调整),所以它和界面里那颗用量甜甜圈不会各说各话; - 按事发那一刻的峰/谷档与官方牌价计价(缓存命中/未命中/写缓存/输出四个桶分开算), 记进北京时间的当日格子;
- 落盘到
$DSH_HOME/peak-whale/wallet. .json(合并写 + 临时文件 rename, 损坏时留.corrupt-备份再从空账继续),重启不丢当天的账。
浏览器侧同源 fetch GET /dsh-deepseek-peak-whale/api/wallet?locale=…(Host 不发 CORS 头、
响应 no-store),60 秒轮询一次。拉取失败时保留上一份数据,而不是把界面清空;
连第一份都还没拿到时,状态条整行不显示,说明写在浮层里,而且按状态码分人话:
| 失败原因 | 浮层里的说法 |
|---|---|
| 404(Host 半身还是旧代码) | 「要重启一次 DSH 才会出现——Host 侧的新半身还没加载;之后每分钟自动刷新」 |
| 其余(断网、服务缺席、500) | 「暂时取不到,每分钟会自动重试」 |
布局本身用截图验收(node tools/strip_preview.mjs → assets/strip-preview.html →
无头 Edge 截到 assets/strip-preview.png),覆盖浅色/深色、有数据、未加载、未登录四种状态;
价格表四列都是不换行的数字,浮层宽度下限(≥400px)已写成断言,防止「输出」那列再次被挤出卡片。
v0.4.0 的版式约定(按使用反馈改的,改了就别退回去):
- 浮层顶部不再摆模式开关——简洁版 / 详细版只在「设置 → 插件 → 小鲸鱼」里切;
- 浮层钉死尺寸(
width:420px; max-height:452px,量自简洁版实测卡片),详细版超出部分 在卡片内滚动,不再把浮层撑成一整屏; - 详细版底部有一个吸底的「设置」按钮,点了走
layout.selectPanel('plugins')跳内置「插件」设置面板(我们的 tab 就在那一页);拿不到 layout 服务时安静失败,绝不抛错; - 卡片是亚克力(
backdrop-filter: blur(18px) saturate(1.5)+ 半透明底色),@supports not (backdrop-filter…)退回不透明底色; - 卡片里那行免责小字与「想看完整价格表…」的指路文案已移除(完整说明移到设置页的帮助页)。
这是估算,不是账单:按官方牌价计价,看不到赠送余额抵扣、议价折扣, 也不代表 Platform 后台的记账口径。界面与
/balance都会写明这一点。
鲸鱼娘桌宠
挂在 shell.overlay(官方定义:全屏浮层、在所有列之上、本身就是点击穿透的)。
整层也是 pointer-events: none,只有跟着她身体走的命中区(.pw-hitbox,
约 148×145 px)开回指针事件——所以除了她身上那一小块,你点任何按钮都不受影响。
| 行为 | 触发 | 说明 |
|---|---|---|
| 自由活动 | 常驻 | 在窗口底部来回走 / 跑、坐下、睡觉、转圈、张望——全由上游 roam: 'free' 驱动 |
| 视线跟随 | 鼠标移动 | 眼睛跟着光标转(头上的呆毛和刘海位移最大,后发反向) |
| 顺毛 | 鼠标在她身上划动累积 320px | 呼噜声 + 爱心;睡着时摸会冒爱心,醒着则随机「喜欢 / 害羞」 |
| 戳一下 | 点一下(6px | 揪后颈拎起,拖动时朝向与拖拽方向联动 |
| 抛出去 | 松手 | 按松手瞬间的速度抛飞;速度 >700 有破风声,落地冲击 >1000 会摔晕 |
| 入场 | 挂载时 | 从窗口顶部掉下来落到地面(enter: 'drop') |
| 打字回避 | 在任何文本框里打字 / 把焦点点进文本框 | 无论她在屏幕哪个位置,只要挡在文本框前就让开到框外最近的一侧;距离 >160px 直接跑过去 |
| 投递(优化提示词) | 写完提示词后把她拖到输入框上松手 | 就地放下(不抛飞)→ 读原文 → 调 deepseek-v4-pro(reasoning_effort: max)改写 → 写回 → 浮出「撤销优化」;随后她主动让开 |
这些物理与反应全部来自上游 pet-core(pointerDown/Move/Up、onEvent('touch')),
本插件只负责把浏览器事件转发进去,并把命中区贴到她身上。
打字回避(你打字时她会走开)
用的是上游公开的 ctl.walkTo() + ctl.holdRoam(),没有改 vendor 代码:
- 认出「正在打字」:在文档上捕获
keydown/input/focusin/focusout, 目标或当前焦点是input/textarea/contenteditable就记下来。 判定是宽限期(最后一次按键后 3 秒)或焦点仍留在文本框里—— 光标还停在框里时你随时会接着写,她得一直在外面等着; - 算回避区:文本框矩形横向外扩 40px(别压住输入框旁边的按钮)、 上沿 24px、下沿 72px(下面还有工具行);
- 每帧判定一次(
layout()存下的身体矩形直接用,不多做布局读取): 不挡路就什么都不做;挡路才walkTo,目标取框外最近的一侧, 距离超过 160px 改成跑(走只有 78px/s,你一句话打完她还没让开); - 打字期间持续
holdRoam(0.5):上游的自由漫游(decide())被按住, 不会把她重新派回文本框前,她让开后就待在原地; - 只在目标真的变了才重新下令(容差 16px)——每帧重复
walkTo会把步态重置成 原地踏步,她反而走不动。窗口太窄、两侧都塞不下时返回null, 与其在框里来回蹭,不如下次再试。
判定全部收在 isEditable / editableZone / rectsOverlap / escapeTargetX /
escapePlan / typingActive 这几个纯函数里(不碰全局,视口坐标),
bundle.test.mjs 逐个断言;监听器的挂载与摘除也有断言(helpers.mjs 的 document 桩
会记账,React 桩新增了 __unmount() 来跑 effect 清理)。
提示词优化(把她拖到文本框上)
用法:提示词写完之后 → 揪住她拖到输入框上 → 松手。她不会被抛出去
(pet-core 自带的 dropAt() 正是「就地放下、no throw」,所以还是没改 vendor):
- 认出文本框(聚焦的 → 最近打字的 → 页面上最大的那个,
textarea/contenteditable都认); - 读出正文 →
POST /dsh-deepseek-peak-whale/api/optimize给 Host; - Host 用你自己的 API key 调一次
deepseek-v4-pro+reasoning_effort: max, 系统提示词写死「不改变原意、不增删任务、只输出正文」,Host 再剥掉模型爱加的 代码块与客套话; - 写回文本框(走原型上的原生 setter + 派发
input,React 受控输入才认账), 浮出一颗胶囊:「提示词已优化 [撤销优化]」; - 她念一句「优化好了,不满意可以撤销~」,随后主动走出文本框区域
——复用打字回避那道门,只是由
api.avoidUntil打开。
撤销(不想要了就撤):
| 入口 | 说明 |
|---|---|
| 胶囊上的「撤销优化」 | 一键把原稿一字不差写回去 |
| 右键菜单 → 撤销提示词优化 | 胶囊被你改字或 30 秒超时收掉之后,从这里照样能撤 |
| 自动失效 | 你改过文本框内容后,撤销自己收起——绝不能拿旧稿盖掉你的新输入 |
配置:$DSH_HOME/peak-whale/settings.json(首次启动自动创建;保存即生效,不用重启
——每次拖拽都会重新读一遍文件)。
| 字段 | 默认 | 说明 |
|---|---|---|
deepseekApiKey | "" | 你的 DeepSeek API key。留空时拖一次会得到「把 key 填进 …」的提示 |
model | deepseek-v4-pro | 与本插件价格表同一档 |
reasoningEffort | max | 留空即不发这个字段(有的账户不认它;400 报错里会直接告诉你改哪儿) |
baseUrl | https://api.deepseek.com | |
timeoutMs | 120000 | max 思考就是慢;到点必然返回(内部另有一道不依赖 abort() 的兜底超时) |
maxInputChars | 12000 | 超长直接拒绝,不白花钱 |
兜底:环境变量 DEEPSEEK_API_KEY(文件优先)。key 只在 Host 进程里读——
不进插件代码、不进日志、不进浏览器 bundle。
⚠️ 这次调用走的是你自己的 DeepSeek API 账户,按 token 计费,与侧栏那条 「今日费用」无关(账本只折算会话日志里 provider 报告的用量, Host 自己发的请求不在其中)。同一段原文 5 分钟内不重复扣费。
台词与气泡
每 75–150 秒她会自己念一句,其中约 3/4 是当前峰谷的价格台词
(谷时说「半价 / 便宜 / 打折」类,峰时换「省着点花 / 等半价再跑?」类,
节假日与周末各有专门的一组)。台词逐字揭示,每揭示一个字就调一次 ctl.talk(),
所以嘴会跟着动;气泡位置每帧从 ctl.anchor()(头顶)取,她走动时气泡跟着走。
首次开口在挂载后约 9 秒。想立刻听一句:右键 →「念句价格」。
右键小菜单
| 菜单项 | 作用 |
|---|---|
| 换个姿势 | 随机做一个动作(跳 / 小跳 / 张望 / 转身 / 点头 / 摇头 / 转圈 / 坐下 / 睡觉 / 晕 …) |
| 摸摸头 | 触发「喜欢」表情并 +1 互动(上游把顺毛做成了划动累积,这里给个一键入口) |
| 念句价格 | 立刻念一句当前峰谷的台词 |
| 回家 | 走回出生点(ctl.walkTo) |
| 安静一会儿 | 关掉自由漫游并坐下(setRoam('off')) |
| 自由活动 | 恢复自由漫游(setRoam('free')) |
菜单顶部显示 亲密度 Lv.N · 互动 M 次。点空白处关闭。
⚠️ 踩坑记录:
.pw-roam是pointer-events: none(为了不挡住界面), 菜单作为它的子元素会继承 none——表现是"菜单能弹出来但一个都点不动"。 必须在.pw-menu-backdrop上显式写pointer-events: auto。 这条已经写成断言进了bundle.test.mjs,防止以后被改回去。
亲密度
她身上发生的每一次互动(拎起 / 放下 / 抛出 / 戳 / 摸 / 摔晕)都算一次,
存在 localStorage 的 dsh-peak-whale.affinity.v1 里,跨会话累积,
每 5 次升一级(上限 Lv.10),升级时会专门念一句。
读写都包了 try/catch:隐私模式、配额满、JSON 损坏一律回落到 0,存不上不影响使用。
loadAffinity(storage) / saveAffinity(storage, pets) 把 storage 作为参数传入而不是直接用全局,
所以这两条路径也有单测覆盖。
音效
上游自带 Web Audio 合成音效(呼噜、戳、拎起、破风、说话嘟囔、睡觉打呼),
必须有用户手势之后才会响(浏览器策略),所以第一次点她之后才出声。
想彻底静音:把 localStorage 的 dsh-peak-whale.sound.v1 设为 'off'。