DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

blueziii /

blueziii/dsh-peakvalley

Verified

DSH 插件:输入栏下方最右侧显示峰谷时段徽标,空闲价格为高峰一半;仅 DeepSeek 模型可见,点击可切换 7 档显示内容。

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

峰谷时(dsh-peakvalley)

GitHub:https://github.com/blueziii/dsh-peakvalley · 作者 blueziii · 许可证 MIT

峰谷时徽标演示

在 DeepSeek Harness 输入栏下方那一行的最右侧(上下文占用圆环的右边)加一个时段徽标:空闲时段的价格是高峰时段的一半。

高峰 · 1x · 距空闲 1h51m      ← 高峰时段(橙色圆点)
空闲 · 0.5x · 距高峰 3h09m    ← 空闲时段(绿色圆点)

悬停(title)可以看到完整说明:当前北京时间、当前时段、今天是周末还是哪个法定节假日、两条规则原文、下一次切换的倒计时,以及当前使用的模型。

只有当前模型走 DeepSeek 时才显示。 会话切到非 DeepSeek 的提供方或模型(例如 OpenRouter 的第三方模型)时,徽标完全隐藏。判断依据是提供方 id 或模型 id 里是否含 deepseek(不区分大小写):deepseek-account、deepseek-official、DeepSeek-V3.2 都会显示;模型信息读不到时按「未知」处理,照常显示。模型数据读自 ctx.get('modelDirectories') 服务(ui-model-selection 提供),组件本身不订阅,跟着每秒一次的时钟节拍读一次快照,所以切换模型后最多 1 秒生效。

时段规则

北京时间
高峰时段(1x) 周一至周五 09:00–12:00、14:00–18:00,且当天不是中国法定节假日
空闲时段(0.5x) 其余全部时间:周末全天、法定节假日全天、工作日 12:00–14:00、18:00–次日 09:00
  • 判定一律按 UTC+8 北京时间,与本机时区、夏令时无关。
  • 倒计时单位固定为 d / h / m(8d23h、1h51m、45m)。
  • 节假日按自然日全天算空闲(不区分节假日里的具体钟点)。
  • 时间显示精确到秒(每秒刷新一次),切换倒计时向上取整到分钟。

一个需要说明的口径

调休上班的周末(例如 2026-10-10 周六)仍按周末算,是空闲时段。 按需求原文「周末…均为空闲时段」,周末优先于调休。若你希望调休上班日按工作日计时,只需在 client.js 的 stateAt() 里改一处判断(把周末与「调休上班表」相抵),改完刷新页面即可。

安装

插件就是一个普通的本地包目录,官方插件管理器原生支持「本地绝对路径」安装:

git clone https://github.com/blueziii/dsh-peakvalley.git
  1. 打开 DSH,进入侧栏的 插件 页(设置 → 插件 里也有入口)。

  2. 点 添加插件。

  3. 在输入框里填入克隆下来的包目录的绝对路径,回车或点 安装:

    <你克隆到的位置>\dsh-peakvalley
    
  4. 安装完成后点 立即启用。

也可以让 Agent 代装:把同样的绝对路径交给插件管理器(install_bundle)即可。

插件管理器把输入内容当 pnpm spec 解析,所以也可以直接填 github:blueziii/dsh-peakvalley(未实测);上面那条「本地绝对路径」是实际验证过的方式。

安装后如果徽标没有立刻出现,刷新一次页面(F5)。这是新建的组合包,不是替换已装插件,所以通常不需要重启 DSH;只有将来替换这个包的代码时才要完全退出并重启。

开发机上这份目录已经以 link: 形式装进了 desktop profile,也就是改完 client.js 刷新页面(F5)即可生效,不用重新安装。若刷新没反应,再完全退出并重启 DSH。

卸载

在 插件 → 已安装 里找到 dsh-peakvalley,卸载即可。卸载会删掉它在 profile 里的依赖声明,本项目目录(工作区里的这份源码)不受影响。

关于节假日数据

