dsh-whale-break 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +42 -2
- package/assets/screenshot.png +0 -0
- package/client.js +681 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +11 -0
- package/index.js +2 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 夏悸
|
|
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,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-whale-break
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DeepSeek Harness 的"等待小鲸鱼":对话执行时在页面顶部居中弹出一个 DeepSeek 小鲸鱼跑酷游戏,打发等待时间;回合结束或需要你处理时自动暂停隐藏,下一轮开始再出现。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- **跟随回合状态**:`running` 时出现;回合结束、审批/提问等等待你处理时隐藏并暂停,不占用注意力。
|
|
10
|
+
- **DeepSeek 小鲸鱼**:彩色海底场景、渐变鲸身、摆动尾鳍、珊瑚障碍、小鱼与气泡视差。
|
|
11
|
+
- **操作**:单击画布或按空格起跳;输入法组字不拦截空格,`Shift+Space` 仍可正常输入空格。
|
|
12
|
+
- **位置**:默认顶部居中;拖动底部提示条可移动,双击提示条回到居中。
|
|
13
|
+
- **成绩**:右上角 `HI` 最高分,本地 `localStorage` 保存。
|
|
14
|
+
- **总开关**:输入框左侧工具行的鲸鱼按钮,一键全局开关。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
插件市场(推荐):设置 → 插件 → 发现 → 「等待小鲸鱼」→ 安装。
|
|
19
|
+
|
|
20
|
+
命令行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
dsh plugin --profile web add dsh-whale-break
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
然后在 设置 → 插件 中启用本插件。
|
|
27
|
+
|
|
28
|
+
## 开发
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm test # 纯逻辑自检:物理、碰撞、绘制调用
|
|
32
|
+
npm pack --dry-run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
客户端代码在 `client.js`(无构建步骤,`window.__ModuleLoader__` 直接加载),宿主半侧 `index.js` 为空。
|
|
36
|
+
|
|
37
|
+
## 仓库
|
|
38
|
+
|
|
39
|
+
https://github.com/btboys/dsh-rex-game
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
|
Binary file
|
package/client.js
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-whale-break',
|
|
3
|
+
factory(require) {
|
|
4
|
+
const React = require('react')
|
|
5
|
+
const ReactDOM = require('react-dom')
|
|
6
|
+
const h = React.createElement
|
|
7
|
+
|
|
8
|
+
const NS = 'rex-game'
|
|
9
|
+
const zh = {
|
|
10
|
+
hint: '拖动移动 · 空格 / 点击 起跳',
|
|
11
|
+
enable: '开启等待小鲸鱼',
|
|
12
|
+
disable: '关闭等待小鲸鱼',
|
|
13
|
+
reset: '双击回到顶部居中',
|
|
14
|
+
}
|
|
15
|
+
const en = {
|
|
16
|
+
hint: 'Drag to move · Space / click to jump',
|
|
17
|
+
enable: 'Turn the whale game on',
|
|
18
|
+
disable: 'Turn the whale game off',
|
|
19
|
+
reset: 'Double-click to reset position',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------- game core
|
|
23
|
+
const W = 240
|
|
24
|
+
const H = 120
|
|
25
|
+
const GROUND = 98
|
|
26
|
+
const WHALE = { x: 22, w: 36, h: 22 }
|
|
27
|
+
const FLOOR_Y = GROUND - WHALE.h
|
|
28
|
+
const GRAVITY = 2600
|
|
29
|
+
const JUMP_VELOCITY = -540
|
|
30
|
+
const SPEED_START = 230
|
|
31
|
+
const SPEED_MAX = 430
|
|
32
|
+
const SPEED_ACCEL = 7
|
|
33
|
+
const PANEL_WIDTH = 640
|
|
34
|
+
|
|
35
|
+
function readBest() {
|
|
36
|
+
try {
|
|
37
|
+
const value = Number(localStorage.getItem('dsh-rex-best'))
|
|
38
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0
|
|
39
|
+
} catch (_error) {
|
|
40
|
+
return 0
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function saveBest(value) {
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem('dsh-rex-best', String(Math.floor(value)))
|
|
47
|
+
} catch (_error) {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ENABLED_KEY = 'dsh-rex-enabled'
|
|
51
|
+
const enabledStore = {
|
|
52
|
+
value: undefined,
|
|
53
|
+
listeners: new Set(),
|
|
54
|
+
get() {
|
|
55
|
+
if (enabledStore.value === undefined) {
|
|
56
|
+
try {
|
|
57
|
+
enabledStore.value = localStorage.getItem(ENABLED_KEY) !== '0'
|
|
58
|
+
} catch (_error) {
|
|
59
|
+
enabledStore.value = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return enabledStore.value
|
|
63
|
+
},
|
|
64
|
+
subscribe(listener) {
|
|
65
|
+
enabledStore.listeners.add(listener)
|
|
66
|
+
return () => enabledStore.listeners.delete(listener)
|
|
67
|
+
},
|
|
68
|
+
set(next) {
|
|
69
|
+
enabledStore.value = next
|
|
70
|
+
try {
|
|
71
|
+
localStorage.setItem(ENABLED_KEY, next ? '1' : '0')
|
|
72
|
+
} catch (_error) {}
|
|
73
|
+
for (const listener of enabledStore.listeners) listener()
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createWorld() {
|
|
78
|
+
const bubbles = []
|
|
79
|
+
for (let index = 0; index < 9; index += 1) {
|
|
80
|
+
bubbles.push({
|
|
81
|
+
x: Math.random() * W,
|
|
82
|
+
y: Math.random() * H,
|
|
83
|
+
r: 0.8 + Math.random() * 2.2,
|
|
84
|
+
vy: -10 - Math.random() * 16,
|
|
85
|
+
trail: false,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
const fish = []
|
|
89
|
+
for (let index = 0; index < 4; index += 1) {
|
|
90
|
+
fish.push({ x: Math.random() * W, y: 16 + Math.random() * 52, v: 6 + Math.random() * 12 })
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
phase: 'ready',
|
|
94
|
+
y: FLOOR_Y,
|
|
95
|
+
vy: 0,
|
|
96
|
+
speed: SPEED_START,
|
|
97
|
+
score: 0,
|
|
98
|
+
best: readBest(),
|
|
99
|
+
ground: 0,
|
|
100
|
+
leg: 0,
|
|
101
|
+
spawnIn: 260 + Math.random() * 120,
|
|
102
|
+
corals: [],
|
|
103
|
+
fish,
|
|
104
|
+
bubbles,
|
|
105
|
+
bubbleIn: 0.2,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resetWorld(world) {
|
|
110
|
+
const fresh = createWorld()
|
|
111
|
+
world.phase = fresh.phase
|
|
112
|
+
world.y = fresh.y
|
|
113
|
+
world.vy = fresh.vy
|
|
114
|
+
world.speed = fresh.speed
|
|
115
|
+
world.score = 0
|
|
116
|
+
world.best = fresh.best
|
|
117
|
+
world.ground = 0
|
|
118
|
+
world.leg = 0
|
|
119
|
+
world.spawnIn = fresh.spawnIn
|
|
120
|
+
world.corals = []
|
|
121
|
+
world.fish = fresh.fish
|
|
122
|
+
world.bubbles = fresh.bubbles
|
|
123
|
+
world.bubbleIn = fresh.bubbleIn
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function jump(world) {
|
|
127
|
+
if (world.phase === 'over') resetWorld(world)
|
|
128
|
+
if (world.phase === 'ready') world.phase = 'run'
|
|
129
|
+
if (Math.abs(world.y - FLOOR_Y) < 0.5) world.vy = JUMP_VELOCITY
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function whaleBox(world) {
|
|
133
|
+
return { x: WHALE.x + 5, y: world.y + 5, w: WHALE.w - 10, h: WHALE.h - 8 }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function coralBox(coral) {
|
|
137
|
+
return { x: coral.x - 1, y: GROUND - coral.h + 3, w: 10, h: coral.h - 3 }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hit(a, b) {
|
|
141
|
+
return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function stepWorld(world, dt) {
|
|
145
|
+
for (const bubble of world.bubbles) {
|
|
146
|
+
bubble.y += bubble.vy * dt
|
|
147
|
+
bubble.x -= world.speed * 0.4 * dt + Math.sin(bubble.y * 0.04) * 4 * dt
|
|
148
|
+
if (bubble.y < -6) {
|
|
149
|
+
bubble.y = H + 4 + Math.random() * 18
|
|
150
|
+
bubble.x = Math.random() * W
|
|
151
|
+
bubble.trail = false
|
|
152
|
+
}
|
|
153
|
+
if (bubble.x < -8) bubble.x = W + 8
|
|
154
|
+
}
|
|
155
|
+
for (const fish of world.fish) {
|
|
156
|
+
fish.x -= (world.speed * 0.12 + fish.v) * dt
|
|
157
|
+
if (fish.x < -12) {
|
|
158
|
+
fish.x = W + 10 + Math.random() * 20
|
|
159
|
+
fish.y = 16 + Math.random() * 52
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (world.phase !== 'run') return
|
|
163
|
+
|
|
164
|
+
world.speed = Math.min(SPEED_MAX, world.speed + SPEED_ACCEL * dt)
|
|
165
|
+
world.score += world.speed * dt * 0.06
|
|
166
|
+
world.ground += world.speed * dt
|
|
167
|
+
world.leg += (world.speed * dt) / 24
|
|
168
|
+
|
|
169
|
+
world.vy += GRAVITY * dt
|
|
170
|
+
world.y += world.vy * dt
|
|
171
|
+
if (world.y > FLOOR_Y) {
|
|
172
|
+
world.y = FLOOR_Y
|
|
173
|
+
world.vy = 0
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
world.bubbleIn -= dt
|
|
177
|
+
if (world.bubbleIn <= 0) {
|
|
178
|
+
world.bubbles.push({
|
|
179
|
+
x: WHALE.x + 3,
|
|
180
|
+
y: world.y + 9 + Math.random() * 8,
|
|
181
|
+
r: 0.9 + Math.random() * 1.6,
|
|
182
|
+
vy: -16 - Math.random() * 14,
|
|
183
|
+
trail: true,
|
|
184
|
+
})
|
|
185
|
+
if (world.bubbles.length > 26) world.bubbles.shift()
|
|
186
|
+
world.bubbleIn = 0.16 + Math.random() * 0.12
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
world.spawnIn -= world.speed * dt
|
|
190
|
+
if (world.spawnIn <= 0) {
|
|
191
|
+
world.corals.push({ x: W + 14, h: 26 + Math.random() * 16, tint: Math.floor(Math.random() * 4) })
|
|
192
|
+
world.spawnIn = 250 + Math.random() * 200
|
|
193
|
+
}
|
|
194
|
+
for (const coral of world.corals) coral.x -= world.speed * dt
|
|
195
|
+
world.corals = world.corals.filter((coral) => coral.x > -24)
|
|
196
|
+
|
|
197
|
+
const box = whaleBox(world)
|
|
198
|
+
for (const coral of world.corals) {
|
|
199
|
+
if (!hit(box, coralBox(coral))) continue
|
|
200
|
+
world.phase = 'over'
|
|
201
|
+
world.best = Math.max(world.best, Math.floor(world.score))
|
|
202
|
+
saveBest(world.best)
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------- painting
|
|
208
|
+
const ART = {
|
|
209
|
+
coral: ['#ff7a9c', '#ffb15e', '#9b7bff', '#4fd6b8'],
|
|
210
|
+
fish: 'rgba(170, 220, 255, 0.22)',
|
|
211
|
+
bubble: 'rgba(220, 240, 255, 0.55)',
|
|
212
|
+
trailBubble: 'rgba(140, 180, 255, 0.5)',
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function drawCoral(ctx2d, coral) {
|
|
216
|
+
const x = coral.x
|
|
217
|
+
const height = coral.h
|
|
218
|
+
const base = GROUND - 1
|
|
219
|
+
const color = ART.coral[(coral.tint ?? 0) % ART.coral.length]
|
|
220
|
+
ctx2d.save()
|
|
221
|
+
ctx2d.strokeStyle = color
|
|
222
|
+
ctx2d.globalAlpha = 0.95
|
|
223
|
+
ctx2d.lineWidth = 2.6
|
|
224
|
+
ctx2d.lineCap = 'round'
|
|
225
|
+
ctx2d.beginPath()
|
|
226
|
+
ctx2d.moveTo(x, base)
|
|
227
|
+
ctx2d.quadraticCurveTo(x + 3, base - height * 0.5, x + 1, base - height)
|
|
228
|
+
ctx2d.moveTo(x + 1, base - height * 0.42)
|
|
229
|
+
ctx2d.quadraticCurveTo(x - 3, base - height * 0.55, x - 5, base - height * 0.78)
|
|
230
|
+
ctx2d.moveTo(x + 2, base - height * 0.58)
|
|
231
|
+
ctx2d.quadraticCurveTo(x + 6, base - height * 0.64, x + 7, base - height * 0.86)
|
|
232
|
+
ctx2d.stroke()
|
|
233
|
+
|
|
234
|
+
ctx2d.fillStyle = color
|
|
235
|
+
ctx2d.globalAlpha = 0.55
|
|
236
|
+
for (const [dx, dy] of [
|
|
237
|
+
[1, -height],
|
|
238
|
+
[-5, -height * 0.78],
|
|
239
|
+
[7, -height * 0.86],
|
|
240
|
+
]) {
|
|
241
|
+
ctx2d.beginPath()
|
|
242
|
+
ctx2d.arc(x + dx, base + dy, 1.7, 0, Math.PI * 2)
|
|
243
|
+
ctx2d.fill()
|
|
244
|
+
}
|
|
245
|
+
ctx2d.restore()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function drawWhale(ctx2d, world) {
|
|
249
|
+
const x = WHALE.x
|
|
250
|
+
const airborne = world.y < FLOOR_Y - 0.5
|
|
251
|
+
const bob = airborne ? 0 : Math.sin(world.leg * 1.7) * 1.1
|
|
252
|
+
const y = world.y + bob
|
|
253
|
+
const wag = Math.sin(world.leg * 1.9) * (airborne ? 0.38 : 0.22)
|
|
254
|
+
|
|
255
|
+
ctx2d.save()
|
|
256
|
+
ctx2d.translate(x + 5, y + 11)
|
|
257
|
+
ctx2d.rotate(wag)
|
|
258
|
+
ctx2d.fillStyle = '#4d6bfe'
|
|
259
|
+
ctx2d.beginPath()
|
|
260
|
+
ctx2d.moveTo(1, 0)
|
|
261
|
+
ctx2d.quadraticCurveTo(-6, -8, -10, -8)
|
|
262
|
+
ctx2d.quadraticCurveTo(-7, -2, -7, 2)
|
|
263
|
+
ctx2d.quadraticCurveTo(-10, 8, -9, 9)
|
|
264
|
+
ctx2d.quadraticCurveTo(-4, 5, 1, 3)
|
|
265
|
+
ctx2d.closePath()
|
|
266
|
+
ctx2d.fill()
|
|
267
|
+
ctx2d.restore()
|
|
268
|
+
|
|
269
|
+
if (airborne) {
|
|
270
|
+
ctx2d.fillStyle = 'rgba(225, 240, 255, 0.85)'
|
|
271
|
+
for (const [dx, dy, radius] of [
|
|
272
|
+
[22, -2, 1.8],
|
|
273
|
+
[26, -6, 1.4],
|
|
274
|
+
[19, -9, 1.1],
|
|
275
|
+
]) {
|
|
276
|
+
ctx2d.beginPath()
|
|
277
|
+
ctx2d.arc(x + dx, y + dy, radius, 0, Math.PI * 2)
|
|
278
|
+
ctx2d.fill()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const body = ctx2d.createLinearGradient(0, y, 0, y + 22)
|
|
283
|
+
body.addColorStop(0, '#8b9dff')
|
|
284
|
+
body.addColorStop(0.5, '#5b77ff')
|
|
285
|
+
body.addColorStop(1, '#3550cc')
|
|
286
|
+
ctx2d.fillStyle = body
|
|
287
|
+
ctx2d.beginPath()
|
|
288
|
+
ctx2d.ellipse(x + 18, y + 11, 16, 10.5, 0, 0, Math.PI * 2)
|
|
289
|
+
ctx2d.fill()
|
|
290
|
+
|
|
291
|
+
ctx2d.fillStyle = '#e6eeff'
|
|
292
|
+
ctx2d.globalAlpha = 0.92
|
|
293
|
+
ctx2d.beginPath()
|
|
294
|
+
ctx2d.ellipse(x + 19, y + 15.5, 10.5, 4.8, 0, 0, Math.PI * 2)
|
|
295
|
+
ctx2d.fill()
|
|
296
|
+
ctx2d.globalAlpha = 1
|
|
297
|
+
|
|
298
|
+
ctx2d.fillStyle = '#5b77ff'
|
|
299
|
+
ctx2d.beginPath()
|
|
300
|
+
ctx2d.ellipse(x + 13, y + 16, 5, 2.6, -0.5, 0, Math.PI * 2)
|
|
301
|
+
ctx2d.fill()
|
|
302
|
+
|
|
303
|
+
ctx2d.fillStyle = '#ffffff'
|
|
304
|
+
ctx2d.beginPath()
|
|
305
|
+
ctx2d.arc(x + 28, y + 8, 2.7, 0, Math.PI * 2)
|
|
306
|
+
ctx2d.fill()
|
|
307
|
+
ctx2d.fillStyle = '#0d1830'
|
|
308
|
+
ctx2d.beginPath()
|
|
309
|
+
ctx2d.arc(x + 28.9, y + 8.2, 1.35, 0, Math.PI * 2)
|
|
310
|
+
ctx2d.fill()
|
|
311
|
+
ctx2d.fillStyle = 'rgba(255, 255, 255, 0.9)'
|
|
312
|
+
ctx2d.beginPath()
|
|
313
|
+
ctx2d.arc(x + 28.4, y + 7.6, 0.6, 0, Math.PI * 2)
|
|
314
|
+
ctx2d.fill()
|
|
315
|
+
|
|
316
|
+
ctx2d.strokeStyle = 'rgba(255, 255, 255, 0.8)'
|
|
317
|
+
ctx2d.lineWidth = 1.1
|
|
318
|
+
ctx2d.lineCap = 'round'
|
|
319
|
+
ctx2d.beginPath()
|
|
320
|
+
ctx2d.arc(x + 29, y + 11.5, 3.4, 0.15 * Math.PI, 0.55 * Math.PI)
|
|
321
|
+
ctx2d.stroke()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function pad(value) {
|
|
325
|
+
return String(Math.floor(value)).padStart(5, '0')
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function paintWorld(ctx2d, world, scale) {
|
|
329
|
+
ctx2d.setTransform(scale, 0, 0, scale, 0, 0)
|
|
330
|
+
ctx2d.clearRect(0, 0, W, H)
|
|
331
|
+
|
|
332
|
+
const water = ctx2d.createLinearGradient(0, 0, 0, GROUND)
|
|
333
|
+
water.addColorStop(0, '#0a1a45')
|
|
334
|
+
water.addColorStop(0.55, '#0e4f83')
|
|
335
|
+
water.addColorStop(1, '#0f7196')
|
|
336
|
+
ctx2d.fillStyle = water
|
|
337
|
+
ctx2d.fillRect(0, 0, W, GROUND)
|
|
338
|
+
|
|
339
|
+
ctx2d.fillStyle = 'rgba(255, 255, 255, 0.05)'
|
|
340
|
+
for (const shaft of [34, 118, 194]) {
|
|
341
|
+
ctx2d.beginPath()
|
|
342
|
+
ctx2d.moveTo(shaft, 0)
|
|
343
|
+
ctx2d.lineTo(shaft + 26, 0)
|
|
344
|
+
ctx2d.lineTo(shaft + 74, GROUND)
|
|
345
|
+
ctx2d.lineTo(shaft + 50, GROUND)
|
|
346
|
+
ctx2d.closePath()
|
|
347
|
+
ctx2d.fill()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (const fish of world.fish) {
|
|
351
|
+
ctx2d.fillStyle = ART.fish
|
|
352
|
+
ctx2d.beginPath()
|
|
353
|
+
ctx2d.ellipse(fish.x, fish.y, 4.2, 2.2, 0, 0, Math.PI * 2)
|
|
354
|
+
ctx2d.fill()
|
|
355
|
+
ctx2d.beginPath()
|
|
356
|
+
ctx2d.moveTo(fish.x - 3.4, fish.y)
|
|
357
|
+
ctx2d.lineTo(fish.x - 7.6, fish.y - 2.4)
|
|
358
|
+
ctx2d.lineTo(fish.x - 7.6, fish.y + 2.4)
|
|
359
|
+
ctx2d.closePath()
|
|
360
|
+
ctx2d.fill()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const sand = ctx2d.createLinearGradient(0, GROUND, 0, H)
|
|
364
|
+
sand.addColorStop(0, '#e8b877')
|
|
365
|
+
sand.addColorStop(1, '#a4683a')
|
|
366
|
+
ctx2d.fillStyle = sand
|
|
367
|
+
ctx2d.fillRect(0, GROUND, W, H - GROUND)
|
|
368
|
+
ctx2d.fillStyle = 'rgba(255, 255, 255, 0.35)'
|
|
369
|
+
ctx2d.fillRect(0, GROUND, W, 1)
|
|
370
|
+
ctx2d.fillStyle = 'rgba(120, 70, 30, 0.35)'
|
|
371
|
+
for (let x = -((world.ground * 0.6) % 40); x < W; x += 40) {
|
|
372
|
+
ctx2d.fillRect(x + 6, GROUND + 7, 2, 2)
|
|
373
|
+
ctx2d.fillRect(x + 22, GROUND + 13, 2, 2)
|
|
374
|
+
ctx2d.fillRect(x + 33, GROUND + 5, 2, 2)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const coral of world.corals) drawCoral(ctx2d, coral)
|
|
378
|
+
drawWhale(ctx2d, world)
|
|
379
|
+
|
|
380
|
+
for (const bubble of world.bubbles) {
|
|
381
|
+
ctx2d.fillStyle = bubble.trail ? ART.trailBubble : ART.bubble
|
|
382
|
+
ctx2d.beginPath()
|
|
383
|
+
ctx2d.arc(bubble.x, bubble.y, bubble.r, 0, Math.PI * 2)
|
|
384
|
+
ctx2d.fill()
|
|
385
|
+
if (!bubble.trail) {
|
|
386
|
+
ctx2d.fillStyle = 'rgba(255, 255, 255, 0.8)'
|
|
387
|
+
ctx2d.beginPath()
|
|
388
|
+
ctx2d.arc(bubble.x - bubble.r * 0.3, bubble.y - bubble.r * 0.3, bubble.r * 0.3, 0, Math.PI * 2)
|
|
389
|
+
ctx2d.fill()
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
ctx2d.save()
|
|
394
|
+
ctx2d.shadowColor = 'rgba(3, 10, 28, 0.6)'
|
|
395
|
+
ctx2d.shadowBlur = 4
|
|
396
|
+
ctx2d.fillStyle = '#ffffff'
|
|
397
|
+
ctx2d.font = '600 11px ui-monospace, SFMono-Regular, Menlo, monospace'
|
|
398
|
+
ctx2d.textAlign = 'right'
|
|
399
|
+
ctx2d.fillText('HI ' + pad(world.best) + ' ' + pad(world.score), W - 10, 17)
|
|
400
|
+
ctx2d.restore()
|
|
401
|
+
|
|
402
|
+
if (world.phase === 'over') {
|
|
403
|
+
ctx2d.fillStyle = 'rgba(5, 13, 34, 0.58)'
|
|
404
|
+
ctx2d.fillRect(0, 0, W, H)
|
|
405
|
+
ctx2d.save()
|
|
406
|
+
ctx2d.shadowColor = 'rgba(0, 0, 0, 0.5)'
|
|
407
|
+
ctx2d.shadowBlur = 6
|
|
408
|
+
ctx2d.fillStyle = '#ffffff'
|
|
409
|
+
ctx2d.font = '700 13px ui-monospace, SFMono-Regular, Menlo, monospace'
|
|
410
|
+
ctx2d.textAlign = 'center'
|
|
411
|
+
ctx2d.fillText('GAME OVER', W / 2, 58)
|
|
412
|
+
ctx2d.restore()
|
|
413
|
+
}
|
|
414
|
+
ctx2d.textAlign = 'left'
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------- react
|
|
418
|
+
function useTranslate(locale) {
|
|
419
|
+
React.useSyncExternalStore(
|
|
420
|
+
(listener) => locale.subscribe(listener),
|
|
421
|
+
() => locale.getSnapshot().revision,
|
|
422
|
+
)
|
|
423
|
+
return React.useMemo(() => locale.bind(NS), [locale])
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function useEnabled() {
|
|
427
|
+
return React.useSyncExternalStore(enabledStore.subscribe, enabledStore.get)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function RexDockEntry(props) {
|
|
431
|
+
const status = props.useSessionStatus((all) => all.get(props.sessionId))
|
|
432
|
+
const enabled = useEnabled()
|
|
433
|
+
const waiting = enabled && status !== undefined && status.running === true && status.pendingInteraction === undefined
|
|
434
|
+
const t = useTranslate(props.locale)
|
|
435
|
+
|
|
436
|
+
const canvasRef = React.useRef(null)
|
|
437
|
+
const loopRef = React.useRef(null)
|
|
438
|
+
const [pos, setPos] = React.useState(undefined)
|
|
439
|
+
const dragRef = React.useRef(null)
|
|
440
|
+
|
|
441
|
+
const finishDrag = () => {
|
|
442
|
+
const drag = dragRef.current
|
|
443
|
+
if (!drag) return
|
|
444
|
+
dragRef.current = null
|
|
445
|
+
window.removeEventListener('pointermove', drag.move)
|
|
446
|
+
window.removeEventListener('pointerup', drag.end)
|
|
447
|
+
window.removeEventListener('pointercancel', drag.end)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const resetPos = () => setPos(undefined)
|
|
451
|
+
|
|
452
|
+
const startDrag = (event) => {
|
|
453
|
+
if (event.button !== 0) return
|
|
454
|
+
const rect = event.currentTarget.parentElement.getBoundingClientRect()
|
|
455
|
+
const drag = { dx: event.clientX - rect.left, dy: event.clientY - rect.top, w: rect.width, h: rect.height }
|
|
456
|
+
drag.move = (moveEvent) => {
|
|
457
|
+
const next = {
|
|
458
|
+
x: Math.max(0, Math.min(moveEvent.clientX - drag.dx, window.innerWidth - drag.w)),
|
|
459
|
+
y: Math.max(0, Math.min(moveEvent.clientY - drag.dy, window.innerHeight - drag.h)),
|
|
460
|
+
}
|
|
461
|
+
setPos(next)
|
|
462
|
+
}
|
|
463
|
+
drag.end = () => finishDrag()
|
|
464
|
+
dragRef.current = drag
|
|
465
|
+
event.preventDefault()
|
|
466
|
+
window.addEventListener('pointermove', drag.move)
|
|
467
|
+
window.addEventListener('pointerup', drag.end)
|
|
468
|
+
window.addEventListener('pointercancel', drag.end)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
React.useEffect(() => {
|
|
472
|
+
const canvas = canvasRef.current
|
|
473
|
+
if (!canvas || typeof canvas.getContext !== 'function') return undefined
|
|
474
|
+
const ctx2d = canvas.getContext('2d')
|
|
475
|
+
const world = createWorld()
|
|
476
|
+
let raf = 0
|
|
477
|
+
let last = 0
|
|
478
|
+
let active = false
|
|
479
|
+
let scale = 1
|
|
480
|
+
|
|
481
|
+
const resize = () => {
|
|
482
|
+
const width = canvas.getBoundingClientRect().width || 480
|
|
483
|
+
const ratio = Math.min(2, window.devicePixelRatio || 1)
|
|
484
|
+
canvas.width = Math.round(width * ratio)
|
|
485
|
+
canvas.height = Math.round((width / 2) * ratio)
|
|
486
|
+
scale = canvas.width / W
|
|
487
|
+
}
|
|
488
|
+
const paint = () => paintWorld(ctx2d, world, scale)
|
|
489
|
+
const frame = (now) => {
|
|
490
|
+
if (!active) return
|
|
491
|
+
const dt = Math.min(0.05, Math.max(0, (now - last) / 1000))
|
|
492
|
+
last = now
|
|
493
|
+
stepWorld(world, dt)
|
|
494
|
+
paint()
|
|
495
|
+
raf = requestAnimationFrame(frame)
|
|
496
|
+
}
|
|
497
|
+
const stop = () => {
|
|
498
|
+
active = false
|
|
499
|
+
cancelAnimationFrame(raf)
|
|
500
|
+
}
|
|
501
|
+
const onResize = () => {
|
|
502
|
+
if (!active) return
|
|
503
|
+
resize()
|
|
504
|
+
paint()
|
|
505
|
+
}
|
|
506
|
+
window.addEventListener('resize', onResize)
|
|
507
|
+
|
|
508
|
+
loopRef.current = {
|
|
509
|
+
show() {
|
|
510
|
+
if (world.phase === 'ready' || world.phase === 'over') {
|
|
511
|
+
resetWorld(world)
|
|
512
|
+
world.phase = 'run'
|
|
513
|
+
}
|
|
514
|
+
resize()
|
|
515
|
+
paint()
|
|
516
|
+
if (active) return
|
|
517
|
+
active = true
|
|
518
|
+
last = performance.now()
|
|
519
|
+
raf = requestAnimationFrame(frame)
|
|
520
|
+
},
|
|
521
|
+
hide: stop,
|
|
522
|
+
tap() {
|
|
523
|
+
jump(world)
|
|
524
|
+
if (!active) paint()
|
|
525
|
+
},
|
|
526
|
+
dispose: stop,
|
|
527
|
+
}
|
|
528
|
+
return () => {
|
|
529
|
+
window.removeEventListener('resize', onResize)
|
|
530
|
+
stop()
|
|
531
|
+
loopRef.current = null
|
|
532
|
+
}
|
|
533
|
+
}, [])
|
|
534
|
+
|
|
535
|
+
React.useEffect(() => () => finishDrag(), [])
|
|
536
|
+
|
|
537
|
+
React.useEffect(() => {
|
|
538
|
+
const loop = loopRef.current
|
|
539
|
+
if (!loop) return
|
|
540
|
+
if (waiting) {
|
|
541
|
+
setPos(undefined)
|
|
542
|
+
loop.show()
|
|
543
|
+
} else {
|
|
544
|
+
loop.hide()
|
|
545
|
+
}
|
|
546
|
+
}, [waiting])
|
|
547
|
+
|
|
548
|
+
React.useEffect(() => {
|
|
549
|
+
if (!waiting) return undefined
|
|
550
|
+
const onKey = (event) => {
|
|
551
|
+
if (event.isComposing || event.keyCode === 229) return
|
|
552
|
+
if (event.code !== 'Space' && event.key !== 'ArrowUp' && event.key !== 'w' && event.key !== 'W') return
|
|
553
|
+
// Shift+Space stays a literal space so a message can still be typed mid-turn.
|
|
554
|
+
if (event.shiftKey) return
|
|
555
|
+
event.preventDefault()
|
|
556
|
+
event.stopPropagation()
|
|
557
|
+
loopRef.current?.tap()
|
|
558
|
+
}
|
|
559
|
+
window.addEventListener('keydown', onKey, true)
|
|
560
|
+
return () => window.removeEventListener('keydown', onKey, true)
|
|
561
|
+
}, [waiting])
|
|
562
|
+
|
|
563
|
+
const panelStyle = { display: waiting ? 'block' : 'none' }
|
|
564
|
+
if (pos) {
|
|
565
|
+
panelStyle.left = pos.x + 'px'
|
|
566
|
+
panelStyle.top = pos.y + 'px'
|
|
567
|
+
panelStyle.transform = 'none'
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return ReactDOM.createPortal(
|
|
571
|
+
h(
|
|
572
|
+
'div',
|
|
573
|
+
{ className: 'dre-wrap', style: panelStyle, 'aria-hidden': waiting ? undefined : 'true' },
|
|
574
|
+
h('canvas', {
|
|
575
|
+
ref: canvasRef,
|
|
576
|
+
className: 'dre-canvas',
|
|
577
|
+
role: 'img',
|
|
578
|
+
'aria-label': t('hint'),
|
|
579
|
+
onPointerDown: (event) => {
|
|
580
|
+
event.preventDefault()
|
|
581
|
+
loopRef.current?.tap()
|
|
582
|
+
},
|
|
583
|
+
}),
|
|
584
|
+
h(
|
|
585
|
+
'p',
|
|
586
|
+
{
|
|
587
|
+
className: 'dre-hint',
|
|
588
|
+
title: t('reset'),
|
|
589
|
+
onPointerDown: startDrag,
|
|
590
|
+
onDoubleClick: resetPos,
|
|
591
|
+
},
|
|
592
|
+
t('hint'),
|
|
593
|
+
),
|
|
594
|
+
),
|
|
595
|
+
document.body,
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function RexToggle(props) {
|
|
600
|
+
const enabled = useEnabled()
|
|
601
|
+
const t = useTranslate(props.locale)
|
|
602
|
+
const label = enabled ? t('disable') : t('enable')
|
|
603
|
+
return h(
|
|
604
|
+
'button',
|
|
605
|
+
{
|
|
606
|
+
type: 'button',
|
|
607
|
+
className: enabled ? 'dre-toggle dre-toggle-on' : 'dre-toggle',
|
|
608
|
+
title: label,
|
|
609
|
+
'aria-label': label,
|
|
610
|
+
'aria-pressed': enabled,
|
|
611
|
+
onClick: () => enabledStore.set(!enabled),
|
|
612
|
+
},
|
|
613
|
+
h(
|
|
614
|
+
'svg',
|
|
615
|
+
{ viewBox: '0 0 24 24', width: 17, height: 17, 'aria-hidden': true, fill: 'currentColor' },
|
|
616
|
+
h('ellipse', { cx: 11, cy: 14, rx: 7, ry: 4.6 }),
|
|
617
|
+
h('path', { d: 'M4.4 12.6 1.6 9.8c1.9-.3 3.7.4 4.9 1.9z' }),
|
|
618
|
+
h('path', { d: 'M10.4 9.6c.7-1.5 1.8-2.4 3.3-2.6-.5 1.1-1.2 1.9-2.3 2.5z' }),
|
|
619
|
+
h('circle', { cx: 16.4, cy: 6.4, r: 1.5 }),
|
|
620
|
+
h('circle', { cx: 19.2, cy: 4.2, r: 1 }),
|
|
621
|
+
),
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const CSS = [
|
|
626
|
+
'.dre-wrap{position:fixed;top:88px;left:50%;transform:translateX(-50%);z-index:100;width:min(' + PANEL_WIDTH + 'px,calc(100vw - 40px));border:1px solid rgba(120,150,220,.28);border-radius:18px;background:#071127;box-shadow:var(--dsw-shadow-lv3,0 12px 32px rgba(0,0,0,.24));overflow:hidden;user-select:none;-webkit-user-select:none;animation:dre-fade .18s ease-out}',
|
|
627
|
+
'.dre-wrap::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#ff7a9c,#4d6bfe 45%,#4fd6b8);opacity:.9}',
|
|
628
|
+
'.dre-canvas{display:block;width:100%;height:auto;aspect-ratio:2/1;cursor:pointer;touch-action:manipulation}',
|
|
629
|
+
'.dre-hint{display:flex;align-items:center;justify-content:center;gap:8px;margin:0;padding:8px 12px 9px;font-size:12px;letter-spacing:.02em;line-height:1.3;color:#a9c2ee;background:linear-gradient(180deg,#0b1c3d,#071127);border-top:1px solid rgba(120,150,220,.16);cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}',
|
|
630
|
+
'.dre-hint:active{cursor:grabbing}',
|
|
631
|
+
'.dre-hint::before{content:"";width:16px;height:10px;flex:none;background:radial-gradient(circle,currentColor 1px,transparent 1.4px) 0 0/5px 5px repeat;opacity:.5}',
|
|
632
|
+
'.dre-toggle{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
|
|
633
|
+
'.dre-toggle:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14));color:var(--dsw-alias-label-primary)}',
|
|
634
|
+
'.dre-toggle-on{color:var(--dsw-alias-brand-primary)}',
|
|
635
|
+
'.dre-toggle-on:hover{color:var(--dsw-alias-brand-primary)}',
|
|
636
|
+
'.dre-toggle svg{display:block}',
|
|
637
|
+
'@keyframes dre-fade{from{opacity:0}to{opacity:1}}',
|
|
638
|
+
'@media (prefers-reduced-motion:reduce){.dre-wrap{animation:none}}',
|
|
639
|
+
].join('')
|
|
640
|
+
|
|
641
|
+
if (typeof document !== 'undefined') {
|
|
642
|
+
const style = document.createElement('style')
|
|
643
|
+
style.textContent = CSS
|
|
644
|
+
document.head.append(style)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Run the pure game core in Node (see test/selfcheck.mjs); absent in the browser.
|
|
648
|
+
if (typeof globalThis.__REX_SELFTEST__ === 'function') {
|
|
649
|
+
globalThis.__REX_SELFTEST__({ W, H, GROUND, FLOOR_Y, createWorld, resetWorld, jump, stepWorld, whaleBox, coralBox, hit, paintWorld })
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
inject: ['slots', 'locale'],
|
|
654
|
+
apply(ctx) {
|
|
655
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'rex-game: locale dictionary')
|
|
656
|
+
ctx.slots.inject('conversation.input.left', () =>
|
|
657
|
+
ctx.slots.register(
|
|
658
|
+
{
|
|
659
|
+
name: 'conversation.input.left',
|
|
660
|
+
id: 'whale-toggle',
|
|
661
|
+
order: 30,
|
|
662
|
+
inject: () => ({ locale: ctx.locale }),
|
|
663
|
+
},
|
|
664
|
+
RexToggle,
|
|
665
|
+
),
|
|
666
|
+
)
|
|
667
|
+
ctx.slots.inject('conversation.composer.dock', () =>
|
|
668
|
+
ctx.slots.register(
|
|
669
|
+
{
|
|
670
|
+
name: 'conversation.composer.dock',
|
|
671
|
+
id: 'whale-break',
|
|
672
|
+
order: 40,
|
|
673
|
+
inject: () => ({ locale: ctx.locale }),
|
|
674
|
+
},
|
|
675
|
+
RexDockEntry,
|
|
676
|
+
),
|
|
677
|
+
)
|
|
678
|
+
},
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
})
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#4d6bfe"/>
|
|
3
|
+
<g fill="#ffffff">
|
|
4
|
+
<path d="M16 41c0-6 5-11 12-11h15c5 0 8 2 9 6 1 3 3 5 5 5-2 4-6 6-11 6H25c-5 0-9-2-9-6z"/>
|
|
5
|
+
<path d="M17 38c-4-1-7-4-8-8 4 0 8 2 10 5l-2 3z"/>
|
|
6
|
+
<circle cx="30" cy="21" r="2.6"/>
|
|
7
|
+
<circle cx="35" cy="15" r="2"/>
|
|
8
|
+
<circle cx="27" cy="14" r="1.6"/>
|
|
9
|
+
</g>
|
|
10
|
+
<circle cx="44" cy="35" r="2.4" fill="#10203a"/>
|
|
11
|
+
</svg>
|
package/index.js
ADDED
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-whale-break",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A DeepSeek whale mini-game for DeepSeek Harness: it pops up while a turn is running and hides when the turn finishes or waits for you.",
|
|
6
|
+
"author": "夏悸",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/btboys/dsh-rex-game#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/btboys/dsh-rex-game.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/btboys/dsh-rex-game/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./index.js",
|
|
18
|
+
"./client": "./client.js",
|
|
19
|
+
"./locale/*.json": "./locale/*.json",
|
|
20
|
+
"./icon": "./icon.svg",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"client.js",
|
|
26
|
+
"cordis.patch.yml",
|
|
27
|
+
"locale/*.json",
|
|
28
|
+
"icon.svg",
|
|
29
|
+
"assets/*.png"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"dsh": ">=0.2.2-rc.1",
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"dsh",
|
|
37
|
+
"dsh-plugin",
|
|
38
|
+
"deepseek-harness",
|
|
39
|
+
"cordis",
|
|
40
|
+
"game",
|
|
41
|
+
"mini-game",
|
|
42
|
+
"whale",
|
|
43
|
+
"dino"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "node test/selfcheck.mjs",
|
|
47
|
+
"pack:check": "npm pack --dry-run"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"platform": "web",
|
|
55
|
+
"immediately": true,
|
|
56
|
+
"inject": [
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|