dsh-xrxs-lingting 0.4.0-dev.48 → 0.4.0-dev.50

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
@@ -98,7 +98,7 @@ LINGTING_XIN_PROBE=1 nohup "/path/to/DSH Desktop Dev.app/Contents/MacOS/DSH Desk
98
98
 
99
99
  ## 客户端兼容性验证
100
100
 
101
- `0.4.0-dev.48` 起不再要求宿主提供旧版补丁的 `layout.business`。标准薪灵客户端通过公开的 `shell.overlay` 插槽显示灵听页面,页面中的「返回聊天」关闭页面;提供业务页扩展的旧宿主仍沿用原入口。
101
+ `0.4.0-dev.48` 起不再要求宿主提供旧版补丁的 `layout.business`。`0.4.0-dev.50` 起,标准薪灵客户端通过渲染器公开的 `[data-slot="conversation"]` 标记,将原有灵听页面挂载到主内容区:保留侧栏,暂时隐藏而不卸载聊天,返回时恢复原聊天和草稿。弹窗、列表、实时转写仍复用原组件;转写面板继续使用 `shell.overlay`。提供业务页扩展的旧宿主仍沿用原入口。
102
102
 
103
103
  发布前除单元测试外,应在构建后对目标薪灵源码运行客户端产物冒烟,验证真实布局对象、公开插槽、页面打开及返回聊天:
104
104
 
