@linxin666/dsh-pet 0.2.3 → 0.2.4
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 +64 -1
- package/README.zh.md +64 -1
- package/assets/decorations/whale/decoration.json +20 -0
- package/assets/decorations/whale/whale-frames.png +0 -0
- package/contracts/pet-manifest-v2.schema.json +281 -0
- package/contracts/status-decoration-v1.schema.json +144 -0
- package/contracts/voice-pack-v1.schema.json +234 -0
- package/lib/client.js +144 -18
- package/lib/client.js.map +1 -1
- package/lib/index.js +979 -70
- package/lib/types/chatter.d.ts +61 -3
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +71 -12
- package/lib/types/client/PetSettingsCard.d.ts +4 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +3 -1
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +103 -4
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/renderers/live2d.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d.js +13 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +9 -6
- package/lib/types/contracts/status-decoration.d.ts +85 -0
- package/lib/types/contracts/status-decoration.d.ts.map +1 -0
- package/lib/types/contracts/status-decoration.js +21 -0
- package/lib/types/decoration.d.ts +39 -0
- package/lib/types/decoration.d.ts.map +1 -0
- package/lib/types/decoration.js +210 -0
- package/lib/types/event-projection.d.ts +8 -3
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +9 -4
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/registry.d.ts +55 -2
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +198 -16
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +131 -3
- package/lib/types/service.d.ts +33 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +42 -3
- package/lib/types/voice-pack.d.ts +98 -0
- package/lib/types/voice-pack.d.ts.map +1 -0
- package/lib/types/voice-pack.js +384 -0
- package/package.json +11 -10
- package/src/chatter.test.ts +89 -2
- package/src/chatter.ts +120 -14
- package/src/client/PetSettingsCard.tsx +18 -0
- package/src/client/PetSprite.test.tsx +254 -12
- package/src/client/PetSprite.tsx +142 -25
- package/src/client/locales.ts +4 -0
- package/src/client/renderers/live2d.test.ts +23 -2
- package/src/client/renderers/live2d.ts +14 -1
- package/src/client/settings-form.ts +8 -6
- package/src/contracts/status-decoration.ts +78 -0
- package/src/decoration.test.ts +178 -0
- package/src/decoration.ts +220 -0
- package/src/event-projection.ts +10 -5
- package/src/index.ts +2 -0
- package/src/registry.test.ts +223 -0
- package/src/registry.ts +235 -15
- package/src/routes.ts +128 -3
- package/src/service.ts +59 -3
- package/src/voice-pack.test.ts +216 -0
- package/src/voice-pack.ts +413 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.linxin666.org/dsh-pet/voice-pack-v1.schema.json",
|
|
4
|
+
"title": "DSH Pet Voice Pack v1",
|
|
5
|
+
"description": "Pet-center voice pack (issue #677). Optional voice.json inside a pet directory, or the global $DSH_HOME/pets/.voice.json override — pure JSON content layering status / tool / whisper copy over the built-in pools, plus hover-panel chrome. Structure is fail-closed per file (a non-object root drops the pack); content is warn-and-drop per slot.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"voicePackVersion": {
|
|
13
|
+
"const": 1,
|
|
14
|
+
"description": "Optional; absent reads as v1."
|
|
15
|
+
},
|
|
16
|
+
"status": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"description": "Status copy pools keyed by the eleven StatusScene ids; each key replaces that scene's built-in pool.",
|
|
20
|
+
"properties": {
|
|
21
|
+
"prepare": {
|
|
22
|
+
"$ref": "#/definitions/pool"
|
|
23
|
+
},
|
|
24
|
+
"waiting": {
|
|
25
|
+
"$ref": "#/definitions/pool"
|
|
26
|
+
},
|
|
27
|
+
"thinking": {
|
|
28
|
+
"$ref": "#/definitions/pool"
|
|
29
|
+
},
|
|
30
|
+
"review": {
|
|
31
|
+
"$ref": "#/definitions/pool"
|
|
32
|
+
},
|
|
33
|
+
"toolResult": {
|
|
34
|
+
"$ref": "#/definitions/pool"
|
|
35
|
+
},
|
|
36
|
+
"done": {
|
|
37
|
+
"$ref": "#/definitions/pool"
|
|
38
|
+
},
|
|
39
|
+
"failed": {
|
|
40
|
+
"$ref": "#/definitions/pool"
|
|
41
|
+
},
|
|
42
|
+
"toolFailed": {
|
|
43
|
+
"$ref": "#/definitions/pool"
|
|
44
|
+
},
|
|
45
|
+
"maxTokens": {
|
|
46
|
+
"$ref": "#/definitions/pool"
|
|
47
|
+
},
|
|
48
|
+
"interrupted": {
|
|
49
|
+
"$ref": "#/definitions/pool"
|
|
50
|
+
},
|
|
51
|
+
"blocked": {
|
|
52
|
+
"$ref": "#/definitions/pool"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"tools": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"additionalProperties": false,
|
|
59
|
+
"description": "Tool copy pools keyed by the seventeen ToolCategory ids; {tool} and {hint} interpolate.",
|
|
60
|
+
"properties": {
|
|
61
|
+
"read": {
|
|
62
|
+
"$ref": "#/definitions/pool"
|
|
63
|
+
},
|
|
64
|
+
"write": {
|
|
65
|
+
"$ref": "#/definitions/pool"
|
|
66
|
+
},
|
|
67
|
+
"edit": {
|
|
68
|
+
"$ref": "#/definitions/pool"
|
|
69
|
+
},
|
|
70
|
+
"shell": {
|
|
71
|
+
"$ref": "#/definitions/pool"
|
|
72
|
+
},
|
|
73
|
+
"grep": {
|
|
74
|
+
"$ref": "#/definitions/pool"
|
|
75
|
+
},
|
|
76
|
+
"find": {
|
|
77
|
+
"$ref": "#/definitions/pool"
|
|
78
|
+
},
|
|
79
|
+
"ls": {
|
|
80
|
+
"$ref": "#/definitions/pool"
|
|
81
|
+
},
|
|
82
|
+
"webSearch": {
|
|
83
|
+
"$ref": "#/definitions/pool"
|
|
84
|
+
},
|
|
85
|
+
"webFetch": {
|
|
86
|
+
"$ref": "#/definitions/pool"
|
|
87
|
+
},
|
|
88
|
+
"mcp": {
|
|
89
|
+
"$ref": "#/definitions/pool"
|
|
90
|
+
},
|
|
91
|
+
"memory": {
|
|
92
|
+
"$ref": "#/definitions/pool"
|
|
93
|
+
},
|
|
94
|
+
"subagent": {
|
|
95
|
+
"$ref": "#/definitions/pool"
|
|
96
|
+
},
|
|
97
|
+
"todo": {
|
|
98
|
+
"$ref": "#/definitions/pool"
|
|
99
|
+
},
|
|
100
|
+
"browser": {
|
|
101
|
+
"$ref": "#/definitions/pool"
|
|
102
|
+
},
|
|
103
|
+
"git": {
|
|
104
|
+
"$ref": "#/definitions/pool"
|
|
105
|
+
},
|
|
106
|
+
"ask": {
|
|
107
|
+
"$ref": "#/definitions/pool"
|
|
108
|
+
},
|
|
109
|
+
"generic": {
|
|
110
|
+
"$ref": "#/definitions/pool"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"toolRemaining": {
|
|
115
|
+
"$ref": "#/definitions/pool",
|
|
116
|
+
"description": "The parallel-tools count line pool; {n} interpolates the count."
|
|
117
|
+
},
|
|
118
|
+
"whispers": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"additionalProperties": false,
|
|
121
|
+
"description": "Murmur pools; each section replaces the built-in one as a whole. An explicit empty array mutes that channel.",
|
|
122
|
+
"properties": {
|
|
123
|
+
"generic": {
|
|
124
|
+
"$ref": "#/definitions/pool"
|
|
125
|
+
},
|
|
126
|
+
"rules": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"maxItems": 32,
|
|
129
|
+
"items": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"additionalProperties": false,
|
|
132
|
+
"required": [
|
|
133
|
+
"keywords",
|
|
134
|
+
"pool"
|
|
135
|
+
],
|
|
136
|
+
"properties": {
|
|
137
|
+
"keywords": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"maxItems": 16,
|
|
140
|
+
"items": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"maxLength": 40,
|
|
143
|
+
"minLength": 1
|
|
144
|
+
},
|
|
145
|
+
"description": "Lowercase substrings matched against the model's output."
|
|
146
|
+
},
|
|
147
|
+
"pool": {
|
|
148
|
+
"$ref": "#/definitions/pool"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"panel": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"description": "Hover-panel chrome; unset slots keep the client's i18n dictionary copy.",
|
|
159
|
+
"properties": {
|
|
160
|
+
"labels": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"properties": {
|
|
164
|
+
"feed": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"maxLength": 40
|
|
167
|
+
},
|
|
168
|
+
"rename": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"maxLength": 40
|
|
171
|
+
},
|
|
172
|
+
"hide": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"maxLength": 40
|
|
175
|
+
},
|
|
176
|
+
"confirm": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"maxLength": 40
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"stats": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": false,
|
|
185
|
+
"description": "{rank} / {n} / {points} interpolate the live values.",
|
|
186
|
+
"properties": {
|
|
187
|
+
"rank": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"maxLength": 80
|
|
190
|
+
},
|
|
191
|
+
"treats": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"maxLength": 80
|
|
194
|
+
},
|
|
195
|
+
"points": {
|
|
196
|
+
"type": "string",
|
|
197
|
+
"maxLength": 80
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"actions": {
|
|
202
|
+
"type": "array",
|
|
203
|
+
"uniqueItems": true,
|
|
204
|
+
"items": {
|
|
205
|
+
"enum": [
|
|
206
|
+
"feed",
|
|
207
|
+
"rename",
|
|
208
|
+
"hide"
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
"description": "Subset of actions to render in canonical order; absent = all three, [] = none."
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"definitions": {
|
|
217
|
+
"pool": {
|
|
218
|
+
"oneOf": [
|
|
219
|
+
{
|
|
220
|
+
"type": "string",
|
|
221
|
+
"maxLength": 160
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"type": "array",
|
|
225
|
+
"maxItems": 64,
|
|
226
|
+
"items": {
|
|
227
|
+
"type": "string",
|
|
228
|
+
"maxLength": 160
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -220,6 +220,78 @@ window.__ModuleLoader__.load({
|
|
|
220
220
|
return Math.max(0, Math.min(max, value));
|
|
221
221
|
}
|
|
222
222
|
/**
|
|
223
|
+
* The status decoration ornament (pet-center M5, #567). Renders the active
|
|
224
|
+
* phase's frame segment as a CSS-background strip at a compact bubble
|
|
225
|
+
* height; prefers-reduced-motion holds the segment's first frame, and a
|
|
226
|
+
* missing or undecodable asset simply paints nothing (CSS background
|
|
227
|
+
* failure) — the bubble text is never disturbed. The span is aria-hidden;
|
|
228
|
+
* the bubble keeps its own semantics untouched.
|
|
229
|
+
*/
|
|
230
|
+
function StatusOrnament(props) {
|
|
231
|
+
const { decoration, phase } = props;
|
|
232
|
+
const segment = decoration.phases[phase];
|
|
233
|
+
const shown = segment !== void 0 && segment !== "hide";
|
|
234
|
+
const segmentKey = segment !== void 0 && segment !== "hide" ? segment.from + ":" + segment.to : "none";
|
|
235
|
+
const spanRef = (0, react.useRef)(null);
|
|
236
|
+
const scale = 18 / decoration.cell.height;
|
|
237
|
+
const frameWidth = Math.round(decoration.cell.width * scale);
|
|
238
|
+
const stripWidth = decoration.columns * frameWidth;
|
|
239
|
+
const durationsKey = decoration.durations.join(",");
|
|
240
|
+
(0, react.useEffect)(() => {
|
|
241
|
+
if (segment === void 0 || segment === "hide") return;
|
|
242
|
+
const el = spanRef.current;
|
|
243
|
+
if (el === null) return;
|
|
244
|
+
const position = (index) => -index * frameWidth + "px 0px";
|
|
245
|
+
const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
246
|
+
el.style.backgroundPosition = position(segment.from);
|
|
247
|
+
if (reduceMotion) return;
|
|
248
|
+
let raf = 0;
|
|
249
|
+
let index = segment.from;
|
|
250
|
+
let elapsed = 0;
|
|
251
|
+
let last = performance.now();
|
|
252
|
+
const tick = (ts) => {
|
|
253
|
+
const delta = ts - last;
|
|
254
|
+
last = ts;
|
|
255
|
+
elapsed += delta;
|
|
256
|
+
const duration = decoration.durations[index] ?? 160;
|
|
257
|
+
if (elapsed >= duration) {
|
|
258
|
+
elapsed = 0;
|
|
259
|
+
if (index < segment.to) index += 1;
|
|
260
|
+
else if (decoration.loop) index = segment.from;
|
|
261
|
+
}
|
|
262
|
+
el.style.backgroundPosition = position(index);
|
|
263
|
+
if (!decoration.loop && index === segment.to) return;
|
|
264
|
+
raf = requestAnimationFrame(tick);
|
|
265
|
+
};
|
|
266
|
+
raf = requestAnimationFrame(tick);
|
|
267
|
+
return () => cancelAnimationFrame(raf);
|
|
268
|
+
}, [
|
|
269
|
+
shown,
|
|
270
|
+
segmentKey,
|
|
271
|
+
frameWidth,
|
|
272
|
+
decoration.loop,
|
|
273
|
+
durationsKey
|
|
274
|
+
]);
|
|
275
|
+
if (!shown) return null;
|
|
276
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
277
|
+
ref: spanRef,
|
|
278
|
+
"aria-hidden": "true",
|
|
279
|
+
"data-dsh-pet-decoration": decoration.id,
|
|
280
|
+
style: {
|
|
281
|
+
display: "inline-block",
|
|
282
|
+
width: frameWidth,
|
|
283
|
+
height: 18,
|
|
284
|
+
marginRight: 6,
|
|
285
|
+
verticalAlign: "middle",
|
|
286
|
+
flexShrink: 0,
|
|
287
|
+
backgroundImage: "url(" + decoration.entryUrl + ")",
|
|
288
|
+
backgroundSize: stripWidth + "px 18px",
|
|
289
|
+
backgroundRepeat: "no-repeat",
|
|
290
|
+
backgroundPosition: "0px 0px"
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
223
295
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
224
296
|
* with per-frame durations from the definition's tracks; the atlas image is
|
|
225
297
|
* loaded once and the background position is written straight to the sprite
|
|
@@ -253,6 +325,21 @@ window.__ModuleLoader__.load({
|
|
|
253
325
|
const rows = definition.rows;
|
|
254
326
|
const tracks = definition.tracks;
|
|
255
327
|
const sequences = definition.sequences;
|
|
328
|
+
const panel = definition.panel;
|
|
329
|
+
const panelLabel = (slot, i18n) => panel?.labels?.[slot] ?? i18n;
|
|
330
|
+
const panelStat = (slot, i18nKey, values) => {
|
|
331
|
+
const format = panel?.stats?.[slot] ?? props.t(i18nKey, values);
|
|
332
|
+
if (panel?.stats?.[slot] === void 0) return format;
|
|
333
|
+
const all = {
|
|
334
|
+
rank: snapshot?.affinity.rank ?? "?",
|
|
335
|
+
n: snapshot?.treats.stocked ?? 0,
|
|
336
|
+
points: snapshot?.affinity.points ?? 0
|
|
337
|
+
};
|
|
338
|
+
let text = format;
|
|
339
|
+
for (const [name, value] of Object.entries(all)) text = text.replaceAll("{" + name + "}", String(value));
|
|
340
|
+
return text;
|
|
341
|
+
};
|
|
342
|
+
const panelShows = (action) => panel?.actions === void 0 || panel.actions.includes(action);
|
|
256
343
|
(0, react.useEffect)(() => {
|
|
257
344
|
if (props.visual !== void 0) return;
|
|
258
345
|
let cancelled = false;
|
|
@@ -392,6 +479,7 @@ window.__ModuleLoader__.load({
|
|
|
392
479
|
const whisper = feedback === null ? snapshot?.whisper : void 0;
|
|
393
480
|
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== void 0 || whisper !== void 0;
|
|
394
481
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
482
|
+
const decoration = snapshot?.decoration;
|
|
395
483
|
(0, react.useEffect)(() => {
|
|
396
484
|
if (sessionBubbles.length <= 1) setStackPinned(false);
|
|
397
485
|
}, [sessionBubbles.length]);
|
|
@@ -481,14 +569,17 @@ window.__ModuleLoader__.load({
|
|
|
481
569
|
onPointerLeave: () => setStackPeek(false),
|
|
482
570
|
children: [visibleSessions.map((session, index) => {
|
|
483
571
|
const speaksWhisper = index === 0 && whisper !== void 0;
|
|
484
|
-
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.
|
|
572
|
+
const bubble = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
485
573
|
type: "button",
|
|
486
574
|
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, pet_module_css_default.bubbleClickable, speaksWhisper && pet_module_css_default.bubbleWhisper),
|
|
487
575
|
title: props.t("pet.openSessionHint"),
|
|
488
576
|
onClick: () => {
|
|
489
577
|
props.onOpenSession(session.sessionId);
|
|
490
578
|
},
|
|
491
|
-
children: speaksWhisper
|
|
579
|
+
children: [index === 0 && !speaksWhisper && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
580
|
+
decoration,
|
|
581
|
+
phase
|
|
582
|
+
}), speaksWhisper ? whisper : session.bubble]
|
|
492
583
|
}, speaksWhisper ? "whisper:" + whisper : session.sessionId);
|
|
493
584
|
if (index !== 0 || sessionBubbles.length <= 1) return bubble;
|
|
494
585
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -506,11 +597,14 @@ window.__ModuleLoader__.load({
|
|
|
506
597
|
children: stackOpen ? "×" : "+" + String(sessionBubbles.length - 1)
|
|
507
598
|
})]
|
|
508
599
|
}, "primary");
|
|
509
|
-
}), sessionBubbles.length === 0 && (statusBubble !== void 0 || whisper !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
600
|
+
}), sessionBubbles.length === 0 && (statusBubble !== void 0 || whisper !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
510
601
|
className: clsx(pet_module_css_default.bubble, pet_module_css_default.bubbleStatus, whisper !== void 0 && pet_module_css_default.bubbleWhisper),
|
|
511
602
|
role: "status",
|
|
512
603
|
"aria-live": "polite",
|
|
513
|
-
children: whisper
|
|
604
|
+
children: [whisper === void 0 && decoration !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusOrnament, {
|
|
605
|
+
decoration,
|
|
606
|
+
phase
|
|
607
|
+
}), whisper ?? statusBubble]
|
|
514
608
|
}, whisper === void 0 ? "status" : "whisper:" + whisper)]
|
|
515
609
|
}),
|
|
516
610
|
hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -556,7 +650,7 @@ window.__ModuleLoader__.load({
|
|
|
556
650
|
setRenaming(false);
|
|
557
651
|
}
|
|
558
652
|
},
|
|
559
|
-
children: props.t("pet.confirm")
|
|
653
|
+
children: panelLabel("confirm", props.t("pet.confirm"))
|
|
560
654
|
})]
|
|
561
655
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
562
656
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -566,29 +660,29 @@ window.__ModuleLoader__.load({
|
|
|
566
660
|
children: displayName
|
|
567
661
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
568
662
|
className: pet_module_css_default.statRank,
|
|
569
|
-
children:
|
|
663
|
+
children: panelStat("rank", "pet.rank", { rank: snapshot?.affinity.rank ?? "?" })
|
|
570
664
|
})]
|
|
571
665
|
}),
|
|
572
666
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
573
667
|
className: pet_module_css_default.rankRow,
|
|
574
668
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
575
669
|
className: pet_module_css_default.statTreats,
|
|
576
|
-
children:
|
|
670
|
+
children: panelStat("treats", "pet.treats", { n: snapshot?.treats.stocked ?? 0 })
|
|
577
671
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
578
672
|
className: pet_module_css_default.statPoints,
|
|
579
|
-
children:
|
|
673
|
+
children: panelStat("points", "pet.points", { points: snapshot?.affinity.points ?? 0 })
|
|
580
674
|
})]
|
|
581
675
|
}),
|
|
582
676
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
583
677
|
className: pet_module_css_default.actions,
|
|
584
678
|
children: [
|
|
585
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
679
|
+
panelShows("feed") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
586
680
|
type: "button",
|
|
587
681
|
className: pet_module_css_default.action,
|
|
588
682
|
onClick: props.onFeed,
|
|
589
|
-
children: props.t("pet.feed")
|
|
683
|
+
children: panelLabel("feed", props.t("pet.feed"))
|
|
590
684
|
}),
|
|
591
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
685
|
+
panelShows("rename") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
592
686
|
type: "button",
|
|
593
687
|
className: pet_module_css_default.action,
|
|
594
688
|
onClick: () => {
|
|
@@ -596,13 +690,13 @@ window.__ModuleLoader__.load({
|
|
|
596
690
|
setNameDraft(displayName);
|
|
597
691
|
setRenaming(true);
|
|
598
692
|
},
|
|
599
|
-
children: props.t("pet.rename")
|
|
693
|
+
children: panelLabel("rename", props.t("pet.rename"))
|
|
600
694
|
}),
|
|
601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
695
|
+
panelShows("hide") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
602
696
|
type: "button",
|
|
603
697
|
className: pet_module_css_default.action,
|
|
604
698
|
onClick: props.onHide,
|
|
605
|
-
children: props.t("pet.hide")
|
|
699
|
+
children: panelLabel("hide", props.t("pet.hide"))
|
|
606
700
|
})
|
|
607
701
|
]
|
|
608
702
|
})
|
|
@@ -917,6 +1011,14 @@ window.__ModuleLoader__.load({
|
|
|
917
1011
|
//#region src/client/renderers/live2d.ts
|
|
918
1012
|
/** The de-facto tap-motion group of Cubism sample models. */
|
|
919
1013
|
const TAP_GROUP = "TapBody";
|
|
1014
|
+
/**
|
|
1015
|
+
* Keep one screen-appropriate atlas LOD instead of asking Pixi for the
|
|
1016
|
+
* engine's default full mip chain. A user model can legitimately carry an
|
|
1017
|
+
* 8192px texture while the pet itself is only a few hundred pixels tall;
|
|
1018
|
+
* `single-auto` preserves the source for larger renders and generates one
|
|
1019
|
+
* downsampled atlas only when the effective on-screen scale warrants it.
|
|
1020
|
+
*/
|
|
1021
|
+
const TEXTURE_OPTIONS = { lod: "single-auto" };
|
|
920
1022
|
let vendorConfigured = false;
|
|
921
1023
|
/** Configure pixi extensions + the Cubism SDK once per page. */
|
|
922
1024
|
function configureOnce(vendor) {
|
|
@@ -995,7 +1097,8 @@ window.__ModuleLoader__.load({
|
|
|
995
1097
|
ctx.container.appendChild(pixiApp.canvas);
|
|
996
1098
|
const loaded = await vendor.Live2DModel.from(config.modelUrl, {
|
|
997
1099
|
autoHitTest: false,
|
|
998
|
-
autoFocus: false
|
|
1100
|
+
autoFocus: false,
|
|
1101
|
+
textureOptions: TEXTURE_OPTIONS
|
|
999
1102
|
});
|
|
1000
1103
|
if (disposed) {
|
|
1001
1104
|
pixiApp.destroy(true, { children: true });
|
|
@@ -1717,7 +1820,8 @@ window.__ModuleLoader__.load({
|
|
|
1717
1820
|
const valid = plan.filter((item) => item.run !== void 0);
|
|
1718
1821
|
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
1719
1822
|
const plannedWrites = valid.map((item) => item.op);
|
|
1720
|
-
const
|
|
1823
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1824
|
+
for (const item of plan) pending.set(item.field, this.staged.get(item.field));
|
|
1721
1825
|
this.saving = true;
|
|
1722
1826
|
this.failed = false;
|
|
1723
1827
|
this.failedReason = void 0;
|
|
@@ -1730,9 +1834,9 @@ window.__ModuleLoader__.load({
|
|
|
1730
1834
|
for (const field of result.fields) if (field.landed) landed.add(field.field);
|
|
1731
1835
|
} else this.failedReason = result.message;
|
|
1732
1836
|
} else for (const item of valid) if (await item.run()) landed.add(item.field);
|
|
1733
|
-
for (const field of
|
|
1837
|
+
for (const [field, before] of pending) if (landed.has(field) && this.staged.get(field) === before) this.staged.delete(field);
|
|
1734
1838
|
this.saving = false;
|
|
1735
|
-
this.failed = landed.size !==
|
|
1839
|
+
this.failed = landed.size !== pending.size;
|
|
1736
1840
|
this.publish();
|
|
1737
1841
|
}
|
|
1738
1842
|
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
@@ -1875,6 +1979,7 @@ window.__ModuleLoader__.load({
|
|
|
1875
1979
|
constructor(scope) {
|
|
1876
1980
|
this.form = new CardForm(scope, [
|
|
1877
1981
|
booleanField("enabled"),
|
|
1982
|
+
booleanField("decorationEnabled"),
|
|
1878
1983
|
booleanField("visible"),
|
|
1879
1984
|
numberField("size"),
|
|
1880
1985
|
numberField("right"),
|
|
@@ -1914,6 +2019,7 @@ window.__ModuleLoader__.load({
|
|
|
1914
2019
|
return {
|
|
1915
2020
|
...this.form.shell(),
|
|
1916
2021
|
enabled: this.form.field("enabled"),
|
|
2022
|
+
decorationEnabled: this.form.field("decorationEnabled"),
|
|
1917
2023
|
visible: this.form.field("visible"),
|
|
1918
2024
|
size: this.form.field("size"),
|
|
1919
2025
|
right: this.form.field("right"),
|
|
@@ -1984,6 +2090,22 @@ window.__ModuleLoader__.load({
|
|
|
1984
2090
|
props.resetField("enabled");
|
|
1985
2091
|
}
|
|
1986
2092
|
}),
|
|
2093
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
|
|
2094
|
+
id: "settings-pet-decoration",
|
|
2095
|
+
label: t("settings.decoration"),
|
|
2096
|
+
hint: t("settings.decorationHint"),
|
|
2097
|
+
inheritLabel: t("settings.inherit"),
|
|
2098
|
+
onLabel: t("settings.on"),
|
|
2099
|
+
offLabel: t("settings.off"),
|
|
2100
|
+
...fieldProps,
|
|
2101
|
+
...state.decorationEnabled,
|
|
2102
|
+
onEdit: (text) => {
|
|
2103
|
+
props.edit("decorationEnabled", text);
|
|
2104
|
+
},
|
|
2105
|
+
onReset: () => {
|
|
2106
|
+
props.resetField("decorationEnabled");
|
|
2107
|
+
}
|
|
2108
|
+
}),
|
|
1987
2109
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChoiceField, {
|
|
1988
2110
|
id: "settings-pet-pet",
|
|
1989
2111
|
label: t("settings.pet"),
|
|
@@ -2115,6 +2237,8 @@ window.__ModuleLoader__.load({
|
|
|
2115
2237
|
"settings.petHint": "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。",
|
|
2116
2238
|
"settings.enabled": "启用宠物",
|
|
2117
2239
|
"settings.enabledHint": "关闭后隐藏宠物并停止轮询,可在设置里重新启用。",
|
|
2240
|
+
"settings.decoration": "状态装饰",
|
|
2241
|
+
"settings.decorationHint": "在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。",
|
|
2118
2242
|
"settings.visible": "显示宠物",
|
|
2119
2243
|
"settings.visibleHint": "关闭后宠物隐藏,可从聊天输入区重新召唤。",
|
|
2120
2244
|
"settings.size": "大小(px)",
|
|
@@ -2166,6 +2290,8 @@ window.__ModuleLoader__.load({
|
|
|
2166
2290
|
"settings.petHint": "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.",
|
|
2167
2291
|
"settings.enabled": "Enable the pet",
|
|
2168
2292
|
"settings.enabledHint": "When off, the pet hides and polling stops; re-enable it here.",
|
|
2293
|
+
"settings.decoration": "Status decoration",
|
|
2294
|
+
"settings.decorationHint": "Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.",
|
|
2169
2295
|
"settings.visible": "Show the pet",
|
|
2170
2296
|
"settings.visibleHint": "When off, the pet hides; summon it again from the input row.",
|
|
2171
2297
|
"settings.size": "Size (px)",
|