节假日安排在运行时自动获取,不需要手动更新内置表,也就不会出现「明年调休表还没内置」的问题。数据来自 NateScarlet/holiday-cn,内容取自国务院办公厅每年发布的节假日安排通知。

数据源与镜像

  • 先取 https://cdn.jsdelivr.net/gh/NateScarlet/holiday-cn@master/<年>.json,失败再取 https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/<年>.json。
  • 单次请求 8 秒超时(AbortController),带 cache: 'no-store',两个镜像都失败才判定为失败。
  • 请求只带上年份(…/2027.json),不带任何本地信息,也不经过本插件自己的服务器;除此之外插件不访问任何地址。

抓什么、多久抓一次

  • 每次抓本年与下一年(2026 年的机器会同时拿 2026 与 2027)。
  • 已公布的年份(数据里 days 非空):每 30 天复查一次。
  • 尚未公布的年份(文件存在但 days 为空):每 12 小时重试一次。国务院通常在 11 月发布次年安排,公告一出,最迟 12 小时内插件就自动用上了,不用改代码、不用升级。
  • 抓取时机是徽标每秒一次的时钟节拍里顺手检查(未过期时是个空操作),插件 apply 阶段不联网。

缓存与降级

  • 结果缓存在浏览器 localStorage 的 dsh-peakvalley.holidays({ v: 1, years: { '2027': { at, days } } }),刷新页面后先用缓存渲染、再联网复查。缓存损坏、版本不符、读写被拒(隐私模式)都只是静默降级。
  • 三级降级:在线 → 内置表(2026) → 公历估算。tooltip 里会写明这一年用的是哪一种。
  • 联网全部失败时保留上一次拿到的数据(连抓取时刻一起保留,下个周期再试),不会退回估算。
  • 内置表仍保留(2026 共 33 天:元旦 1/1–1/3、春节 2/15–2/23、清明 4/4–4/6、劳动节 5/1–5/5、端午 6/19–6/21、中秋 9/25–9/27、国庆 10/1–10/7),只作为首次启动 / 断网时的兜底。离线且没有缓存时,农历节日(春节、端午、中秋)无法预知,只会按公历惯例估算并在提示里注明。

调休口径

只取 isOffDay: true 的条目,isOffDay: false 的「调休上班」周末(例如 2026-10-10 周六)会被忽略,与上文「调休上班的周末仍按周末算空闲」一致。

实现要点

package.json          dsh.bundle.patch → cordis.patch.yml;dsh.client.platform = web
cordis.patch.yml      - insert: - id: peakvalley / name: 'dsh-peakvalley'
index.js              Host 半边,空实现(可见内容全在客户端)
client.js             客户端 bundle:峰谷判定 + 徽标组件 + 插槽注册
locale/zh.json        插件列表里显示的名字与描述
locale/en.json        同上(英文)
icon.svg              插件列表图标
dev/check.mjs         开发期自检脚本(不随包发布)
  • 徽标注册在宿主 slot conversation.composer.dock(输入卡片下方那一行,即「会话统计」所在的工具栏),order: 20 排在会话统计之后。
  • 「最右侧 / 上下文右边」怎么做到的:宿主把 ContextMeter(上下文占用圆环)作为这一行 flex 容器的最后一个子项渲染,而插槽外壳带 display: contents(Anchor 的 ANCHOR_STYLE),插槽条目的根元素因此就是该容器的直接 flex item。于是给根元素加 CSS order: 1(宿主各条目与圆环默认都是 0),就能越过默认顺序、排到圆环右侧。这一行 justify-content: center,宽度收缩为内容宽,所以「最右」= 该行内顺序最末的条目。
  • client.js 只 require('react')(宿主模块表提供),不 import 任何 Harness Client 包;样式只用 --dsw-alias-* 主题 token(pkv_ 前缀类名),因此明暗主题都跟随宿主。
  • 颜色:高峰用 --dsw-alias-state-warn-primary,空闲用 --dsw-alias-state-success-primary,次要文字用 --dsw-alias-label-tertiary。
  • 组件内 setInterval(1000) 驱动每秒刷新,useEffect 返回清理函数,卸载即停。

