@linxin666/dsh-pet 0.1.12 → 0.1.14
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 +6 -0
- package/README.md +64 -59
- package/README.zh.md +113 -0
- package/lib/client.js +78 -53
- package/lib/client.js.map +1 -1
- package/lib/index.js +164 -25
- package/lib/invariant.js +1 -1
- package/lib/{state-C9EyycwI.js → state-P-wsVJ6O.js} +7 -6
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/WhalePet.d.ts.map +1 -1
- package/lib/types/client/WhalePet.js +2 -1
- package/lib/types/service.d.ts +16 -5
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +159 -35
- package/lib/types/state.d.ts +10 -12
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +14 -10
- package/package.json +1 -11
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/WhalePet.test.tsx +25 -1
- package/src/client/WhalePet.tsx +6 -0
- package/src/client/pet.module.css +9 -0
- package/src/client/settings-card.module.css +141 -127
- package/src/service.ts +187 -34
- package/src/state.test.ts +4 -1
- package/src/state.ts +17 -13
package/lib/types/state.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pet state machine — pure, clock-injected. Maps
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* transitions the web UI exposes (turn end celebration, no-session idle).
|
|
2
|
+
* Pet state machine — pure, clock-injected. Maps official DSH session activity
|
|
3
|
+
* and the legacy `activity/status` vocabulary onto the 9-state Codex pet
|
|
4
|
+
* animation contract, plus turn-end celebration and no-session idle.
|
|
6
5
|
*
|
|
7
6
|
* The machine is deliberately dumb: it holds the last input phase, the
|
|
8
7
|
* animation decision, and a one-shot "celebration" window after `done` so the
|
|
@@ -10,13 +9,13 @@
|
|
|
10
9
|
* function of (input, nowMs); persistence and RPC live in the service.
|
|
11
10
|
* @module @linxin666/dsh-pet/state
|
|
12
11
|
*/
|
|
13
|
-
/**
|
|
14
|
-
export type ActivityPhase = 'idle' | 'waiting' | 'thinking' | 'tool' | 'done';
|
|
12
|
+
/** Activity phases understood by the pet host. */
|
|
13
|
+
export type ActivityPhase = 'idle' | 'waiting' | 'thinking' | 'tool' | 'review' | 'done' | 'failed';
|
|
15
14
|
/** The Codex-compatible 9-state animation contract (spritesheet rows). */
|
|
16
15
|
export type PetAnimation = 'idle' | 'running-right' | 'running-left' | 'waving' | 'jumping' | 'failed' | 'waiting' | 'running' | 'review';
|
|
17
16
|
/** One input snapshot consumed by the machine. */
|
|
18
17
|
export interface PetStateInput {
|
|
19
|
-
/** Current
|
|
18
|
+
/** Current activity phase of the active session. */
|
|
20
19
|
phase: ActivityPhase;
|
|
21
20
|
/** Human-readable status line (plain text). */
|
|
22
21
|
line?: string;
|
|
@@ -44,13 +43,12 @@ export interface PetStateConfig {
|
|
|
44
43
|
export declare const defaultPetStateConfig: PetStateConfig;
|
|
45
44
|
/**
|
|
46
45
|
* Map one activity phase onto the animation contract.
|
|
47
|
-
* - thinking
|
|
48
|
-
*
|
|
46
|
+
* - thinking → `running` and tool → `running-right` (focused work).
|
|
47
|
+
* - review → `review` while answer text is streaming.
|
|
49
48
|
* - waiting → `waiting` (expectant pose, needs user input).
|
|
50
49
|
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
50
|
+
* - failed → `failed` until another activity event arrives.
|
|
51
51
|
* - idle → `idle` (calm breathing loop).
|
|
52
|
-
* `failed` has no DSH phase source yet; the machine keeps the mapping table
|
|
53
|
-
* so a future error event can light it up.
|
|
54
52
|
*/
|
|
55
53
|
export declare function animationForPhase(phase: ActivityPhase): PetAnimation;
|
|
56
54
|
/** The spritesheet row index for one animation track. */
|
|
@@ -68,7 +66,7 @@ export declare class PetStateMachine {
|
|
|
68
66
|
private sessionActive;
|
|
69
67
|
private doneAt;
|
|
70
68
|
constructor(config?: PetStateConfig, now?: () => number);
|
|
71
|
-
/** Consume one
|
|
69
|
+
/** Consume one projected activity update. */
|
|
72
70
|
onActivityStatus(input: PetStateInput): void;
|
|
73
71
|
/** A session became the active one (or a fresh session started). */
|
|
74
72
|
onSessionActive(): void;
|
package/lib/types/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEnG,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,eAAe,GACf,cAAc,GACd,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAA;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,SAAS,EAAE,YAAY,CAAA;IACvB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oEAAoE;IACpE,KAAK,EAAE,aAAa,CAAA;IACpB,0DAA0D;IAC1D,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAsC,CAAA;AAE1E;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAUpE;AAED,yDAAyD;AACzD,wBAAgB,KAAK,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAarD;AAED;;;GAGG;AACH,qBAAa,eAAe;IAQxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IARtB,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,MAAM,CAAoB;gBAGf,MAAM,GAAE,cAAsC,EAC9C,GAAG,GAAE,MAAM,MAAiB;IAG/C,6CAA6C;IAC7C,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAO5C,oEAAoE;IACpE,eAAe,IAAI,IAAI;IAIvB,sDAAsD;IACtD,iBAAiB,IAAI,IAAI;IAQzB,6CAA6C;IAC7C,MAAM,IAAI,gBAAgB;CAwB3B"}
|
package/lib/types/state.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pet state machine — pure, clock-injected. Maps
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* transitions the web UI exposes (turn end celebration, no-session idle).
|
|
2
|
+
* Pet state machine — pure, clock-injected. Maps official DSH session activity
|
|
3
|
+
* and the legacy `activity/status` vocabulary onto the 9-state Codex pet
|
|
4
|
+
* animation contract, plus turn-end celebration and no-session idle.
|
|
6
5
|
*
|
|
7
6
|
* The machine is deliberately dumb: it holds the last input phase, the
|
|
8
7
|
* animation decision, and a one-shot "celebration" window after `done` so the
|
|
@@ -13,20 +12,21 @@
|
|
|
13
12
|
export const defaultPetStateConfig = { celebrateMs: 2400 };
|
|
14
13
|
/**
|
|
15
14
|
* Map one activity phase onto the animation contract.
|
|
16
|
-
* - thinking
|
|
17
|
-
*
|
|
15
|
+
* - thinking → `running` and tool → `running-right` (focused work).
|
|
16
|
+
* - review → `review` while answer text is streaming.
|
|
18
17
|
* - waiting → `waiting` (expectant pose, needs user input).
|
|
19
18
|
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
19
|
+
* - failed → `failed` until another activity event arrives.
|
|
20
20
|
* - idle → `idle` (calm breathing loop).
|
|
21
|
-
* `failed` has no DSH phase source yet; the machine keeps the mapping table
|
|
22
|
-
* so a future error event can light it up.
|
|
23
21
|
*/
|
|
24
22
|
export function animationForPhase(phase) {
|
|
25
23
|
switch (phase) {
|
|
26
24
|
case 'thinking': return 'running';
|
|
27
25
|
case 'tool': return 'running-right';
|
|
26
|
+
case 'review': return 'review';
|
|
28
27
|
case 'waiting': return 'waiting';
|
|
29
28
|
case 'done': return 'jumping';
|
|
29
|
+
case 'failed': return 'failed';
|
|
30
30
|
case 'idle': return 'idle';
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -61,7 +61,7 @@ export class PetStateMachine {
|
|
|
61
61
|
this.config = config;
|
|
62
62
|
this.now = now;
|
|
63
63
|
}
|
|
64
|
-
/** Consume one
|
|
64
|
+
/** Consume one projected activity update. */
|
|
65
65
|
onActivityStatus(input) {
|
|
66
66
|
this.phase = input.phase;
|
|
67
67
|
this.line = input.line;
|
|
@@ -94,7 +94,11 @@ export class PetStateMachine {
|
|
|
94
94
|
animation = 'idle';
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
const bubble = this.
|
|
97
|
+
const bubble = this.phase === 'done'
|
|
98
|
+
&& this.doneAt !== undefined
|
|
99
|
+
&& nowMs - this.doneAt >= this.config.celebrateMs
|
|
100
|
+
? undefined
|
|
101
|
+
: this.phrase ?? this.line;
|
|
98
102
|
return {
|
|
99
103
|
animation,
|
|
100
104
|
...(bubble === undefined ? {} : { bubble }),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "鲸鱼娘宠物插件 for the dsh web GUI: a soft healing whale-girl companion that reacts to model activity (idle/waiting/thinking/tool/done), with petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -53,16 +53,6 @@
|
|
|
53
53
|
"schemastery": "^3.18.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
57
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
58
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
63
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
64
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
65
|
-
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
66
56
|
"react": "^18.2.0",
|
|
67
57
|
"react-dom": "^18.2.0"
|
|
68
58
|
},
|
|
@@ -41,13 +41,14 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
41
41
|
if (!state.available) return null
|
|
42
42
|
const title = props.t(props.titleKey)
|
|
43
43
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
44
|
+
const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card
|
|
44
45
|
// The namespace exists but the Host does not serve it to this client (the
|
|
45
46
|
// official settings allowlist omits third-party namespaces): show a card
|
|
46
47
|
// that explains the gap instead of vanishing, so a missing card never
|
|
47
48
|
// reads as a missing plugin.
|
|
48
49
|
if (!state.exposed) {
|
|
49
50
|
return (
|
|
50
|
-
<li className={
|
|
51
|
+
<li className={cardClass}>
|
|
51
52
|
<button
|
|
52
53
|
type="button"
|
|
53
54
|
className={css.header}
|
|
@@ -59,7 +60,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
59
60
|
<span className={css.name}>{title}</span>
|
|
60
61
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
61
62
|
</span>
|
|
62
|
-
<
|
|
63
|
+
<svg
|
|
64
|
+
width="14"
|
|
65
|
+
height="14"
|
|
66
|
+
viewBox="0 0 14 14"
|
|
67
|
+
fill="none"
|
|
68
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
69
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
70
|
+
>
|
|
71
|
+
<path
|
|
72
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
73
|
+
fill="currentColor"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
63
76
|
</button>
|
|
64
77
|
{open
|
|
65
78
|
? (
|
|
@@ -72,7 +85,7 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
72
85
|
)
|
|
73
86
|
}
|
|
74
87
|
return (
|
|
75
|
-
<li className={
|
|
88
|
+
<li className={cardClass}>
|
|
76
89
|
<button
|
|
77
90
|
type="button"
|
|
78
91
|
className={css.header}
|
|
@@ -85,7 +98,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
85
98
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
86
99
|
</span>
|
|
87
100
|
{state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}
|
|
88
|
-
<
|
|
101
|
+
<svg
|
|
102
|
+
width="14"
|
|
103
|
+
height="14"
|
|
104
|
+
viewBox="0 0 14 14"
|
|
105
|
+
fill="none"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
108
|
+
>
|
|
109
|
+
<path
|
|
110
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
111
|
+
fill="currentColor"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
89
114
|
</button>
|
|
90
115
|
{open
|
|
91
116
|
? (
|
|
@@ -234,4 +259,4 @@ export function BooleanField(props: FieldProps & {
|
|
|
234
259
|
<p className={css.hint}>{props.hint}</p>
|
|
235
260
|
</div>
|
|
236
261
|
)
|
|
237
|
-
}
|
|
262
|
+
}
|
|
@@ -55,7 +55,7 @@ afterEach(() => {
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
/** Render the pet with mocked callbacks; returns the rename spy. */
|
|
58
|
-
function renderPet(): { onRename: ReturnType<typeof vi.fn> } {
|
|
58
|
+
function renderPet(overrides: Partial<WhalePetProps> = {}): { onRename: ReturnType<typeof vi.fn> } {
|
|
59
59
|
const onRename = vi.fn()
|
|
60
60
|
const props: WhalePetProps = {
|
|
61
61
|
snapshot,
|
|
@@ -68,6 +68,7 @@ function renderPet(): { onRename: ReturnType<typeof vi.fn> } {
|
|
|
68
68
|
onRename,
|
|
69
69
|
onFeedbackDone: vi.fn(),
|
|
70
70
|
t,
|
|
71
|
+
...overrides,
|
|
71
72
|
}
|
|
72
73
|
render(<WhalePet {...props} />)
|
|
73
74
|
return { onRename }
|
|
@@ -136,3 +137,26 @@ describe('WhalePet rename input', () => {
|
|
|
136
137
|
expect(screen.queryByPlaceholderText('输入新名字')).toBeNull()
|
|
137
138
|
})
|
|
138
139
|
})
|
|
140
|
+
|
|
141
|
+
describe('WhalePet status bubble', () => {
|
|
142
|
+
const workingSnapshot: PetStateView = {
|
|
143
|
+
...snapshot,
|
|
144
|
+
animation: 'running',
|
|
145
|
+
phase: 'thinking',
|
|
146
|
+
bubble: '正在思考',
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
it('renders host activity when no interaction feedback is active', () => {
|
|
150
|
+
renderPet({ snapshot: workingSnapshot })
|
|
151
|
+
expect(screen.queryByText('正在思考')).not.toBeNull()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('lets transient interaction feedback replace host activity', () => {
|
|
155
|
+
renderPet({
|
|
156
|
+
snapshot: workingSnapshot,
|
|
157
|
+
feedback: { text: '摸摸成功', kind: 'pet', at: 1 },
|
|
158
|
+
})
|
|
159
|
+
expect(screen.queryByText('摸摸成功')).not.toBeNull()
|
|
160
|
+
expect(screen.queryByText('正在思考')).toBeNull()
|
|
161
|
+
})
|
|
162
|
+
})
|
package/src/client/WhalePet.tsx
CHANGED
|
@@ -228,6 +228,7 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
228
228
|
const pos = dragPos ?? { right: display.right, bottom: display.bottom }
|
|
229
229
|
const spriteWidth = Math.round(FRAME_WIDTH * spriteScale)
|
|
230
230
|
const spriteHeight = Math.round(FRAME_HEIGHT * spriteScale)
|
|
231
|
+
const statusBubble = feedback === null && !hovered ? snapshot?.bubble : undefined
|
|
231
232
|
|
|
232
233
|
const float = (
|
|
233
234
|
<div
|
|
@@ -281,6 +282,11 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
281
282
|
{feedback.text}
|
|
282
283
|
</div>
|
|
283
284
|
)}
|
|
285
|
+
{statusBubble !== undefined && (
|
|
286
|
+
<div className={`${styles.bubble} ${styles.bubbleStatus}`} role="status" aria-live="polite">
|
|
287
|
+
{statusBubble}
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
284
290
|
{hovered && dragRef.current === null && (
|
|
285
291
|
<div
|
|
286
292
|
className={styles.panel}
|
|
@@ -37,6 +37,15 @@
|
|
|
37
37
|
background: rgba(56, 189, 248, 0.92);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
.bubbleStatus {
|
|
41
|
+
max-width: min(280px, calc(100vw - 24px));
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
background: rgba(15, 23, 42, 0.9);
|
|
45
|
+
border: 1px solid rgba(125, 211, 252, 0.5);
|
|
46
|
+
animation: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
@keyframes pet-bubble-pop {
|
|
41
50
|
0% {
|
|
42
51
|
opacity: 0;
|