@@ -108,3 +108,5 @@ npm run smoke:client -- /path/to/cinlyn-desktop
108
108
  ```
109
109
 
110
110
  该检查不代替安装后对授权、录音、转写和回放的人工验证。
111
+
112
+ 布局回归验证:`node scripts/smoke-layout-browser.mjs <playwright/index.mjs 路径>`,使用 Chrome 无头浏览器运行真实插件产物,验证页面边界、弹窗居中、转写面板层级、窗口缩放与卸载后聊天恢复。使用合成录音状态,不访问业务数据、不采集声音。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-xrxs-lingting",
3
- "version": "0.4.0-dev.48",
3
+ "version": "0.4.0-dev.50",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite --host 127.0.0.1",
@@ -30,7 +30,7 @@ module.exports = __toCommonJS(client_exports);
30
30
  var define_import_meta_env_default = { VITE_XIN_PACKAGED: "1" };
31
31
 
32
32
  // src/plugin/client.tsx
33
- var import_react22 = require("react");
33
+ var import_react23 = require("react");
34
34
 
35
35
  // node_modules/lucide-react/dist/esm/createLucideIcon.js
36
36
  var import_react2 = require("react");
@@ -4449,24 +4449,39 @@ function isState(value) {
4449
4449
  const state = value;
4450
4450
  return typeof state?.reason === "string" && typeof state.businessReady === "boolean";
4451
4451
  }
4452
+ function unavailableHostState() {
4453
+ return {
4454
+ protocol: 1,
4455
+ providerAvailable: false,
4456
+ configured: false,
4457
+ authorized: false,
4458
+ businessReady: false,
4459
+ reason: "state-unavailable",
4460
+ context: null,
4461
+ capabilities: { atomicSnapshot: false, recoverSession: false, versioned: false }
4462
+ };
4463
+ }
4452
4464
  async function fetchXinHostState(fetchImpl = (input, init) => fetch(input, init)) {
4453
4465
  let response;
4454
4466
  try {
4455
4467
  response = await fetchImpl(XIN_STATE_ENDPOINT, { headers: { accept: "application/json" } });
4456
4468
  } catch {
4457
- return null;
4469
+ return unavailableHostState();
4458
4470
  }
4459
- if (!response.ok) return null;
4471
+ if (response.status === 404) return null;
4472
+ if (!response.ok) return unavailableHostState();
4460
4473
  try {
4461
4474
  const payload = await response.json();
4462
- return isState(payload) ? payload : null;
4475
+ return isState(payload) ? payload : unavailableHostState();
4463
4476
  } catch {
4464
- return null;
4477
+ return unavailableHostState();
4465
4478
  }
4466
4479
  }
4467
4480
  function explainXinState(state) {
4468
4481
  if (!state || state.businessReady) return "";
4469
4482
  switch (state.reason) {
4483
+ case "state-unavailable":
4484
+ return "\u65E0\u6CD5\u8BFB\u53D6\u85AA\u4EBA\u85AA\u4E8B\u6388\u6743\u72B6\u6001\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u8BF7\u91CD\u542F\u5BA2\u6237\u7AEF\u3002";
4470
4485
  case "provider-missing":
4471
4486
  return "\u672A\u5B89\u88C5\u300C\u85AA\u4EBA\u85AA\u4E8B\u6388\u6743\u300D\u63D2\u4EF6\uFF1A\u8BF7\u5728\u540C\u4E00 profile \u4E2D\u5B89\u88C5\u540E\u91CD\u542F DSH\u3002";
4472
4487
  case "provider-unconfigured":
@@ -5404,11 +5419,63 @@ function createBusinessNavigation() {
5404
5419
  };
5405
5420
  }
5406
5421
 
5422
+ // src/plugin/business-surface.tsx
5423
+ var import_react22 = require("react");
5424
+ var import_react_dom = require("react-dom");
5425
+ function mountBusinessSurface(doc) {
5426
+ const conversation = doc.querySelector('[data-slot="conversation"]');
5427
+ const center = conversation?.parentElement;
5428
+ if (!conversation || !center) return null;
5429
+ const target = doc.createElement("section");
5430
+ target.className = "lt-business-surface";
5431
+ target.setAttribute("aria-label", "\u7075\u542C");
5432
+ const display = conversation.style.getPropertyValue("display");
5433
+ const displayPriority = conversation.style.getPropertyPriority("display");
5434
+ const position = center.style.getPropertyValue("position");
5435
+ const positionPriority = center.style.getPropertyPriority("position");
5436
+ const inert = conversation.inert;
5437
+ conversation.style.setProperty("display", "none", "important");
5438
+ conversation.inert = true;
5439
+ center.style.setProperty("position", "relative");
5440
+ center.appendChild(target);
5441
+ let disposed = false;
5442
+ return { target, dispose() {
5443
+ if (disposed) return;
5444
+ disposed = true;
5445
+ target.remove();
5446
+ if (display) conversation.style.setProperty("display", display, displayPriority);
5447
+ else conversation.style.removeProperty("display");
5448
+ conversation.inert = inert;
5449
+ if (position) center.style.setProperty("position", position, positionPriority);
5450
+ else center.style.removeProperty("position");
5451
+ } };
5452
+ }
5453
+ function BusinessSurface({ children }) {
5454
+ const [target, setTarget] = (0, import_react22.useState)(null);
5455
+ (0, import_react22.useLayoutEffect)(() => {
5456
+ let mounted = null;
5457
+ const attach = () => {
5458
+ if (mounted?.target.isConnected) return;
5459
+ mounted?.dispose();
5460
+ mounted = mountBusinessSurface(document);
5461
+ setTarget(mounted?.target ?? null);
5462
+ };
5463
+ attach();
5464
+ const observer = new MutationObserver(attach);
5465
+ observer.observe(document.body, { childList: true, subtree: true });
5466
+ return () => {
5467
+ observer.disconnect();
5468
+ mounted?.dispose();
5469
+ };
5470
+ }, []);
5471
+ return target ? (0, import_react_dom.createPortal)(children, target) : null;
5472
+ }
5473
+
5407
5474
  // src/plugin/embedded.css
5408
5475
  var tag3 = document.createElement("style");
5409
5476
  tag3.dataset.plugin = "dsh-xrxs-lingting";
5410
5477
  tag3.dataset.pluginCss = "dsh-xrxs-lingting/embedded.css";
5411
- tag3.textContent = ".lingting-root { color-scheme: light; }\n/* Host \u9002\u914D\u5C42\u72B6\u6001\u63D0\u793A\uFF08\u6388\u6743/\u8EAB\u4EFD/\u76EE\u7684\u5730\u672A\u5C31\u7EEA\uFF09\uFF1A\u56FA\u5B9A\u9876\u90E8\uFF0C\u53EF\u5173\u95ED\uFF0C\u4E0D\u53C2\u4E0E\u9875\u9762\u5E03\u5C40 */\n.lt-host-notice { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 60; display: flex; align-items: center; gap: 12px; max-width: min(680px, calc(100vw - 32px)); padding: 10px 14px; border: 1px solid #e7e0fa; border-radius: 10px; background: #fff; box-shadow: 0 6px 24px rgb(40 28 74 / 16%); color: #605972; font-size: 13px; line-height: 1.5; }\n.lt-host-notice-text { flex: 1; }\n.lt-host-notice-close { flex-shrink: 0; border: 0; background: transparent; color: inherit; font-size: 16px; line-height: 1; padding: 2px 4px; border-radius: 6px; cursor: pointer; }\n.lt-host-notice-close:hover { background: #6650d814; }\n.lt-host-notice-close:focus-visible { outline: 2px solid #6650d8; outline-offset: 2px; }\n/* \u5D4C\u5165\u6A21\u5F0F\u4E0B\u6839\u5143\u7D20\u5373 .app\uFF08lingting-root app \u540C\u5143\u7D20\uFF09\uFF0C\u89C4\u5219\u9700\u7528\u7EC4\u5408\u9009\u62E9\u5668\uFF1B\u4FDD\u7559\u540E\u4EE3\u5F62\u5F0F\u517C\u5BB9\u53CC\u5C42\u5305\u88F9\u7ED3\u6784 */\n.lingting-root[data-embedded] { height: 100%; min-height: 0; container-type: inline-size; }\n.lingting-root[data-embedded].app, .lingting-root[data-embedded] .app { height: 100%; min-height: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; padding-bottom: 0; }\n.lingting-root[data-embedded].app .app-header, .lingting-root[data-embedded] .app-header { height: 66px; flex-shrink: 0; }\n.lingting-root[data-embedded].app .header-inner, .lingting-root[data-embedded] .header-inner { padding: 0 28px; max-width: none; }\n.lingting-root[data-embedded].app .brand-caption, .lingting-root[data-embedded] .brand-caption { display: none; }\n.lingting-root[data-embedded].app .main-content, .lingting-root[data-embedded] .main-content { width: 100%; max-width: none; min-height: 0; flex: 1; overflow: auto; padding: 30px 28px 0; scrollbar-width: thin; }\n\n.lingting-root[data-embedded].app .toast, .lingting-root[data-embedded] .toast { position: absolute; top: 77px; }\n.lingting-root[data-global-status] { flex-shrink: 0; border-top: 1px solid #e7e0fa; background: white; padding: 0 24px; }\n.lingting-root[data-global-status] .recording-controls.compact { min-height: 76px; padding: 12px 0; }\n.lingting-root[data-global-status] .error-notice { margin: 8px 0 0; }\n.lingting-sidebar-entry { display: flex; align-items: center; gap: 10px; border: 0; background: transparent; color: var(--dsw-alias-text-primary, #605972); padding: 10px 12px; width: 100%; border-radius: 9px; cursor: pointer; font: inherit; font-size: 14px; }\n.lingting-sidebar-entry:hover, .lingting-sidebar-entry[data-active] { background: #6650d814; color: #6650d8; }\n.lingting-sidebar-entry:focus-visible { outline: 2px solid #6650d8; outline-offset: 2px; }\n@container (max-width: 900px) {\n .lingting-root[data-embedded] .overview-note { display: none; }\n .lingting-root[data-embedded] .overview { grid-template-columns: 1fr 1fr; }\n .lingting-root[data-embedded] .list-heading { flex-wrap: wrap; }\n .lingting-root[data-embedded] .source-label { display: none; }\n}\n\n/* Standard DSH has no layout.business extension; keep the page in its public overlay slot. */\n.lt-business-surface { position: fixed; inset: 48px 12px 12px; z-index: 40; min-width: 0; overflow: hidden; border: 1px solid #e7e0fa; border-radius: 12px; background: #fff; box-shadow: 0 8px 32px #2c245221; pointer-events: auto; -webkit-app-region: no-drag; }\n";
5478
+ tag3.textContent = ".lingting-root { color-scheme: light; }\n/* Host \u9002\u914D\u5C42\u72B6\u6001\u63D0\u793A\uFF08\u6388\u6743/\u8EAB\u4EFD/\u76EE\u7684\u5730\u672A\u5C31\u7EEA\uFF09\uFF1A\u56FA\u5B9A\u9876\u90E8\uFF0C\u53EF\u5173\u95ED\uFF0C\u4E0D\u53C2\u4E0E\u9875\u9762\u5E03\u5C40 */\n.lt-host-notice { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 60; display: flex; align-items: center; gap: 12px; max-width: min(680px, calc(100vw - 32px)); padding: 10px 14px; border: 1px solid #e7e0fa; border-radius: 10px; background: #fff; box-shadow: 0 6px 24px rgb(40 28 74 / 16%); color: #605972; font-size: 13px; line-height: 1.5; }\n.lt-host-notice-text { flex: 1; }\n.lt-host-notice-close { flex-shrink: 0; border: 0; background: transparent; color: inherit; font-size: 16px; line-height: 1; padding: 2px 4px; border-radius: 6px; cursor: pointer; }\n.lt-host-notice-close:hover { background: #6650d814; }\n.lt-host-notice-close:focus-visible { outline: 2px solid #6650d8; outline-offset: 2px; }\n/* \u5D4C\u5165\u6A21\u5F0F\u4E0B\u6839\u5143\u7D20\u5373 .app\uFF08lingting-root app \u540C\u5143\u7D20\uFF09\uFF0C\u89C4\u5219\u9700\u7528\u7EC4\u5408\u9009\u62E9\u5668\uFF1B\u4FDD\u7559\u540E\u4EE3\u5F62\u5F0F\u517C\u5BB9\u53CC\u5C42\u5305\u88F9\u7ED3\u6784 */\n.lingting-root[data-embedded] { height: 100%; min-height: 0; container-type: inline-size; }\n.lingting-root[data-embedded].app, .lingting-root[data-embedded] .app { height: 100%; min-height: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; padding-bottom: 0; }\n.lingting-root[data-embedded].app .app-header, .lingting-root[data-embedded] .app-header { height: 66px; flex-shrink: 0; }\n.lingting-root[data-embedded].app .header-inner, .lingting-root[data-embedded] .header-inner { padding: 0 28px; max-width: none; }\n.lingting-root[data-embedded].app .brand-caption, .lingting-root[data-embedded] .brand-caption { display: none; }\n.lingting-root[data-embedded].app .main-content, .lingting-root[data-embedded] .main-content { width: 100%; max-width: none; min-height: 0; flex: 1; overflow: auto; padding: 30px 28px 0; scrollbar-width: thin; }\n\n.lingting-root[data-embedded].app .toast, .lingting-root[data-embedded] .toast { position: absolute; top: 77px; }\n.lingting-root[data-global-status] { flex-shrink: 0; border-top: 1px solid #e7e0fa; background: white; padding: 0 24px; }\n.lingting-root[data-global-status] .recording-controls.compact { min-height: 76px; padding: 12px 0; }\n.lingting-root[data-global-status] .error-notice { margin: 8px 0 0; }\n.lingting-sidebar-entry { display: flex; align-items: center; gap: 10px; border: 0; background: transparent; color: var(--dsw-alias-text-primary, #605972); padding: 10px 12px; width: 100%; border-radius: 9px; cursor: pointer; font: inherit; font-size: 14px; }\n.lingting-sidebar-entry:hover, .lingting-sidebar-entry[data-active] { background: #6650d814; color: #6650d8; }\n.lingting-sidebar-entry:focus-visible { outline: 2px solid #6650d8; outline-offset: 2px; }\n@container (max-width: 900px) {\n .lingting-root[data-embedded] .overview-note { display: none; }\n .lingting-root[data-embedded] .overview { grid-template-columns: 1fr 1fr; }\n .lingting-root[data-embedded] .list-heading { flex-wrap: wrap; }\n .lingting-root[data-embedded] .source-label { display: none; }\n}\n\n/* Use the real center column, preserving the host sidebar and conversation state. */\n.lt-business-surface { position: absolute; inset: 0; z-index: 1; min-width: 0; overflow: hidden; background: #f8f9fc; pointer-events: auto; -webkit-app-region: no-drag; }\n/* Host reset styles must not remove the native dialog's centered placement. */\n.lingting-root .modal { margin: auto; background: #fff; }\n";
5412
5479
  document.querySelectorAll("style[data-plugin-css=" + JSON.stringify("dsh-xrxs-lingting/embedded.css") + "]").forEach((old) => old.remove());
5413
5480
  document.head.appendChild(tag3);
5414
5481
 
@@ -5416,7 +5483,7 @@ document.head.appendChild(tag3);
5416
5483
  var import_jsx_runtime21 = require("react/jsx-runtime");
5417
5484
  var ID = "dsh-xrxs-lingting";
5418
5485
  var inject = ["slots", "layout"];
5419
- var PanelBoundary = class extends import_react22.Component {
5486
+ var PanelBoundary = class extends import_react23.Component {
5420
5487
  state = { failed: false };
5421
5488
  static getDerivedStateFromError() {
5422
5489
  return { failed: true };
@@ -5457,8 +5524,8 @@ async function apply(ctx) {
5457
5524
  }));
5458
5525
  const unregisterPage = business.register(ID);
5459
5526
  function Entry({ wide }) {
5460
- const panel = (0, import_react22.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
5461
- const layout = (0, import_react22.useSyncExternalStore)(business.subscribe, business.getSnapshot);
5527
+ const panel = (0, import_react23.useSyncExternalStore)(controller.subscribe, controller.getSnapshot);
5528
+ const layout = (0, import_react23.useSyncExternalStore)(business.subscribe, business.getSnapshot);
5462
5529
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("button", { type: "button", className: "lingting-sidebar-entry", "data-active": layout.activeId === ID || panel.open || void 0, title: "\u7075\u542C", "aria-label": "\u7075\u542C", onClick: () => business.open(ID), children: [
5463
5530
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Headphones, { size: 19 }),
5464
5531
  wide && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: "\u7075\u542C" })
@@ -5468,9 +5535,9 @@ async function apply(ctx) {
5468
5535
  return activeId === ID ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PanelBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LingtingApp, { service, navigation, visible: true, embedded: true, hostState, onBackToHost: () => business.close(), onOpenLive: () => controller.open(), onOpenRecord: (id) => controller.openRecord(id) }) }) : null;
5469
5536
  }
5470
5537
  function Overlay() {
5471
- const page = (0, import_react22.useSyncExternalStore)(business.subscribe, business.getSnapshot, business.getSnapshot);
5538
+ const page = (0, import_react23.useSyncExternalStore)(business.subscribe, business.getSnapshot, business.getSnapshot);
5472
5539
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
5473
- !hostBusiness && page.activeId === ID && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("section", { className: "lt-business-surface", "aria-label": "\u7075\u542C", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Page, { activeId: page.activeId }) }),
5540
+ !hostBusiness && page.activeId === ID && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BusinessSurface, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Page, { activeId: page.activeId }) }),
5474
5541
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PanelBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(XinHostNotice, { store: hostState }) }),
5475
5542
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PanelBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LingtingSidebar, { service, controller, onOpenPage: () => business.open(ID) }) }),
5476
5543
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PanelBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(MeetingPrompt, { service }) })
@@ -65,7 +65,7 @@ var PRESENCE_STATES = /* @__PURE__ */ new Set(["idle", "starting", "recording",
65
65
  function registerMeetingRoutes(connection, runtime) {
66
66
  const disposers = [];
67
67
  const register = (path, methods, fetch2) => {
68
- disposers.push(connection.fetch.register({ path, methods, fetch: fetch2 }));
68
+ disposers.push(connection.fetch.register({ path, methods, requestBody: "buffered", fetch: fetch2 }));
69
69
  };
70
70
  register("/api/lingting/meeting.state", ["GET"], () => json(runtime.view()));
71
71
  register("/api/lingting/runtime.state", ["GET"], () => {
@@ -2840,7 +2840,7 @@ function registerXinRoutes(deps) {
2840
2840
  let counter = 0;
2841
2841
  const requestId = () => `xz-${Date.now().toString(36)}-${(counter += 1).toString(36)}`;
2842
2842
  const register = (path, methods, fetch2) => {
2843
- disposers.push(deps.connection.fetch.register({ path, methods, fetch: fetch2 }));
2843
+ disposers.push(deps.connection.fetch.register({ path, methods, requestBody: "buffered", fetch: fetch2 }));
2844
2844
  };
2845
2845
  register("/api/lingting/xin.state", ["GET"], async () => {
2846
2846
  const state = await deps.context.state();