@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
package/lib/types/chatter.d.ts
CHANGED
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* plugin has always shown, so existing installs keep their wording until the
|
|
18
18
|
* scene cycles. No emoji anywhere (repository rule); ~ is the whale-girl's
|
|
19
19
|
* signature.
|
|
20
|
+
*
|
|
21
|
+
* Since pet-center M4 (issue #677) every pool is overridable through a
|
|
22
|
+
* {@link VoicePoolsProvider}: the built-in pools are the fallback layer, and
|
|
23
|
+
* voice packs (per-pet voice.json / the global .voice.json) layer their
|
|
24
|
+
* pools on top at draw time.
|
|
20
25
|
* @module @linxin666/dsh-pet/chatter
|
|
21
26
|
*/
|
|
22
27
|
/** Status copy scenes — the situations a session bubble can report. */
|
|
@@ -27,6 +32,10 @@ export declare const STATUS_ROTATE_MS = 4000;
|
|
|
27
32
|
export declare const STATUS_POOLS: Readonly<Record<StatusScene, readonly string[]>>;
|
|
28
33
|
/** Tool families for friendlier per-tool status copy. */
|
|
29
34
|
export type ToolCategory = 'read' | 'write' | 'edit' | 'shell' | 'grep' | 'find' | 'ls' | 'webSearch' | 'webFetch' | 'mcp' | 'memory' | 'subagent' | 'todo' | 'browser' | 'git' | 'ask' | 'generic';
|
|
35
|
+
/** Every status scene key, in declaration order (voice-pack key allow-list). */
|
|
36
|
+
export declare const STATUS_SCENES: readonly StatusScene[];
|
|
37
|
+
/** Every tool-family key, in declaration order (voice-pack key allow-list). */
|
|
38
|
+
export declare const TOOL_CATEGORIES: readonly ToolCategory[];
|
|
30
39
|
/** Map a raw tool name onto its copy family (working-activity style regexes). */
|
|
31
40
|
export declare function toolCategory(toolName: string): ToolCategory;
|
|
32
41
|
/**
|
|
@@ -52,16 +61,23 @@ export declare function toolArgHint(toolName: string, argumentsJson: string): st
|
|
|
52
61
|
* stretch keeps changing its wording.
|
|
53
62
|
*/
|
|
54
63
|
export declare class StatusVoice {
|
|
64
|
+
private readonly pools;
|
|
55
65
|
private readonly rotateMs;
|
|
56
66
|
private readonly counters;
|
|
57
67
|
private lastScene;
|
|
58
68
|
private lastLine;
|
|
59
69
|
private lastLineAt;
|
|
60
|
-
constructor(rotateMs?: number);
|
|
70
|
+
constructor(pools?: VoicePoolsProvider, rotateMs?: number);
|
|
61
71
|
/** Draw the next line of one pool, advancing its round-robin cursor. */
|
|
62
72
|
private draw;
|
|
63
73
|
/** Reuse the stable line or advance when the cadence elapsed. */
|
|
64
74
|
private voice;
|
|
75
|
+
/**
|
|
76
|
+
* A scene's effective pool: the voice-pack override when it carries lines,
|
|
77
|
+
* else the built-in pool. Empty overrides fall back rather than blank the
|
|
78
|
+
* bubble — a scene line always renders.
|
|
79
|
+
*/
|
|
80
|
+
private scenePool;
|
|
65
81
|
/** Status line for a phase scene. */
|
|
66
82
|
scene(scene: StatusScene, nowMs: number): string;
|
|
67
83
|
/** Status line for a tool call, with the real-argument hint when known. */
|
|
@@ -85,22 +101,64 @@ export declare const WHISPER_TTL_MS = 8000;
|
|
|
85
101
|
export declare const WHISPER_GENERIC_POOL: readonly string[];
|
|
86
102
|
/** Keyword-triggered whisper rules, most specific moods first. */
|
|
87
103
|
export declare const WHISPER_RULES: readonly WhisperRule[];
|
|
104
|
+
/**
|
|
105
|
+
* Voice-pack overrides (pet-center M4, issue #677): the content a voice
|
|
106
|
+
* pack can replace, one pool at a time. Every field is optional — missing
|
|
107
|
+
* keys inherit the built-in pools. Resolution happens at draw time through
|
|
108
|
+
* a provider function, so swapping pets (or editing the global file) re-
|
|
109
|
+
* voices live engines without rebuilding them.
|
|
110
|
+
*
|
|
111
|
+
* Override semantics:
|
|
112
|
+
* - status/tools/toolRemaining: a non-empty override replaces the built-in
|
|
113
|
+
* pool for that key; an empty override falls back to the built-in pool
|
|
114
|
+
* (a scene line always renders, so it can never be blanked).
|
|
115
|
+
* - whispers.generic / whispers.rules: the override REPLACES the built-in
|
|
116
|
+
* section; an empty array mutes that channel (ambient or keyword).
|
|
117
|
+
*/
|
|
118
|
+
export interface VoicePackOverrides {
|
|
119
|
+
/** Status copy pools by scene; each key replaces that scene's pool. */
|
|
120
|
+
status?: Partial<Record<StatusScene, readonly string[]>>;
|
|
121
|
+
/** Tool copy pools by family; each key replaces that family's pool. */
|
|
122
|
+
tools?: Partial<Record<ToolCategory, readonly string[]>>;
|
|
123
|
+
/** The parallel-tools count line pool ({n} interpolates the count). */
|
|
124
|
+
toolRemaining?: readonly string[];
|
|
125
|
+
/** Murmur pools; each section replaces the built-in one as a whole. */
|
|
126
|
+
whispers?: {
|
|
127
|
+
/** Ambient inner-whisper pool (empty mutes ambient whispers). */
|
|
128
|
+
generic?: readonly string[];
|
|
129
|
+
/** Ordered keyword rules (empty disables keyword-triggered whispers). */
|
|
130
|
+
rules?: readonly WhisperRule[];
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Read the current effective voice-pack overrides (draw-time resolution). */
|
|
134
|
+
export type VoicePoolsProvider = () => VoicePackOverrides;
|
|
135
|
+
/** The built-in voice pack: the plugin's default copy, unchanged since v1. */
|
|
136
|
+
export declare const BUILTIN_VOICE_PACK: VoicePackOverrides;
|
|
88
137
|
/**
|
|
89
138
|
* The murmur engine (碎碎念): watches the model's own output and lets the pet
|
|
90
139
|
* whisper its inner voice. Two ways to earn a whisper:
|
|
91
140
|
* - a keyword rule matches the fresh chunk text (themed whisper);
|
|
92
141
|
* - enough output volume flowed by without one (ambient whisper).
|
|
93
142
|
* A cooldown keeps whispers occasional; all picks are round-robin so tests
|
|
94
|
-
* reproduce exact lines.
|
|
143
|
+
* reproduce exact lines. The voice-pack provider (pet-center M4) swaps the
|
|
144
|
+
* pools at draw time, so a pet switch re-voices live engines in place.
|
|
95
145
|
*/
|
|
96
146
|
export declare class WhisperEngine {
|
|
147
|
+
private readonly pools;
|
|
97
148
|
private readonly cooldownMs;
|
|
98
149
|
private readonly charBudget;
|
|
99
150
|
private readonly counters;
|
|
100
151
|
private genericCursor;
|
|
101
152
|
private lastWhisperAt;
|
|
102
153
|
private charsSinceWhisper;
|
|
103
|
-
constructor(cooldownMs?: number, charBudget?: number);
|
|
154
|
+
constructor(pools?: VoicePoolsProvider, cooldownMs?: number, charBudget?: number);
|
|
155
|
+
/**
|
|
156
|
+
* Effective keyword rules: an override replaces the built-in rules as a
|
|
157
|
+
* whole; an explicit empty array disables keyword-triggered whispers.
|
|
158
|
+
*/
|
|
159
|
+
private rules;
|
|
160
|
+
/** Effective ambient pool (an explicit empty array mutes ambient whispers). */
|
|
161
|
+
private generic;
|
|
104
162
|
/**
|
|
105
163
|
* Feed one model-output chunk (reasoning or text). Returns the whisper to
|
|
106
164
|
* show, or undefined when the moment stays quiet.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatter.d.ts","sourceRoot":"","sources":["../../src/chatter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"chatter.d.ts","sourceRoot":"","sources":["../../src/chatter.ts"],"names":[],"mappings":"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"}
|
package/lib/types/chatter.js
CHANGED
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* plugin has always shown, so existing installs keep their wording until the
|
|
18
18
|
* scene cycles. No emoji anywhere (repository rule); ~ is the whale-girl's
|
|
19
19
|
* signature.
|
|
20
|
+
*
|
|
21
|
+
* Since pet-center M4 (issue #677) every pool is overridable through a
|
|
22
|
+
* {@link VoicePoolsProvider}: the built-in pools are the fallback layer, and
|
|
23
|
+
* voice packs (per-pet voice.json / the global .voice.json) layer their
|
|
24
|
+
* pools on top at draw time.
|
|
20
25
|
* @module @linxin666/dsh-pet/chatter
|
|
21
26
|
*/
|
|
22
27
|
/** While a scene persists, its copy advances on this cadence (ms). */
|
|
@@ -156,6 +161,16 @@ export const STATUS_POOLS = {
|
|
|
156
161
|
'蹲一个继续的指令',
|
|
157
162
|
],
|
|
158
163
|
};
|
|
164
|
+
/** Every status scene key, in declaration order (voice-pack key allow-list). */
|
|
165
|
+
export const STATUS_SCENES = [
|
|
166
|
+
'prepare', 'waiting', 'thinking', 'review', 'toolResult', 'done',
|
|
167
|
+
'failed', 'toolFailed', 'maxTokens', 'interrupted', 'blocked',
|
|
168
|
+
];
|
|
169
|
+
/** Every tool-family key, in declaration order (voice-pack key allow-list). */
|
|
170
|
+
export const TOOL_CATEGORIES = [
|
|
171
|
+
'read', 'write', 'edit', 'shell', 'grep', 'find', 'ls', 'webSearch',
|
|
172
|
+
'webFetch', 'mcp', 'memory', 'subagent', 'todo', 'browser', 'git', 'ask', 'generic',
|
|
173
|
+
];
|
|
159
174
|
/** Map a raw tool name onto its copy family (working-activity style regexes). */
|
|
160
175
|
export function toolCategory(toolName) {
|
|
161
176
|
const name = toolName.toLowerCase();
|
|
@@ -414,12 +429,16 @@ export function toolArgHint(toolName, argumentsJson) {
|
|
|
414
429
|
* stretch keeps changing its wording.
|
|
415
430
|
*/
|
|
416
431
|
export class StatusVoice {
|
|
432
|
+
pools;
|
|
417
433
|
rotateMs;
|
|
418
434
|
counters = new Map();
|
|
419
435
|
lastScene = '';
|
|
420
436
|
lastLine = '';
|
|
421
437
|
lastLineAt = Number.NEGATIVE_INFINITY;
|
|
422
|
-
constructor(rotateMs = STATUS_ROTATE_MS) {
|
|
438
|
+
constructor(pools = () => BUILTIN_VOICE_PACK, rotateMs = STATUS_ROTATE_MS) {
|
|
439
|
+
// Plain property assignment, not parameter properties: this module is
|
|
440
|
+
// imported by scripts/ under node's strip-only mode (pet-center M4).
|
|
441
|
+
this.pools = pools;
|
|
423
442
|
this.rotateMs = rotateMs;
|
|
424
443
|
}
|
|
425
444
|
/** Draw the next line of one pool, advancing its round-robin cursor. */
|
|
@@ -437,22 +456,35 @@ export class StatusVoice {
|
|
|
437
456
|
this.lastLineAt = nowMs;
|
|
438
457
|
return this.lastLine;
|
|
439
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* A scene's effective pool: the voice-pack override when it carries lines,
|
|
461
|
+
* else the built-in pool. Empty overrides fall back rather than blank the
|
|
462
|
+
* bubble — a scene line always renders.
|
|
463
|
+
*/
|
|
464
|
+
scenePool(scene) {
|
|
465
|
+
const override = this.pools().status?.[scene];
|
|
466
|
+
return override !== undefined && override.length > 0 ? override : STATUS_POOLS[scene];
|
|
467
|
+
}
|
|
440
468
|
/** Status line for a phase scene. */
|
|
441
469
|
scene(scene, nowMs) {
|
|
442
|
-
return this.voice('scene:' + scene, 'pool:' + scene,
|
|
470
|
+
return this.voice('scene:' + scene, 'pool:' + scene, this.scenePool(scene), nowMs);
|
|
443
471
|
}
|
|
444
472
|
/** Status line for a tool call, with the real-argument hint when known. */
|
|
445
473
|
tool(toolName, displayName, hint, nowMs) {
|
|
446
474
|
const category = toolCategory(toolName);
|
|
447
|
-
const
|
|
475
|
+
const override = this.pools().tools?.[category];
|
|
476
|
+
const pool = override !== undefined && override.length > 0 ? override : TOOL_POOLS[category];
|
|
477
|
+
const line = this.voice('tool:' + category, 'tool:' + category, pool, nowMs);
|
|
448
478
|
return line
|
|
449
|
-
.
|
|
450
|
-
.
|
|
479
|
+
.replaceAll('{tool}', displayName)
|
|
480
|
+
.replaceAll('{hint}', hint ?? displayName);
|
|
451
481
|
}
|
|
452
482
|
/** Status line while sibling tools still run (always reflects the count). */
|
|
453
483
|
toolRemaining(count, nowMs) {
|
|
454
|
-
|
|
455
|
-
|
|
484
|
+
const override = this.pools().toolRemaining;
|
|
485
|
+
const pool = override !== undefined && override.length > 0 ? override : TOOL_REMAINING_POOL;
|
|
486
|
+
return this.voice('toolRemaining', 'toolRemaining', pool, nowMs)
|
|
487
|
+
.replaceAll('{n}', String(count));
|
|
456
488
|
}
|
|
457
489
|
}
|
|
458
490
|
/** Murmur pacing: cooldown between whispers and output volume that earns one. */
|
|
@@ -701,25 +733,48 @@ export const WHISPER_RULES = [
|
|
|
701
733
|
],
|
|
702
734
|
},
|
|
703
735
|
];
|
|
736
|
+
/** The built-in voice pack: the plugin's default copy, unchanged since v1. */
|
|
737
|
+
export const BUILTIN_VOICE_PACK = {
|
|
738
|
+
status: STATUS_POOLS,
|
|
739
|
+
tools: TOOL_POOLS,
|
|
740
|
+
toolRemaining: TOOL_REMAINING_POOL,
|
|
741
|
+
whispers: { generic: WHISPER_GENERIC_POOL, rules: WHISPER_RULES },
|
|
742
|
+
};
|
|
704
743
|
/**
|
|
705
744
|
* The murmur engine (碎碎念): watches the model's own output and lets the pet
|
|
706
745
|
* whisper its inner voice. Two ways to earn a whisper:
|
|
707
746
|
* - a keyword rule matches the fresh chunk text (themed whisper);
|
|
708
747
|
* - enough output volume flowed by without one (ambient whisper).
|
|
709
748
|
* A cooldown keeps whispers occasional; all picks are round-robin so tests
|
|
710
|
-
* reproduce exact lines.
|
|
749
|
+
* reproduce exact lines. The voice-pack provider (pet-center M4) swaps the
|
|
750
|
+
* pools at draw time, so a pet switch re-voices live engines in place.
|
|
711
751
|
*/
|
|
712
752
|
export class WhisperEngine {
|
|
753
|
+
pools;
|
|
713
754
|
cooldownMs;
|
|
714
755
|
charBudget;
|
|
715
756
|
counters = new Map();
|
|
716
757
|
genericCursor = 0;
|
|
717
758
|
lastWhisperAt = Number.NEGATIVE_INFINITY;
|
|
718
759
|
charsSinceWhisper = 0;
|
|
719
|
-
constructor(cooldownMs = WHISPER_COOLDOWN_MS, charBudget = WHISPER_CHAR_BUDGET) {
|
|
760
|
+
constructor(pools = () => BUILTIN_VOICE_PACK, cooldownMs = WHISPER_COOLDOWN_MS, charBudget = WHISPER_CHAR_BUDGET) {
|
|
761
|
+
this.pools = pools;
|
|
720
762
|
this.cooldownMs = cooldownMs;
|
|
721
763
|
this.charBudget = charBudget;
|
|
722
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Effective keyword rules: an override replaces the built-in rules as a
|
|
767
|
+
* whole; an explicit empty array disables keyword-triggered whispers.
|
|
768
|
+
*/
|
|
769
|
+
rules() {
|
|
770
|
+
const override = this.pools().whispers?.rules;
|
|
771
|
+
return override === undefined ? WHISPER_RULES : override;
|
|
772
|
+
}
|
|
773
|
+
/** Effective ambient pool (an explicit empty array mutes ambient whispers). */
|
|
774
|
+
generic() {
|
|
775
|
+
const override = this.pools().whispers?.generic;
|
|
776
|
+
return override === undefined ? WHISPER_GENERIC_POOL : override;
|
|
777
|
+
}
|
|
723
778
|
/**
|
|
724
779
|
* Feed one model-output chunk (reasoning or text). Returns the whisper to
|
|
725
780
|
* show, or undefined when the moment stays quiet.
|
|
@@ -733,8 +788,9 @@ export class WhisperEngine {
|
|
|
733
788
|
return undefined;
|
|
734
789
|
}
|
|
735
790
|
const haystack = text.toLowerCase();
|
|
736
|
-
|
|
737
|
-
|
|
791
|
+
const rules = this.rules();
|
|
792
|
+
for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex += 1) {
|
|
793
|
+
const rule = rules[ruleIndex];
|
|
738
794
|
if (!rule.keywords.some(keyword => haystack.includes(keyword)))
|
|
739
795
|
continue;
|
|
740
796
|
const index = (this.counters.get(ruleIndex) ?? 0) % rule.pool.length;
|
|
@@ -744,7 +800,10 @@ export class WhisperEngine {
|
|
|
744
800
|
this.charsSinceWhisper += text.length;
|
|
745
801
|
if (this.charsSinceWhisper < this.charBudget)
|
|
746
802
|
return undefined;
|
|
747
|
-
const
|
|
803
|
+
const generic = this.generic();
|
|
804
|
+
if (generic.length === 0)
|
|
805
|
+
return undefined;
|
|
806
|
+
const line = generic[this.genericCursor % generic.length];
|
|
748
807
|
this.genericCursor += 1;
|
|
749
808
|
return this.speak(line, nowMs);
|
|
750
809
|
}
|
|
@@ -24,6 +24,8 @@ export interface PetSettings {
|
|
|
24
24
|
bottom?: number;
|
|
25
25
|
/** Selected pet id (a registry entry). */
|
|
26
26
|
petId?: string;
|
|
27
|
+
/** Status-decoration master switch (pet-center M5, #567). */
|
|
28
|
+
decorationEnabled?: boolean;
|
|
27
29
|
}
|
|
28
30
|
/** What the pet settings card renders. */
|
|
29
31
|
export interface PetSettingsCardState extends CardShell {
|
|
@@ -39,6 +41,8 @@ export interface PetSettingsCardState extends CardShell {
|
|
|
39
41
|
bottom: CardFieldState;
|
|
40
42
|
/** Selected pet. */
|
|
41
43
|
petId: CardFieldState;
|
|
44
|
+
/** Status-decoration master switch. */
|
|
45
|
+
decorationEnabled: CardFieldState;
|
|
42
46
|
/** Pet choices (registry ids + display names), loaded from the host. */
|
|
43
47
|
petChoices: readonly {
|
|
44
48
|
value: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAI1F,OAAO,EAAoD,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG1J,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qBAAqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"PetSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PetSettingsCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,7 @@ export class PetSettingsCardController {
|
|
|
33
33
|
constructor(scope) {
|
|
34
34
|
this.form = new CardForm(scope, [
|
|
35
35
|
booleanField('enabled'),
|
|
36
|
+
booleanField('decorationEnabled'),
|
|
36
37
|
booleanField('visible'),
|
|
37
38
|
numberField('size'),
|
|
38
39
|
numberField('right'),
|
|
@@ -76,6 +77,7 @@ export class PetSettingsCardController {
|
|
|
76
77
|
return {
|
|
77
78
|
...this.form.shell(),
|
|
78
79
|
enabled: this.form.field('enabled'),
|
|
80
|
+
decorationEnabled: this.form.field('decorationEnabled'),
|
|
79
81
|
visible: this.form.field('visible'),
|
|
80
82
|
size: this.form.field('size'),
|
|
81
83
|
right: this.form.field('right'),
|
|
@@ -115,7 +117,7 @@ export function PetSettingsCard(props) {
|
|
|
115
117
|
invalidLabel: t('settings.invalidNumber'),
|
|
116
118
|
disabled,
|
|
117
119
|
};
|
|
118
|
-
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), state.petDiagnostics.length === 0 ? null : (_jsxs("li", { className: sectionCss.diagnostics, "data-dsh-part": "diagnostics", children: [_jsx("span", { className: sectionCss.diagnosticsTitle, children: t('settings.diagnosticsTitle') }), _jsx("ul", { children: state.petDiagnostics.map((diagnostic, index) => (_jsx("li", { "data-level": diagnostic.level, children: diagnostic.message }, index))) })] })), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
|
|
120
|
+
return (_jsxs(PluginSettingsCard, { t: t, titleKey: "settings.title", descriptionKey: "settings.description", state: state, onSave: props.save, onDiscard: props.discard, alwaysOpen: true, children: [_jsx(BooleanField, { id: "settings-pet-enabled", label: t('settings.enabled'), hint: t('settings.enabledHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.enabled, onEdit: (text) => { props.edit('enabled', text); }, onReset: () => { props.resetField('enabled'); } }), _jsx(BooleanField, { id: "settings-pet-decoration", label: t('settings.decoration'), hint: t('settings.decorationHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.decorationEnabled, onEdit: (text) => { props.edit('decorationEnabled', text); }, onReset: () => { props.resetField('decorationEnabled'); } }), _jsx(ChoiceField, { id: "settings-pet-pet", label: t('settings.pet'), hint: t('settings.petHint'), inheritLabel: t('settings.inherit'), ...fieldProps, ...state.petId, choices: state.petChoices, onEdit: (text) => { props.edit('petId', text); }, onReset: () => { props.resetField('petId'); } }), state.petDiagnostics.length === 0 ? null : (_jsxs("li", { className: sectionCss.diagnostics, "data-dsh-part": "diagnostics", children: [_jsx("span", { className: sectionCss.diagnosticsTitle, children: t('settings.diagnosticsTitle') }), _jsx("ul", { children: state.petDiagnostics.map((diagnostic, index) => (_jsx("li", { "data-level": diagnostic.level, children: diagnostic.message }, index))) })] })), _jsx(BooleanField, { id: "settings-pet-visible", label: t('settings.visible'), hint: t('settings.visibleHint'), inheritLabel: t('settings.inherit'), onLabel: t('settings.on'), offLabel: t('settings.off'), ...fieldProps, ...state.visible, onEdit: (text) => { props.edit('visible', text); }, onReset: () => { props.resetField('visible'); } }), _jsx(ValueField, { id: "settings-pet-size", label: t('settings.size'), hint: t('settings.sizeHint'), numeric: true, ...fieldProps, ...state.size, onEdit: (text) => { props.edit('size', text); }, onReset: () => { props.resetField('size'); } }), _jsx(ValueField, { id: "settings-pet-right", label: t('settings.right'), hint: t('settings.rightHint'), numeric: true, ...fieldProps, ...state.right, onEdit: (text) => { props.edit('right', text); }, onReset: () => { props.resetField('right'); } }), _jsx(ValueField, { id: "settings-pet-bottom", label: t('settings.bottom'), hint: t('settings.bottomHint'), numeric: true, ...fieldProps, ...state.bottom, onEdit: (text) => { props.edit('bottom', text); }, onReset: () => { props.resetField('bottom'); } })] }));
|
|
119
121
|
}
|
|
120
122
|
/** Render the pet settings card as a first-level settings page. */
|
|
121
123
|
export function PetSettingsSection(props) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAkFD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CAygB5D"}
|
|
@@ -20,6 +20,80 @@ import styles from './pet.module.css';
|
|
|
20
20
|
function clampOffset(value, max) {
|
|
21
21
|
return Math.max(0, Math.min(max, value));
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The status decoration ornament (pet-center M5, #567). Renders the active
|
|
25
|
+
* phase's frame segment as a CSS-background strip at a compact bubble
|
|
26
|
+
* height; prefers-reduced-motion holds the segment's first frame, and a
|
|
27
|
+
* missing or undecodable asset simply paints nothing (CSS background
|
|
28
|
+
* failure) — the bubble text is never disturbed. The span is aria-hidden;
|
|
29
|
+
* the bubble keeps its own semantics untouched.
|
|
30
|
+
*/
|
|
31
|
+
function StatusOrnament(props) {
|
|
32
|
+
const { decoration, phase } = props;
|
|
33
|
+
const segment = decoration.phases[phase];
|
|
34
|
+
const shown = segment !== undefined && segment !== 'hide';
|
|
35
|
+
const segmentKey = segment !== undefined && segment !== 'hide' ? segment.from + ':' + segment.to : 'none';
|
|
36
|
+
const spanRef = useRef(null);
|
|
37
|
+
const scale = 18 / decoration.cell.height;
|
|
38
|
+
const frameWidth = Math.round(decoration.cell.width * scale);
|
|
39
|
+
const stripWidth = decoration.columns * frameWidth;
|
|
40
|
+
// Value-stable dependency key: the host serves a fresh DecorationView
|
|
41
|
+
// object on every state poll (2 s), so the effect must not depend on the
|
|
42
|
+
// object identity — otherwise each poll would cancel and restart the
|
|
43
|
+
// frame loop and the animation would jump back to its first frame.
|
|
44
|
+
const durationsKey = decoration.durations.join(',');
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (segment === undefined || segment === 'hide')
|
|
47
|
+
return;
|
|
48
|
+
const el = spanRef.current;
|
|
49
|
+
if (el === null)
|
|
50
|
+
return;
|
|
51
|
+
const position = (index) => (-index * frameWidth) + 'px 0px';
|
|
52
|
+
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true;
|
|
53
|
+
el.style.backgroundPosition = position(segment.from);
|
|
54
|
+
if (reduceMotion)
|
|
55
|
+
return;
|
|
56
|
+
let raf = 0;
|
|
57
|
+
let index = segment.from;
|
|
58
|
+
let elapsed = 0;
|
|
59
|
+
let last = performance.now();
|
|
60
|
+
const tick = (ts) => {
|
|
61
|
+
const delta = ts - last;
|
|
62
|
+
last = ts;
|
|
63
|
+
elapsed += delta;
|
|
64
|
+
const duration = decoration.durations[index] ?? 160;
|
|
65
|
+
if (elapsed >= duration) {
|
|
66
|
+
elapsed = 0;
|
|
67
|
+
if (index < segment.to)
|
|
68
|
+
index += 1;
|
|
69
|
+
else if (decoration.loop)
|
|
70
|
+
index = segment.from;
|
|
71
|
+
}
|
|
72
|
+
el.style.backgroundPosition = position(index);
|
|
73
|
+
// A non-looping segment settles on its last frame; stop scheduling
|
|
74
|
+
// instead of repainting the same position every frame.
|
|
75
|
+
if (!decoration.loop && index === segment.to)
|
|
76
|
+
return;
|
|
77
|
+
raf = requestAnimationFrame(tick);
|
|
78
|
+
};
|
|
79
|
+
raf = requestAnimationFrame(tick);
|
|
80
|
+
return () => cancelAnimationFrame(raf);
|
|
81
|
+
}, [shown, segmentKey, frameWidth, decoration.loop, durationsKey]);
|
|
82
|
+
if (!shown)
|
|
83
|
+
return null;
|
|
84
|
+
return (_jsx("span", { ref: spanRef, "aria-hidden": "true", "data-dsh-pet-decoration": decoration.id, style: {
|
|
85
|
+
display: 'inline-block',
|
|
86
|
+
width: frameWidth,
|
|
87
|
+
height: 18,
|
|
88
|
+
marginRight: 6,
|
|
89
|
+
verticalAlign: 'middle',
|
|
90
|
+
flexShrink: 0,
|
|
91
|
+
backgroundImage: 'url(' + decoration.entryUrl + ')',
|
|
92
|
+
backgroundSize: stripWidth + 'px 18px',
|
|
93
|
+
backgroundRepeat: 'no-repeat',
|
|
94
|
+
backgroundPosition: '0px 0px',
|
|
95
|
+
} }));
|
|
96
|
+
}
|
|
23
97
|
/**
|
|
24
98
|
* The floating pet. The spritesheet frame advances on requestAnimationFrame
|
|
25
99
|
* with per-frame durations from the definition's tracks; the atlas image is
|
|
@@ -66,6 +140,29 @@ export function PetSprite(props) {
|
|
|
66
140
|
const rows = definition.rows;
|
|
67
141
|
const tracks = definition.tracks;
|
|
68
142
|
const sequences = definition.sequences;
|
|
143
|
+
// Hover-panel chrome from the pet's voice pack (pet-center M4, issue
|
|
144
|
+
// #677): every slot falls back to the i18n dictionary when unset. Stat
|
|
145
|
+
// formats carry {rank}/{n}/{points} placeholders the host validated.
|
|
146
|
+
const panel = definition.panel;
|
|
147
|
+
const panelLabel = (slot, i18n) => panel?.labels?.[slot] ?? i18n;
|
|
148
|
+
const panelStat = (slot, i18nKey, values) => {
|
|
149
|
+
const format = panel?.stats?.[slot] ?? props.t(i18nKey, values);
|
|
150
|
+
if (panel?.stats?.[slot] === undefined)
|
|
151
|
+
return format;
|
|
152
|
+
// The host whitelists {rank}/{n}/{points} in every stat slot, so a pack
|
|
153
|
+
// format may reference any of them; substitute all three live values
|
|
154
|
+
// (the slot's own value plus the siblings) instead of only the slot's.
|
|
155
|
+
const all = {
|
|
156
|
+
rank: snapshot?.affinity.rank ?? '?',
|
|
157
|
+
n: snapshot?.treats.stocked ?? 0,
|
|
158
|
+
points: snapshot?.affinity.points ?? 0,
|
|
159
|
+
};
|
|
160
|
+
let text = format;
|
|
161
|
+
for (const [name, value] of Object.entries(all))
|
|
162
|
+
text = text.replaceAll('{' + name + '}', String(value));
|
|
163
|
+
return text;
|
|
164
|
+
};
|
|
165
|
+
const panelShows = (action) => panel?.actions === undefined || panel.actions.includes(action);
|
|
69
166
|
// Load the atlas once; the definition carries the authoritative per-row
|
|
70
167
|
// frame counts and per-track durations, so nothing else is fetched. A
|
|
71
168
|
// custom visual (pet-center M3) replaces the atlas entirely.
|
|
@@ -242,6 +339,8 @@ export function PetSprite(props) {
|
|
|
242
339
|
const whisper = feedback === null ? snapshot?.whisper : undefined;
|
|
243
340
|
const bubblePresent = feedback !== null || sessionBubbles.length > 0 || statusBubble !== undefined || whisper !== undefined;
|
|
244
341
|
const displayName = snapshot?.name ?? definition.displayName;
|
|
342
|
+
// The host-served status decoration (M5, #567); absent = text-only bubbles.
|
|
343
|
+
const decoration = snapshot?.decoration;
|
|
245
344
|
// A settled session list can no longer stay pinned open.
|
|
246
345
|
useEffect(() => {
|
|
247
346
|
if (sessionBubbles.length <= 1)
|
|
@@ -319,7 +418,7 @@ export function PetSprite(props) {
|
|
|
319
418
|
// The key swap restarts the entrance animation so the mood change
|
|
320
419
|
// reads as the bubble re-speaking.
|
|
321
420
|
const speaksWhisper = index === 0 && whisper !== undefined;
|
|
322
|
-
const bubble = (
|
|
421
|
+
const bubble = (_jsxs("button", { type: "button", className: clsx(styles.bubble, styles.bubbleStatus, styles.bubbleClickable, speaksWhisper && styles.bubbleWhisper), title: props.t('pet.openSessionHint'), onClick: () => { props.onOpenSession(session.sessionId); }, children: [index === 0 && !speaksWhisper && decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), speaksWhisper ? whisper : session.bubble] }, speaksWhisper ? 'whisper:' + whisper : session.sessionId));
|
|
323
422
|
// The primary bubble carries the '+N' badge while other sessions
|
|
324
423
|
// hide behind it; the badge toggles the pinned (touch) expansion.
|
|
325
424
|
if (index !== 0 || sessionBubbles.length <= 1)
|
|
@@ -332,7 +431,7 @@ export function PetSprite(props) {
|
|
|
332
431
|
e.stopPropagation();
|
|
333
432
|
setStackPinned(open => !open);
|
|
334
433
|
}, children: stackOpen ? '×' : '+' + String(sessionBubbles.length - 1) })] }, "primary"));
|
|
335
|
-
}), sessionBubbles.length === 0 && (statusBubble !== undefined || whisper !== undefined) && (
|
|
434
|
+
}), sessionBubbles.length === 0 && (statusBubble !== undefined || whisper !== undefined) && (_jsxs("div", { className: clsx(styles.bubble, styles.bubbleStatus, whisper !== undefined && styles.bubbleWhisper), role: "status", "aria-live": "polite", children: [whisper === undefined && decoration !== undefined && (_jsx(StatusOrnament, { decoration: decoration, phase: phase })), whisper ?? statusBubble] }, whisper === undefined ? 'status' : 'whisper:' + whisper))] })), hovered && dragRef.current === null && (_jsx("div", { ref: panelRef, className: clsx(styles.panel, panelAbove && styles.panelAbove), "data-placement": panelAbove ? 'above' : 'below', style: panelAbove && panelLift > 0
|
|
336
435
|
? { marginBottom: panelLift }
|
|
337
436
|
: undefined, onPointerEnter: () => {
|
|
338
437
|
// Reaching the panel (or its bridge) must cancel any hide timer
|
|
@@ -364,12 +463,12 @@ export function PetSprite(props) {
|
|
|
364
463
|
props.onRename(trimmed);
|
|
365
464
|
setRenaming(false);
|
|
366
465
|
}
|
|
367
|
-
}, children: props.t('pet.confirm') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children:
|
|
466
|
+
}, children: panelLabel('confirm', props.t('pet.confirm')) })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.nameCell, children: displayName }), _jsx("span", { className: styles.statRank, children: panelStat('rank', 'pet.rank', { rank: snapshot?.affinity.rank ?? '?' }) })] }), _jsxs("div", { className: styles.rankRow, children: [_jsx("span", { className: styles.statTreats, children: panelStat('treats', 'pet.treats', { n: snapshot?.treats.stocked ?? 0 }) }), _jsx("span", { className: styles.statPoints, children: panelStat('points', 'pet.points', { points: snapshot?.affinity.points ?? 0 }) })] }), _jsxs("div", { className: styles.actions, children: [panelShows('feed') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onFeed, children: panelLabel('feed', props.t('pet.feed')) })), panelShows('rename') && (_jsx("button", { type: "button", className: styles.action, onClick: () => {
|
|
368
467
|
// Cancel any pending hide so the rename box cannot
|
|
369
468
|
// unmount right as the user starts typing (#303).
|
|
370
469
|
clearHideTimer();
|
|
371
470
|
setNameDraft(displayName);
|
|
372
471
|
setRenaming(true);
|
|
373
|
-
}, children: props.t('pet.rename') }), _jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: props.t('pet.hide') })] })] })) }))] }));
|
|
472
|
+
}, children: panelLabel('rename', props.t('pet.rename')) })), panelShows('hide') && (_jsx("button", { type: "button", className: styles.action, onClick: props.onHide, children: panelLabel('hide', props.t('pet.hide')) }))] })] })) }))] }));
|
|
374
473
|
return createPortal(float, document.body);
|
|
375
474
|
}
|
|
@@ -31,6 +31,8 @@ export declare const zh: {
|
|
|
31
31
|
readonly 'settings.petHint': '选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。';
|
|
32
32
|
readonly 'settings.enabled': '启用宠物';
|
|
33
33
|
readonly 'settings.enabledHint': '关闭后隐藏宠物并停止轮询,可在设置里重新启用。';
|
|
34
|
+
readonly 'settings.decoration': '状态装饰';
|
|
35
|
+
readonly 'settings.decorationHint': '在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。';
|
|
34
36
|
readonly 'settings.visible': '显示宠物';
|
|
35
37
|
readonly 'settings.visibleHint': '关闭后宠物隐藏,可从聊天输入区重新召唤。';
|
|
36
38
|
readonly 'settings.size': '大小(px)';
|
|
@@ -82,6 +84,8 @@ export declare const en: {
|
|
|
82
84
|
readonly 'settings.petHint': 'Choose which pet shows. Names are stored per pet; rename from the pet hover panel.';
|
|
83
85
|
readonly 'settings.enabled': 'Enable the pet';
|
|
84
86
|
readonly 'settings.enabledHint': 'When off, the pet hides and polling stops; re-enable it here.';
|
|
87
|
+
readonly 'settings.decoration': 'Status decoration';
|
|
88
|
+
readonly 'settings.decorationHint': 'Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.';
|
|
85
89
|
readonly 'settings.visible': 'Show the pet';
|
|
86
90
|
readonly 'settings.visibleHint': 'When off, the pet hides; summon it again from the input row.';
|
|
87
91
|
readonly 'settings.size': 'Size (px)';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,8 @@ export const zh = {
|
|
|
32
32
|
'settings.petHint': '选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。',
|
|
33
33
|
'settings.enabled': '启用宠物',
|
|
34
34
|
'settings.enabledHint': '关闭后隐藏宠物并停止轮询,可在设置里重新启用。',
|
|
35
|
+
'settings.decoration': '状态装饰',
|
|
36
|
+
'settings.decorationHint': '在宠物状态气泡里显示喷水鲸鱼等状态装饰;关闭后气泡只剩文字。',
|
|
35
37
|
'settings.visible': '显示宠物',
|
|
36
38
|
'settings.visibleHint': '关闭后宠物隐藏,可从聊天输入区重新召唤。',
|
|
37
39
|
'settings.size': '大小(px)',
|
|
@@ -84,6 +86,8 @@ export const en = {
|
|
|
84
86
|
'settings.petHint': 'Choose which pet shows. Names are stored per pet; rename from the pet hover panel.',
|
|
85
87
|
'settings.enabled': 'Enable the pet',
|
|
86
88
|
'settings.enabledHint': 'When off, the pet hides and polling stops; re-enable it here.',
|
|
89
|
+
'settings.decoration': 'Status decoration',
|
|
90
|
+
'settings.decorationHint': 'Show ornaments like the spouting whale inside the pet status bubbles; when off, bubbles stay text-only.',
|
|
87
91
|
'settings.visible': 'Show the pet',
|
|
88
92
|
'settings.visibleHint': 'When off, the pet hides; summon it again from the input row.',
|
|
89
93
|
'settings.size': 'Size (px)',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/live2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAA;AASpC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;CACzD;
|
|
1
|
+
{"version":3,"file":"live2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/live2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAA;AASpC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;CACzD;AAwBD,kCAAkC;AAClC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAcD,0CAA0C;AAC1C,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,eAAe,CAmIvD,CAAA"}
|
|
@@ -24,6 +24,14 @@ import { PET_RENDERER_API_VERSION, } from '../../contracts/renderer.js';
|
|
|
24
24
|
import { ensureCubismCore, ensureLive2dVendor, } from './live2d/runtime.js';
|
|
25
25
|
/** The de-facto tap-motion group of Cubism sample models. */
|
|
26
26
|
const TAP_GROUP = 'TapBody';
|
|
27
|
+
/**
|
|
28
|
+
* Keep one screen-appropriate atlas LOD instead of asking Pixi for the
|
|
29
|
+
* engine's default full mip chain. A user model can legitimately carry an
|
|
30
|
+
* 8192px texture while the pet itself is only a few hundred pixels tall;
|
|
31
|
+
* `single-auto` preserves the source for larger renders and generates one
|
|
32
|
+
* downsampled atlas only when the effective on-screen scale warrants it.
|
|
33
|
+
*/
|
|
34
|
+
const TEXTURE_OPTIONS = { lod: 'single-auto' };
|
|
27
35
|
let vendorConfigured = false;
|
|
28
36
|
/** Configure pixi extensions + the Cubism SDK once per page. */
|
|
29
37
|
function configureOnce(vendor) {
|
|
@@ -115,7 +123,11 @@ export const live2dRenderer = {
|
|
|
115
123
|
pixiApp.canvas.style.width = '100%';
|
|
116
124
|
pixiApp.canvas.style.height = '100%';
|
|
117
125
|
ctx.container.appendChild(pixiApp.canvas);
|
|
118
|
-
const loaded = await vendor.Live2DModel.from(config.modelUrl, {
|
|
126
|
+
const loaded = await vendor.Live2DModel.from(config.modelUrl, {
|
|
127
|
+
autoHitTest: false,
|
|
128
|
+
autoFocus: false,
|
|
129
|
+
textureOptions: TEXTURE_OPTIONS,
|
|
130
|
+
});
|
|
119
131
|
if (disposed) {
|
|
120
132
|
pixiApp.destroy(true, { children: true });
|
|
121
133
|
return;
|