dsh-code 1.0.4 → 1.0.6
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/README.en.md +287 -286
- package/README.md +16 -13
- package/bin/deepseek.mjs +336 -11
- package/cordis.patch.yml +26 -18
- package/lib/index.mjs +1310 -439
- package/lib/types/app.d.ts +25 -6
- package/lib/types/attachments.d.ts +36 -4
- package/lib/types/git-workflow.d.ts +7 -2
- package/lib/types/history.d.ts +18 -11
- package/lib/types/index.d.ts +11 -2
- package/lib/types/presets.d.ts +4 -1
- package/lib/types/provider-settings.d.ts +6 -11
- package/lib/types/questions.d.ts +16 -12
- package/lib/types/render/animations.d.ts +74 -7
- package/lib/types/render/export.d.ts +0 -6
- package/lib/types/render/fuzzy.d.ts +21 -0
- package/lib/types/render/projection.d.ts +47 -5
- package/lib/types/session-directory.d.ts +48 -13
- package/lib/types/settings-file.d.ts +8 -0
- package/lib/types/store.d.ts +3 -0
- package/package.json +168 -159
- package/src/app.ts +480 -199
- package/src/attachments.ts +110 -11
- package/src/commands.ts +35 -5
- package/src/git-workflow.ts +29 -10
- package/src/history.ts +22 -13
- package/src/index.ts +1868 -1752
- package/src/internals.ts +61 -40
- package/src/permissions.ts +1 -1
- package/src/presets.ts +19 -6
- package/src/provider-settings.ts +12 -12
- package/src/questions.ts +57 -74
- package/src/render/animations.ts +606 -403
- package/src/render/export.ts +20 -10
- package/src/render/fuzzy.ts +83 -0
- package/src/render/projection.ts +1833 -1620
- package/src/session-directory.ts +94 -16
- package/src/settings-file.ts +38 -6
- package/src/skills.ts +23 -9
- package/src/store.ts +39 -1
- package/src/subagents.ts +26 -3
package/src/render/animations.ts
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* Terminal animation helpers derived from the web design language:
|
|
3
3
|
* thinking uses Codex's slow shimmer sweep, the busy composer marker uses the
|
|
4
4
|
* original braille chase, and the streaming caret blink is the Claude-Code
|
|
5
5
|
* convention.
|
|
6
|
-
*
|
|
7
|
-
* The DeepSeek model-switch easter egg ports Codex's effort-ignition "Wave"
|
|
8
|
-
* style (`codex-rs/tui/src/bottom_pane/effort_ignition_styles.rs`): switching
|
|
9
|
-
* INTO an official DeepSeek route sweeps a blue wave across the composer's
|
|
10
|
-
* input row — one column per cell, `backgroundColor` = the sampled wave
|
|
11
|
-
* color — then, on the deepseek (Ultra-equivalent) tier, drops the `· ✦ ✧`
|
|
12
|
-
* sparkle sequence into the rightmost blank cell before fading. The prompt
|
|
13
|
-
* marker keeps the tier accent afterwards (persistent, like Codex's prompt
|
|
14
|
-
* charge). Pure functions only — the Ink layer owns timers and colors.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
*
|
|
7
|
+
* The DeepSeek model-switch easter egg ports Codex's effort-ignition "Wave"
|
|
8
|
+
* style (`codex-rs/tui/src/bottom_pane/effort_ignition_styles.rs`): switching
|
|
9
|
+
* INTO an official DeepSeek route sweeps a blue wave across the composer's
|
|
10
|
+
* input row — one column per cell, `backgroundColor` = the sampled wave
|
|
11
|
+
* color — then, on the deepseek (Ultra-equivalent) tier, drops the `· ✦ ✧`
|
|
12
|
+
* sparkle sequence into the rightmost blank cell before fading. The prompt
|
|
13
|
+
* marker keeps the tier accent afterwards (persistent, like Codex's prompt
|
|
14
|
+
* charge). Pure functions only — the Ink layer owns timers and colors.
|
|
15
|
+
*
|
|
16
|
+
* Wave and Pulse deliberately extend the Codex port after in-terminal
|
|
17
|
+
* testing: the per-row phase cascade was removed (Codex tints each column
|
|
18
|
+
* across the whole band), Wave became a WATER SURFACE — one continuous sine
|
|
19
|
+
* swell spanning the band, mirror-symmetric about the center column, its
|
|
20
|
+
* crests flowing outward from the center with a symmetric fade envelope
|
|
21
|
+
* (the deepseek tier adds one faster harmonic crossing it), painted with
|
|
22
|
+
* Aurora's recipe: wide soft gradients, mirrored second-hue mixing, and a
|
|
23
|
+
* low alpha cap — no hard core line — while Pulse became true
|
|
24
|
+
* two-dimensional, cell-aspect-corrected detonations: soft wide rings whose
|
|
25
|
+
* color grades across their width, expanding outward through a symmetric
|
|
26
|
+
* fade envelope and each trailing an echo ripple in the next blue. Aurora
|
|
27
|
+
* keeps Codex's geometry verbatim.
|
|
28
|
+
*
|
|
29
|
+
* @module @deepseek-ai/dsh-code/render/animations
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { RgbTriple } from '../theme.ts'
|
|
33
|
+
|
|
21
34
|
/** Cadence for the original busy braille chase (8 frames × 125ms = 1s). */
|
|
22
35
|
export const BUSY_CHASE_TICK_MS = 125
|
|
23
36
|
|
|
@@ -78,390 +91,580 @@ export function deepDivingSparkColor(tick: number, base: RgbTriple, highlight: R
|
|
|
78
91
|
return blendRgb(highlight, base, deepDivingSparkIntensity(tick))
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
/** Caret
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
*
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
*
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
*
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
*
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
94
|
+
/** Caret blink cadence: one blink step (on or off) per tick. */
|
|
95
|
+
export const CARET_BLINK_TICK_MS = 530
|
|
96
|
+
|
|
97
|
+
/** Caret visibility: half the ticks on, half off (530ms blink). */
|
|
98
|
+
export function caretVisible(tick: number): boolean {
|
|
99
|
+
return tick % 2 === 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The one-shot DeepSeek model-switch easter egg: when the status bar model
|
|
104
|
+
* label switches to an official DeepSeek route, the composer's input row
|
|
105
|
+
* plays Codex's effort-ignition "Wave" — a blue crest sweeping the content
|
|
106
|
+
* row column by column (background tint ≤ 0.55 under the draft), plus the
|
|
107
|
+
* Ultra-style `· ✦ ✧` sparkles on the deepseek tier — and the prompt marker
|
|
108
|
+
* keeps the tier accent afterwards. The Ink layer owns the timer and reads
|
|
109
|
+
* the ACTIVE palette anchors (`getPalette`); everything below is pure
|
|
110
|
+
* interpolation over the colors it is given.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
/** Frame cadence of the DeepSeek wave: Codex's IGNITION_FRAME_TICK (33ms ≈ 30fps). */
|
|
114
|
+
export const DEEPSEEK_WAVE_TICK_MS = 33
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The DeepSeek wave tiers. The concept maps Codex's reasoning tiers to
|
|
118
|
+
* model ids: `flash` runs the Max parameters, `deepseek` (pro models) runs
|
|
119
|
+
* the Ultra parameters (dual band + tail sparkles on the Wave style).
|
|
120
|
+
* `unknown` is the "Into the Unknown" variant: it reuses the deepseek tier's
|
|
121
|
+
* exact parameters (dual band, durations, sparkles) for NON-DeepSeek models
|
|
122
|
+
* running a reasoning effort above high — the wordmark renders differently
|
|
123
|
+
* but the motion is identical.
|
|
124
|
+
*/
|
|
125
|
+
export type DeepseekWaveTier = 'flash' | 'deepseek' | 'unknown'
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The three ignition styles — Codex `IgnitionStyle`: a traveling crest
|
|
129
|
+
* (Wave), a drifting multi-hue band (Aurora), and an expanding ring (Pulse).
|
|
130
|
+
* One style is picked at random per trigger and never repeats the previous.
|
|
131
|
+
*/
|
|
132
|
+
export type DeepseekWaveStyle = 'wave' | 'aurora' | 'pulse'
|
|
133
|
+
|
|
134
|
+
/** All styles in canonical order, for random selection. */
|
|
135
|
+
const DEEPSEEK_WAVE_STYLES: readonly DeepseekWaveStyle[] = ['wave', 'aurora', 'pulse']
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The water surface: ONE continuous sine line spanning the whole band,
|
|
139
|
+
* mirror-symmetric about the center column, its crests flowing OUTWARD from
|
|
140
|
+
* the center (phase k·|x − center| − ω·t). No sweep window, no return trip —
|
|
141
|
+
* the surface fades in, flows, and fades out, symmetric in both space and
|
|
142
|
+
* time. The deepseek tier adds one faster, finer HARMONIC line whose crests
|
|
143
|
+
* cross the fundamental's: interleaved richness with both lines still
|
|
144
|
+
* symmetric and still only ever flowing outward.
|
|
145
|
+
*/
|
|
146
|
+
export const WAVE_SURFACE_AMPLITUDE = 0.8
|
|
147
|
+
export const WAVE_SURFACE_HARMONIC = 0.45
|
|
148
|
+
export const WAVE_SURFACE_WAVELENGTH = 40
|
|
149
|
+
export const WAVE_SURFACE_OMEGA = 9
|
|
150
|
+
/** Vertical thickness in lane units — Aurora-wide: soft gradients, no hard edges. */
|
|
151
|
+
export const WAVE_SURFACE_THICKNESS = 1.2
|
|
152
|
+
/**
|
|
153
|
+
* The mirrored second-hue profile: the space BELOW the surface carries a
|
|
154
|
+
* second blue at this strength, so color (not just brightness) varies
|
|
155
|
+
* continuously across the wave — Aurora-style hue mixing instead of a
|
|
156
|
+
* single flat tint.
|
|
157
|
+
*/
|
|
158
|
+
export const WAVE_SURFACE_MIRROR = 0.6
|
|
159
|
+
/** Aurora-style soft alpha: low gain, capped well under the pulse ring's. */
|
|
160
|
+
export const WAVE_SURFACE_ALPHA_GAIN = 0.45
|
|
161
|
+
export const WAVE_SURFACE_ALPHA_CAP = 0.68
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Pulse ring geometry, softened to the Wave standard: a WIDE band
|
|
165
|
+
* (half-width 5.5) with a moderate peak riding the radius — all inside the
|
|
166
|
+
* hue blend, no hard white line — and an inner profile one hue over at a
|
|
167
|
+
* slightly smaller radius, so the ring's color grades continuously across
|
|
168
|
+
* its width (the radial analog of the water surface's mirrored hues).
|
|
169
|
+
*/
|
|
170
|
+
const PULSE_HALF_WIDTH = 5.5
|
|
171
|
+
const PULSE_PEAK_HALF_WIDTH = 1.8
|
|
172
|
+
const PULSE_PEAK_GAIN = 0.35
|
|
173
|
+
const PULSE_INNER_OFFSET = 2.5
|
|
174
|
+
const PULSE_INNER_STRENGTH = 0.6
|
|
175
|
+
/** The inner edge of each ring carries the tier's third blue. */
|
|
176
|
+
const PULSE_INNER_HUE = 2
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The trailing echo ripple: every pulse ring drags a second, weaker ring at
|
|
180
|
+
* a fraction of its radius in the NEXT hue of the tier's blues, fading in a
|
|
181
|
+
* little after the primary so the center hole opens first.
|
|
182
|
+
*/
|
|
183
|
+
const PULSE_ECHO_RADIUS = 0.7
|
|
184
|
+
const PULSE_ECHO_STRENGTH = 0.65
|
|
185
|
+
const PULSE_ECHO_DELAY = 0.12
|
|
186
|
+
|
|
187
|
+
/** Aurora-grade soft alpha for the detonation — a notch above the swell. */
|
|
188
|
+
export const PULSE_ALPHA_GAIN = 0.45
|
|
189
|
+
export const PULSE_ALPHA_CAP = 0.72
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Terminal cell aspect (row height ÷ column width, ≈2.2 for common fonts).
|
|
193
|
+
* A ring computed in raw cell units looks vertically squashed; weighting row
|
|
194
|
+
* distance by the aspect makes the Pulse ring appear circular on screen.
|
|
195
|
+
*/
|
|
196
|
+
const PULSE_ROW_ASPECT = 2.2
|
|
197
|
+
|
|
198
|
+
/** Sparkle start and frame cadence — Codex SPARK_START / SPARK_FRAME. */
|
|
199
|
+
const SPARK_START_MS = 900
|
|
200
|
+
const SPARK_FRAME_MS = 100
|
|
201
|
+
|
|
202
|
+
/** Sparkle glyphs in frame order — Codex SPARK_GLYPHS (`· ✦ ✧`). */
|
|
203
|
+
export const SPARK_GLYPHS = ['·', '✦', '✧'] as const
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Band tables — Codex `bands(style, tier)`. Each entry is a triple whose
|
|
207
|
+
* meaning depends on the style: Wave/Pulse use `(launch, travel, strength)`;
|
|
208
|
+
* Aurora uses `(speed, phase, hueIndex)`.
|
|
209
|
+
*/
|
|
210
|
+
export type DeepseekWaveBand = readonly [number, number, number]
|
|
211
|
+
export const DEEPSEEK_WAVE_BANDS: Readonly<Record<DeepseekWaveStyle, Readonly<Record<DeepseekWaveTier, readonly DeepseekWaveBand[]>>>> = {
|
|
212
|
+
wave: {
|
|
213
|
+
// Wave-Max: one band sweeping 0.10s..0.85s.
|
|
214
|
+
flash: [[0.10, 0.75, 1.0]],
|
|
215
|
+
// Wave-Ultra: two offset bands for a richer crest.
|
|
216
|
+
deepseek: [[0.10, 0.70, 1.0], [0.35, 0.55, 1.0]],
|
|
217
|
+
// Into the Unknown reuses the Ultra parameters verbatim.
|
|
218
|
+
unknown: [[0.10, 0.70, 1.0], [0.35, 0.55, 1.0]],
|
|
219
|
+
},
|
|
220
|
+
aurora: {
|
|
221
|
+
// Aurora-Max: two drifting bands (hues 0 and 1).
|
|
222
|
+
flash: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0]],
|
|
223
|
+
// Aurora-Ultra: a third band adds hue 2.
|
|
224
|
+
deepseek: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0], [0.75, 0.35, 2.0]],
|
|
225
|
+
unknown: [[0.35, 0.15, 0.0], [-0.50, 0.60, 1.0], [0.75, 0.35, 2.0]],
|
|
226
|
+
},
|
|
227
|
+
pulse: {
|
|
228
|
+
// Pulse-Max: one expanding ring.
|
|
229
|
+
flash: [[0.10, 0.60, 1.0]],
|
|
230
|
+
// Pulse-Ultra: two rings (inner weaker, outer stronger).
|
|
231
|
+
deepseek: [[0.10, 0.55, 0.8], [0.45, 0.55, 1.1]],
|
|
232
|
+
unknown: [[0.10, 0.55, 0.8], [0.45, 0.55, 1.1]],
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Extra display time applied to every Codex ignition style. */
|
|
237
|
+
const DEEPSEEK_WAVE_DURATION_EXTENSION_MS = 200
|
|
238
|
+
|
|
239
|
+
/** Original Codex duration used as the animation's sampling timeline. */
|
|
240
|
+
function deepseekWaveBaseDuration(tier: DeepseekWaveTier, style: DeepseekWaveStyle): number {
|
|
241
|
+
// The unknown tier reuses the deepseek (pro) durations exactly.
|
|
242
|
+
const pro = tier === 'deepseek' || tier === 'unknown'
|
|
243
|
+
switch (style) {
|
|
244
|
+
case 'aurora': return pro ? 1600 : 1300
|
|
245
|
+
case 'pulse': return pro ? 1250 : 900
|
|
246
|
+
case 'wave': return pro ? 1300 : 1000
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Total visible duration: the Codex ignition duration plus 200ms so its motion
|
|
252
|
+
* remains readable in a busy terminal.
|
|
253
|
+
* @param tier - the active wave tier.
|
|
254
|
+
* @param style - the active ignition style.
|
|
255
|
+
* @returns the duration in milliseconds.
|
|
256
|
+
*/
|
|
257
|
+
export function deepseekWaveDuration(tier: DeepseekWaveTier, style: DeepseekWaveStyle = 'wave'): number {
|
|
258
|
+
return deepseekWaveBaseDuration(tier, style) + DEEPSEEK_WAVE_DURATION_EXTENSION_MS
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Map the extended display timeline back onto the original Codex samples. */
|
|
262
|
+
function deepseekWaveSampleElapsedMs(tick: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle): number {
|
|
263
|
+
const base = deepseekWaveBaseDuration(tier, style)
|
|
264
|
+
return tick * DEEPSEEK_WAVE_TICK_MS * base / deepseekWaveDuration(tier, style)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Pick one ignition style at random, never repeating the previous one —
|
|
269
|
+
* Codex `IgnitionStyle::random`. Falls back to the remaining styles.
|
|
270
|
+
* @param previous - the style of the last trigger, if any.
|
|
271
|
+
* @returns a style different from `previous`.
|
|
272
|
+
*/
|
|
273
|
+
export function deepseekWaveStyleRandom(previous: DeepseekWaveStyle | undefined): DeepseekWaveStyle {
|
|
274
|
+
const candidates = DEEPSEEK_WAVE_STYLES.filter(style => style !== previous)
|
|
275
|
+
return candidates[Math.floor(Math.random() * candidates.length)] ?? 'wave'
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Tier for a `provider/model` label: a MODEL ID containing `flash` runs the
|
|
280
|
+
* single-band flash tier; everything else (pro/reasoner/chat) runs the
|
|
281
|
+
* dual-band deepseek tier. Mirrors Codex's Max→Ultra mapping. Only the model
|
|
282
|
+
* segment (after the `/`) is matched, so a provider whose name contains
|
|
283
|
+
* `flash` cannot flip an unrelated model onto the flash tier.
|
|
284
|
+
* @param model - the `provider/model` label of the applied model.
|
|
285
|
+
* @returns the wave tier for that model.
|
|
286
|
+
*/
|
|
287
|
+
export function deepseekWaveTier(model: string): DeepseekWaveTier {
|
|
288
|
+
const slash = model.indexOf('/')
|
|
289
|
+
const id = slash < 0 ? model : model.slice(slash + 1)
|
|
290
|
+
return id.toLowerCase().includes('flash') ? 'flash' : 'deepseek'
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Cosine window — Codex `crest`: 1 exactly under the wave center, 0 from
|
|
295
|
+
* one half-width away.
|
|
296
|
+
* @param distance - distance from the crest center in half-widths.
|
|
297
|
+
* @returns the crest strength in 0..1.
|
|
298
|
+
*/
|
|
299
|
+
export function crest(distance: number): number {
|
|
300
|
+
if (distance >= 1) return 0
|
|
301
|
+
return 0.5 * (1 + Math.cos(Math.PI * distance))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Cubic ease-in-out — Codex `ease_in_out`: flat at both ends, steepest in
|
|
306
|
+
* the middle, so the crest accelerates and eases instead of sliding linearly.
|
|
307
|
+
* @param progress - raw progress (clamped to 0..1).
|
|
308
|
+
* @returns the eased progress in 0..1.
|
|
309
|
+
*/
|
|
310
|
+
export function easeInOut(progress: number): number {
|
|
311
|
+
const p = Math.min(1, Math.max(0, progress))
|
|
312
|
+
if (p < 0.5) return 4 * p * p * p
|
|
313
|
+
const inverse = -2 * p + 2
|
|
314
|
+
return 1 - (inverse * inverse * inverse) / 2
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Fade-in/fade-out envelope — Codex `envelope`: linear ramp over `fadeIn`
|
|
319
|
+
* at the start and `fadeOut` at the end, plateau at 1 between, 0 outside the
|
|
320
|
+
* total. The Wave style keeps the envelope at 1 (Codex paints Wave without
|
|
321
|
+
* an envelope); exported for the Aurora-style fades and for tests.
|
|
322
|
+
* @param elapsed - seconds since the animation started.
|
|
323
|
+
* @param total - total duration in seconds.
|
|
324
|
+
* @param fadeIn - seconds of fade-in.
|
|
325
|
+
* @param fadeOut - seconds of fade-out.
|
|
326
|
+
* @returns the envelope value in 0..1.
|
|
327
|
+
*/
|
|
328
|
+
export function envelope(elapsed: number, total: number, fadeIn: number, fadeOut: number): number {
|
|
329
|
+
if (elapsed <= 0 || elapsed >= total) return 0
|
|
330
|
+
const rise = elapsed / Math.max(fadeIn, Number.EPSILON)
|
|
331
|
+
const fall = (total - elapsed) / Math.max(fadeOut, Number.EPSILON)
|
|
332
|
+
return Math.min(Math.max(Math.min(rise, fall), 0), 1)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Per-band sampling context: everything geometry needs beyond the timeline. */
|
|
336
|
+
interface BandContext {
|
|
337
|
+
/** Aspect-weighted row offset from the band's center row (Pulse 2-D ring). */
|
|
338
|
+
dy: number
|
|
339
|
+
/** Normalized vertical position: -1 = top row, +1 = bottom row, 0 = middle
|
|
340
|
+
* (or a single-row band, where the undulation cannot render). */
|
|
341
|
+
u: number
|
|
342
|
+
/** Whether the band has more than one row to undulate across. */
|
|
343
|
+
undulating: boolean
|
|
344
|
+
/** The pulse ring radius that covers the band's far corner. */
|
|
345
|
+
pulseSpan: number
|
|
346
|
+
/** Band ordinal: pulse rings and the wave harmonic key off it. */
|
|
347
|
+
bandIndex: number
|
|
348
|
+
/** The style's base duration in seconds (envelope timelines). */
|
|
349
|
+
total: number
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* One band's contributions at a column — Codex `band_sample`, redesigned:
|
|
354
|
+
* Wave is a WATER SURFACE — one continuous sine line, mirror-symmetric
|
|
355
|
+
* about the center column, crests flowing outward from the center (band 1
|
|
356
|
+
* of the deepseek tier is a faster harmonic line crossing it). Pulse
|
|
357
|
+
* detonates in TWO dimensions: soft rings that keep expanding through a
|
|
358
|
+
* symmetric fade envelope, color grading across each ring's width, with a
|
|
359
|
+
* trailing echo ripple. Aurora matches Codex verbatim.
|
|
360
|
+
* @param style - the ignition style.
|
|
361
|
+
* @param band - the band triple (meaning depends on the style).
|
|
362
|
+
* @param elapsed - seconds since the animation started.
|
|
363
|
+
* @param column - column index in the content row (0..width-1).
|
|
364
|
+
* @param width - content-row width in columns.
|
|
365
|
+
* @param context - band geometry (row position, undulation flag, pulse span).
|
|
366
|
+
* @returns one or two `[hueIndex, strength, core]` contributions.
|
|
367
|
+
*/
|
|
368
|
+
function bandSample(
|
|
369
|
+
style: DeepseekWaveStyle,
|
|
370
|
+
band: DeepseekWaveBand,
|
|
371
|
+
elapsed: number,
|
|
372
|
+
column: number,
|
|
373
|
+
width: number,
|
|
374
|
+
context: BandContext,
|
|
375
|
+
): [number, number, number][] {
|
|
376
|
+
const [first, second, third] = band
|
|
377
|
+
switch (style) {
|
|
378
|
+
case 'wave': {
|
|
379
|
+
// The water surface: one continuous sine line across the whole band,
|
|
380
|
+
// mirror-symmetric about the center column (distance d = |x − center|
|
|
381
|
+
// drives the phase), crests flowing OUTWARD from the center — one
|
|
382
|
+
// direction, never a return trip. The band table's (launch, travel)
|
|
383
|
+
// becomes the line's fade envelope: fade in at `launch`, full until
|
|
384
|
+
// `launch + travel`, then fade out to the end — symmetric in time.
|
|
385
|
+
const fadeIn = first
|
|
386
|
+
const fadeOut = Math.max(0.05, context.total - (first + second))
|
|
387
|
+
const fade = envelope(elapsed, context.total, fadeIn, fadeOut)
|
|
388
|
+
if (fade <= 0.01) return [[0, 0, 0]]
|
|
389
|
+
// Band 0 is the fundamental swell; band 1 (deepseek tier) is a faster,
|
|
390
|
+
// finer harmonic crossing the fundamental's crests — two continuous
|
|
391
|
+
// symmetric lines interleaving instead of one shuttling back and forth.
|
|
392
|
+
const harmonic = context.bandIndex % 2 === 1
|
|
393
|
+
const wavelength = harmonic ? WAVE_SURFACE_WAVELENGTH / 1.5 : WAVE_SURFACE_WAVELENGTH
|
|
394
|
+
const omega = (harmonic ? 1.5 : 1) * WAVE_SURFACE_OMEGA
|
|
395
|
+
const amplitude = (harmonic ? WAVE_SURFACE_HARMONIC : 1) * WAVE_SURFACE_AMPLITUDE
|
|
396
|
+
const d = Math.abs(column - (width - 1) / 2)
|
|
397
|
+
const surface = amplitude * Math.sin(Math.PI * 2 * d / wavelength - omega * elapsed + (harmonic ? Math.PI / 2 : 0))
|
|
398
|
+
const fromSurface = Math.abs(context.u - surface)
|
|
399
|
+
const vertical = context.undulating ? crest(fromSurface / WAVE_SURFACE_THICKNESS) : 1
|
|
400
|
+
// Aurora-style hue mixing: the MIRRORED profile below the surface
|
|
401
|
+
// carries a second blue (the harmonic band carries the third), so the
|
|
402
|
+
// color distribution flows continuously with the wave instead of one
|
|
403
|
+
// flat tint broken by a hard core line.
|
|
404
|
+
const mirrorHue = harmonic ? 1 : 2
|
|
405
|
+
const below = context.undulating ? crest(Math.abs(context.u + surface) / WAVE_SURFACE_THICKNESS) : vertical
|
|
406
|
+
return [
|
|
407
|
+
[0, fade * vertical, 0],
|
|
408
|
+
[mirrorHue, fade * below * WAVE_SURFACE_MIRROR, 0],
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
case 'aurora': {
|
|
412
|
+
const center = (0.5 + 0.38 * Math.sin(Math.PI * 2 * (first * elapsed + second))) * width
|
|
413
|
+
const halfWidth = Math.max(width * 0.22, 4)
|
|
414
|
+
return [[Math.trunc(third), crest(Math.abs(column - center) / halfWidth), 0]]
|
|
415
|
+
}
|
|
416
|
+
case 'pulse': {
|
|
417
|
+
// Symmetric lifetime: the band table's (launch, travel) becomes the
|
|
418
|
+
// fade envelope (fade in at launch, full until launch+travel, then
|
|
419
|
+
// fade to the end) while the radius keeps expanding THROUGH the fade —
|
|
420
|
+
// a shockwave that never hits a wall, it dissolves mid-flight.
|
|
421
|
+
const launch = first
|
|
422
|
+
const travel = second
|
|
423
|
+
const fadeOut = Math.max(0.05, context.total - (launch + travel))
|
|
424
|
+
const fade = envelope(elapsed, context.total, launch, fadeOut)
|
|
425
|
+
if (fade <= 0.01) return [[0, 0, 0]]
|
|
426
|
+
const progress = (elapsed - launch) / travel
|
|
427
|
+
const radius = (1 - (1 - progress) ** 3) * context.pulseSpan
|
|
428
|
+
const decay = third * (1 - 0.35 * Math.min(Math.max(progress, 0), 1))
|
|
429
|
+
const distance = Math.hypot(column - width / 2, context.dy)
|
|
430
|
+
const fromRing = Math.abs(distance - radius)
|
|
431
|
+
// Wide soft band with a moderate peak riding the radius — all in the
|
|
432
|
+
// hue blend, no hard line — and an inner edge one hue over at a
|
|
433
|
+
// slightly smaller radius: the ring's color grades across its width.
|
|
434
|
+
const band = crest(fromRing / PULSE_HALF_WIDTH) + PULSE_PEAK_GAIN * crest(fromRing / PULSE_PEAK_HALF_WIDTH)
|
|
435
|
+
const inner = crest(Math.abs(distance - (radius - PULSE_INNER_OFFSET)) / PULSE_HALF_WIDTH)
|
|
436
|
+
// Trailing echo ripple in the next blue, fading in after the primary
|
|
437
|
+
// so the center hole opens first.
|
|
438
|
+
const echoGate = envelope(elapsed, context.total, launch + PULSE_ECHO_DELAY, fadeOut)
|
|
439
|
+
const echo = crest(Math.abs(distance - radius * PULSE_ECHO_RADIUS) / PULSE_HALF_WIDTH)
|
|
440
|
+
* PULSE_ECHO_STRENGTH * echoGate
|
|
441
|
+
return [
|
|
442
|
+
[context.bandIndex, fade * band * decay, 0],
|
|
443
|
+
[PULSE_INNER_HUE, fade * inner * decay * PULSE_INNER_STRENGTH, 0],
|
|
444
|
+
[context.bandIndex + 1, fade * echo * decay, 0],
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Linear RGB blend — Codex `blend`: `fg * alpha + bg * (1 - alpha)`. */
|
|
451
|
+
function blendRgb(fg: RgbTriple, bg: RgbTriple, alpha: number): RgbTriple {
|
|
452
|
+
return [
|
|
453
|
+
Math.round(fg[0] * alpha + bg[0] * (1 - alpha)),
|
|
454
|
+
Math.round(fg[1] * alpha + bg[1] * (1 - alpha)),
|
|
455
|
+
Math.round(fg[2] * alpha + bg[2] * (1 - alpha)),
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Aurora-only per-row phase share of the duration: its drifting bands reach
|
|
461
|
+
* the top row first and the bottom row last, sweeping down the band. 0.12
|
|
462
|
+
* keeps the bottom row's lag inside the 200ms duration extension. Wave and
|
|
463
|
+
* Pulse deliberately share one timeline (see `deepseekWaveColumnBg`).
|
|
464
|
+
*/
|
|
465
|
+
const DEEPSEEK_WAVE_ROW_PHASE = 0.12
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The background color for one composer-band column at a tick — Codex
|
|
469
|
+
* `paint_bands` + `Canvas::tint` for all three styles. Bands overlap with a
|
|
470
|
+
* max for Wave/Pulse and a SUM for Aurora (Codex differs by style), the
|
|
471
|
+
* weighted hues mix per column (Wave/Pulse always end on hue 0), the tint
|
|
472
|
+
* blends the mixed hue toward the blank-cell base at the style's alpha cap,
|
|
473
|
+
* and Aurora applies its own fade envelope. Returns `null` when the column
|
|
474
|
+
* should stay transparent, so the row returns to no `backgroundColor` on
|
|
475
|
+
* both ends. With `rows > 1`: Wave is a water surface — every column
|
|
476
|
+
* lights the row nearest the surface's current height, so the light reads
|
|
477
|
+
* as ONE continuous wavy line spanning the band, symmetric about the center
|
|
478
|
+
* column and flowing outward (a single-row band falls back to a flat glow);
|
|
479
|
+
* Pulse rings in two dimensions around the band's center cell with trailing
|
|
480
|
+
* echo ripples; only Aurora samples the timeline shifted by a per-row phase
|
|
481
|
+
* offset.
|
|
482
|
+
* @param tick - wave frame (0, 1, … at DEEPSEEK_WAVE_TICK_MS).
|
|
483
|
+
* @param column - column index in the content row (0..width-1).
|
|
484
|
+
* @param width - content-row width in columns.
|
|
485
|
+
* @param tier - the wave tier (flash = Max, deepseek = Ultra parameters).
|
|
486
|
+
* @param style - the ignition style.
|
|
487
|
+
* @param hues - the tier's three hues.
|
|
488
|
+
* @param base - the blank-cell base color the tint blends toward.
|
|
489
|
+
* @param row - row index in the band (0..rows-1; default 0 = old single-row).
|
|
490
|
+
* @param rows - band height in rows (default 1).
|
|
491
|
+
* @returns the blended RGB background, or null for transparent.
|
|
492
|
+
*/
|
|
493
|
+
export function deepseekWaveColumnBg(
|
|
494
|
+
tick: number,
|
|
495
|
+
column: number,
|
|
496
|
+
width: number,
|
|
497
|
+
tier: DeepseekWaveTier,
|
|
498
|
+
style: DeepseekWaveStyle,
|
|
499
|
+
hues: readonly [RgbTriple, RgbTriple, RgbTriple],
|
|
500
|
+
base: RgbTriple,
|
|
501
|
+
row = 0,
|
|
502
|
+
rows = 1,
|
|
503
|
+
): RgbTriple | null {
|
|
504
|
+
const total = deepseekWaveBaseDuration(tier, style) / 1000
|
|
505
|
+
// Only Aurora keeps the per-row phase cascade (its drifting bands stagger
|
|
506
|
+
// nicely). Wave and Pulse sample ONE shared timeline — Codex paints each
|
|
507
|
+
// column across the whole band height (`tint_column`), and the cascade we
|
|
508
|
+
// added on top of the port tore Pulse into misaligned per-row rings and
|
|
509
|
+
// smeared Wave's crest into a diagonal.
|
|
510
|
+
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1000
|
|
511
|
+
- (style === 'aurora' ? (row - (rows - 1) / 2) * total * DEEPSEEK_WAVE_ROW_PHASE : 0)
|
|
512
|
+
const dy = style === 'pulse' ? (row - (rows - 1) / 2) * PULSE_ROW_ASPECT : 0
|
|
513
|
+
const undulating = rows > 1
|
|
514
|
+
const u = undulating ? (row - (rows - 1) / 2) / ((rows - 1) / 2) : 0
|
|
515
|
+
// The span is EXACTLY the band's far corner: the ring front reaches the
|
|
516
|
+
// edges as the fade envelope closes, so the detonation stays visible for
|
|
517
|
+
// its whole lifetime instead of rushing off the band mid-flight (the old
|
|
518
|
+
// +2·half-width margin made the ring's second half invisible).
|
|
519
|
+
const pulseSpan = Math.hypot(width / 2, ((rows - 1) / 2) * PULSE_ROW_ASPECT)
|
|
520
|
+
const fade = style === 'aurora' ? envelope(elapsed, total, 0.25, 0.40) : 1
|
|
521
|
+
const weights = [0, 0, 0]
|
|
522
|
+
let bandIndex = 0
|
|
523
|
+
for (const band of DEEPSEEK_WAVE_BANDS[style][tier]) {
|
|
524
|
+
for (const [hue, strength] of bandSample(style, band, elapsed, column, width, { dy, u, undulating, pulseSpan, bandIndex, total })) {
|
|
525
|
+
weights[hue] = style === 'aurora' ? weights[hue]! + strength : Math.max(weights[hue]!, strength)
|
|
526
|
+
}
|
|
527
|
+
bandIndex += 1
|
|
528
|
+
}
|
|
529
|
+
const weight = weights[0]! + weights[1]! + weights[2]!
|
|
530
|
+
if (weight <= 0.01) return null
|
|
531
|
+
let red = 0
|
|
532
|
+
let green = 0
|
|
533
|
+
let blue = 0
|
|
534
|
+
for (let index = 0; index < 3; index += 1) {
|
|
535
|
+
red += weights[index]! * hues[index]![0]
|
|
536
|
+
green += weights[index]! * hues[index]![1]
|
|
537
|
+
blue += weights[index]! * hues[index]![2]
|
|
538
|
+
}
|
|
539
|
+
const mixed: RgbTriple = [
|
|
540
|
+
Math.round(red / weight),
|
|
541
|
+
Math.round(green / weight),
|
|
542
|
+
Math.round(blue / weight),
|
|
543
|
+
]
|
|
544
|
+
// Alpha caps, all Aurora-grade now: Aurora ≤0.50, the swell ≤0.68, the
|
|
545
|
+
// detonation a notch above at ≤0.72 — soft gradients everywhere, no hard
|
|
546
|
+
// lines, the color work done by hue mixing instead of brightness spikes.
|
|
547
|
+
const alpha = style === 'aurora'
|
|
548
|
+
? Math.min(weight * 0.40, 0.50) * fade
|
|
549
|
+
: style === 'wave'
|
|
550
|
+
? Math.min(weight * WAVE_SURFACE_ALPHA_GAIN, WAVE_SURFACE_ALPHA_CAP)
|
|
551
|
+
: Math.min(weight * PULSE_ALPHA_GAIN, PULSE_ALPHA_CAP)
|
|
552
|
+
if (alpha < 0.02) return null
|
|
553
|
+
return blendRgb(mixed, base, alpha)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* The sparkle glyph for a tick — Codex `spark_frame`, sampled on the same
|
|
558
|
+
* proportionally slowed DeepSeek Wave timeline as the composer background.
|
|
559
|
+
* The Ink layer still must skip occupied cells.
|
|
560
|
+
* @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
|
|
561
|
+
* @returns the sparkle glyph, or null outside the stretched tail window.
|
|
562
|
+
*/
|
|
563
|
+
export function deepseekWaveSpark(tick: number): string | null {
|
|
564
|
+
const elapsed = deepseekWaveSampleElapsedMs(tick, 'deepseek', 'wave')
|
|
565
|
+
if (elapsed < SPARK_START_MS) return null
|
|
566
|
+
const frame = Math.floor((elapsed - SPARK_START_MS) / SPARK_FRAME_MS)
|
|
567
|
+
return SPARK_GLYPHS[frame] ?? null
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Whether the `deepseek` wordmark rides the wave at this tick: it fades in
|
|
572
|
+
* shortly after the first crest launches and out before the wave settles,
|
|
573
|
+
* so the brand name surfaces through the sweep's middle. The Ink layer
|
|
574
|
+
* places it in the row's blank mid-section (never over real draft text).
|
|
575
|
+
* @param tick - wave frame at DEEPSEEK_WAVE_TICK_MS.
|
|
576
|
+
* @param tier - the wave tier.
|
|
577
|
+
* @returns true while the wordmark should be visible.
|
|
578
|
+
*/
|
|
579
|
+
export function deepseekWaveWordVisible(tick: number, tier: DeepseekWaveTier, style: DeepseekWaveStyle = 'wave'): boolean {
|
|
580
|
+
const total = deepseekWaveBaseDuration(tier, style) / 1000
|
|
581
|
+
const elapsed = deepseekWaveSampleElapsedMs(tick, tier, style) / 1000
|
|
582
|
+
return envelope(elapsed, total, total * 0.2, total * 0.35) > 0.25
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* The per-character color for the `deepseek` wordmark: the tier's hues
|
|
587
|
+
* cycled per character (d→hue0, e→hue1, e→hue2, …), a brand-gradient text.
|
|
588
|
+
* @param index - character index in the wordmark.
|
|
589
|
+
* @param hues - the tier's three hues.
|
|
590
|
+
* @returns the hue for that character.
|
|
591
|
+
*/
|
|
592
|
+
export function deepseekWaveWordHue(index: number, hues: readonly [RgbTriple, RgbTriple, RgbTriple]): RgbTriple {
|
|
593
|
+
return hues[index % hues.length]!
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* True when a `provider/model` status label addresses the official DeepSeek
|
|
598
|
+
* route: either segment contains `deepseek` (case-insensitive), covering the
|
|
599
|
+
* `deepseek-official` provider route and its `deepseek-*` model ids.
|
|
600
|
+
* @param label - the status bar model label (`provider/model`).
|
|
601
|
+
* @returns whether the label names an official DeepSeek model.
|
|
602
|
+
*/
|
|
603
|
+
export function isOfficialDeepSeekLabel(label: string): boolean {
|
|
604
|
+
const slash = label.indexOf('/')
|
|
605
|
+
const provider = slash < 0 ? label : label.slice(0, slash)
|
|
606
|
+
const model = slash < 0 ? '' : label.slice(slash + 1)
|
|
607
|
+
return provider.toLowerCase().includes('deepseek') || model.toLowerCase().includes('deepseek')
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Known reasoning-effort ranks in ascending order. Effort ids are opaque
|
|
612
|
+
* adapter-owned strings, so the rank table covers the conventional names
|
|
613
|
+
* (off → low → medium → high → xhigh → max/ultra); an unrecognized id
|
|
614
|
+
* ranks as unknown (0), which never triggers the high-effort wave.
|
|
615
|
+
*/
|
|
616
|
+
const EFFORT_RANK: Readonly<Record<string, number>> = {
|
|
617
|
+
off: 0,
|
|
618
|
+
none: 0,
|
|
619
|
+
low: 1,
|
|
620
|
+
medium: 2,
|
|
621
|
+
med: 2,
|
|
622
|
+
high: 3,
|
|
623
|
+
xhigh: 4,
|
|
624
|
+
'x-high': 4,
|
|
625
|
+
'very-high': 4,
|
|
626
|
+
max: 5,
|
|
627
|
+
maximum: 5,
|
|
628
|
+
ultra: 5,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* True when an effective reasoning effort is STRICTLY above `high` — the
|
|
633
|
+
* trigger gate for the "Into the Unknown" wave on non-DeepSeek routes.
|
|
634
|
+
* Absent efforts and unrecognized ids never qualify.
|
|
635
|
+
* @param effort - the effective reasoning-effort id ('' or undefined when none).
|
|
636
|
+
* @returns whether the effort ranks above high.
|
|
637
|
+
*/
|
|
638
|
+
export function effortAboveHigh(effort: string | undefined): boolean {
|
|
639
|
+
if (effort === undefined || effort === '') return false
|
|
640
|
+
const rank = EFFORT_RANK[effort.trim().toLowerCase()]
|
|
641
|
+
return rank !== undefined && rank > 3
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Parse a persisted animations preference (`animations.json`): timed
|
|
646
|
+
* animations are on by default and only an explicit `false` disables them —
|
|
647
|
+
* a missing key, corrupt value, or absent file all mean enabled, so the
|
|
648
|
+
* /animation toggle degrades exactly like every other user preference.
|
|
649
|
+
* @param value - the raw parsed JSON value (expected boolean).
|
|
650
|
+
* @returns whether timed animations should run.
|
|
651
|
+
*/
|
|
652
|
+
export function parseAnimationsPref(value: unknown): boolean {
|
|
653
|
+
return value !== false
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* One parsed `/animation` argument: '' toggles, `on|true|1` enables,
|
|
658
|
+
* `off|false|0` disables (case-insensitive, surrounding whitespace ignored),
|
|
659
|
+
* and anything else is a usage error the caller surfaces. Kept pure so the
|
|
660
|
+
* command's entire decision table is unit-testable.
|
|
661
|
+
* @param argument - the raw text after `/animation`.
|
|
662
|
+
* @returns `{ enabled }`, `'toggle'`, or `'usage'`.
|
|
663
|
+
*/
|
|
664
|
+
export function parseAnimationsArgument(argument: string): { enabled: boolean } | 'toggle' | 'usage' {
|
|
665
|
+
const normalized = argument.trim().toLowerCase()
|
|
666
|
+
if (normalized === '') return 'toggle'
|
|
667
|
+
if (normalized === 'on' || normalized === 'true' || normalized === '1') return { enabled: true }
|
|
668
|
+
if (normalized === 'off' || normalized === 'false' || normalized === '0') return { enabled: false }
|
|
669
|
+
return 'usage'
|
|
670
|
+
}
|