@linxin666/dsh-pet 0.1.7 → 0.1.9

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 CHANGED
@@ -47,8 +47,8 @@ dsh-pet/
47
47
  | |-- persist.ts # 持久化($DSH_HOME/pet.json,原子写入)
48
48
  | |-- routes.ts # /api/pet/* JSON API + /pet/whale/* 素材静态路由
49
49
  | `-- client/ # 浏览器半区
50
- | |-- index.ts # slots 注册 + 轮询(800ms)+ 交互接线(fetch)
51
- | |-- PetDockEntry.tsx # conversation.input.selector.context 挂载点
50
+ | |-- index.ts # 全局挂载(createRoot body)+ 轮询(800ms)+ 交互接线(fetch)
51
+ | |-- PetDockEntry.tsx # 全局浮层入口(document.body,无会话/新会话/会话中全程显示)
52
52
  | |-- WhalePet.tsx # 浮层组件(portal + rAF 帧动画 + 拖动)
53
53
  | |-- spritesheet.ts # 图集几何 + 每状态动画轨道(帧/时长)
54
54
  | `-- pet.module.css
@@ -61,13 +61,13 @@ dsh-pet/
61
61
  ```
62
62
  activity/status session 事件(原 working-activity 插件发布) --> PetService(host)
63
63
  | /api/pet/* JSON
64
- conversation.input.selector.context 槽位 <-- 轮询 800ms -- pet-client(浏览器)
64
+ global React root(createRoot → document.body) <-- 轮询 800ms -- pet-client(浏览器)
65
65
  |
66
66
  WhalePet 浮层(portal + rAF)
67
67
  ```
68
68
 
69
69
  - **状态源**:监听 `activity/status` 会话事件(phase: idle/waiting/thinking/tool/done + 状态短语),由 host 半区消费;该事件曾由 working-activity 插件发布,插件已从本仓库移除,未安装时宠物只随会话生命周期变化。
70
- - **挂载点**:`conversation.input.selector.context`(list 槽位,输入选择行,无会话/新会话/会话中全程挂载——旧挂载点 `conversation.composer.dock` 只在活跃会话渲染,导致新会话界面看不到宠物),组件内部 `createPortal` 渲染全局浮层。
70
+ - **挂载点**:`document.body`(全局 React root,无会话/新会话/会话中全程显示——旧挂载点 `conversation.composer.dock` 只在活跃会话渲染,导致新会话界面看不到宠物),组件内部 `createPortal` 渲染全局浮层。
71
71
  - **渲染**:CSS sprite(background-position)逐帧动画,帧时长来自 `spritesheet.ts` 的轨道定义。
72
72
  - **通信**:浏览器 ↔ host 走同源 `/api/pet/*` JSON 端点(state/interact/set-visible/set-config),图集从 `/pet/whale/spritesheet.webp` 加载——RPC 域与 `/plugins/` 静态服务都是平台注册的,插件自足地提供自己的 API 与素材(与 dsh-remote-web-ui 的 `/api/pair` 同一模式)。
73
73
 
package/lib/client.js CHANGED
@@ -4,8 +4,9 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
7
  let react = require("react");
8
+ let react_dom_client = require("react-dom/client");
9
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
9
10
  let react_dom = require("react-dom");
10
11
  let react_jsx_runtime = require("react/jsx-runtime");
11
12
  //#region src/client/pet-store.ts