显示 / 消失动画

目标是让同一行的其它条目(上下文圆环、会话统计)不跳位:收起时宽度真正归零,而且几何变化必须逐帧过渡,不能一帧走完。

结构上分两层,各管一件事:

  • 盒子(根元素)只管几何:width / padding / margin / border-width 一起过渡。退场时全部收到 0,其中 margin-right: -12px 正好抵消宿主这一行的 gap: 12px,所以徽标收起时宽度真正归零。只淡出不收宽是不够的——文字占的那段宽度会在卸载瞬间消失,同一行其它条目就会突然跳到空缺位置。
  • 内容(.pkv_inner)只管淡入淡出:进场时延迟 110ms 淡入(盒子先展开),退场时先 100ms 淡出干净、空盒子再收。文字在可见期间基本不会被盒子的 overflow: hidden 裁到一半,因此没有「文字被切着走」的生硬感。

时序(常规动效,ANIM_MS = 260):

  • 进场:以 width:0 + 内容 opacity:0 挂载,两帧后转常态(等两帧是让浏览器先按初始状态画过一次,否则过渡会被跳过)。
  • 退场:切到非 DeepSeek 模型时先保留节点播完动画,320ms 后(UNMOUNT_MS = 260 + 60)才真正卸载;几何过渡额外延迟 40ms(EXIT_LAG),让内容先消失、空盒子后收。
  • 半途切回:退场动画没播完又切回 DeepSeek,会取消卸载、直接回到常态,不会闪一下空。

系统「减少动态效果」是开着的怎么办

这台机器的 Windows「显示动画」是关闭的:HKCU\Control Panel\Desktop\WindowMetrics\MinAnimate = 0,SPI_GETCLIENTAREAANIMATION 返回 False。Windows 上 Chromium/Electron 就是据此判定 prefers-reduced-motion: reduce,所以当前 DSH 里走的就是「减少动态效果」这条路径。

早期版本的 reduce 分支被写成了 transition: none,几何变化一帧走完 —— 那正是「其它组件突然移动到空缺位置」和「还是很生硬」的根因(那时无头 Chrome 也恒报 reduce: true,与用户实际看到的路径一致)。reduce 分支绝不能再把过渡关掉,现在只把它缩短为 140ms 线性(SHORT_MS = 140),内容仍然后显/先隐:

  • 进场 140ms:盒子 0 → 145.91px,圆环每 10ms 最多移动 5.66px(实测 maxJumpPer10ms),140ms 就停稳,而卸载在 320ms —— 卸载那一刻位移早已归零,不再跳变。
  • 退场同理:内容 60ms 内隐掉,空盒子 140ms 收完,圆环平滑回到原位。

想看到完整的 260ms 版本,打开 Windows 的「在 Windows 中显示动画」(设置 → 辅助功能 → 视觉效果)再重启 DSH;或把 client.js 里的 SHORT_MS 调到与 ANIM_MS 相同,让两条路径共用一套时序。

「auto ↔ 0 也能补间」靠根元素上的 interpolate-size: allow-keywords(Chrome 129+ 起支持;本机实测 Electron 44.0.0 = Chromium 152.0.7977.54)。常态宽度就是 width: auto,与不加动画时完全一致(零回归);万一该特性不可用,只是退化成瞬间收起,不会变形。内容子项带 flex:none; white-space: nowrap,宽度收缩时不会被压扁。

点击切换显示内容

徽标整块就是按钮(role="button"、tabIndex=0、Enter / 空格同效、cursor: pointer、悬停与键盘聚焦给一层 color-mix 底色),点一下换一档,七档循环:

