@linxin666/dsh-pet 0.1.18 → 0.1.20
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.i18n.yaml +2 -2
- package/README.md +12 -5
- package/README.zh.md +12 -5
- package/lib/client.js +95 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +144 -31
- package/lib/invariant.js +2 -2
- package/lib/state-Diiq8vjk.js +394 -0
- package/lib/types/affinity.d.ts +39 -11
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +21 -7
- package/lib/types/client/PetDockEntry.d.ts +2 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -2
- package/lib/types/client/PetSettingsCard.d.ts +5 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +9 -2
- package/lib/types/client/PetSprite.d.ts +2 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +22 -15
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +45 -14
- package/lib/types/client/locales.d.ts +82 -80
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/settings-form.d.ts +8 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +15 -1
- package/lib/types/client/spritesheet.d.ts +1 -1
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +3 -12
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +3 -1
- package/lib/types/index.d.ts +6 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +15 -12
- package/lib/types/invariant.js +2 -2
- package/lib/types/ledger.d.ts +16 -1
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +31 -6
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/persist.js +3 -3
- package/lib/types/registry.d.ts +10 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +3 -0
- package/lib/types/remarks.d.ts +56 -0
- package/lib/types/remarks.d.ts.map +1 -0
- package/lib/types/remarks.js +157 -0
- package/lib/types/routes.js +2 -2
- package/lib/types/service.d.ts +44 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +100 -23
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +5 -5
- package/lib/types/treats.d.ts +3 -0
- package/lib/types/treats.d.ts.map +1 -1
- package/lib/types/treats.js +5 -2
- package/package.json +1 -1
- package/src/affinity.test.ts +19 -0
- package/src/affinity.ts +22 -7
- package/src/client/PetDockEntry.tsx +3 -0
- package/src/client/PetSettingsCard.tsx +8 -0
- package/src/client/PetSprite.test.tsx +89 -3
- package/src/client/PetSprite.tsx +36 -12
- package/src/client/index.ts +40 -11
- package/src/client/locales.ts +2 -0
- package/src/client/pet.module.css +52 -10
- package/src/client/settings-form.ts +15 -1
- package/src/client/spritesheet.ts +3 -13
- package/src/event-projection.ts +3 -1
- package/src/index.ts +16 -2
- package/src/ledger.test.ts +40 -9
- package/src/ledger.ts +33 -4
- package/src/mount-once.ts +48 -0
- package/src/persist.test.ts +1 -1
- package/src/registry.test.ts +27 -0
- package/src/registry.ts +12 -0
- package/src/remarks.test.ts +89 -0
- package/src/remarks.ts +177 -0
- package/src/service.ts +124 -18
- package/src/state.test.ts +9 -0
- package/src/state.ts +5 -5
- package/src/treats.test.ts +13 -11
- package/src/treats.ts +5 -2
- package/lib/state-CFyJv0sQ.js +0 -213
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: b8705d67cb561b581ecfad2d644e6d70e17cf6b1
|
|
6
|
+
README.zh.md: fb5bc5846a273a667c0f72adac1ecc1c9e0d5130
|
package/README.md
CHANGED
|
@@ -18,12 +18,13 @@ Re-implemented from the pet feature of the Codex desktop app, as an official DSH
|
|
|
18
18
|
| State animation | Official session activity → 9-state animation: `thinking → running`, `tool → running-right`, `review → review`, `waiting → waiting`, `done → jumping`, `failed → failed` |
|
|
19
19
|
| Head-pat interaction | Click the pet → bubble feedback + affinity +1 (10s cooldown) |
|
|
20
20
|
| Feeding | Hover panel 喂食 (Feed) → consumes 1 dried fish + affinity +5 (30s cooldown) |
|
|
21
|
-
| Treat economy | Dried-fish stock (cap 20): +1 every
|
|
22
|
-
| Affinity | +1 per round completed;
|
|
21
|
+
| Treat economy | Dried-fish stock (cap 20): +1 every 30 rounds of work, +1 every 300 minutes (5 hours) — 10x rarer than the original cadence |
|
|
22
|
+
| Affinity | +1 per round completed; 9 levels: 幼鲸 → 伙伴 → 挚友 → 深海羁绊 → 心有灵犀 → 传说羁绊 → 神话羁绊 → 永恒之契 → 鲸生共渡 (capped at 999,999,999) |
|
|
23
23
|
| Dragging | Hold and drag the pet to reposition; position persisted |
|
|
24
24
|
| Hide/Summon | Hover panel 隐藏 (Hide); after hiding, a 召唤{name} (Summon {name}) button appears |
|
|
25
|
-
|
|
|
26
|
-
|
|
|
25
|
+
| Witty remarks | Built-in remark library (10 lines per event) plus per-pet custom lines from a manifest remarks block — community PRs give their pet its own voice |
|
|
26
|
+
| Status bubbles | Each concurrently active top-level session gets its own bubble, stacked above the pet (up to 12); subagent sessions report through their spawning conversation and never occupy a bubble of their own; click a bubble to jump to its session; transient interaction feedback temporarily takes priority |
|
|
27
|
+
| Multi-session activity | The pet is host-global: the most recent meaningful event drives the sprite animation while every active top-level session reports its own state in a separate bubble; completed turns from every session (subagents included) contribute affinity and treats |
|
|
27
28
|
|
|
28
29
|
## Pet contract
|
|
29
30
|
|
|
@@ -40,11 +41,16 @@ A pet is a directory holding one `pet.json` manifest and one atlas image. Nothin
|
|
|
40
41
|
"frames": [6, 8, 8, 4, 5, 8, 6, 6, 6], // optional per-row frame counts
|
|
41
42
|
"tracks": { // optional per-track rhythm overrides
|
|
42
43
|
"idle": { "durations": [400, 400, 500, 400, 400, 500] }
|
|
44
|
+
},
|
|
45
|
+
"remarks": { // optional witty remarks (one line or a pool per slot)
|
|
46
|
+
"pet": "摸摸水獭的头~",
|
|
47
|
+
"feed": ["小鱼干真香", "再来一条~"]
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
```
|
|
46
51
|
|
|
47
52
|
- The atlas is an 8-column × 9-row grid (192×208 cells by default); rows are fixed in this order: 0 idle, 1 running-right, 2 running-left, 3 waving, 4 jumping, 5 failed, 6 waiting, 7 running, 8 review. Unused cells stay fully transparent.
|
|
53
|
+
- The optional remarks block overrides the reaction bubbles the pet speaks on pet / petCooldown / feed / feedCooldown / noTreats events. Each slot accepts one line or a pool of lines (cycled round-robin); a declared slot replaces the built-in pool for that slot only. This is how community contributions give their pet its own witty voice.
|
|
48
54
|
- `frames` counts the used columns per row (defaults to the hatch-pet contract table `[6, 8, 8, 4, 5, 8, 6, 6, 6]`); `tracks` overrides per-frame durations (cycled to the row's frame count), `loop`, and `fallback` per animation (defaults: everything loops; `jumping` and `failed` hold their last frame, then fall back to `idle`).
|
|
49
55
|
|
|
50
56
|
Where pets come from (later sources override earlier ones on id collision):
|
|
@@ -76,6 +82,7 @@ dsh-pet/
|
|
|
76
82
|
| |-- registry.ts # multi-pet contract: manifest scan + normalization (assets + custom pets)
|
|
77
83
|
| |-- service.ts # PetService: pet selection + state machine + affinity + config
|
|
78
84
|
| |-- state.ts # pet state machine: projected session activity → 9 state animations
|
|
85
|
+
| |-- remarks.ts # witty-remark library: built-in pools + per-pet overrides + picker
|
|
79
86
|
| |-- affinity.ts # affinity ledger (pure functions + cooldowns)
|
|
80
87
|
| |-- treats.ts # dried-fish stock ledger
|
|
81
88
|
| |-- persist.ts # persistence ($DSH_HOME/pet.json: selection + per-pet names, atomic write)
|
|
@@ -106,7 +113,7 @@ global React root (createRoot → document.body) <-- polling 2s -- pet-client (b
|
|
|
106
113
|
- **Status source**: the host projects official `turn/start`, `step/start`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, and `turn/end` events into waiting/thinking/tool/review/done/failed states. Optional legacy `activity/status` events remain a compatibility input.
|
|
107
114
|
- **Registry**: the host normalizes every manifest into a full render definition (geometry, per-row frame counts, per-track durations) and serves it over `/api/pet/pets`; the browser half renders any entry from that definition and carries no per-pet code.
|
|
108
115
|
- **Selection & naming**: `petId` lives in the settings namespace; per-pet names live in `pet.json` under `names`, edited through the hover-panel rename of the active pet. Legacy installs migrate their flat `name` onto the whale girl.
|
|
109
|
-
- **Multi-session semantics**: the API and browser mount are host-global and expose no foreground-session identity
|
|
116
|
+
- **Multi-session semantics**: the API and browser mount are host-global and expose no foreground-session identity. Concurrent sessions each keep their own projected state: the most recent meaningful event drives the sprite animation, while every active TOP-LEVEL session reports its stage in its own bubble (the state view's sessions list, capped at 12 most-recent). Subagent children are tracked for animation, rewards, and the single display bubble but render no bubble of their own, so N conversations never multiply into an N-plus-subagents stack. Every session's completed turns are still rewarded independently; disposing a session removes its bubble, and disposing the display session falls back to the most recent remaining one.
|
|
110
117
|
- **Mount point**: `document.body` (global React root, always shown: no session / new session / mid-session — the old mount point `conversation.composer.dock` only rendered in an active session, hiding the pet in new sessions); the component uses `createPortal` internally to render the global floating layer.
|
|
111
118
|
- **Rendering**: CSS sprite (background-position) per-frame animation; frame durations come from the served definition's tracks.
|
|
112
119
|
- **Communication**: browser ↔ host over the same-origin `/api/pet/*` JSON endpoints (state/pets/interact/set-visible/set-config/set-name/set-pet); each pet's atlas loads from `/pet/<id>/<spritesheetPath>` — the plugin self-sufficiently provides its own API and assets (the same pattern as dsh-remote-web-ui's `/api/pair`).
|
package/README.zh.md
CHANGED
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
| 状态动画 | 官方会话活动 → 9 态动画:`thinking → running`、`tool → running-right`、`review → review`、`waiting → waiting`、`done → jumping`、`failed → failed` |
|
|
19
19
|
| 摸头互动 | 点击宠物 → 气泡反馈 + 亲密度 +1(10s 冷却) |
|
|
20
20
|
| 喂食 | 悬浮面板 喂食 → 消耗 1 条小鱼干 + 亲密度 +5(30s 冷却) |
|
|
21
|
-
| 小鱼干经济 | 库存(上限 20):每工作
|
|
22
|
-
| 亲密度 | 每完成一轮 +1;
|
|
21
|
+
| 小鱼干经济 | 库存(上限 20):每工作 30 轮 +1,每 300 分钟(5 小时)+1 —— 获取难度为原来的 10 倍 |
|
|
22
|
+
| 亲密度 | 每完成一轮 +1;9 级:幼鲸 → 伙伴 → 挚友 → 深海羁绊 → 心有灵犀 → 传说羁绊 → 神话羁绊 → 永恒之契 → 鲸生共渡(上限 999,999,999) |
|
|
23
23
|
| 拖动 | 按住拖动宠物换位置;位置持久化 |
|
|
24
24
|
| 隐藏/召唤 | 悬浮面板 隐藏;隐藏后出现 召唤{name} 按钮 |
|
|
25
|
-
|
|
|
26
|
-
|
|
|
25
|
+
| 妙语库 | 内置默认妙语库(每类事件 10 句)+ 宠物 manifest 的 remarks 块自定义台词——社区 PR 可以给自家宠物配上专属妙语 |
|
|
26
|
+
| 状态气泡 | 每个并行活动的顶层会话各自一个气泡,在宠物头顶堆叠(最多 12 个);子代理会话借由其发起会话体现,不占用独立气泡;点击气泡跳转到对应会话;瞬时互动反馈临时优先 |
|
|
27
|
+
| 多会话活动 | 宠物是宿主全局的:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话用自己的气泡报告各自状态;每个会话(含子代理)完成的轮次都计入亲密度与小鱼干 |
|
|
27
28
|
|
|
28
29
|
## 宠物契约
|
|
29
30
|
|
|
@@ -40,11 +41,16 @@
|
|
|
40
41
|
"frames": [6, 8, 8, 4, 5, 8, 6, 6, 6], // 可选的每行帧数
|
|
41
42
|
"tracks": { // 可选的每轨节奏覆盖
|
|
42
43
|
"idle": { "durations": [400, 400, 500, 400, 400, 500] }
|
|
44
|
+
},
|
|
45
|
+
"remarks": { // 可选妙语(每个槽位一行或多行)
|
|
46
|
+
"pet": "摸摸水獭的头~",
|
|
47
|
+
"feed": ["小鱼干真香", "再来一条~"]
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
```
|
|
46
51
|
|
|
47
52
|
- 图集是 8 列 × 9 行网格(默认 192×208 单元格);行序固定:0 idle、1 running-right、2 running-left、3 waving、4 jumping、5 failed、6 waiting、7 running、8 review。未使用的格子保持全透明。
|
|
53
|
+
- 可选 remarks 块覆盖宠物在 pet(摸头)/ petCooldown / feed(喂食)/ feedCooldown / noTreats(缺粮)事件上的气泡台词。每个槽位接受一句或一组台词(轮询循环);声明过的槽位只替换该槽位的内置默认池。社区贡献就是这样给自家宠物配上专属妙语的。
|
|
48
54
|
- `frames` 记录每行用到的列数(缺省按 hatch-pet 契约表 `[6, 8, 8, 4, 5, 8, 6, 6, 6]`);`tracks` 按动画覆盖每帧时长(按该行帧数循环补足)、`loop` 与 `fallback`(默认:全部循环;`jumping` 与 `failed` 停在最后一帧后回到 `idle`)。
|
|
49
55
|
|
|
50
56
|
宠物的来源(后注册的来源在同 id 冲突时覆盖前者):
|
|
@@ -76,6 +82,7 @@ dsh-pet/
|
|
|
76
82
|
| |-- registry.ts # 多宠物契约:manifest 扫描 + 归一化(内置 + 自定义宠物)
|
|
77
83
|
| |-- service.ts # PetService:宠物选择 + 状态机 + 亲密度 + 配置
|
|
78
84
|
| |-- state.ts # 宠物状态机:会话活动投影 → 9 态动画
|
|
85
|
+
| |-- remarks.ts # 妙语库:内置默认池 + 每宠物覆盖 + 轮询选取
|
|
79
86
|
| |-- affinity.ts # 亲密度账本(纯函数 + 冷却)
|
|
80
87
|
| |-- treats.ts # 小鱼干库存账本
|
|
81
88
|
| |-- persist.ts # 持久化($DSH_HOME/pet.json:选择 + 每宠物名字,原子写入)
|
|
@@ -106,7 +113,7 @@ dsh-pet/
|
|
|
106
113
|
- **状态来源**:宿主把官方 `turn/start`、`step/start`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`turn/end` 事件投影为 waiting/thinking/tool/review/done/failed 状态。可选兼容 `activity/status` 事件仍作为输入。
|
|
107
114
|
- **注册表**:宿主把每份 manifest 归一化为完整渲染定义(几何、每行帧数、每轨时长),经 `/api/pet/pets` 下发;浏览器半区用该定义渲染任意条目,不携带任何宠物专属代码。
|
|
108
115
|
- **选择与命名**:`petId` 存于设置命名空间;每只宠物的名字存于 `pet.json` 的 `names`,通过悬浮面板对当前宠物改名编辑。旧版安装的平铺 `name` 自动迁移到鲸鱼娘名下。
|
|
109
|
-
- **多会话语义**:API
|
|
116
|
+
- **多会话语义**:API 与浏览器挂载都是宿主全局的,不暴露前台会话身份。并行会话各自保留投影状态:最近一次有意义事件驱动精灵动画,同时每个活动的顶层会话在独立气泡里报告自己的阶段(state 视图的 sessions 列表,最多保留最近 12 个)。子代理会话仍参与动画、计奖与单一显示气泡,但不占独立气泡位——N 个对话不会变成"N + 子代理数"的气泡堆。每个会话完成的轮次仍独立计奖;销毁会话移除它的气泡,销毁当前显示会话则回退到最近仍在活动的会话。
|
|
110
117
|
- **挂载点**:`document.body`(全局 React 根,始终显示:无会话 / 新会话 / 会话中都可见——旧挂载点 `conversation.composer.dock` 只在活动会话里渲染,新会话里宠物消失);组件内部用 `createPortal` 渲染全局浮层。
|
|
111
118
|
- **渲染**:CSS 精灵(background-position)逐帧动画;帧时长来自下发定义里的轨道表。
|
|
112
119
|
- **通信**:浏览器 ↔ 宿主走同源 `/api/pet/*` JSON 端点(state/pets/interact/set-visible/set-config/set-name/set-pet);每只宠物的图集从 `/pet/<id>/<spritesheetPath>` 加载——插件自给自足地提供自己的 API 与资源(与 dsh-remote-web-ui 的 `/api/pair` 同一模式)。
|
package/lib/client.js
CHANGED
|
@@ -62,21 +62,35 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
return n;
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
65
|
-
//#region src/
|
|
66
|
-
/**
|
|
67
|
-
function
|
|
65
|
+
//#region src/state.ts
|
|
66
|
+
/** The spritesheet row index for one animation track. */
|
|
67
|
+
function rowOf(animation) {
|
|
68
68
|
return {
|
|
69
|
-
idle: 0,
|
|
69
|
+
"idle": 0,
|
|
70
70
|
"running-right": 1,
|
|
71
71
|
"running-left": 2,
|
|
72
|
-
waving: 3,
|
|
73
|
-
jumping: 4,
|
|
74
|
-
failed: 5,
|
|
75
|
-
waiting: 6,
|
|
76
|
-
running: 7,
|
|
77
|
-
review: 8
|
|
72
|
+
"waving": 3,
|
|
73
|
+
"jumping": 4,
|
|
74
|
+
"failed": 5,
|
|
75
|
+
"waiting": 6,
|
|
76
|
+
"running": 7,
|
|
77
|
+
"review": 8
|
|
78
78
|
}[animation];
|
|
79
79
|
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/client/spritesheet.ts
|
|
82
|
+
/**
|
|
83
|
+
* Spritesheet geometry helpers — parameterized by the pet definition the
|
|
84
|
+
* host serves over '/api/pet/pets', so the browser half renders any registry
|
|
85
|
+
* entry without per-pet code. The per-track tables (frames, durations, loop,
|
|
86
|
+
* fallback) also come from the registry; these helpers only place frames,
|
|
87
|
+
* guard track lengths, and map the fixed 9-row animation contract.
|
|
88
|
+
* @module @linxin666/dsh-pet/client/spritesheet
|
|
89
|
+
*/
|
|
90
|
+
/** Row index of one animation track (the fixed 9-row contract). */
|
|
91
|
+
function rowOfTrack(animation) {
|
|
92
|
+
return rowOf(animation);
|
|
93
|
+
}
|
|
80
94
|
/**
|
|
81
95
|
* Background-position (px) of one frame cell within the scaled atlas.
|
|
82
96
|
* The background image is scaled by `scale` (element size ÷ cell size), and
|
|
@@ -107,7 +121,7 @@ window.__ModuleLoader__.load({
|
|
|
107
121
|
}
|
|
108
122
|
//#endregion
|
|
109
123
|
//#region \0dsh-css:packages/dsh-pet/src/client/pet.module.css.mjs
|
|
110
|
-
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#f472b6eb}.kz2Bea_bubbleFeed{background:#38bdf8eb}.kz2Bea_bubbleStatus{text-overflow:ellipsis;background:#0f172ae6;border:1px solid #7dd3fc80;max-width:min(280px,100vw - 24px);animation:none;overflow:hidden}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e2e8f0;backdrop-filter:blur(6px);background:#0f172aeb;border:1px solid #94a3b859;border-radius:10px;flex-direction:column;gap:6px;min-width:132px;padding:8px 10px;font-size:12px;display:flex;position:absolute;bottom:100%;box-shadow:0 4px 16px #00000059}.kz2Bea_panel:after{content:\"\";
|
|
124
|
+
const css$2 = ".kz2Bea_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.kz2Bea_sprite{image-rendering:auto;touch-action:none;position:relative}.kz2Bea_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards kz2Bea_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.kz2Bea_bubblePet{background:#f472b6eb}.kz2Bea_bubbleFeed{background:#38bdf8eb}.kz2Bea_bubbleStatus{text-overflow:ellipsis;background:#0f172ae6;border:1px solid #7dd3fc80;max-width:min(280px,100vw - 24px);animation:none;overflow:hidden}.kz2Bea_bubbleStack{pointer-events:auto;flex-direction:column;align-items:center;gap:4px;display:flex;position:absolute;bottom:100%}.kz2Bea_bubbleStack .kz2Bea_bubble{pointer-events:auto;margin-bottom:0;position:relative;bottom:auto}.kz2Bea_bubbleClickable{cursor:pointer;font:inherit;text-align:center;appearance:none;transition:filter .12s,box-shadow .12s}.kz2Bea_bubbleClickable:hover{filter:brightness(1.12)}.kz2Bea_bubbleClickable:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@keyframes kz2Bea_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.kz2Bea_panel{color:#e2e8f0;backdrop-filter:blur(6px);background:#0f172aeb;border:1px solid #94a3b859;border-radius:10px;flex-direction:column;gap:6px;min-width:132px;margin-right:8px;padding:8px 10px;font-size:12px;display:flex;position:absolute;bottom:0;right:100%;box-shadow:0 4px 16px #00000059}.kz2Bea_panel:after{content:\"\";width:14px;position:absolute;top:0;bottom:0;left:100%}.kz2Bea_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.kz2Bea_nameCell{font-weight:600}.kz2Bea_renameRow{align-items:center;gap:6px;display:flex}.kz2Bea_nameInput{color:#e2e8f0;background:#1e293be6;border:1px solid #7dd3fc80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.kz2Bea_nameInput:focus{border-color:#38bdf8;box-shadow:0 0 0 2px #38bdf873}.kz2Bea_actions{gap:6px;display:flex}.kz2Bea_action{cursor:pointer;color:#0f172a;background:linear-gradient(#7dd3fc,#38bdf8);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;transition:filter .12s,box-shadow .12s}.kz2Bea_action:hover{filter:brightness(1.08)}.kz2Bea_action:active{filter:brightness(.94)}.kz2Bea_action:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}.kz2Bea_summon{color:#7dd3fc;cursor:pointer;background:#0f172abf;border:1px dashed #7dd3fc99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.kz2Bea_summon:hover{color:#bae6fd;background:#0f172ae6;border-color:#7dd3fcf2}.kz2Bea_summon:active{color:#7dd3fc;border-color:#7dd3fccc}.kz2Bea_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@media (prefers-reduced-motion:reduce){.kz2Bea_bubble{opacity:1;animation:none}.kz2Bea_action,.kz2Bea_summon{transition:none}}";
|
|
111
125
|
const tagId$2 = "@linxin666/dsh-pet/pet.module.css";
|
|
112
126
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
113
127
|
const tag = document.createElement("style");
|
|
@@ -120,8 +134,10 @@ window.__ModuleLoader__.load({
|
|
|
120
134
|
"action": "kz2Bea_action",
|
|
121
135
|
"actions": "kz2Bea_actions",
|
|
122
136
|
"bubble": "kz2Bea_bubble",
|
|
137
|
+
"bubbleClickable": "kz2Bea_bubbleClickable",
|
|
123
138
|
"bubbleFeed": "kz2Bea_bubbleFeed",
|
|
124
139
|
"bubblePet": "kz2Bea_bubblePet",
|
|
140
|
+
"bubbleStack": "kz2Bea_bubbleStack",
|
|
125
141
|
"bubbleStatus": "kz2Bea_bubbleStatus",
|
|
126
142
|
"float": "kz2Bea_float",
|
|
127
143
|
"nameCell": "kz2Bea_nameCell",
|
|
@@ -194,7 +210,8 @@ window.__ModuleLoader__.load({
|
|
|
194
210
|
(0, react.useEffect)(() => {
|
|
195
211
|
const reduceMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
196
212
|
const row = rowOfTrack(animation);
|
|
197
|
-
const
|
|
213
|
+
const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
|
|
214
|
+
const leadCol = track.frames[0];
|
|
198
215
|
const lead = framePosition(cell, columns, row, leadCol, scaleRef.current);
|
|
199
216
|
if (spriteRef.current !== null) spriteRef.current.style.backgroundPosition = lead.x + "px " + lead.y + "px";
|
|
200
217
|
if (reduceMotion) return;
|
|
@@ -203,8 +220,6 @@ window.__ModuleLoader__.load({
|
|
|
203
220
|
const tick = (ts) => {
|
|
204
221
|
const delta = ts - last;
|
|
205
222
|
last = ts;
|
|
206
|
-
const row = rowOfTrack(animation);
|
|
207
|
-
const track = trimTrack(tracks[animation], rows[row] ?? tracks[animation].frames.length);
|
|
208
223
|
const st = frameRef.current;
|
|
209
224
|
if (st.track !== animation) {
|
|
210
225
|
st.track = animation;
|
|
@@ -288,7 +303,8 @@ window.__ModuleLoader__.load({
|
|
|
288
303
|
};
|
|
289
304
|
const spriteWidth = Math.round(cell.width * spriteScale);
|
|
290
305
|
const spriteHeight = Math.round(cell.height * spriteScale);
|
|
291
|
-
const
|
|
306
|
+
const sessionBubbles = snapshot?.sessions ?? [];
|
|
307
|
+
const statusBubble = feedback === null && sessionBubbles.length === 0 ? snapshot?.bubble : void 0;
|
|
292
308
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
293
309
|
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
294
310
|
ref: floatRef,
|
|
@@ -335,6 +351,18 @@ window.__ModuleLoader__.load({
|
|
|
335
351
|
className: clsx(pet_module_css_default.bubble, feedback.kind === "feed" ? pet_module_css_default.bubbleFeed : pet_module_css_default.bubblePet),
|
|
336
352
|
children: feedback.text
|
|
337
353
|
}, feedback.at),
|
|
354
|
+
feedback === null && sessionBubbles.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
355
|
+
className: pet_module_css_default.bubbleStack,
|
|
356
|
+
children: sessionBubbles.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
357
|
+
type: "button",
|
|
358
|
+
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable),
|
|
359
|
+
title: props.t("pet.openSessionHint"),
|
|
360
|
+
onClick: () => {
|
|
361
|
+
props.onOpenSession(session.sessionId);
|
|
362
|
+
},
|
|
363
|
+
children: session.bubble
|
|
364
|
+
}, session.sessionId))
|
|
365
|
+
}),
|
|
338
366
|
statusBubble !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
339
367
|
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus),
|
|
340
368
|
role: "status",
|
|
@@ -470,6 +498,7 @@ window.__ModuleLoader__.load({
|
|
|
470
498
|
onHide: props.hide,
|
|
471
499
|
onDragEnd: props.dragEnd,
|
|
472
500
|
onRename: props.rename,
|
|
501
|
+
onOpenSession: props.openSession,
|
|
473
502
|
onFeedbackDone: props.feedbackDone,
|
|
474
503
|
t: props.t
|
|
475
504
|
})
|
|
@@ -878,6 +907,9 @@ window.__ModuleLoader__.load({
|
|
|
878
907
|
specs;
|
|
879
908
|
staged = /* @__PURE__ */ new Map();
|
|
880
909
|
listeners = /* @__PURE__ */ new Set();
|
|
910
|
+
/** The scope subscription installed in the constructor; released by dispose(). */
|
|
911
|
+
disposeScope;
|
|
912
|
+
disposed = false;
|
|
881
913
|
saving = false;
|
|
882
914
|
failed = false;
|
|
883
915
|
failedReason;
|
|
@@ -885,10 +917,20 @@ window.__ModuleLoader__.load({
|
|
|
885
917
|
constructor(scope, specs) {
|
|
886
918
|
this.scope = scope;
|
|
887
919
|
this.specs = new Map(specs.map((spec) => [spec.field, spec]));
|
|
888
|
-
scope.subscribe(() => {
|
|
920
|
+
this.disposeScope = scope.subscribe(() => {
|
|
889
921
|
this.publish();
|
|
890
922
|
});
|
|
891
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Release the scope subscription and every bound store listener. The card
|
|
926
|
+
* must call this on teardown; later calls are no-ops.
|
|
927
|
+
*/
|
|
928
|
+
dispose() {
|
|
929
|
+
if (this.disposed) return;
|
|
930
|
+
this.disposed = true;
|
|
931
|
+
this.disposeScope();
|
|
932
|
+
this.listeners.clear();
|
|
933
|
+
}
|
|
892
934
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
893
935
|
bind(project) {
|
|
894
936
|
const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
|
|
@@ -1168,6 +1210,13 @@ window.__ModuleLoader__.load({
|
|
|
1168
1210
|
...this.form.actions()
|
|
1169
1211
|
};
|
|
1170
1212
|
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Release the card's scope subscription and bound stores; the slot
|
|
1215
|
+
* disposer calls this on teardown.
|
|
1216
|
+
*/
|
|
1217
|
+
dispose() {
|
|
1218
|
+
this.form.dispose();
|
|
1219
|
+
}
|
|
1171
1220
|
};
|
|
1172
1221
|
/**
|
|
1173
1222
|
* Render the pet settings card.
|
|
@@ -1315,6 +1364,7 @@ window.__ModuleLoader__.load({
|
|
|
1315
1364
|
"pet.treats": "小鱼干 ×{n}",
|
|
1316
1365
|
"pet.state.loading": "宠物正在赶来…",
|
|
1317
1366
|
"pet.state.error": "宠物迷路了(连接失败)",
|
|
1367
|
+
"pet.openSessionHint": "点击跳转到对应会话",
|
|
1318
1368
|
"settings.title": "宠物",
|
|
1319
1369
|
"settings.description": "选择宠物并调整它的显示布局。",
|
|
1320
1370
|
"settings.pet": "宠物",
|
|
@@ -1358,6 +1408,7 @@ window.__ModuleLoader__.load({
|
|
|
1358
1408
|
"pet.treats": "Treats ×{n}",
|
|
1359
1409
|
"pet.state.loading": "The pet is on its way…",
|
|
1360
1410
|
"pet.state.error": "The pet is lost (connection failed)",
|
|
1411
|
+
"pet.openSessionHint": "Click to jump to this session",
|
|
1361
1412
|
"settings.title": "Pet",
|
|
1362
1413
|
"settings.description": "Pick a pet and tune its display layout.",
|
|
1363
1414
|
"settings.pet": "Pet",
|
|
@@ -1436,13 +1487,14 @@ window.__ModuleLoader__.load({
|
|
|
1436
1487
|
const POLL_MS = 2e3;
|
|
1437
1488
|
/** Settings namespace the pet settings card edits (the Host plugin registers it). */
|
|
1438
1489
|
const PET_SETTINGS_NS = "pet";
|
|
1439
|
-
/** Required services. */
|
|
1490
|
+
/** Required services (sessions powers bubble-to-session navigation). */
|
|
1440
1491
|
const inject = [
|
|
1441
1492
|
"slots",
|
|
1442
1493
|
"locale",
|
|
1443
1494
|
"connection",
|
|
1444
1495
|
"settingsScope",
|
|
1445
|
-
"remote"
|
|
1496
|
+
"remote",
|
|
1497
|
+
"sessions"
|
|
1446
1498
|
];
|
|
1447
1499
|
/**
|
|
1448
1500
|
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
@@ -1461,14 +1513,20 @@ window.__ModuleLoader__.load({
|
|
|
1461
1513
|
return snapshot.status === "ready" ? snapshot.value?.enabled ?? true : snapshot.status === "unavailable";
|
|
1462
1514
|
};
|
|
1463
1515
|
const petSettings = new PetSettingsCardController(settingsScope);
|
|
1464
|
-
ctx.slots.inject("settings.section", () =>
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1516
|
+
ctx.slots.inject("settings.section", () => {
|
|
1517
|
+
const unregister = ctx.slots.register({
|
|
1518
|
+
name: "settings.section",
|
|
1519
|
+
id: "pet",
|
|
1520
|
+
order: 130,
|
|
1521
|
+
label: () => ctx.locale.bind("pet")("settings.title"),
|
|
1522
|
+
locale: "pet",
|
|
1523
|
+
inject: () => petSettings.inject()
|
|
1524
|
+
}, PetSettingsSection);
|
|
1525
|
+
return () => {
|
|
1526
|
+
petSettings.dispose();
|
|
1527
|
+
unregister();
|
|
1528
|
+
};
|
|
1529
|
+
});
|
|
1472
1530
|
let disposeUi;
|
|
1473
1531
|
const syncUi = () => {
|
|
1474
1532
|
if (enabled() && disposeUi === void 0) {
|
|
@@ -1478,14 +1536,19 @@ window.__ModuleLoader__.load({
|
|
|
1478
1536
|
const setState = petStore.actions.setState;
|
|
1479
1537
|
const setFeedback = petStore.actions.setFeedback;
|
|
1480
1538
|
let petsLoaded = false;
|
|
1539
|
+
let stateSeq = 0;
|
|
1481
1540
|
const pollNow = () => {
|
|
1482
1541
|
if (!petsLoaded) petApi.pets().then((list) => {
|
|
1483
1542
|
petsLoaded = true;
|
|
1484
1543
|
setPets(list);
|
|
1485
1544
|
}, () => {});
|
|
1545
|
+
const seq = stateSeq + 1;
|
|
1546
|
+
stateSeq = seq;
|
|
1486
1547
|
petApi.state().then((snapshot) => {
|
|
1548
|
+
if (seq !== stateSeq) return;
|
|
1487
1549
|
setSnapshot(snapshot);
|
|
1488
1550
|
}, () => {
|
|
1551
|
+
if (seq !== stateSeq) return;
|
|
1489
1552
|
setState("error", "pet.state transport error");
|
|
1490
1553
|
});
|
|
1491
1554
|
};
|
|
@@ -1513,9 +1576,15 @@ window.__ModuleLoader__.load({
|
|
|
1513
1576
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
1514
1577
|
};
|
|
1515
1578
|
}, "pet: poll");
|
|
1579
|
+
const sessions = ctx.sessions;
|
|
1580
|
+
const openSession = (sessionId) => {
|
|
1581
|
+
if (sessions.list.getSnapshot().byId[sessionId] === void 0) return;
|
|
1582
|
+
sessions.open(sessionId);
|
|
1583
|
+
};
|
|
1516
1584
|
const injected = () => ({
|
|
1517
1585
|
store: petStore,
|
|
1518
1586
|
ensure: pollNow,
|
|
1587
|
+
openSession,
|
|
1519
1588
|
pet: () => {
|
|
1520
1589
|
petApi.interact("pet").then((result) => {
|
|
1521
1590
|
setFeedback({
|