@@ -304,7 +305,7 @@ window.__ModuleLoader__.load({
304
305
  }
305
306
  //#endregion
306
307
  //#region \0dsh-css:/home/runner/work/dsh-web-ui/dsh-web-ui/packages/dsh-pet/src/client/pet.module.css.mjs
307
- const css$1 = ".l0P8iq_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.l0P8iq_sprite{image-rendering:auto;touch-action:none;position:relative}.l0P8iq_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards l0P8iq_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.l0P8iq_bubblePet{background:#f472b6eb}.l0P8iq_bubbleFeed{background:#38bdf8eb}@keyframes l0P8iq_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.l0P8iq_panel{color:#e2e8f0;backdrop-filter:blur(6px);background:#0f172aeb;border:1px solid #94a3b859;border-radius:10px;flex-direction:column;gap:6px;min-width:132px;padding:8px 10px;font-size:12px;display:flex;position:absolute;bottom:100%;box-shadow:0 4px 16px #00000059}.l0P8iq_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.l0P8iq_nameCell{font-weight:600}.l0P8iq_renameRow{align-items:center;gap:6px;display:flex}.l0P8iq_nameInput{color:#e2e8f0;background:#1e293be6;border:1px solid #7dd3fc80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.l0P8iq_nameInput:focus{border-color:#38bdf8;box-shadow:0 0 0 2px #38bdf873}.l0P8iq_actions{gap:6px;display:flex}.l0P8iq_action{cursor:pointer;color:#0f172a;background:linear-gradient(#7dd3fc,#38bdf8);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;transition:filter .12s,box-shadow .12s}.l0P8iq_action:hover{filter:brightness(1.08)}.l0P8iq_action:active{filter:brightness(.94)}.l0P8iq_action:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}.l0P8iq_summon{color:#7dd3fc;cursor:pointer;background:#0f172abf;border:1px dashed #7dd3fc99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.l0P8iq_summon:hover{color:#bae6fd;background:#0f172ae6;border-color:#7dd3fcf2}.l0P8iq_summon:active{color:#7dd3fc;border-color:#7dd3fccc}.l0P8iq_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@media (prefers-reduced-motion:reduce){.l0P8iq_bubble{opacity:1;animation:none}.l0P8iq_action,.l0P8iq_summon{transition:none}}";
308
+ const css$1 = ".l0P8iq_float{pointer-events:auto;-webkit-user-select:none;user-select:none;flex-direction:column;align-items:center;display:flex;position:fixed}.l0P8iq_sprite{image-rendering:auto;touch-action:none;position:relative}.l0P8iq_bubble{white-space:nowrap;color:#fff;pointer-events:none;border-radius:999px;margin-bottom:6px;padding:4px 10px;font-size:12px;line-height:1.4;animation:2.6s ease-out forwards l0P8iq_pet-bubble-pop;position:absolute;bottom:100%;box-shadow:0 2px 8px #00000040}.l0P8iq_bubblePet{background:#f472b6eb}.l0P8iq_bubbleFeed{background:#38bdf8eb}@keyframes l0P8iq_pet-bubble-pop{0%{opacity:0;transform:translateY(6px)scale(.85)}15%{opacity:1;transform:translateY(0)scale(1.05)}25%{transform:translateY(0)scale(1)}75%{opacity:1}to{opacity:0;transform:translateY(-8px)scale(.95)}}.l0P8iq_panel{color:#e2e8f0;backdrop-filter:blur(6px);background:#0f172aeb;border:1px solid #94a3b859;border-radius:10px;flex-direction:column;gap:6px;min-width:132px;padding:8px 10px;font-size:12px;display:flex;position:absolute;bottom:100%;box-shadow:0 4px 16px #00000059}.l0P8iq_panel:after{content:\"\";height:14px;position:absolute;top:100%;left:0;right:0}.l0P8iq_rankRow{white-space:nowrap;justify-content:space-between;gap:10px;display:flex}.l0P8iq_nameCell{font-weight:600}.l0P8iq_renameRow{align-items:center;gap:6px;display:flex}.l0P8iq_nameInput{color:#e2e8f0;background:#1e293be6;border:1px solid #7dd3fc80;border-radius:6px;outline:none;flex:1;min-width:0;padding:3px 6px;font-size:12px}.l0P8iq_nameInput:focus{border-color:#38bdf8;box-shadow:0 0 0 2px #38bdf873}.l0P8iq_actions{gap:6px;display:flex}.l0P8iq_action{cursor:pointer;color:#0f172a;background:linear-gradient(#7dd3fc,#38bdf8);border:none;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;transition:filter .12s,box-shadow .12s}.l0P8iq_action:hover{filter:brightness(1.08)}.l0P8iq_action:active{filter:brightness(.94)}.l0P8iq_action:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}.l0P8iq_summon{color:#7dd3fc;cursor:pointer;background:#0f172abf;border:1px dashed #7dd3fc99;border-radius:999px;padding:2px 10px;font-size:11px;transition:border-color .12s,color .12s,background .12s,box-shadow .12s}.l0P8iq_summon:hover{color:#bae6fd;background:#0f172ae6;border-color:#7dd3fcf2}.l0P8iq_summon:active{color:#7dd3fc;border-color:#7dd3fccc}.l0P8iq_summon:focus-visible{outline:none;box-shadow:0 0 0 2px #38bdf8d9}@media (prefers-reduced-motion:reduce){.l0P8iq_bubble{opacity:1;animation:none}.l0P8iq_action,.l0P8iq_summon{transition:none}}";
308
309
  const tagId$1 = "@linxin666/dsh-pet/pet.module.css";
309
310
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
310
311
  const tag = document.createElement("style");
@@ -502,7 +503,7 @@ window.__ModuleLoader__.load({
502
503
  const next = e.relatedTarget;
503
504
  if (next instanceof Node && floatRef.current?.contains(next)) return;
504
505
  clearHideTimer();
505
- hideTimerRef.current = window.setTimeout(() => setHovered(false), 200);
506
+ hideTimerRef.current = window.setTimeout(() => setHovered(false), 300);
506
507
  },
507
508
  children: [
508
509
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -533,6 +534,9 @@ window.__ModuleLoader__.load({
533
534
  }, feedback.at),
534
535
  hovered && dragRef.current === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
535
536
  className: pet_module_css_default.panel,
537
+ onPointerEnter: () => {
538
+ clearHideTimer();
539
+ },
536
540
  children: renaming ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
537
541
  className: pet_module_css_default.renameRow,
538
542
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -609,10 +613,13 @@ window.__ModuleLoader__.load({
609
613
  //#endregion
610
614
  //#region src/client/PetDockEntry.tsx
611
615
  /**
612
- * Dock anchor inside the official SDK's `conversation.input.dock` seat
613
- * (the ambient readout band above the composer card, listed so several
614
- * additions can stack). While visible it mounts the floating WhalePet
615
- * (portal); while hidden it renders the summon button.
616
+ * Global floating pet entry. The pet is host-global (its state, display and
617
+ * interactions live on `/api/pet/*` endpoints with no session dimension), so
618
+ * it must not ride a session-scoped slot — on the new-conversation screen no
619
+ * session exists to scope a slot by, and the pet would vanish (issue #48).
620
+ * The client half therefore mounts this entry straight onto `document.body`
621
+ * (see index.ts): while visible it renders the floating WhalePet (a portal),
622
+ * while hidden it renders a fixed-position summon button.
616
623
  * @module @linxin666/dsh-pet/client/PetDockEntry
617
624
  */
618
625
  const DEFAULT_DISPLAY = {
@@ -653,9 +660,16 @@ window.__ModuleLoader__.load({
653
660
  t: props.t
654
661
  })
655
662
  });
663
+ const display = snapshot?.display ?? DEFAULT_DISPLAY;
656
664
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
657
665
  type: "button",
658
666
  className: pet_module_css_default.summon,
667
+ style: {
668
+ position: "fixed",
669
+ right: display.right,
670
+ bottom: display.bottom,
671
+ zIndex: 2147483e3
672
+ },
659
673
  onClick: props.summon,
660
674
  "data-testid": "pet-summon",
661
675
  children: props.t("pet.summon", { name: snapshot?.name ?? "鲸鱼娘" })
@@ -1353,6 +1367,28 @@ window.__ModuleLoader__.load({
1353
1367
  "settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
1354
1368
  "settings.invalidNumber": "Enter a number, or leave blank to use the default."
1355
1369
  };
1370
+ /**
1371
+ * Active dictionary, picked by the document language at call time. The pet
1372
+ * mounts as a global floating surface (not a session-scoped slot), so it has
1373
+ * no framework locale seat and resolves its copy the same tiny way the
1374
+ * task-board's DOM-injected surface does.
1375
+ */
1376
+ function dictionary() {
1377
+ return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? en : zh;
1378
+ }
1379
+ /**
1380
+ * Translate a key with optional `{name}` template params. Mirrors the slot
1381
+ * `Translate` contract `(key, params?) => string` so it can be handed to the
1382
+ * same components that used to receive the framework-injected `t` seat. The
1383
+ * key is typed loosely (`string`) so the function is assignable to the slot's
1384
+ * `TranslateNS<'pet'>` (whose key domain also spans the shared common
1385
+ * vocabulary); a missing key degrades to the key itself rather than throwing.
1386
+ */
1387
+ function t(key, params) {
1388
+ let text = dictionary()[key] ?? key;
1389
+ if (params !== void 0) for (const [name, value] of Object.entries(params)) text = text.replaceAll(`{${name}}`, String(value));
1390
+ return text;
1391
+ }
1356
1392
  //#endregion
1357
1393
  //#region src/client/index.ts
1358
1394
  /** Same-origin JSON fetch helper (GET without body, POST with JSON body). */
@@ -1386,9 +1422,9 @@ window.__ModuleLoader__.load({
1386
1422
  "remote"
1387
1423
  ];
1388
1424
  /**
1389
- * Client plugin body: register dictionaries, mount the dock entry and poll
1390
- * loop while the plugin is enabled, and seat the settings card in the Web UI
1391
- * plugin group.
1425
+ * Client plugin body: register dictionaries, mount the global pet entry and
1426
+ * poll loop while the plugin is enabled, and seat the settings card in the
1427
+ * Web UI plugin group.
1392
1428
  * @param ctx - client root context.
1393
1429
  */
1394
1430
  function apply(ctx) {
@@ -1495,15 +1531,17 @@ window.__ModuleLoader__.load({
1495
1531
  setFeedback(null);
1496
1532
  }
1497
1533
  });
1498
- const disposeDock = ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1499
- name: "conversation.input.dock",
1500
- id: "pet",
1501
- order: 110,
1502
- inject: injected,
1503
- locale: "pet"
1504
- }, PetDockEntry));
1534
+ const container = document.createElement("div");
1535
+ container.dataset.dshPetRoot = "";
1536
+ document.body.appendChild(container);
1537
+ const petRoot = (0, react_dom_client.createRoot)(container);
1538
+ petRoot.render((0, react.createElement)(PetDockEntry, {
1539
+ ...injected(),
1540
+ t
1541
+ }));
1505
1542
  disposeUi = () => {
1506
- disposeDock();
1543
+ petRoot.unmount();
1544
+ container.remove();
1507
1545
  disposePoll();
1508
1546
  disposeUi = void 0;
1509
1547
  };