顺序 id 显示内容
1 all 时段 · 倍率 · 倒计时(默认)
2 state 仅时段(高峰 / 空闲)
3 rate 仅倍率(1x / 0.5x)
4 eta 仅倒计时(距高峰 / 距空闲 d h m)
5 stateRate 时段 + 倍率
6 stateEta 时段 + 倒计时
7 rateEta 倍率 + 倒计时
  • 主次:时段也在显示时,倍率与倒计时压暗一档(--dsw-alias-label-tertiary);只显示倍率/倒计时时用当前色(currentColor),单段更醒目。圆点恒显示,分隔符只插在相邻可见段之间。
  • 记忆:所选档位存在 localStorage['dsh-peakvalley.layout'](存的是模式 id,不是下标);读写全部包在 try 里,隐私模式/被禁用/存了过期值都静默回退到「全部」。
  • 换档也要平滑(否则换内容会改变徽标宽度,同一行的圆环、会话统计又要跳)分两拍:
    1. 点击后先挂 .pkv_switch,.pkv_inner 用 90ms(SWAP_OUT_MS)淡出,盒子原地不动;
    2. 淡完才换文案;同一次提交里用 useLayoutEffect 把根元素钉在切换前的像素宽度(getBoundingClientRect().width),两帧后放开成 width: auto —— auto 到 auto 之间浏览器看不到「变化」、不会插值,必须先钉出一个像素旧值,宽度才会真的做过渡,邻居才会平滑地跟着挪。放在 useLayoutEffect 里才能赶在本次绘制之前钉住。
  • 快速连点不会丢步:基准取「已经在路上的那一档」(swapTo),新一轮会取消上一轮的计时器与 rAF。reduce 分支下换档淡出缩短为 60ms 线性。

开发自检

node dev/check.mjs

覆盖:index.js 可加载、client.js 语法、manifest 字段与 id 一致性、__ModuleLoader__.load 的 id 等于包名、插槽注册契约(含 t 与 modelStore 注入)、峰谷真值表(含 2026 全部节假日与调休日、窗口边界到毫秒)、切换倒计时、d/h/m 文案、模型可见性(非 DeepSeek 隐藏、服务缺失/current 为空/抛错时的回退)、组件在 React 桩下真实执行一次渲染、进出场动画时序(进场两帧转常态、退场计时器卸载、半途切回取消卸载、卸载后再切回能二次进场)、动画几何(interpolate-size、width:auto↔0、flex:none、reduce 分支仍要过渡 width 且绝不能是 transition:none、SHORT_MS < ANIM_MS、内容后显/先隐、UNMOUNT_MS 大于退场总时长)、点击切换显示内容(7 档矩阵与循环、tooltip 模式行、点击/Enter/空格、.pkv_switch 先淡出、换档时把旧宽度钉成像素值再两帧放开、连点不丢步、localStorage 存取与非法值/抛错/无 storage 三种回退、各档段数与主次类名、reduce 下换档淡出更短)、节假日在线同步(离线样本解析、放假日/补班日取舍、中文名映射、年份不符与脏数据、在线→内置→估算三级降级、两种 TTL、双镜像回退、全失败保留旧数据、在飞不重发起、缓存恢复与损坏/版本不符、写缓存失败静默、apply 阶段不联网、内置 2026 表与在线数据逐日一致性回归),以及「未 import Harness Client 包 / CSS 无 NaN / var() 只用主题 token / 根元素带 order:1」等硬约束。

当前状态:299 项全部通过。

还有一个做动画调试用的小工具(可选,不参与插件运行):先 node dev/css-dump.mjs,它会把 client.js 里真实的 CSS 抽出来写成 dev/_real.css,并额外生成一份剥掉 reduce 分支的 dev/_real-full.css;再用浏览器打开 dev/anim-real.html(?dir=enter|exit&series=1&full=1,需要先生成上面两个 CSS 文件,否则页面是没样式的)就能借 Web Animations API 把过渡钉在任意时刻逐帧采样,直接读出几何曲线(travel / maxJumpPer10ms / settledAt)。上面那些数字就是这么量出来的。

注意:这个脚本只做逻辑与契约校验。动效几何已经用无头 Chrome + 真实 CSS 逐帧量过了(就是上面那些数字),但装进 DSH 之后的实际观感(位置、颜色、间距、快慢是否顺眼)只能靠眼睛确认——DSH 的 Web GUI 需要鉴权,无法抓取或截图。

—/ 5

No ratings yet

Verified DSH bundle

Commit a36b89494b44

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