@linxin666/dsh-pet 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/lib/client.js +56 -18
- package/lib/client.js.map +1 -1
- package/lib/types/client/PetDockEntry.d.ts +10 -7
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +14 -5
- package/lib/types/client/WhalePet.d.ts.map +1 -1
- package/lib/types/client/WhalePet.js +10 -3
- package/lib/types/client/index.d.ts +11 -11
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +30 -27
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +27 -0
- package/package.json +1 -1
- package/src/client/PetDockEntry.tsx +17 -9
- package/src/client/WhalePet.tsx +13 -3
- package/src/client/index.ts +30 -28
- package/src/client/locales.ts +29 -0
- package/src/client/pet.module.css +14 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-pet browser half —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* host
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* dsh-pet browser half — mounts the whale-girl as a global floating surface
|
|
3
|
+
* and drives it from the host's same-origin `/api/pet/*` JSON endpoints: poll
|
|
4
|
+
* the host snapshot (~800 ms), forward interactions, persist drag positions.
|
|
5
|
+
* The pet is host-global (no session dimension), so it mounts directly onto
|
|
6
|
+
* `document.body` via a single React root rather than a session-scoped slot —
|
|
7
|
+
* on the new-conversation screen no session exists, and a dock-mounted pet
|
|
8
|
+
* would vanish there (issue #48). When the pet is hidden the entry becomes a
|
|
9
|
+
* fixed-position summon button.
|
|
10
10
|
* @module @linxin666/dsh-pet/client
|
|
11
11
|
*/
|
|
12
12
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
@@ -37,9 +37,9 @@ export interface SettingsPluginItemOwnerProps {
|
|
|
37
37
|
children?: never;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* Client plugin body: register dictionaries, mount the
|
|
41
|
-
* loop while the plugin is enabled, and seat the settings card in the
|
|
42
|
-
* plugin group.
|
|
40
|
+
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
41
|
+
* poll loop while the plugin is enabled, and seat the settings card in the
|
|
42
|
+
* Web UI plugin group.
|
|
43
43
|
* @param ctx - client root context.
|
|
44
44
|
*/
|
|
45
45
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAwD3E,yBAAyB;AACzB,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAEtF,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,OAAO;QACf;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmK9C"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-pet browser half —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* host
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* dsh-pet browser half — mounts the whale-girl as a global floating surface
|
|
3
|
+
* and drives it from the host's same-origin `/api/pet/*` JSON endpoints: poll
|
|
4
|
+
* the host snapshot (~800 ms), forward interactions, persist drag positions.
|
|
5
|
+
* The pet is host-global (no session dimension), so it mounts directly onto
|
|
6
|
+
* `document.body` via a single React root rather than a session-scoped slot —
|
|
7
|
+
* on the new-conversation screen no session exists, and a dock-mounted pet
|
|
8
|
+
* would vanish there (issue #48). When the pet is hidden the entry becomes a
|
|
9
|
+
* fixed-position summon button.
|
|
10
10
|
* @module @linxin666/dsh-pet/client
|
|
11
11
|
*/
|
|
12
|
+
import { createElement } from 'react';
|
|
13
|
+
import { createRoot } from 'react-dom/client';
|
|
12
14
|
import { createPetStore } from "./pet-store.js";
|
|
13
15
|
import { PetDockEntry } from "./PetDockEntry.js";
|
|
14
16
|
import { PetSettingsCard, PetSettingsCardController } from "./PetSettingsCard.js";
|
|
15
|
-
import { NS, en, zh } from "./locales.js";
|
|
17
|
+
import { NS, en, zh, t } from "./locales.js";
|
|
16
18
|
/** Same-origin JSON fetch helper (GET without body, POST with JSON body). */
|
|
17
19
|
async function petFetch(path, body) {
|
|
18
20
|
const response = await fetch(path, body === undefined
|
|
@@ -42,9 +44,9 @@ const PET_SETTINGS_NS = 'pet';
|
|
|
42
44
|
/** Required services. */
|
|
43
45
|
export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote'];
|
|
44
46
|
/**
|
|
45
|
-
* Client plugin body: register dictionaries, mount the
|
|
46
|
-
* loop while the plugin is enabled, and seat the settings card in the
|
|
47
|
-
* plugin group.
|
|
47
|
+
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
48
|
+
* poll loop while the plugin is enabled, and seat the settings card in the
|
|
49
|
+
* Web UI plugin group.
|
|
48
50
|
* @param ctx - client root context.
|
|
49
51
|
*/
|
|
50
52
|
export function apply(ctx) {
|
|
@@ -66,8 +68,8 @@ export function apply(ctx) {
|
|
|
66
68
|
locale: NS,
|
|
67
69
|
inject: () => petSettings.inject(),
|
|
68
70
|
}, PetSettingsCard));
|
|
69
|
-
// The
|
|
70
|
-
// enabled; toggling the setting off hides the pet and stops polling.
|
|
71
|
+
// The global pet entry, its store, and the poll loop live while the plugin
|
|
72
|
+
// is enabled; toggling the setting off hides the pet and stops polling.
|
|
71
73
|
let disposeUi;
|
|
72
74
|
const syncUi = () => {
|
|
73
75
|
if (enabled() && disposeUi === undefined) {
|
|
@@ -179,20 +181,21 @@ export function apply(ctx) {
|
|
|
179
181
|
setFeedback(null);
|
|
180
182
|
},
|
|
181
183
|
});
|
|
182
|
-
// The
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
// The pet is host-global (its state/display/interactions have no session
|
|
185
|
+
// dimension), and the official rc.6 shell declares no root-scoped slot
|
|
186
|
+
// for a global floating surface — the dock is session-scoped, so a pet
|
|
187
|
+
// mounted there would vanish on the new-conversation screen (issue #48).
|
|
188
|
+
// The entry therefore mounts straight onto document.body via a single
|
|
189
|
+
// React root for the page lifetime: WhalePet portals itself to body when
|
|
190
|
+
// visible, and the hidden-state summon button is fixed-positioned.
|
|
191
|
+
const container = document.createElement('div');
|
|
192
|
+
container.dataset.dshPetRoot = '';
|
|
193
|
+
document.body.appendChild(container);
|
|
194
|
+
const petRoot = createRoot(container);
|
|
195
|
+
petRoot.render(createElement(PetDockEntry, { ...injected(), t }));
|
|
194
196
|
disposeUi = () => {
|
|
195
|
-
|
|
197
|
+
petRoot.unmount();
|
|
198
|
+
container.remove();
|
|
196
199
|
disposePoll();
|
|
197
200
|
disposeUi = undefined;
|
|
198
201
|
};
|
|
@@ -92,6 +92,22 @@ export declare const en: {
|
|
|
92
92
|
export type PetKey = keyof typeof zh;
|
|
93
93
|
/** The settings-card slice of the pet dictionary. */
|
|
94
94
|
export type SettingsCardKey = PetKey;
|
|
95
|
+
/**
|
|
96
|
+
* Active dictionary, picked by the document language at call time. The pet
|
|
97
|
+
* mounts as a global floating surface (not a session-scoped slot), so it has
|
|
98
|
+
* no framework locale seat and resolves its copy the same tiny way the
|
|
99
|
+
* task-board's DOM-injected surface does.
|
|
100
|
+
*/
|
|
101
|
+
export declare function dictionary(): Record<PetKey, string>;
|
|
102
|
+
/**
|
|
103
|
+
* Translate a key with optional `{name}` template params. Mirrors the slot
|
|
104
|
+
* `Translate` contract `(key, params?) => string` so it can be handed to the
|
|
105
|
+
* same components that used to receive the framework-injected `t` seat. The
|
|
106
|
+
* key is typed loosely (`string`) so the function is assignable to the slot's
|
|
107
|
+
* `TranslateNS<'pet'>` (whose key domain also spans the shared common
|
|
108
|
+
* vocabulary); a missing key degrades to the key itself rather than throwing.
|
|
109
|
+
*/
|
|
110
|
+
export declare function t(key: string, params?: Record<string, unknown>): string;
|
|
95
111
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
96
112
|
interface LocaleNamespaceMap {
|
|
97
113
|
/** dsh-pet UI copy. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
|
|
@@ -90,3 +90,30 @@ export const en = {
|
|
|
90
90
|
'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',
|
|
91
91
|
'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
|
|
92
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Active dictionary, picked by the document language at call time. The pet
|
|
95
|
+
* mounts as a global floating surface (not a session-scoped slot), so it has
|
|
96
|
+
* no framework locale seat and resolves its copy the same tiny way the
|
|
97
|
+
* task-board's DOM-injected surface does.
|
|
98
|
+
*/
|
|
99
|
+
export function dictionary() {
|
|
100
|
+
const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh';
|
|
101
|
+
return lang.toLowerCase().startsWith('en') ? en : zh;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Translate a key with optional `{name}` template params. Mirrors the slot
|
|
105
|
+
* `Translate` contract `(key, params?) => string` so it can be handed to the
|
|
106
|
+
* same components that used to receive the framework-injected `t` seat. The
|
|
107
|
+
* key is typed loosely (`string`) so the function is assignable to the slot's
|
|
108
|
+
* `TranslateNS<'pet'>` (whose key domain also spans the shared common
|
|
109
|
+
* vocabulary); a missing key degrades to the key itself rather than throwing.
|
|
110
|
+
*/
|
|
111
|
+
export function t(key, params) {
|
|
112
|
+
let text = dictionary()[key] ?? key;
|
|
113
|
+
if (params !== undefined) {
|
|
114
|
+
for (const [name, value] of Object.entries(params)) {
|
|
115
|
+
text = text.replaceAll(`{${name}}`, String(value));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
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.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Global floating pet entry. The pet is host-global (its state, display and
|
|
3
|
+
* interactions live on `/api/pet/*` endpoints with no session dimension), so
|
|
4
|
+
* it must not ride a session-scoped slot — on the new-conversation screen no
|
|
5
|
+
* session exists to scope a slot by, and the pet would vanish (issue #48).
|
|
6
|
+
* The client half therefore mounts this entry straight onto `document.body`
|
|
7
|
+
* (see index.ts): while visible it renders the floating WhalePet (a portal),
|
|
8
|
+
* while hidden it renders a fixed-position summon button.
|
|
6
9
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import { useEffect, useSyncExternalStore, type ReactElement } from 'react'
|
|
10
|
-
import type { PropsLocale
|
|
11
|
-
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
13
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
14
|
import type { PetDisplayConfig } from '../persist.ts'
|
|
13
15
|
import type { PetStoreInstance } from './pet-store.ts'
|
|
14
16
|
import { WhalePet } from './WhalePet.tsx'
|
|
@@ -37,10 +39,9 @@ export interface PetInjected {
|
|
|
37
39
|
feedbackDone: () => void
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
/** Composed props of the
|
|
42
|
+
/** Composed props of the global pet entry (locale + injected; no slot runtime share). */
|
|
41
43
|
export type PetDockEntryProps =
|
|
42
|
-
|
|
43
|
-
& PetInjected
|
|
44
|
+
PetInjected
|
|
44
45
|
& PropsLocale<typeof NS>
|
|
45
46
|
|
|
46
47
|
const DEFAULT_DISPLAY: PetDisplayConfig = { visible: true, size: 160, right: 24, bottom: 20 }
|
|
@@ -81,10 +82,17 @@ export function PetDockEntry(props: PetDockEntryProps): ReactElement {
|
|
|
81
82
|
</span>
|
|
82
83
|
)
|
|
83
84
|
}
|
|
85
|
+
const display = snapshot?.display ?? DEFAULT_DISPLAY
|
|
84
86
|
return (
|
|
85
87
|
<button
|
|
86
88
|
type="button"
|
|
87
89
|
className={styles.summon}
|
|
90
|
+
style={{
|
|
91
|
+
position: 'fixed',
|
|
92
|
+
right: display.right,
|
|
93
|
+
bottom: display.bottom,
|
|
94
|
+
zIndex: 2147483000,
|
|
95
|
+
}}
|
|
88
96
|
onClick={props.summon}
|
|
89
97
|
data-testid="pet-summon"
|
|
90
98
|
>
|
package/src/client/WhalePet.tsx
CHANGED
|
@@ -243,11 +243,13 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
243
243
|
// above the sprite), so moving onto them fires pointerleave on the
|
|
244
244
|
// container. Treat a target still inside the container's DOM (the
|
|
245
245
|
// overflowed panel) as "still hovering"; otherwise give the pointer a
|
|
246
|
-
// short grace period to reach the panel across the gap above it.
|
|
246
|
+
// short grace period to reach the panel across the gap above it. The
|
|
247
|
+
// bridge (`.panel::after`) keeps the pointer inside the hit area, and
|
|
248
|
+
// the grace period covers a slow mouse crossing the remaining sliver.
|
|
247
249
|
const next = e.relatedTarget
|
|
248
250
|
if (next instanceof Node && floatRef.current?.contains(next)) return
|
|
249
251
|
clearHideTimer()
|
|
250
|
-
hideTimerRef.current = window.setTimeout(() => setHovered(false),
|
|
252
|
+
hideTimerRef.current = window.setTimeout(() => setHovered(false), 300)
|
|
251
253
|
}}
|
|
252
254
|
>
|
|
253
255
|
<div
|
|
@@ -280,7 +282,15 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
280
282
|
</div>
|
|
281
283
|
)}
|
|
282
284
|
{hovered && dragRef.current === null && (
|
|
283
|
-
<div
|
|
285
|
+
<div
|
|
286
|
+
className={styles.panel}
|
|
287
|
+
onPointerEnter={() => {
|
|
288
|
+
// Reaching the panel (or its bridge) must cancel any hide timer
|
|
289
|
+
// the container's pointerleave may have armed while the pointer
|
|
290
|
+
// crossed the sliver between the sprite and the panel.
|
|
291
|
+
clearHideTimer()
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
284
294
|
{renaming ? (
|
|
285
295
|
<div className={styles.renameRow}>
|
|
286
296
|
<input
|
package/src/client/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-pet browser half —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* host
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* dsh-pet browser half — mounts the whale-girl as a global floating surface
|
|
3
|
+
* and drives it from the host's same-origin `/api/pet/*` JSON endpoints: poll
|
|
4
|
+
* the host snapshot (~800 ms), forward interactions, persist drag positions.
|
|
5
|
+
* The pet is host-global (no session dimension), so it mounts directly onto
|
|
6
|
+
* `document.body` via a single React root rather than a session-scoped slot —
|
|
7
|
+
* on the new-conversation screen no session exists, and a dock-mounted pet
|
|
8
|
+
* would vanish there (issue #48). When the pet is hidden the entry becomes a
|
|
9
|
+
* fixed-position summon button.
|
|
10
10
|
* @module @linxin666/dsh-pet/client
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -20,10 +20,12 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
|
20
20
|
import type { PetDisplayConfig } from '../persist.ts'
|
|
21
21
|
import type { PetInteractResult, PetStateView } from '../service.ts'
|
|
22
22
|
import type { PetInteraction } from '../affinity.ts'
|
|
23
|
+
import { createElement } from 'react'
|
|
24
|
+
import { createRoot } from 'react-dom/client'
|
|
23
25
|
import { createPetStore, type PetStoreInstance } from './pet-store.ts'
|
|
24
26
|
import { PetDockEntry, type PetInjected } from './PetDockEntry.tsx'
|
|
25
27
|
import { PetSettingsCard, PetSettingsCardController, type PetSettings } from './PetSettingsCard.tsx'
|
|
26
|
-
import { NS, en, zh } from './locales.ts'
|
|
28
|
+
import { NS, en, zh, t } from './locales.ts'
|
|
27
29
|
|
|
28
30
|
/** The host pet API as the browser sees it (same-origin JSON endpoints). */
|
|
29
31
|
interface PetHttpApi {
|
|
@@ -91,9 +93,9 @@ export interface SettingsPluginItemOwnerProps {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
/**
|
|
94
|
-
* Client plugin body: register dictionaries, mount the
|
|
95
|
-
* loop while the plugin is enabled, and seat the settings card in the
|
|
96
|
-
* plugin group.
|
|
96
|
+
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
97
|
+
* poll loop while the plugin is enabled, and seat the settings card in the
|
|
98
|
+
* Web UI plugin group.
|
|
97
99
|
* @param ctx - client root context.
|
|
98
100
|
*/
|
|
99
101
|
export function apply(ctx: ClientContext): void {
|
|
@@ -118,8 +120,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
118
120
|
inject: () => petSettings.inject(),
|
|
119
121
|
}, PetSettingsCard))
|
|
120
122
|
|
|
121
|
-
// The
|
|
122
|
-
// enabled; toggling the setting off hides the pet and stops polling.
|
|
123
|
+
// The global pet entry, its store, and the poll loop live while the plugin
|
|
124
|
+
// is enabled; toggling the setting off hides the pet and stops polling.
|
|
123
125
|
let disposeUi: (() => void) | undefined
|
|
124
126
|
const syncUi = (): void => {
|
|
125
127
|
if (enabled() && disposeUi === undefined) {
|
|
@@ -233,22 +235,22 @@ export function apply(ctx: ClientContext): void {
|
|
|
233
235
|
},
|
|
234
236
|
})
|
|
235
237
|
|
|
236
|
-
// The
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}, PetDockEntry))
|
|
238
|
+
// The pet is host-global (its state/display/interactions have no session
|
|
239
|
+
// dimension), and the official rc.6 shell declares no root-scoped slot
|
|
240
|
+
// for a global floating surface — the dock is session-scoped, so a pet
|
|
241
|
+
// mounted there would vanish on the new-conversation screen (issue #48).
|
|
242
|
+
// The entry therefore mounts straight onto document.body via a single
|
|
243
|
+
// React root for the page lifetime: WhalePet portals itself to body when
|
|
244
|
+
// visible, and the hidden-state summon button is fixed-positioned.
|
|
245
|
+
const container = document.createElement('div')
|
|
246
|
+
container.dataset.dshPetRoot = ''
|
|
247
|
+
document.body.appendChild(container)
|
|
248
|
+
const petRoot = createRoot(container)
|
|
249
|
+
petRoot.render(createElement(PetDockEntry, { ...injected(), t }))
|
|
249
250
|
|
|
250
251
|
disposeUi = () => {
|
|
251
|
-
|
|
252
|
+
petRoot.unmount()
|
|
253
|
+
container.remove()
|
|
252
254
|
disposePoll()
|
|
253
255
|
disposeUi = undefined
|
|
254
256
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -100,6 +100,35 @@ export type PetKey = keyof typeof zh
|
|
|
100
100
|
/** The settings-card slice of the pet dictionary. */
|
|
101
101
|
export type SettingsCardKey = PetKey
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Active dictionary, picked by the document language at call time. The pet
|
|
105
|
+
* mounts as a global floating surface (not a session-scoped slot), so it has
|
|
106
|
+
* no framework locale seat and resolves its copy the same tiny way the
|
|
107
|
+
* task-board's DOM-injected surface does.
|
|
108
|
+
*/
|
|
109
|
+
export function dictionary(): Record<PetKey, string> {
|
|
110
|
+
const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'
|
|
111
|
+
return lang.toLowerCase().startsWith('en') ? en : zh
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Translate a key with optional `{name}` template params. Mirrors the slot
|
|
116
|
+
* `Translate` contract `(key, params?) => string` so it can be handed to the
|
|
117
|
+
* same components that used to receive the framework-injected `t` seat. The
|
|
118
|
+
* key is typed loosely (`string`) so the function is assignable to the slot's
|
|
119
|
+
* `TranslateNS<'pet'>` (whose key domain also spans the shared common
|
|
120
|
+
* vocabulary); a missing key degrades to the key itself rather than throwing.
|
|
121
|
+
*/
|
|
122
|
+
export function t(key: string, params?: Record<string, unknown>): string {
|
|
123
|
+
let text: string = (dictionary() as Record<string, string>)[key] ?? key
|
|
124
|
+
if (params !== undefined) {
|
|
125
|
+
for (const [name, value] of Object.entries(params)) {
|
|
126
|
+
text = text.replaceAll(`{${name}}`, String(value))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return text
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
104
133
|
interface LocaleNamespaceMap {
|
|
105
134
|
/** dsh-pet UI copy. */
|
|
@@ -75,6 +75,20 @@
|
|
|
75
75
|
min-width: 132px;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* 面板底边与图标顶边之间的 hover 桥接(issue #49):
|
|
79
|
+
面板绝对定位在图标上方(bottom: 100%),若两者之间存在哪怕几个
|
|
80
|
+
像素的间隙,指针滑过间隙时会触发容器的 pointerleave,面板在指针
|
|
81
|
+
抵达按钮前就收起。这段透明伪元素把命中区向下延伸、覆盖图标顶部,
|
|
82
|
+
使指针在图标与按钮之间始终停留在 hover 区域内。 */
|
|
83
|
+
.panel::after {
|
|
84
|
+
content: '';
|
|
85
|
+
position: absolute;
|
|
86
|
+
top: 100%;
|
|
87
|
+
left: 0;
|
|
88
|
+
right: 0;
|
|
89
|
+
height: 14px;
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
.rankRow {
|
|
79
93
|
display: flex;
|
|
80
94
|
justify-content: space-between;
|