dsh-wisp 1.29.0 → 1.30.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/README.md +31 -0
- package/lib/client.js +117 -5
- package/lib/client.template.js +117 -5
- package/package.json +1 -1
- package/verify-wisp.mjs +129 -0
package/README.md
CHANGED
|
@@ -448,6 +448,37 @@ node tools/audit-log.mjs --confirm <manifest> # 生成后:并入代理登
|
|
|
448
448
|
|
|
449
449
|
## 变更
|
|
450
450
|
|
|
451
|
+
### 1.30.0
|
|
452
|
+
|
|
453
|
+
这一版是**功能**,不是台词。
|
|
454
|
+
|
|
455
|
+
- **专注计时器。** 桌面陪伴插件最该有的实用功能:替你画出一段时间的边界。
|
|
456
|
+
|
|
457
|
+
- 菜单「行为」组里「**专注 25 分钟**」/「**专注 45 分钟**」(默认时长 `focusMinutes`),
|
|
458
|
+
专注中多一条「**结束专注**」(平时不出现 —— 菜单里不该有按不动的条目);
|
|
459
|
+
- 专注期间**她保持"干活中"的姿势**:稳态轮询不许把她挪走 ✓。这条 hold 放在久坐 / 干饭 /
|
|
460
|
+
劝睡**之后** —— 那三件事该响还得响,只是姿势归专注管;
|
|
461
|
+
- **戳她一下 = 问还剩多久**(*"还有 23 分钟呢,盯着屏幕。"*),而且不计入"连戳",
|
|
462
|
+
不会升级成抗议;
|
|
463
|
+
- 到点她换 `proud` 说收工(*"这一轮 25 分钟,结束了。"*),`doctor().focus.done` 累计完成次数;
|
|
464
|
+
- 她**不碰应用**:不改输入框、不发消息、不点任何按钮 —— 只陪着 + 计时。
|
|
465
|
+
|
|
466
|
+
- **页面藏起来就安静。** 挂 `visibilitychange`:藏起来时停掉**轮询 / 絮叨 / 踱步**三个定时器,
|
|
467
|
+
回来再按配置重新挂上。没人在看的时候不该空转 CPU(顺带省电)。
|
|
468
|
+
|
|
469
|
+
- 这不是"少做点事"而是"诚实":藏起来期间 DOM 再吵她也不该有反应 —— 测试就是这么断言的
|
|
470
|
+
(藏起来时来三个报错她一声不吭;回来之后同一批报错立刻被看见);
|
|
471
|
+
- 恢复**先于**问候:先保证活着,再谈礼貌;
|
|
472
|
+
- `doctor().timers` 报告 `{ poll, chatter, wander, paused }`。
|
|
473
|
+
|
|
474
|
+
- 预检 451 → **473 项**。
|
|
475
|
+
|
|
476
|
+
- 记一个测试教训:戳她要用**既有的那套姿势**(`pointerdown` 落在 `.wisp-body`、
|
|
477
|
+
`pointerup` 落在 `window`、坐标要在她身上)—— 我第一版两个事件都发到 `root` 上,
|
|
478
|
+
她当然没反应,白查了一轮。
|
|
479
|
+
|
|
480
|
+
### 1.29.0
|
|
481
|
+
|
|
451
482
|
### 1.29.0
|
|
452
483
|
|
|
453
484
|
三件和"她真的陪着你"有关的事。都不需要新素材,全是行为 + 台词。
|
package/lib/client.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.30.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -70,6 +70,7 @@ const WHATS_NEW = {
|
|
|
70
70
|
'1.27.0': '她饿了会捧出一碗白米饭,还有第八个表情。',
|
|
71
71
|
'1.28.0': '菜单分成折叠分组,另外多了「关于她」和「动作一览」两个弹窗。',
|
|
72
72
|
'1.29.0': '夜深了她会劝你去睡;你走开再回来她记得;连续来了几天她也记着。',
|
|
73
|
+
'1.30.0': '会陪你专注计时了;页面藏起来时她会安静下来,不再空转。',
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -216,6 +217,7 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
216
217
|
careAfterMs: 5400000, // 连续活动这么久之后提醒你起来动动(90 分钟;0 = 关)
|
|
217
218
|
hungry: true, // 干饭:她饿了会说
|
|
218
219
|
hungerMs: 2700000, // 连续活动这么久就该吃一碗了(45 分钟;0 = 关)
|
|
220
|
+
focusMinutes: 25, // 菜单里「专注 N 分钟」用的默认时长
|
|
219
221
|
night: true, // 深夜劝睡
|
|
220
222
|
bedtimeHour: 23, // 几点算深夜(0-23)
|
|
221
223
|
wakeHour: 5, // 几点算天亮(0-23);bedtime > wake 时窗口跨零点
|
|
@@ -324,6 +326,19 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
324
326
|
'算力不够了,得补点碳水。', '电量告急,切待机。', '你要不要也来一口?',
|
|
325
327
|
'这碗不算加餐,算基础设施。', '吃饱了才有力气摸鱼。', '饭碗见底了,该续命了。', '我数着米粒呢。',
|
|
326
328
|
],
|
|
329
|
+
/* 专注计时器。她是陪着你干活的,所以"替你画出一段时间的边界"比再多说几句更实在。 */
|
|
330
|
+
focusStart: [
|
|
331
|
+
'{n} 分钟。开始吧。', '好,{n} 分钟,我盯着你。', '计时开始,{n} 分钟。', '{n} 分钟不看别的。',
|
|
332
|
+
'这段时间归你了。{n} 分钟。',
|
|
333
|
+
],
|
|
334
|
+
focusDone: [
|
|
335
|
+
'{n} 分钟到了。收工。', '{n} 分钟,干完了?', '时间到。{n} 分钟,去喝口水。', '{n} 分钟到了 —— 站起来伸个懒腰。',
|
|
336
|
+
'这一轮 {n} 分钟,结束了。',
|
|
337
|
+
],
|
|
338
|
+
focusStop: ['行,那就先到这儿。', '不专注了?那我歇着。', '计时取消了。', '好吧,下次再说。'],
|
|
339
|
+
focusLeft: [
|
|
340
|
+
'还有 {n} 分钟。', '{n} 分钟,快了。', '还剩 {n} 分钟,别分心。', '还有 {n} 分钟呢,盯着屏幕。',
|
|
341
|
+
],
|
|
327
342
|
/* 深夜劝睡:23 点后一种语气,凌晨 1 点后另一种 —— 后者是社区设定里"嘴硬心软"那一档。
|
|
328
343
|
分档按 bedtimeHour + 2 推导,所以改了作息这套分档也跟着走。 */
|
|
329
344
|
nightLate: [
|
|
@@ -450,6 +465,7 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
450
465
|
careAfterMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
451
466
|
hungry: { kind: 'bool' },
|
|
452
467
|
hungerMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
468
|
+
focusMinutes: { kind: 'num', lo: 1, hi: 600 },
|
|
453
469
|
night: { kind: 'bool' },
|
|
454
470
|
bedtimeHour: { kind: 'num', lo: 0, hi: 23 },
|
|
455
471
|
wakeHour: { kind: 'num', lo: 0, hi: 23 },
|
|
@@ -1642,6 +1658,13 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1642
1658
|
return
|
|
1643
1659
|
}
|
|
1644
1660
|
|
|
1661
|
+
/* 专注期间不让稳态把她从"干活中"挪走 —— 她在盯着你,这是这个功能的全部意义。
|
|
1662
|
+
位置在这里而不是函数开头:久坐、干饭、劝睡该响还得响。 */
|
|
1663
|
+
if (focusUntil > 0) {
|
|
1664
|
+
if (mood !== 'alert') setMood('alert')
|
|
1665
|
+
return
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1645
1668
|
// 临时状态(happy / worried)在自己的计时器到期前不能被稳态赋值覆盖 ——
|
|
1646
1669
|
// 否则刚摆出的表情在同一个 poll 周期里就被 setMood('idle') 抹掉了。
|
|
1647
1670
|
if (mood === 'sleep' || mood === 'happy' || mood === 'poked' || mood === 'proud' || mood === 'care'
|
|
@@ -1651,10 +1674,62 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1651
1674
|
setMood((busy || typing) ? 'alert' : 'idle')
|
|
1652
1675
|
}
|
|
1653
1676
|
|
|
1677
|
+
/* ---- 专注计时器 ---------------------------------------------------------
|
|
1678
|
+
桌面陪伴插件最该有的实用功能:替你画出一段时间的边界。
|
|
1679
|
+
专注期间她保持"干活中"的姿势(poll 里给她一个 hold —— 否则稳态会把她挪走),
|
|
1680
|
+
到点收工庆祝;戳她一下就是问"还剩多久"。
|
|
1681
|
+
她**不碰应用**:不改输入框、不发消息,只是陪着 + 计时。 */
|
|
1682
|
+
let focusUntil = 0
|
|
1683
|
+
let focusHandle = null
|
|
1684
|
+
let focusMinutes = 0
|
|
1685
|
+
let focusDone = 0
|
|
1686
|
+
|
|
1687
|
+
const focusLeftMs = () => Math.max(0, focusUntil - nowMs())
|
|
1688
|
+
const focusState = () => (focusUntil > 0
|
|
1689
|
+
? { active: true, minutes: focusMinutes, leftMs: Math.round(focusLeftMs()), leftMin: Math.max(1, Math.ceil(focusLeftMs() / 60000)), done: focusDone }
|
|
1690
|
+
: { active: false, minutes: 0, leftMs: 0, leftMin: 0, done: focusDone })
|
|
1691
|
+
|
|
1692
|
+
const stopFocus = (silent) => {
|
|
1693
|
+
if (focusUntil === 0 && focusHandle === null) return false
|
|
1694
|
+
focusUntil = 0
|
|
1695
|
+
focusMinutes = 0
|
|
1696
|
+
if (focusHandle) { clock.cancel(focusHandle); focusHandle = null }
|
|
1697
|
+
if (silent !== true) {
|
|
1698
|
+
wake(true)
|
|
1699
|
+
flashHappy(pick(LINES.focusStop))
|
|
1700
|
+
}
|
|
1701
|
+
return true
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
const finishFocus = () => {
|
|
1705
|
+
const minutes = focusMinutes
|
|
1706
|
+
focusUntil = 0
|
|
1707
|
+
focusHandle = null
|
|
1708
|
+
focusMinutes = 0
|
|
1709
|
+
focusDone++
|
|
1710
|
+
wake(true)
|
|
1711
|
+
flashProud(pick(LINES.focusDone).split('{n}').join(String(minutes)))
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
const startFocus = (minutes) => {
|
|
1715
|
+
const mins = clamp(Math.round(Number(minutes) || cfg.focusMinutes), 1, 600)
|
|
1716
|
+
stopFocus(true)
|
|
1717
|
+
focusMinutes = mins
|
|
1718
|
+
focusUntil = nowMs() + mins * 60000
|
|
1719
|
+
wake(true)
|
|
1720
|
+
say(pick(LINES.focusStart).split('{n}').join(String(mins)))
|
|
1721
|
+
focusHandle = clock.after(mins * 60000, () => { if (focusUntil > 0) finishFocus() })
|
|
1722
|
+
return focusState()
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/* 没人在看的时候别空转:页面藏起来就停掉轮询、絮叨、踱步三个定时器。
|
|
1726
|
+
它们本来就是"给看得见的人看的",停下来是纯赚(CPU 与电量)。 */
|
|
1727
|
+
let paused = false
|
|
1728
|
+
|
|
1654
1729
|
let chatterHandle = null
|
|
1655
1730
|
const syncChatter = () => {
|
|
1656
1731
|
if (chatterHandle) { clock.cancel(chatterHandle); chatterHandle = null }
|
|
1657
|
-
if (cfg.chatterMs <= 0) return
|
|
1732
|
+
if (cfg.chatterMs <= 0 || paused) return
|
|
1658
1733
|
chatterHandle = clock.every(cfg.chatterMs, () => {
|
|
1659
1734
|
if (destroyed || mood !== 'idle') return
|
|
1660
1735
|
if (agentIsBusy()) return
|
|
@@ -1665,7 +1740,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1665
1740
|
let pollHandle = null
|
|
1666
1741
|
const syncPoll = () => {
|
|
1667
1742
|
if (pollHandle) { clock.cancel(pollHandle); pollHandle = null }
|
|
1668
|
-
if (cfg.reactions) pollHandle = clock.every(POLL_MS, poll)
|
|
1743
|
+
if (cfg.reactions && !paused) pollHandle = clock.every(POLL_MS, poll)
|
|
1669
1744
|
}
|
|
1670
1745
|
syncPoll()
|
|
1671
1746
|
syncChatter()
|
|
@@ -1692,7 +1767,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1692
1767
|
let wanderHandle = null
|
|
1693
1768
|
const syncWander = () => {
|
|
1694
1769
|
if (wanderHandle) { clock.cancel(wanderHandle); wanderHandle = null }
|
|
1695
|
-
if (!cfg.wander || cfg.wanderRange <= 0) return
|
|
1770
|
+
if (!cfg.wander || cfg.wanderRange <= 0 || paused) return
|
|
1696
1771
|
wanderHandle = clock.every(cfg.wanderMs, () => {
|
|
1697
1772
|
if (destroyed || hidden || mood !== 'idle' || start !== null) return
|
|
1698
1773
|
if (agentIsBusy()) return
|
|
@@ -1822,6 +1897,12 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1822
1897
|
if (now - pokeWindowStart > POKE_WINDOW_MS) { pokeWindowStart = now; pokeStreak = 0 }
|
|
1823
1898
|
pokeStreak++
|
|
1824
1899
|
wake(true)
|
|
1900
|
+
/* 专注中戳她是在问进度,不算戳着玩(也就不会升级成"抗议")。 */
|
|
1901
|
+
if (focusUntil > 0) {
|
|
1902
|
+
const left = Math.max(1, Math.ceil(focusLeftMs() / 60000))
|
|
1903
|
+
say(pick(LINES.focusLeft).split('{n}').join(String(left)))
|
|
1904
|
+
return
|
|
1905
|
+
}
|
|
1825
1906
|
if (pokeStreak >= POKE_ANNOYED_AT) {
|
|
1826
1907
|
pokeStreak = 0
|
|
1827
1908
|
flashPoked(pick(LINES.poked))
|
|
@@ -2098,6 +2179,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2098
2179
|
para('DeepSeek娘 —— DSH 界面上的浮动桌宠。她会跟着你的状态换表情:你打字她在忙,你闲着她在晃,你跑完一轮她替你高兴。'),
|
|
2099
2180
|
para('她的样子照社区规范来:深蓝色渐变长直发、头顶一个大圆环呆毛、两片鲸鳍耳、女仆装。'),
|
|
2100
2181
|
para('白米饭是算力的硬通货 —— 所以她每隔一阵会捧碗出来补点碳水。'),
|
|
2182
|
+
para('她还能陪你专注计时:菜单里开一段 25 或 45 分钟,期间她保持"干活中",戳她一下是问还剩多久,到点她替你收工。'),
|
|
2101
2183
|
para('这一版:' + (note || VERSION)),
|
|
2102
2184
|
para('形象与设定参考社区项目 Neko3000/deepseek-whalechan(设定文档 CC BY-NC-SA 4.0,原作者 B站 ZipZipPipe、上善无形)。'),
|
|
2103
2185
|
dialogFoot('非官方同人作品,与 DeepSeek 官方无关', 'v' + VERSION),
|
|
@@ -2129,6 +2211,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2129
2211
|
children: [
|
|
2130
2212
|
{ label: '说句话', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2131
2213
|
{ label: '睡一会儿', run: () => setMood('sleep') },
|
|
2214
|
+
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes) },
|
|
2215
|
+
{ label: '专注 45 分钟', run: () => startFocus(45) },
|
|
2216
|
+
/* 只有专注中才出现"结束专注" —— 平时的菜单不该多一个按不动的条目 */
|
|
2217
|
+
...(focusUntil > 0 ? [{ label: '结束专注', run: () => stopFocus(false) }] : []),
|
|
2132
2218
|
...TOGGLES.map((t) => ({
|
|
2133
2219
|
label: (cfg[t.key] === true ? '✓ ' : ' ') + t.label,
|
|
2134
2220
|
run: () => applyConfig({ [t.key]: cfg[t.key] !== true }),
|
|
@@ -2329,7 +2415,22 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2329
2415
|
const onVisibility = () => {
|
|
2330
2416
|
let state = 'visible'
|
|
2331
2417
|
try { state = document.visibilityState === 'hidden' ? 'hidden' : 'visible' } catch (e) { state = 'visible' }
|
|
2332
|
-
if (state === 'hidden') {
|
|
2418
|
+
if (state === 'hidden') {
|
|
2419
|
+
awayAt = nowMs()
|
|
2420
|
+
/* 没人在看,停掉三个"给看得见的人看"的定时器 */
|
|
2421
|
+
paused = true
|
|
2422
|
+
syncPoll()
|
|
2423
|
+
syncChatter()
|
|
2424
|
+
syncWander()
|
|
2425
|
+
return
|
|
2426
|
+
}
|
|
2427
|
+
/* 回来了:先恢复再算问候 —— 恢复是必须的,问候才是锦上添花 */
|
|
2428
|
+
if (paused) {
|
|
2429
|
+
paused = false
|
|
2430
|
+
syncPoll()
|
|
2431
|
+
syncChatter()
|
|
2432
|
+
syncWander()
|
|
2433
|
+
}
|
|
2333
2434
|
if (awayAt === 0) return
|
|
2334
2435
|
const away = nowMs() - awayAt
|
|
2335
2436
|
awayAt = 0
|
|
@@ -2563,6 +2664,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2563
2664
|
showUpdate,
|
|
2564
2665
|
checkForUpdate,
|
|
2565
2666
|
/* 弹窗与折叠分组:菜单结构变了之后,测试要能问"现在开着哪个弹窗 / 哪个分组"。 */
|
|
2667
|
+
/* 专注计时器:脚本与测试都能驱动 */
|
|
2668
|
+
startFocus,
|
|
2669
|
+
stopFocus,
|
|
2670
|
+
get focus() { return focusState() },
|
|
2566
2671
|
openAbout: () => aboutDialog(),
|
|
2567
2672
|
openActions: () => actionsDialog(),
|
|
2568
2673
|
closeDialog,
|
|
@@ -2691,6 +2796,13 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2691
2796
|
},
|
|
2692
2797
|
streak: streakOf(memory.days),
|
|
2693
2798
|
awaySince: Math.round(awayAt),
|
|
2799
|
+
/* 专注:不用猜"她为什么一直站在干活中" */
|
|
2800
|
+
focus: focusState(),
|
|
2801
|
+
/* 定时器是否挂着;页面藏起来时三个都该是 false */
|
|
2802
|
+
timers: {
|
|
2803
|
+
poll: pollHandle !== null, chatter: chatterHandle !== null,
|
|
2804
|
+
wander: wanderHandle !== null, paused,
|
|
2805
|
+
},
|
|
2694
2806
|
/* 今日轮次:不用猜"她为什么突然得意",数字就在这里 */
|
|
2695
2807
|
runsToday: { day: runsToday.day, count: runsToday.count, said: runsToday.said.slice() },
|
|
2696
2808
|
/* 她记着你:一起多少天、习惯放在哪、常穿哪套、已经说过哪些 */
|
package/lib/client.template.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.30.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -70,6 +70,7 @@ const WHATS_NEW = {
|
|
|
70
70
|
'1.27.0': '她饿了会捧出一碗白米饭,还有第八个表情。',
|
|
71
71
|
'1.28.0': '菜单分成折叠分组,另外多了「关于她」和「动作一览」两个弹窗。',
|
|
72
72
|
'1.29.0': '夜深了她会劝你去睡;你走开再回来她记得;连续来了几天她也记着。',
|
|
73
|
+
'1.30.0': '会陪你专注计时了;页面藏起来时她会安静下来,不再空转。',
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -165,6 +166,7 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
165
166
|
careAfterMs: 5400000, // 连续活动这么久之后提醒你起来动动(90 分钟;0 = 关)
|
|
166
167
|
hungry: true, // 干饭:她饿了会说
|
|
167
168
|
hungerMs: 2700000, // 连续活动这么久就该吃一碗了(45 分钟;0 = 关)
|
|
169
|
+
focusMinutes: 25, // 菜单里「专注 N 分钟」用的默认时长
|
|
168
170
|
night: true, // 深夜劝睡
|
|
169
171
|
bedtimeHour: 23, // 几点算深夜(0-23)
|
|
170
172
|
wakeHour: 5, // 几点算天亮(0-23);bedtime > wake 时窗口跨零点
|
|
@@ -273,6 +275,19 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
273
275
|
'算力不够了,得补点碳水。', '电量告急,切待机。', '你要不要也来一口?',
|
|
274
276
|
'这碗不算加餐,算基础设施。', '吃饱了才有力气摸鱼。', '饭碗见底了,该续命了。', '我数着米粒呢。',
|
|
275
277
|
],
|
|
278
|
+
/* 专注计时器。她是陪着你干活的,所以"替你画出一段时间的边界"比再多说几句更实在。 */
|
|
279
|
+
focusStart: [
|
|
280
|
+
'{n} 分钟。开始吧。', '好,{n} 分钟,我盯着你。', '计时开始,{n} 分钟。', '{n} 分钟不看别的。',
|
|
281
|
+
'这段时间归你了。{n} 分钟。',
|
|
282
|
+
],
|
|
283
|
+
focusDone: [
|
|
284
|
+
'{n} 分钟到了。收工。', '{n} 分钟,干完了?', '时间到。{n} 分钟,去喝口水。', '{n} 分钟到了 —— 站起来伸个懒腰。',
|
|
285
|
+
'这一轮 {n} 分钟,结束了。',
|
|
286
|
+
],
|
|
287
|
+
focusStop: ['行,那就先到这儿。', '不专注了?那我歇着。', '计时取消了。', '好吧,下次再说。'],
|
|
288
|
+
focusLeft: [
|
|
289
|
+
'还有 {n} 分钟。', '{n} 分钟,快了。', '还剩 {n} 分钟,别分心。', '还有 {n} 分钟呢,盯着屏幕。',
|
|
290
|
+
],
|
|
276
291
|
/* 深夜劝睡:23 点后一种语气,凌晨 1 点后另一种 —— 后者是社区设定里"嘴硬心软"那一档。
|
|
277
292
|
分档按 bedtimeHour + 2 推导,所以改了作息这套分档也跟着走。 */
|
|
278
293
|
nightLate: [
|
|
@@ -399,6 +414,7 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
399
414
|
careAfterMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
400
415
|
hungry: { kind: 'bool' },
|
|
401
416
|
hungerMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
417
|
+
focusMinutes: { kind: 'num', lo: 1, hi: 600 },
|
|
402
418
|
night: { kind: 'bool' },
|
|
403
419
|
bedtimeHour: { kind: 'num', lo: 0, hi: 23 },
|
|
404
420
|
wakeHour: { kind: 'num', lo: 0, hi: 23 },
|
|
@@ -1591,6 +1607,13 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1591
1607
|
return
|
|
1592
1608
|
}
|
|
1593
1609
|
|
|
1610
|
+
/* 专注期间不让稳态把她从"干活中"挪走 —— 她在盯着你,这是这个功能的全部意义。
|
|
1611
|
+
位置在这里而不是函数开头:久坐、干饭、劝睡该响还得响。 */
|
|
1612
|
+
if (focusUntil > 0) {
|
|
1613
|
+
if (mood !== 'alert') setMood('alert')
|
|
1614
|
+
return
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1594
1617
|
// 临时状态(happy / worried)在自己的计时器到期前不能被稳态赋值覆盖 ——
|
|
1595
1618
|
// 否则刚摆出的表情在同一个 poll 周期里就被 setMood('idle') 抹掉了。
|
|
1596
1619
|
if (mood === 'sleep' || mood === 'happy' || mood === 'poked' || mood === 'proud' || mood === 'care'
|
|
@@ -1600,10 +1623,62 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1600
1623
|
setMood((busy || typing) ? 'alert' : 'idle')
|
|
1601
1624
|
}
|
|
1602
1625
|
|
|
1626
|
+
/* ---- 专注计时器 ---------------------------------------------------------
|
|
1627
|
+
桌面陪伴插件最该有的实用功能:替你画出一段时间的边界。
|
|
1628
|
+
专注期间她保持"干活中"的姿势(poll 里给她一个 hold —— 否则稳态会把她挪走),
|
|
1629
|
+
到点收工庆祝;戳她一下就是问"还剩多久"。
|
|
1630
|
+
她**不碰应用**:不改输入框、不发消息,只是陪着 + 计时。 */
|
|
1631
|
+
let focusUntil = 0
|
|
1632
|
+
let focusHandle = null
|
|
1633
|
+
let focusMinutes = 0
|
|
1634
|
+
let focusDone = 0
|
|
1635
|
+
|
|
1636
|
+
const focusLeftMs = () => Math.max(0, focusUntil - nowMs())
|
|
1637
|
+
const focusState = () => (focusUntil > 0
|
|
1638
|
+
? { active: true, minutes: focusMinutes, leftMs: Math.round(focusLeftMs()), leftMin: Math.max(1, Math.ceil(focusLeftMs() / 60000)), done: focusDone }
|
|
1639
|
+
: { active: false, minutes: 0, leftMs: 0, leftMin: 0, done: focusDone })
|
|
1640
|
+
|
|
1641
|
+
const stopFocus = (silent) => {
|
|
1642
|
+
if (focusUntil === 0 && focusHandle === null) return false
|
|
1643
|
+
focusUntil = 0
|
|
1644
|
+
focusMinutes = 0
|
|
1645
|
+
if (focusHandle) { clock.cancel(focusHandle); focusHandle = null }
|
|
1646
|
+
if (silent !== true) {
|
|
1647
|
+
wake(true)
|
|
1648
|
+
flashHappy(pick(LINES.focusStop))
|
|
1649
|
+
}
|
|
1650
|
+
return true
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const finishFocus = () => {
|
|
1654
|
+
const minutes = focusMinutes
|
|
1655
|
+
focusUntil = 0
|
|
1656
|
+
focusHandle = null
|
|
1657
|
+
focusMinutes = 0
|
|
1658
|
+
focusDone++
|
|
1659
|
+
wake(true)
|
|
1660
|
+
flashProud(pick(LINES.focusDone).split('{n}').join(String(minutes)))
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const startFocus = (minutes) => {
|
|
1664
|
+
const mins = clamp(Math.round(Number(minutes) || cfg.focusMinutes), 1, 600)
|
|
1665
|
+
stopFocus(true)
|
|
1666
|
+
focusMinutes = mins
|
|
1667
|
+
focusUntil = nowMs() + mins * 60000
|
|
1668
|
+
wake(true)
|
|
1669
|
+
say(pick(LINES.focusStart).split('{n}').join(String(mins)))
|
|
1670
|
+
focusHandle = clock.after(mins * 60000, () => { if (focusUntil > 0) finishFocus() })
|
|
1671
|
+
return focusState()
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/* 没人在看的时候别空转:页面藏起来就停掉轮询、絮叨、踱步三个定时器。
|
|
1675
|
+
它们本来就是"给看得见的人看的",停下来是纯赚(CPU 与电量)。 */
|
|
1676
|
+
let paused = false
|
|
1677
|
+
|
|
1603
1678
|
let chatterHandle = null
|
|
1604
1679
|
const syncChatter = () => {
|
|
1605
1680
|
if (chatterHandle) { clock.cancel(chatterHandle); chatterHandle = null }
|
|
1606
|
-
if (cfg.chatterMs <= 0) return
|
|
1681
|
+
if (cfg.chatterMs <= 0 || paused) return
|
|
1607
1682
|
chatterHandle = clock.every(cfg.chatterMs, () => {
|
|
1608
1683
|
if (destroyed || mood !== 'idle') return
|
|
1609
1684
|
if (agentIsBusy()) return
|
|
@@ -1614,7 +1689,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1614
1689
|
let pollHandle = null
|
|
1615
1690
|
const syncPoll = () => {
|
|
1616
1691
|
if (pollHandle) { clock.cancel(pollHandle); pollHandle = null }
|
|
1617
|
-
if (cfg.reactions) pollHandle = clock.every(POLL_MS, poll)
|
|
1692
|
+
if (cfg.reactions && !paused) pollHandle = clock.every(POLL_MS, poll)
|
|
1618
1693
|
}
|
|
1619
1694
|
syncPoll()
|
|
1620
1695
|
syncChatter()
|
|
@@ -1641,7 +1716,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1641
1716
|
let wanderHandle = null
|
|
1642
1717
|
const syncWander = () => {
|
|
1643
1718
|
if (wanderHandle) { clock.cancel(wanderHandle); wanderHandle = null }
|
|
1644
|
-
if (!cfg.wander || cfg.wanderRange <= 0) return
|
|
1719
|
+
if (!cfg.wander || cfg.wanderRange <= 0 || paused) return
|
|
1645
1720
|
wanderHandle = clock.every(cfg.wanderMs, () => {
|
|
1646
1721
|
if (destroyed || hidden || mood !== 'idle' || start !== null) return
|
|
1647
1722
|
if (agentIsBusy()) return
|
|
@@ -1771,6 +1846,12 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1771
1846
|
if (now - pokeWindowStart > POKE_WINDOW_MS) { pokeWindowStart = now; pokeStreak = 0 }
|
|
1772
1847
|
pokeStreak++
|
|
1773
1848
|
wake(true)
|
|
1849
|
+
/* 专注中戳她是在问进度,不算戳着玩(也就不会升级成"抗议")。 */
|
|
1850
|
+
if (focusUntil > 0) {
|
|
1851
|
+
const left = Math.max(1, Math.ceil(focusLeftMs() / 60000))
|
|
1852
|
+
say(pick(LINES.focusLeft).split('{n}').join(String(left)))
|
|
1853
|
+
return
|
|
1854
|
+
}
|
|
1774
1855
|
if (pokeStreak >= POKE_ANNOYED_AT) {
|
|
1775
1856
|
pokeStreak = 0
|
|
1776
1857
|
flashPoked(pick(LINES.poked))
|
|
@@ -2047,6 +2128,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2047
2128
|
para('DeepSeek娘 —— DSH 界面上的浮动桌宠。她会跟着你的状态换表情:你打字她在忙,你闲着她在晃,你跑完一轮她替你高兴。'),
|
|
2048
2129
|
para('她的样子照社区规范来:深蓝色渐变长直发、头顶一个大圆环呆毛、两片鲸鳍耳、女仆装。'),
|
|
2049
2130
|
para('白米饭是算力的硬通货 —— 所以她每隔一阵会捧碗出来补点碳水。'),
|
|
2131
|
+
para('她还能陪你专注计时:菜单里开一段 25 或 45 分钟,期间她保持"干活中",戳她一下是问还剩多久,到点她替你收工。'),
|
|
2050
2132
|
para('这一版:' + (note || VERSION)),
|
|
2051
2133
|
para('形象与设定参考社区项目 Neko3000/deepseek-whalechan(设定文档 CC BY-NC-SA 4.0,原作者 B站 ZipZipPipe、上善无形)。'),
|
|
2052
2134
|
dialogFoot('非官方同人作品,与 DeepSeek 官方无关', 'v' + VERSION),
|
|
@@ -2078,6 +2160,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2078
2160
|
children: [
|
|
2079
2161
|
{ label: '说句话', run: () => { wake(true); say(pick(LINES.click)) } },
|
|
2080
2162
|
{ label: '睡一会儿', run: () => setMood('sleep') },
|
|
2163
|
+
{ label: '专注 ' + cfg.focusMinutes + ' 分钟', run: () => startFocus(cfg.focusMinutes) },
|
|
2164
|
+
{ label: '专注 45 分钟', run: () => startFocus(45) },
|
|
2165
|
+
/* 只有专注中才出现"结束专注" —— 平时的菜单不该多一个按不动的条目 */
|
|
2166
|
+
...(focusUntil > 0 ? [{ label: '结束专注', run: () => stopFocus(false) }] : []),
|
|
2081
2167
|
...TOGGLES.map((t) => ({
|
|
2082
2168
|
label: (cfg[t.key] === true ? '✓ ' : ' ') + t.label,
|
|
2083
2169
|
run: () => applyConfig({ [t.key]: cfg[t.key] !== true }),
|
|
@@ -2278,7 +2364,22 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2278
2364
|
const onVisibility = () => {
|
|
2279
2365
|
let state = 'visible'
|
|
2280
2366
|
try { state = document.visibilityState === 'hidden' ? 'hidden' : 'visible' } catch (e) { state = 'visible' }
|
|
2281
|
-
if (state === 'hidden') {
|
|
2367
|
+
if (state === 'hidden') {
|
|
2368
|
+
awayAt = nowMs()
|
|
2369
|
+
/* 没人在看,停掉三个"给看得见的人看"的定时器 */
|
|
2370
|
+
paused = true
|
|
2371
|
+
syncPoll()
|
|
2372
|
+
syncChatter()
|
|
2373
|
+
syncWander()
|
|
2374
|
+
return
|
|
2375
|
+
}
|
|
2376
|
+
/* 回来了:先恢复再算问候 —— 恢复是必须的,问候才是锦上添花 */
|
|
2377
|
+
if (paused) {
|
|
2378
|
+
paused = false
|
|
2379
|
+
syncPoll()
|
|
2380
|
+
syncChatter()
|
|
2381
|
+
syncWander()
|
|
2382
|
+
}
|
|
2282
2383
|
if (awayAt === 0) return
|
|
2283
2384
|
const away = nowMs() - awayAt
|
|
2284
2385
|
awayAt = 0
|
|
@@ -2512,6 +2613,10 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2512
2613
|
showUpdate,
|
|
2513
2614
|
checkForUpdate,
|
|
2514
2615
|
/* 弹窗与折叠分组:菜单结构变了之后,测试要能问"现在开着哪个弹窗 / 哪个分组"。 */
|
|
2616
|
+
/* 专注计时器:脚本与测试都能驱动 */
|
|
2617
|
+
startFocus,
|
|
2618
|
+
stopFocus,
|
|
2619
|
+
get focus() { return focusState() },
|
|
2515
2620
|
openAbout: () => aboutDialog(),
|
|
2516
2621
|
openActions: () => actionsDialog(),
|
|
2517
2622
|
closeDialog,
|
|
@@ -2640,6 +2745,13 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2640
2745
|
},
|
|
2641
2746
|
streak: streakOf(memory.days),
|
|
2642
2747
|
awaySince: Math.round(awayAt),
|
|
2748
|
+
/* 专注:不用猜"她为什么一直站在干活中" */
|
|
2749
|
+
focus: focusState(),
|
|
2750
|
+
/* 定时器是否挂着;页面藏起来时三个都该是 false */
|
|
2751
|
+
timers: {
|
|
2752
|
+
poll: pollHandle !== null, chatter: chatterHandle !== null,
|
|
2753
|
+
wander: wanderHandle !== null, paused,
|
|
2754
|
+
},
|
|
2643
2755
|
/* 今日轮次:不用猜"她为什么突然得意",数字就在这里 */
|
|
2644
2756
|
runsToday: { day: runsToday.day, count: runsToday.count, said: runsToday.said.slice() },
|
|
2645
2757
|
/* 她记着你:一起多少天、习惯放在哪、常穿哪套、已经说过哪些 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/verify-wisp.mjs
CHANGED
|
@@ -3064,6 +3064,135 @@ head('3y-2. night nagging, coming back, and the streak')
|
|
|
3064
3064
|
active = keepBroken
|
|
3065
3065
|
}
|
|
3066
3066
|
|
|
3067
|
+
/* ============================== 3y-3. focus timer & hidden pause ========== */
|
|
3068
|
+
head('3y-3. the focus timer, and going quiet while the page is hidden')
|
|
3069
|
+
|
|
3070
|
+
{
|
|
3071
|
+
const watch = (h, ms, step = 250) => {
|
|
3072
|
+
const live = () => h.all('wisp-say').filter((el) => el.removed !== true).map((el) => el.textContent)
|
|
3073
|
+
const seen = new Set(live())
|
|
3074
|
+
for (let left = ms; left > 0; left -= step) {
|
|
3075
|
+
h.advance(Math.min(step, left), Math.min(step, 200))
|
|
3076
|
+
for (const t of live()) seen.add(t)
|
|
3077
|
+
}
|
|
3078
|
+
return [...seen]
|
|
3079
|
+
}
|
|
3080
|
+
const strip = (pool, n) => (pool ?? []).map((t) => t.split('{n}').join(String(n)))
|
|
3081
|
+
const moodOf = (h) => h.find('wisp-root').dataset.mood
|
|
3082
|
+
|
|
3083
|
+
/* ---- 专注计时器 -------------------------------------------------------- */
|
|
3084
|
+
const focus = createHarness({ timer: true, composerText: '' })
|
|
3085
|
+
const keepFocus = active
|
|
3086
|
+
active = focus
|
|
3087
|
+
focus.evaluate(clientSrc)
|
|
3088
|
+
focus.module().default.apply(focus.ctx, {
|
|
3089
|
+
reactions: true, wander: false, celebrate: false,
|
|
3090
|
+
careAfterMs: 0, hungerMs: 0, night: false, sleepAfterMs: 3600000,
|
|
3091
|
+
})
|
|
3092
|
+
focus.advance(1300, 100)
|
|
3093
|
+
const focusApi = focus.win.__wisp
|
|
3094
|
+
|
|
3095
|
+
check(focusApi.focus.active === false, 'no focus session at the start')
|
|
3096
|
+
check(focusApi.doctor().focus.active === false && focusApi.doctor().focus.done === 0,
|
|
3097
|
+
'and doctor says so', JSON.stringify(focusApi.doctor().focus))
|
|
3098
|
+
|
|
3099
|
+
const started = focusApi.startFocus(25)
|
|
3100
|
+
check(started.active === true && started.minutes === 25 && started.leftMin === 25,
|
|
3101
|
+
'starting a session reports it back', JSON.stringify(started))
|
|
3102
|
+
check(focusApi.doctor().focus.active === true, 'doctor sees the session too')
|
|
3103
|
+
const startSaid = watch(focus, 1200)
|
|
3104
|
+
check(startSaid.some((t) => strip(linesInBundle()?.focusStart, 25).includes(t)),
|
|
3105
|
+
'and she says the session started', startSaid.slice(-1).join('') || '(没说)')
|
|
3106
|
+
|
|
3107
|
+
/* 专注期间即使她没在忙,姿势也该是"干活中" —— 稳态不许把她挪走 */
|
|
3108
|
+
focus.advance(120000, 5000)
|
|
3109
|
+
check(moodOf(focus) === 'alert', 'she holds the working pose for the whole session', moodOf(focus))
|
|
3110
|
+
|
|
3111
|
+
/* 戳她 = 问还剩多久,而且不算戳着玩 */
|
|
3112
|
+
const pokeOnce = () => {
|
|
3113
|
+
const b = focus.find('wisp-body')
|
|
3114
|
+
b.dispatch('pointerdown', {
|
|
3115
|
+
button: 0, clientX: focusApi.position.x + 260, clientY: focusApi.position.y + 400,
|
|
3116
|
+
preventDefault() {}, stopPropagation() {},
|
|
3117
|
+
})
|
|
3118
|
+
focus.win.dispatch('pointerup', {})
|
|
3119
|
+
focus.advance(200, 50)
|
|
3120
|
+
}
|
|
3121
|
+
/* 24 分钟时戳她 —— 报的应该是"还剩 24 分钟"这类的话 */
|
|
3122
|
+
pokeOnce()
|
|
3123
|
+
const pokeSaid = watch(focus, 800)
|
|
3124
|
+
const leftPool = [].concat(strip(linesInBundle()?.focusLeft, 24), strip(linesInBundle()?.focusLeft, 23))
|
|
3125
|
+
check(pokeSaid.some((t) => leftPool.includes(t)),
|
|
3126
|
+
'poking her during a session asks how long is left', pokeSaid.slice(-2).join(' / ') || '(没说)')
|
|
3127
|
+
check(moodOf(focus) !== 'poked', 'and it does not count as poking for fun', moodOf(focus))
|
|
3128
|
+
|
|
3129
|
+
/* 到点:她说收工,状态回到非激活,计数 +1 */
|
|
3130
|
+
const doneSaid = watch(focus, 26 * 60000, 30000)
|
|
3131
|
+
check(focusApi.focus.active === false, 'the session ends on its own', JSON.stringify(focusApi.focus))
|
|
3132
|
+
check(focusApi.focus.done === 1, 'and the finished count goes up', String(focusApi.focus.done))
|
|
3133
|
+
check(doneSaid.some((t) => strip(linesInBundle()?.focusDone, 25).includes(t)),
|
|
3134
|
+
'and she tells you it is over', doneSaid.slice(-1).join('') || '(没说)')
|
|
3135
|
+
|
|
3136
|
+
/* 参数夹取:0 用默认时长,过大的被夹到 600 */
|
|
3137
|
+
check(focusApi.startFocus(0).minutes === focusApi.config.focusMinutes,
|
|
3138
|
+
'zero falls back to the configured default', String(focusApi.startFocus(0).minutes))
|
|
3139
|
+
check(focusApi.startFocus(9999).minutes === 600, 'an absurd duration is clamped', String(focusApi.focus.minutes))
|
|
3140
|
+
const stopSaid = watch(focus, 400)
|
|
3141
|
+
focusApi.stopFocus()
|
|
3142
|
+
const stopSaid2 = watch(focus, 600)
|
|
3143
|
+
void stopSaid
|
|
3144
|
+
check(focusApi.focus.active === false, 'stopFocus ends it', JSON.stringify(focusApi.focus))
|
|
3145
|
+
check([].concat(stopSaid, stopSaid2).some((t) => (linesInBundle()?.focusStop ?? []).includes(t)),
|
|
3146
|
+
'and she acknowledges the stop', [].concat(stopSaid, stopSaid2).slice(-1).join('') || '(没说)')
|
|
3147
|
+
check(focusApi.stopFocus() === false, 'stopping when nothing is running is a no-op')
|
|
3148
|
+
focus.win.__wisp.destroy()
|
|
3149
|
+
active = keepFocus
|
|
3150
|
+
|
|
3151
|
+
/* ---- 页面藏起来就暂停 -------------------------------------------------- */
|
|
3152
|
+
const quiet = createHarness({ timer: true, composerText: '' })
|
|
3153
|
+
const keepQuiet = active
|
|
3154
|
+
active = quiet
|
|
3155
|
+
quiet.evaluate(clientSrc)
|
|
3156
|
+
quiet.module().default.apply(quiet.ctx, {
|
|
3157
|
+
reactions: true, wander: true, celebrate: false,
|
|
3158
|
+
careAfterMs: 0, hungerMs: 0, night: false, sleepAfterMs: 3600000, backAfterMs: 60000,
|
|
3159
|
+
})
|
|
3160
|
+
quiet.advance(1300, 100)
|
|
3161
|
+
const quietApi = quiet.win.__wisp
|
|
3162
|
+
check(quietApi.doctor().timers.poll === true && quietApi.doctor().timers.paused === false,
|
|
3163
|
+
'while visible the poll is armed', JSON.stringify(quietApi.doctor().timers))
|
|
3164
|
+
|
|
3165
|
+
quiet.document.visibilityState = 'hidden'
|
|
3166
|
+
quiet.document.dispatch('visibilitychange', {})
|
|
3167
|
+
const timers = quietApi.doctor().timers
|
|
3168
|
+
check(timers.paused === true && timers.poll === false && timers.chatter === false && timers.wander === false,
|
|
3169
|
+
'hiding the page stops all three timers', JSON.stringify(timers))
|
|
3170
|
+
|
|
3171
|
+
/* 藏起来期间:来了一堆报错,她也不该有任何反应(轮询停了) */
|
|
3172
|
+
quiet.state.errors = 3
|
|
3173
|
+
quiet.state.busy = true
|
|
3174
|
+
quiet.advance(2000, 500)
|
|
3175
|
+
quiet.state.busy = false
|
|
3176
|
+
quiet.advance(20000, 2000)
|
|
3177
|
+
check(moodOf(quiet) !== 'worried', 'a hidden page gets no reaction at all', moodOf(quiet))
|
|
3178
|
+
check(quiet.all('wisp-say').filter((el) => el.removed !== true).length === 0,
|
|
3179
|
+
'and she stays completely quiet', String(quiet.all('wisp-say').length))
|
|
3180
|
+
|
|
3181
|
+
quiet.document.visibilityState = 'visible'
|
|
3182
|
+
quiet.document.dispatch('visibilitychange', {})
|
|
3183
|
+
const after = quietApi.doctor().timers
|
|
3184
|
+
check(after.paused === false && after.poll === true && after.wander === true,
|
|
3185
|
+
'coming back re-arms them', JSON.stringify(after))
|
|
3186
|
+
/* 回来之后同一批报错就该被看见了 */
|
|
3187
|
+
quiet.state.busy = true
|
|
3188
|
+
quiet.advance(1300, 100)
|
|
3189
|
+
quiet.state.busy = false
|
|
3190
|
+
quiet.advance(3000, 200)
|
|
3191
|
+
check(moodOf(quiet) === 'worried', 'and now the same errors do get a reaction', moodOf(quiet))
|
|
3192
|
+
quiet.win.__wisp.destroy()
|
|
3193
|
+
active = keepQuiet
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3067
3196
|
/* ================================================== 3z. generation audit ==== */
|
|
3068
3197
|
head('3z. every shipped sprite traces back to a text-only call')
|
|
3069
3198
|
|