dsh-peak-hours 0.0.0-stage → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/assets/icon-180.png +0 -0
- package/assets/icon-512.png +0 -0
- package/client.js +578 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +12 -0
- package/index.js +8 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +66 -4
- package/screenshots/screenshot-1.png +0 -0
- package/screenshots.json +3 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
## 1.11.0 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
### 修正
|
|
6
|
+
|
|
7
|
+
- **说明文案与实现对齐**:日程条的说明原来写「红色=高峰、绿色=空闲」,但条子实际是琥珀橙 / DeepSeek 蓝,现已改为「橙色=高峰、蓝色=空闲」(英文同步为 `amber` / `blue`)。
|
|
8
|
+
- 节假日说明不再暴露源码里的 `HOLIDAYS` 常量名,改成数据驱动:从已收录年份自动生成「节假日表已收录 2026 年中秋、国庆」。
|
|
9
|
+
- `package.json` 补充 `./package.json` 导出、`engines`、`scripts.test`,发布物更规范。
|
|
10
|
+
|
|
11
|
+
### 新增
|
|
12
|
+
|
|
13
|
+
- **点击复制状态**:点一下角标(或聚焦后按 Enter / 空格)即可把「高峰 ×2 · 3小时11分后转空闲(北京时间)」复制到剪贴板,1.5 秒内显示「已复制」。剪贴板不可用时回退到临时 textarea。
|
|
14
|
+
- **切换前 10 分钟强调**:距峰↔谷切换不足 10 分钟时,倒计时变琥珀色加粗,圆点轻微脉冲(`prefers-reduced-motion` 下自动关闭动画)。
|
|
15
|
+
- **更完整的 tooltip**:新增「明天」预告(明天周末 / 法定节假日 / 工作日),以及点击复制的提示。
|
|
16
|
+
- **可访问性**:角标改为 `role="button"` + `tabIndex`,带 `aria-label`(含状态、倒计时与操作提示),键盘 `:focus-visible` 有可见焦点环。
|
|
17
|
+
- **单元测试与 CI**:`npm test` 用 `node --test` 跑 15 个用例(时段边界 09:00/12:00/14:00/18:00、周末、节假日、跨日倒计时、日程条分段与宽度、游标位置、折叠态、复制、中英文 key 对齐),GitHub Actions 在 22.x 上跑测试与打包检查。
|
|
18
|
+
|
|
19
|
+
## 1.10.1 — 2026-10-28
|
|
20
|
+
|
|
21
|
+
- 修正 bundle patch 里的行名仍然指向改名前的 `@local/dsh-peak-hours`,导致重命名后客户端半不装载、角标消失。
|
|
22
|
+
|
|
23
|
+
## 1.10.0 — 2026-10-28
|
|
24
|
+
|
|
25
|
+
- 收窄空闲色渐变(`#4d6bfe → #6383ff`),让条子底色与侧栏 logo 的品牌蓝一致。
|
|
26
|
+
- 发布物整理:包名改为 `dsh-peak-hours`,补 LICENSE / README / 图标 / 截图 / `screenshots.json`。
|
|
27
|
+
|
|
28
|
+
## 1.9.0 — 2026-10-27
|
|
29
|
+
|
|
30
|
+
- 空闲色从绿色换成 DeepSeek 品牌蓝 `#4d6bfe`;高峰仍为琥珀色 `#f59e0b`。
|
|
31
|
+
|
|
32
|
+
## 1.4.0 — 2026-10-27
|
|
33
|
+
|
|
34
|
+
- 进度条改为「当天 00:00–24:00 的日程条」:红/绿分段 + 白色当前时刻标尺。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 run666888
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-peak-hours
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个 DeepSeek Harness 的侧栏角标:**现在是不是高峰计价时段**。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 它显示什么
|
|
10
|
+
|
|
11
|
+
挂在侧栏底部动作行(`sidebar.footer.action`,「插件市场」下面),一行搞定三件事:
|
|
12
|
+
|
|
13
|
+
- **状态**:橙点 = 高峰,蓝点 = 空闲;后面跟当前倍率(高峰 `×2`,空闲 `5 折`)
|
|
14
|
+
- **倒计时**:还有多久切换(`3小时11分后转空闲`)
|
|
15
|
+
- **全天日程条**:00:00–24:00 一条 8px 高的条,橙色段是高峰、蓝色段是空闲,白色竖线是当前时刻——一天里什么时候贵,一眼看全
|
|
16
|
+
|
|
17
|
+
侧栏折叠成窄轨时自动退化为「圆点 + 一小截进度条」,不占地方。
|
|
18
|
+
|
|
19
|
+
**距切换不到 10 分钟**时,倒计时会变成琥珀色加粗、圆点轻微脉冲,提醒你「要涨价了」或「要降价了」(系统开启减少动态效果时动画自动关闭)。
|
|
20
|
+
|
|
21
|
+
**点一下角标**(或聚焦后按 Enter / 空格)会把当前状态复制到剪贴板,例如
|
|
22
|
+
`DeepSeek 高峰 ×2 · 3小时11分后转空闲(北京时间)`,方便贴给同事。
|
|
23
|
+
|
|
24
|
+
鼠标悬停能看到完整说明:当前段起止时间、明天是工作日还是休息、以及高峰/空闲的价格表。
|
|
25
|
+
|
|
26
|
+
## 计费规则
|
|
27
|
+
|
|
28
|
+
按 DeepSeek 的峰谷定价:
|
|
29
|
+
|
|
30
|
+
- **高峰**:北京时间 **09:00–12:00**、**14:00–18:00**
|
|
31
|
+
- **空闲**:其余时间(含午间 12:00–14:00、18:00 之后、09:00 之前)
|
|
32
|
+
- **整天空闲**:周六、周日,以及中国的法定节假日
|
|
33
|
+
- 闲时价 = 高峰价的一半
|
|
34
|
+
|
|
35
|
+
节假日表是**内置的,不联网**:目前覆盖 2026 年(中秋 9/25–27、国庆 10/1–7)。2026 年 9/20(周日)和 10/10(周六)是调休上班日,按规则仍算空闲。表里没有的年份退化成「周末空闲」规则——这是保守选择:宁可把某天当成空闲(少标高峰),也不误报高峰;遇到新的节假日安排需要更新 `client.js` 里的 `HOLIDAYS`,tooltip 里显示的年份由该表自动推导。
|
|
36
|
+
|
|
37
|
+
时间一律按北京时间(UTC+8)算,和你浏览器的时区无关。
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
从 npm:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
dsh plugin --profile web add dsh-peak-hours
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
从本地目录:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
dsh plugin --profile web add /绝对路径/dsh-peak-hours
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
装完刷新页面(或重启 `dsh web`)即可生效。
|
|
54
|
+
|
|
55
|
+
## 卸载
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
dsh plugin --profile web remove dsh-peak-hours
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 兼容性
|
|
62
|
+
|
|
63
|
+
- 需要带客户端 UI 的 DeepSeek Harness Web GUI(`dsh.client` 声明为 `platform: web`)
|
|
64
|
+
- 纯客户端插件:不注册 host 工具、不写文件、不发网络请求、不需要任何配置或凭据
|
|
65
|
+
- 颜色是固定值(高峰 `#f59e0b`、空闲 `#4d6bfe`,即 DeepSeek 品牌蓝),不跟随主题 `brand-primary`,以免换皮肤后蓝色被改成别的色
|
|
66
|
+
|
|
67
|
+
## 开发
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
npm test # 15 个单元测试(node --test,零依赖)
|
|
71
|
+
npm run verify # npm pack --dry-run,检查发布清单
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
测试通过 `vm` 加载 `client.js`,用最小的 React / locale / document 替身渲染组件,因此不用起 GUI 也能验证时段判定、分段宽度、折叠态与复制逻辑。
|
|
75
|
+
|
|
76
|
+
## 许可
|
|
77
|
+
|
|
78
|
+
MIT
|
|
Binary file
|
|
Binary file
|
package/client.js
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-peak-hours',
|
|
3
|
+
factory(require) {
|
|
4
|
+
const React = require('react');
|
|
5
|
+
const h = React.createElement;
|
|
6
|
+
|
|
7
|
+
const NS = 'dsh-peak-hours';
|
|
8
|
+
const MINUTE = 60 * 1000;
|
|
9
|
+
const BEIJING_OFFSET = 8 * 60 * MINUTE;
|
|
10
|
+
|
|
11
|
+
// 高峰时段(北京时间,分钟数):09:00–12:00、14:00–18:00
|
|
12
|
+
const PEAK_WINDOWS = [
|
|
13
|
+
[9 * 60, 12 * 60],
|
|
14
|
+
[14 * 60, 18 * 60],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// 距峰↔谷切换不足该分钟数时,倒计时与圆点进入强调态。
|
|
18
|
+
const SOON_MINUTES = 10;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 中国法定节假日(全天空闲计价)。来源:2026-09-19 官方说明
|
|
22
|
+
* 「法定节假日全天、调休上班的周末全天均按空闲时段计费」。
|
|
23
|
+
* 已核对 2026 年中秋、国庆;新一年的放假安排公布后按公告补充,
|
|
24
|
+
* 未收录的年份只按周末规则判定(周末本就全天空闲,因此不会判错方向)。
|
|
25
|
+
*/
|
|
26
|
+
const HOLIDAYS = {
|
|
27
|
+
2026: [
|
|
28
|
+
'2026-09-25', '2026-09-26', '2026-09-27',
|
|
29
|
+
'2026-10-01', '2026-10-02', '2026-10-03', '2026-10-04',
|
|
30
|
+
'2026-10-05', '2026-10-06', '2026-10-07',
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
const HOLIDAY_YEARS = Object.keys(HOLIDAYS).sort();
|
|
34
|
+
const holidayCache = new Map();
|
|
35
|
+
function holidaySet(year) {
|
|
36
|
+
if (holidayCache.has(year)) return holidayCache.get(year);
|
|
37
|
+
const list = HOLIDAYS[year];
|
|
38
|
+
const set = list ? new Set(list) : null;
|
|
39
|
+
holidayCache.set(year, set);
|
|
40
|
+
return set;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pad2 = (n) => (n < 10 ? '0' + n : '' + n);
|
|
44
|
+
|
|
45
|
+
/** 把瞬间投影到北京时间墙上时钟。 */
|
|
46
|
+
function beijing(ms) {
|
|
47
|
+
const d = new Date(ms + BEIJING_OFFSET);
|
|
48
|
+
const year = d.getUTCFullYear();
|
|
49
|
+
const month = d.getUTCMonth() + 1;
|
|
50
|
+
const day = d.getUTCDate();
|
|
51
|
+
return {
|
|
52
|
+
year,
|
|
53
|
+
month,
|
|
54
|
+
day,
|
|
55
|
+
key: year + '-' + pad2(month) + '-' + pad2(day),
|
|
56
|
+
dow: d.getUTCDay(),
|
|
57
|
+
minute: d.getUTCHours() * 60 + d.getUTCMinutes(),
|
|
58
|
+
clock: pad2(d.getUTCHours()) + ':' + pad2(d.getUTCMinutes()),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stateAt(ms) {
|
|
63
|
+
const t = beijing(ms);
|
|
64
|
+
const holidays = holidaySet(t.year);
|
|
65
|
+
const isWeekend = t.dow === 0 || t.dow === 6;
|
|
66
|
+
const isHoliday = holidays ? holidays.has(t.key) : false;
|
|
67
|
+
const allDayOff = isWeekend || isHoliday;
|
|
68
|
+
const inWindow = PEAK_WINDOWS.some(
|
|
69
|
+
(w) => t.minute >= w[0] && t.minute < w[1],
|
|
70
|
+
);
|
|
71
|
+
return {
|
|
72
|
+
peak: !allDayOff && inWindow,
|
|
73
|
+
isWeekend,
|
|
74
|
+
isHoliday,
|
|
75
|
+
allDayOff,
|
|
76
|
+
beijing: t,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 次日的计价形态:holiday / weekend / workday。 */
|
|
81
|
+
function kindAt(ms) {
|
|
82
|
+
const t = beijing(ms);
|
|
83
|
+
const holidays = holidaySet(t.year);
|
|
84
|
+
if (holidays && holidays.has(t.key)) return 'holiday';
|
|
85
|
+
if (t.dow === 0 || t.dow === 6) return 'weekend';
|
|
86
|
+
return 'workday';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 下一个峰↔谷切换的瞬间(分钟对齐),最多向前找 14 天。 */
|
|
90
|
+
function nextSwitch(ms) {
|
|
91
|
+
const current = stateAt(ms);
|
|
92
|
+
const base = Math.floor(ms / MINUTE) * MINUTE;
|
|
93
|
+
const limit = 14 * 1440;
|
|
94
|
+
for (let i = 1; i <= limit; i += 1) {
|
|
95
|
+
const at = base + i * MINUTE;
|
|
96
|
+
if (stateAt(at).peak !== current.peak) return at;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 当前这一段(高峰段或空闲段)的起点。 */
|
|
102
|
+
function prevSwitch(ms) {
|
|
103
|
+
const current = stateAt(ms);
|
|
104
|
+
const base = Math.floor(ms / MINUTE) * MINUTE;
|
|
105
|
+
const limit = 14 * 1440;
|
|
106
|
+
for (let i = 1; i <= limit; i += 1) {
|
|
107
|
+
const at = base - i * MINUTE;
|
|
108
|
+
if (stateAt(at).peak !== current.peak) return at + MINUTE;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function phaseOf(ms) {
|
|
114
|
+
const start = prevSwitch(ms);
|
|
115
|
+
const end = nextSwitch(ms);
|
|
116
|
+
let progress = 0;
|
|
117
|
+
if (start !== null && end !== null && end > start) {
|
|
118
|
+
progress = Math.min(1, Math.max(0, (ms - start) / (end - start)));
|
|
119
|
+
}
|
|
120
|
+
return { start, end, progress };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 当天 00:00–24:00 的峰谷分段(分钟),画成橙蓝相间的日程条。 */
|
|
124
|
+
function daySchedule(ms) {
|
|
125
|
+
const st = stateAt(ms);
|
|
126
|
+
if (st.allDayOff) return [{ from: 0, to: 1440, peak: false }];
|
|
127
|
+
const segs = [];
|
|
128
|
+
let cur = 0;
|
|
129
|
+
for (const w of PEAK_WINDOWS) {
|
|
130
|
+
if (w[0] > cur) segs.push({ from: cur, to: w[0], peak: false });
|
|
131
|
+
segs.push({ from: w[0], to: w[1], peak: true });
|
|
132
|
+
cur = w[1];
|
|
133
|
+
}
|
|
134
|
+
if (cur < 1440) segs.push({ from: cur, to: 1440, peak: false });
|
|
135
|
+
return segs;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const pct = (minute) => ((minute / 1440) * 100).toFixed(3) + '%';
|
|
139
|
+
|
|
140
|
+
function fmtDuration(ms, t) {
|
|
141
|
+
const total = Math.max(1, Math.round(ms / MINUTE));
|
|
142
|
+
const days = Math.floor(total / 1440);
|
|
143
|
+
const hours = Math.floor((total % 1440) / 60);
|
|
144
|
+
const minutes = total % 60;
|
|
145
|
+
const parts = [];
|
|
146
|
+
if (days) parts.push(days + t('unit.day'));
|
|
147
|
+
if (hours) parts.push(hours + t('unit.hour'));
|
|
148
|
+
if (!days && minutes) parts.push(minutes + t('unit.minute'));
|
|
149
|
+
const sep = t('unit.sep') || '';
|
|
150
|
+
return parts.join(sep);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function clockLabel(ms) {
|
|
154
|
+
const b = beijing(ms);
|
|
155
|
+
return b.month + '-' + pad2(b.day) + ' ' + b.clock;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function holidayNote(t) {
|
|
159
|
+
return (t('tip.table') || '').replace('{years}', HOLIDAY_YEARS.join(' / '));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function tooltip(t, st, phase, now) {
|
|
163
|
+
const lines = [
|
|
164
|
+
t('tip.head'),
|
|
165
|
+
t('tip.current') + (st.peak ? t('status.peak') : t('status.off')),
|
|
166
|
+
t('tip.window'),
|
|
167
|
+
t('tip.bar'),
|
|
168
|
+
t('tip.rule'),
|
|
169
|
+
t('tip.weekend'),
|
|
170
|
+
];
|
|
171
|
+
if (st.isHoliday) lines.push(t('tip.holidayToday'));
|
|
172
|
+
else if (st.isWeekend) lines.push(t('tip.weekendToday'));
|
|
173
|
+
const tomorrow = kindAt(now + 1440 * MINUTE);
|
|
174
|
+
lines.push(
|
|
175
|
+
tomorrow === 'holiday'
|
|
176
|
+
? t('tip.tomorrowHoliday')
|
|
177
|
+
: tomorrow === 'weekend'
|
|
178
|
+
? t('tip.tomorrowWeekend')
|
|
179
|
+
: t('tip.tomorrowWorkday'),
|
|
180
|
+
);
|
|
181
|
+
if (phase.start !== null && phase.end !== null) {
|
|
182
|
+
lines.push(
|
|
183
|
+
t('tip.phase') + clockLabel(phase.start) + ' → ' + clockLabel(phase.end),
|
|
184
|
+
);
|
|
185
|
+
lines.push(
|
|
186
|
+
t('tip.next') +
|
|
187
|
+
fmtDuration(phase.end - now, t) +
|
|
188
|
+
(st.peak ? t('next.off') : t('next.peak')),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
lines.push(
|
|
192
|
+
'',
|
|
193
|
+
t('tip.prices'),
|
|
194
|
+
t('tip.flash'),
|
|
195
|
+
t('tip.pro'),
|
|
196
|
+
'',
|
|
197
|
+
holidayNote(t),
|
|
198
|
+
t('tip.tz'),
|
|
199
|
+
t('tip.hint'),
|
|
200
|
+
);
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function copySummary(t, st, phase, now) {
|
|
205
|
+
const pill = st.peak ? t('price.multiplier') : t('price.discount');
|
|
206
|
+
const rest =
|
|
207
|
+
phase.end !== null
|
|
208
|
+
? t('tip.next') +
|
|
209
|
+
fmtDuration(phase.end - now, t) +
|
|
210
|
+
(st.peak ? t('next.off') : t('next.peak'))
|
|
211
|
+
: '';
|
|
212
|
+
return (
|
|
213
|
+
'DeepSeek ' +
|
|
214
|
+
(st.peak ? t('status.peak') : t('status.off')) +
|
|
215
|
+
' ' +
|
|
216
|
+
pill +
|
|
217
|
+
' · ' +
|
|
218
|
+
rest +
|
|
219
|
+
' ' +
|
|
220
|
+
t('copy.suffix')
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 复制到剪贴板:Clipboard API 优先,失败退回临时 textarea。 */
|
|
225
|
+
function writeClipboard(text) {
|
|
226
|
+
try {
|
|
227
|
+
if (
|
|
228
|
+
typeof navigator !== 'undefined' &&
|
|
229
|
+
navigator.clipboard &&
|
|
230
|
+
typeof navigator.clipboard.writeText === 'function'
|
|
231
|
+
) {
|
|
232
|
+
return Promise.resolve(navigator.clipboard.writeText(text)).then(
|
|
233
|
+
() => true,
|
|
234
|
+
() => fallbackCopy(text),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
/* 落到兜底 */
|
|
239
|
+
}
|
|
240
|
+
return Promise.resolve(fallbackCopy(text));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function fallbackCopy(text) {
|
|
244
|
+
try {
|
|
245
|
+
if (typeof document === 'undefined' || !document.body) return false;
|
|
246
|
+
const area = document.createElement('textarea');
|
|
247
|
+
area.value = text;
|
|
248
|
+
area.setAttribute('readonly', '');
|
|
249
|
+
area.style.position = 'fixed';
|
|
250
|
+
area.style.opacity = '0';
|
|
251
|
+
document.body.appendChild(area);
|
|
252
|
+
area.select();
|
|
253
|
+
const ok = document.execCommand && document.execCommand('copy');
|
|
254
|
+
document.body.removeChild(area);
|
|
255
|
+
return !!ok;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 字族跟随宿主,字号显式压到侧栏次级字号(0.8125rem ≈ 13px),
|
|
262
|
+
// 因为 footer 动作行从根节点继承的是 1rem,直接用 inherit 会比菜单项大一号。
|
|
263
|
+
const CSS = `
|
|
264
|
+
.dshph-root {
|
|
265
|
+
--dshph-peak: #f59e0b;
|
|
266
|
+
--dshph-off: #4d6bfe;
|
|
267
|
+
--dshph-peak-hi: #fbbf24;
|
|
268
|
+
--dshph-off-hi: #6383ff;
|
|
269
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
270
|
+
box-sizing: border-box; width: 100%; min-width: 0;
|
|
271
|
+
padding: 4px 8px 5px; border-radius: 8px;
|
|
272
|
+
font-family: inherit; font-size: 14px; font-size: .875rem;
|
|
273
|
+
font-weight: 400; line-height: 1.45;
|
|
274
|
+
color: var(--dsw-alias-label-primary);
|
|
275
|
+
user-select: none; cursor: pointer; outline: none;
|
|
276
|
+
transition: background-color .15s ease;
|
|
277
|
+
}
|
|
278
|
+
.dshph-root:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
279
|
+
.dshph-root:focus-visible {
|
|
280
|
+
outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
|
|
281
|
+
outline-offset: 1px;
|
|
282
|
+
}
|
|
283
|
+
.dshph-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
284
|
+
.dshph-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; }
|
|
285
|
+
.dshph-peak .dshph-dot {
|
|
286
|
+
background: var(--dshph-peak);
|
|
287
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, .22);
|
|
288
|
+
}
|
|
289
|
+
.dshph-off .dshph-dot {
|
|
290
|
+
background: var(--dshph-off);
|
|
291
|
+
box-shadow: 0 0 0 3px rgba(77, 107, 254, .2);
|
|
292
|
+
}
|
|
293
|
+
.dshph-dot-soon { animation: dshph-pulse 1.6s ease-in-out infinite; }
|
|
294
|
+
@keyframes dshph-pulse {
|
|
295
|
+
0%, 100% { box-shadow: 0 0 0 2px rgba(245, 158, 11, .22); }
|
|
296
|
+
50% { box-shadow: 0 0 0 5px rgba(245, 158, 11, .06); }
|
|
297
|
+
}
|
|
298
|
+
.dshph-label,
|
|
299
|
+
.dshph-pill,
|
|
300
|
+
.dshph-next {
|
|
301
|
+
font-family: inherit; font-size: inherit; font-weight: inherit;
|
|
302
|
+
line-height: inherit; letter-spacing: inherit; color: inherit;
|
|
303
|
+
}
|
|
304
|
+
.dshph-label { flex: none; }
|
|
305
|
+
.dshph-pill { flex: none; }
|
|
306
|
+
.dshph-spacer { flex: 1 1 auto; min-width: 4px; }
|
|
307
|
+
.dshph-next {
|
|
308
|
+
flex: 0 1 auto; min-width: 0; overflow: hidden;
|
|
309
|
+
text-overflow: ellipsis; white-space: nowrap;
|
|
310
|
+
}
|
|
311
|
+
.dshph-soon .dshph-next { color: #f59e0b; font-weight: 600; }
|
|
312
|
+
.dshph-toast { color: var(--dsw-alias-label-secondary); }
|
|
313
|
+
.dshph-track {
|
|
314
|
+
position: relative; height: 8px; border-radius: 999px;
|
|
315
|
+
background: var(--dsw-alias-bg-layer-2); overflow: hidden;
|
|
316
|
+
}
|
|
317
|
+
.dshph-seg { position: absolute; top: 0; bottom: 0; }
|
|
318
|
+
.dshph-seg-off {
|
|
319
|
+
background: linear-gradient(180deg, var(--dshph-off-hi), var(--dshph-off));
|
|
320
|
+
}
|
|
321
|
+
.dshph-seg-peak {
|
|
322
|
+
background: linear-gradient(180deg, var(--dshph-peak-hi), var(--dshph-peak));
|
|
323
|
+
}
|
|
324
|
+
.dshph-cursor {
|
|
325
|
+
position: absolute; top: 0; bottom: 0; width: 2px;
|
|
326
|
+
border-radius: 1px; background: #fff;
|
|
327
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, .45);
|
|
328
|
+
transform: translateX(-1px);
|
|
329
|
+
transition: left .6s linear;
|
|
330
|
+
}
|
|
331
|
+
.dshph-rail {
|
|
332
|
+
align-items: center; justify-content: center; gap: 4px;
|
|
333
|
+
padding: 0;
|
|
334
|
+
}
|
|
335
|
+
.dshph-rail .dshph-track { width: 26px; height: 6px; }
|
|
336
|
+
.dshph-rail .dshph-cursor { width: 1.5px; }
|
|
337
|
+
@media (prefers-reduced-motion: reduce) {
|
|
338
|
+
.dshph-root, .dshph-root * {
|
|
339
|
+
animation: none !important; transition: none !important;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
function ensureStyle() {
|
|
345
|
+
if (typeof document === 'undefined') return () => {};
|
|
346
|
+
const id = 'dsh-peak-hours-style';
|
|
347
|
+
if (document.getElementById(id)) return () => {};
|
|
348
|
+
const el = document.createElement('style');
|
|
349
|
+
el.id = id;
|
|
350
|
+
el.textContent = CSS;
|
|
351
|
+
document.head.appendChild(el);
|
|
352
|
+
return () => {
|
|
353
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const ZH = {
|
|
358
|
+
label: '峰谷时段',
|
|
359
|
+
'status.peak': '高峰',
|
|
360
|
+
'status.off': '空闲',
|
|
361
|
+
'price.multiplier': '×2',
|
|
362
|
+
'price.discount': '5 折',
|
|
363
|
+
'next.peak': '后转高峰',
|
|
364
|
+
'next.off': '后转空闲',
|
|
365
|
+
'unit.day': '天',
|
|
366
|
+
'unit.hour': '小时',
|
|
367
|
+
'unit.minute': '分',
|
|
368
|
+
'unit.sep': '',
|
|
369
|
+
'tip.head': 'DeepSeek 峰谷计费',
|
|
370
|
+
'tip.current': '当前:',
|
|
371
|
+
'tip.window': '高峰时段:工作日 09:00–12:00、14:00–18:00(北京时间)',
|
|
372
|
+
'tip.bar': '日程条:当天 00:00–24:00,橙色=高峰、蓝色=空闲,白线是当前时刻',
|
|
373
|
+
'tip.rule': '其余时间按空闲计价,闲时价为高峰价的一半',
|
|
374
|
+
'tip.weekend': '周末与法定节假日全天按空闲计价',
|
|
375
|
+
'tip.holidayToday': '今天:法定节假日,全天按空闲计价',
|
|
376
|
+
'tip.weekendToday': '今天:周末,全天按空闲计价',
|
|
377
|
+
'tip.tomorrowHoliday': '明天:法定节假日,全天按空闲计价',
|
|
378
|
+
'tip.tomorrowWeekend': '明天:周末,全天按空闲计价',
|
|
379
|
+
'tip.tomorrowWorkday': '明天:工作日,恢复按 09:00–12:00、14:00–18:00 计价',
|
|
380
|
+
'tip.phase': '本段:',
|
|
381
|
+
'tip.next': '距切换:',
|
|
382
|
+
'tip.prices': '价格(元/百万 tokens,空闲 → 高峰)',
|
|
383
|
+
'tip.flash': 'deepseek-flash:缓存命中 0.02 → 0.04|未命中 1 → 2|输出 4 → 8',
|
|
384
|
+
'tip.pro': 'deepseek-v4-pro:缓存命中 0.15 → 0.30|未命中 4.5 → 9.0|输出 13.5 → 27.0',
|
|
385
|
+
'tip.table': '节假日表已收录 {years} 年中秋、国庆;新一年安排公布后随版本更新',
|
|
386
|
+
'tip.tz': '判定时区:北京时间(UTC+8)',
|
|
387
|
+
'tip.hint': '点击复制当前状态',
|
|
388
|
+
'copy.ok': '已复制',
|
|
389
|
+
'copy.fail': '复制失败',
|
|
390
|
+
'copy.suffix': '(北京时间)',
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const EN = {
|
|
394
|
+
label: 'Peak hours',
|
|
395
|
+
'status.peak': 'Peak',
|
|
396
|
+
'status.off': 'Off-peak',
|
|
397
|
+
'price.multiplier': '×2',
|
|
398
|
+
'price.discount': '50% off',
|
|
399
|
+
'next.peak': ' until peak',
|
|
400
|
+
'next.off': ' until off-peak',
|
|
401
|
+
'unit.day': 'd',
|
|
402
|
+
'unit.hour': 'h',
|
|
403
|
+
'unit.minute': 'm',
|
|
404
|
+
'unit.sep': ' ',
|
|
405
|
+
'tip.head': 'DeepSeek peak/off-peak pricing',
|
|
406
|
+
'tip.current': 'Now: ',
|
|
407
|
+
'tip.window': 'Peak: weekdays 09:00–12:00 and 14:00–18:00 (Beijing time)',
|
|
408
|
+
'tip.bar': 'Day bar: 00:00–24:00, amber = peak, blue = off-peak, white line = now',
|
|
409
|
+
'tip.rule': 'Everything else is off-peak, priced at half of the peak rate',
|
|
410
|
+
'tip.weekend': 'Weekends and public holidays are off-peak all day',
|
|
411
|
+
'tip.holidayToday': 'Today: public holiday, off-peak all day',
|
|
412
|
+
'tip.weekendToday': 'Today: weekend, off-peak all day',
|
|
413
|
+
'tip.tomorrowHoliday': 'Tomorrow: public holiday, off-peak all day',
|
|
414
|
+
'tip.tomorrowWeekend': 'Tomorrow: weekend, off-peak all day',
|
|
415
|
+
'tip.tomorrowWorkday': 'Tomorrow: weekday, peak windows apply again',
|
|
416
|
+
'tip.phase': 'This block: ',
|
|
417
|
+
'tip.next': 'Switch in ',
|
|
418
|
+
'tip.prices': 'Price (CNY / million tokens, off-peak → peak)',
|
|
419
|
+
'tip.flash': 'deepseek-flash: cache hit 0.02 → 0.04 | miss 1 → 2 | output 4 → 8',
|
|
420
|
+
'tip.pro': 'deepseek-v4-pro: cache hit 0.15 → 0.30 | miss 4.5 → 9.0 | output 13.5 → 27.0',
|
|
421
|
+
'tip.table': 'Holiday table covers {years} (Mid-Autumn, National Day); updated per release',
|
|
422
|
+
'tip.tz': 'Evaluated in Beijing time (UTC+8)',
|
|
423
|
+
'tip.hint': 'Click to copy the current state',
|
|
424
|
+
'copy.ok': 'Copied',
|
|
425
|
+
'copy.fail': 'Copy failed',
|
|
426
|
+
'copy.suffix': '(Beijing time)',
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
let translate = (key) => (EN[key] !== undefined ? EN[key] : key);
|
|
430
|
+
|
|
431
|
+
function PeakBadge(props) {
|
|
432
|
+
const t = translate;
|
|
433
|
+
const [now, setNow] = React.useState(() => Date.now());
|
|
434
|
+
const [toast, setToast] = React.useState('');
|
|
435
|
+
|
|
436
|
+
React.useEffect(() => {
|
|
437
|
+
const id = setInterval(() => setNow(Date.now()), 15000);
|
|
438
|
+
return () => clearInterval(id);
|
|
439
|
+
}, []);
|
|
440
|
+
|
|
441
|
+
const st = stateAt(now);
|
|
442
|
+
const phase = phaseOf(now);
|
|
443
|
+
const schedule = daySchedule(now);
|
|
444
|
+
const off = !st.peak;
|
|
445
|
+
const wide = !props || props.wide !== false;
|
|
446
|
+
const stateText = off ? t('status.off') : t('status.peak');
|
|
447
|
+
const title = tooltip(t, st, phase, now);
|
|
448
|
+
const soon =
|
|
449
|
+
phase.end !== null && phase.end - now <= SOON_MINUTES * MINUTE;
|
|
450
|
+
const rootClass =
|
|
451
|
+
'dshph-root ' +
|
|
452
|
+
(off ? 'dshph-off' : 'dshph-peak') +
|
|
453
|
+
(soon ? ' dshph-soon' : '');
|
|
454
|
+
const cursorLeft = pct(st.beijing.minute);
|
|
455
|
+
const remaining = phase.end
|
|
456
|
+
? fmtDuration(phase.end - now, t) + (off ? t('next.peak') : t('next.off'))
|
|
457
|
+
: '';
|
|
458
|
+
|
|
459
|
+
const onCopy = () => {
|
|
460
|
+
const text = copySummary(t, st, phase, now);
|
|
461
|
+
let result;
|
|
462
|
+
try {
|
|
463
|
+
result = writeClipboard(text);
|
|
464
|
+
} catch {
|
|
465
|
+
result = Promise.resolve(false);
|
|
466
|
+
}
|
|
467
|
+
Promise.resolve(result)
|
|
468
|
+
.then((ok) => setToast(ok ? t('copy.ok') : t('copy.fail')))
|
|
469
|
+
.catch(() => setToast(t('copy.fail')));
|
|
470
|
+
setTimeout(() => setToast(''), 1500);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const onKeyDown = (event) => {
|
|
474
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
475
|
+
event.preventDefault();
|
|
476
|
+
onCopy();
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const headText = toast
|
|
481
|
+
? h('span', { className: 'dshph-next dshph-toast' }, toast)
|
|
482
|
+
: h('span', { className: 'dshph-next' }, remaining);
|
|
483
|
+
|
|
484
|
+
const track = h(
|
|
485
|
+
'div',
|
|
486
|
+
{ className: 'dshph-track' },
|
|
487
|
+
...schedule.map((seg, i) =>
|
|
488
|
+
h('span', {
|
|
489
|
+
key: 'seg' + i,
|
|
490
|
+
className:
|
|
491
|
+
'dshph-seg ' + (seg.peak ? 'dshph-seg-peak' : 'dshph-seg-off'),
|
|
492
|
+
style: { left: pct(seg.from), width: pct(seg.to - seg.from) },
|
|
493
|
+
}),
|
|
494
|
+
),
|
|
495
|
+
h('span', { key: 'cursor', className: 'dshph-cursor', style: { left: cursorLeft } }),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
const common = {
|
|
499
|
+
title,
|
|
500
|
+
role: 'button',
|
|
501
|
+
tabIndex: 0,
|
|
502
|
+
onClick: onCopy,
|
|
503
|
+
onKeyDown,
|
|
504
|
+
'aria-label':
|
|
505
|
+
t('label') + ': ' + stateText + ', ' + remaining + '. ' + t('tip.hint'),
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
if (!wide) {
|
|
509
|
+
return h(
|
|
510
|
+
'div',
|
|
511
|
+
{ ...common, className: rootClass + ' dshph-rail' },
|
|
512
|
+
h('span', {
|
|
513
|
+
className: 'dshph-dot' + (soon ? ' dshph-dot-soon' : ''),
|
|
514
|
+
}),
|
|
515
|
+
track,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return h(
|
|
520
|
+
'div',
|
|
521
|
+
{ ...common, className: rootClass },
|
|
522
|
+
h(
|
|
523
|
+
'div',
|
|
524
|
+
{ className: 'dshph-head' },
|
|
525
|
+
h('span', {
|
|
526
|
+
className: 'dshph-dot' + (soon ? ' dshph-dot-soon' : ''),
|
|
527
|
+
}),
|
|
528
|
+
h('span', { className: 'dshph-label' }, stateText),
|
|
529
|
+
h(
|
|
530
|
+
'span',
|
|
531
|
+
{ className: 'dshph-pill' },
|
|
532
|
+
off ? t('price.discount') : t('price.multiplier'),
|
|
533
|
+
),
|
|
534
|
+
h('span', { className: 'dshph-spacer' }),
|
|
535
|
+
headText,
|
|
536
|
+
),
|
|
537
|
+
track,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
inject: ['slots'],
|
|
543
|
+
apply(ctx) {
|
|
544
|
+
ctx.effect(ensureStyle, 'dsh-peak-hours-style');
|
|
545
|
+
|
|
546
|
+
// 语言包晚到也不影响:locale 可用时立刻补注册并接管文案。
|
|
547
|
+
ctx.inject(['locale'], (c) => {
|
|
548
|
+
if (!c.locale || typeof c.locale.register !== 'function') return;
|
|
549
|
+
try {
|
|
550
|
+
c.locale.register(NS, { zh: ZH, en: EN });
|
|
551
|
+
} catch {
|
|
552
|
+
/* 已注册过(例如热重载),沿用现有字典 */
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const bound = c.locale.bind(NS);
|
|
556
|
+
if (typeof bound === 'function') translate = bound;
|
|
557
|
+
} catch {
|
|
558
|
+
/* 保留内置兜底 */
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
ctx.slots.inject('sidebar.footer.action', () =>
|
|
563
|
+
ctx.slots.register(
|
|
564
|
+
{
|
|
565
|
+
name: 'sidebar.footer.action',
|
|
566
|
+
id: 'dsh-peak-hours',
|
|
567
|
+
key: 'dsh-peak-hours',
|
|
568
|
+
order: 20,
|
|
569
|
+
locale: NS,
|
|
570
|
+
label: () => translate('label'),
|
|
571
|
+
},
|
|
572
|
+
PeakBadge,
|
|
573
|
+
),
|
|
574
|
+
);
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
},
|
|
578
|
+
});
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Peak hours badge">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="dshphRing" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#f2a03d"/>
|
|
5
|
+
<stop offset="1" stop-color="#d9534f"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="15" fill="#14181f"/>
|
|
9
|
+
<circle cx="32" cy="32" r="17" fill="none" stroke="url(#dshphRing)" stroke-width="3.6"/>
|
|
10
|
+
<path d="M32 21.5V32l7.4 5.2" fill="none" stroke="url(#dshphRing)" stroke-width="3.6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
<circle cx="32" cy="14.5" r="2.4" fill="#f2a03d"/>
|
|
12
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of the peak-hours badge.
|
|
3
|
+
*
|
|
4
|
+
* The visible UI is a Client module (`./client`), which the Web runtime loads on
|
|
5
|
+
* its own. The Host entry exists so the bundle has a valid plugin row; it needs
|
|
6
|
+
* no services, tools or config.
|
|
7
|
+
*/
|
|
8
|
+
export function apply() {}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-peak-hours",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.11.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Sidebar badge that marks the current DeepSeek peak-pricing window: 24-hour schedule bar, now-cursor, countdown to the next switch, click to copy the state.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "run666888 <zhirunli6@gmail.com>",
|
|
8
|
+
"homepage": "https://github.com/run666888/dsh-peak-hours#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/run666888/dsh-peak-hours.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/run666888/dsh-peak-hours/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test",
|
|
21
|
+
"verify": "npm pack --dry-run"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"dsh",
|
|
25
|
+
"dsh-plugin",
|
|
26
|
+
"deepseek-harness",
|
|
27
|
+
"peak-hours",
|
|
28
|
+
"off-peak",
|
|
29
|
+
"pricing",
|
|
30
|
+
"sidebar"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./index.js",
|
|
37
|
+
"./client": "./client.js",
|
|
38
|
+
"./locale/*.json": "./locale/*.json",
|
|
39
|
+
"./icon": "./icon.svg",
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"index.js",
|
|
44
|
+
"client.js",
|
|
45
|
+
"cordis.patch.yml",
|
|
46
|
+
"locale/*.json",
|
|
47
|
+
"icon.svg",
|
|
48
|
+
"assets",
|
|
49
|
+
"screenshots",
|
|
50
|
+
"screenshots.json",
|
|
51
|
+
"README.md",
|
|
52
|
+
"CHANGELOG.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"dsh": {
|
|
56
|
+
"bundle": {
|
|
57
|
+
"patch": "./cordis.patch.yml"
|
|
58
|
+
},
|
|
59
|
+
"client": {
|
|
60
|
+
"platform": "web",
|
|
61
|
+
"immediately": true,
|
|
62
|
+
"inject": [
|
|
63
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-slots"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
Binary file
|
package/screenshots.json
ADDED