@huaqiu/dsh-tool-schematic-gen 0.3.13 → 0.3.15

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/lib/client.js CHANGED
@@ -50,12 +50,17 @@ window.__ModuleLoader__.load({
50
50
  artifact = artOf(o.zipArtifact);
51
51
  if (artifact) artifacts.push(artifact);
52
52
  } else {
53
+ const zipRef = artOf(o.zipArtifact);
54
+ if (zipRef) {
55
+ artifact = zipRef;
56
+ artifacts.push(zipRef);
57
+ }
53
58
  const list = Array.isArray(o.schArtifacts) ? o.schArtifacts : [];
54
59
  for (const entry of list) {
55
60
  const ref = artOf(entry);
56
61
  if (ref) artifacts.push(ref);
57
62
  }
58
- artifact = artifacts.length > 0 ? artifacts[0] : null;
63
+ if (!artifact) artifact = artifacts.length > 0 ? artifacts[0] : null;
59
64
  }
60
65
  return {
61
66
  status,
@@ -380,6 +385,8 @@ window.__ModuleLoader__.load({
380
385
  .hq-sch__login-desc { margin: 0 0 10px; font: var(--dsw-font-xxs-12, 12px/1.4 system-ui, sans-serif); color: var(--dsw-alias-label-secondary, currentColor); line-height: 1.5; }
381
386
  .hq-sch__login-status { margin: 0 0 10px; font: var(--dsw-font-xxs-12, 12px/1.4 system-ui, sans-serif); line-height: 1.5; }
382
387
  .hq-sch__login-iframe { width: 100%; height: 440px; border: 0; border-radius: 8px; display: block; }
388
+ .hq-sch__login-btn { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--dsw-alias-border-l1, currentColor); border-radius: 6px; padding: 6px 16px; background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,0.08)); color: var(--dsw-alias-label-primary, currentColor); font: var(--dsw-font-xxs-12, 12px/1.4 system-ui, sans-serif); cursor: pointer; }
389
+ .hq-sch__login-btn:hover { filter: brightness(1.08); }
383
390
 
384
391
  /* ── live call stack (long-running generations) ───────────────────────────── */
385
392
  .hq-sch__progress { display: flex; align-items: center; gap: 8px; padding: 0 12px 8px; font: var(--dsw-font-xxs-12, 12px/1.4 system-ui, sans-serif); color: var(--dsw-alias-label-secondary, currentColor); }
@@ -487,6 +494,10 @@ window.__ModuleLoader__.load({
487
494
  "card.action.downloading": "下载中…",
488
495
  "card.action.place": "放置到编辑器",
489
496
  "card.action.placing": "放置中…",
497
+ "card.action.openInEda": "在 EDA 中打开",
498
+ "card.action.openingInEda": "导入中…",
499
+ "card.import.done": "已在 EDA 中打开设计({id})",
500
+ "card.import.failed": "在 EDA 中打开失败:{detail}",
490
501
  "card.place.done": "已放置 {count} 个文件到编辑器",
491
502
  "card.place.partial": "已放置 {placed} 个,{failed} 个失败:",
492
503
  "card.place.failed": "放置失败:",
@@ -499,6 +510,8 @@ window.__ModuleLoader__.load({
499
510
  "card.auth.desc": "工具「{tool}」需要登录华秋 EDA AI 账号才能继续。请在下方的登录框完成登录(或点击左侧「华秋EDA AI登录」按钮);登录完成后,回复助手「已登录,请重试」,助手会自动重新调用该工具。",
500
511
  "card.auth.loggedIn": "✓ 已登录{nickname}—— 现在可以回复助手「已登录,请重试」,助手会重新调用工具。",
501
512
  "card.auth.loggedOut": "未登录 —— 请在上方登录华秋 EDA AI(eda.cn)账号,或点击左侧「华秋EDA AI 登录」按钮;登录完成后让助手重试。",
513
+ "card.auth.descHost": "工具「{tool}」需要登录华秋 EDA AI 账号才能继续。点击下方按钮后,EDA(KiCad)将弹出登录窗口,请在弹出的窗口中完成登录;登录完成后回复助手「已登录,请重试」,助手会自动重新调用该工具。",
514
+ "card.auth.loginBtn": "登录华秋 EDA AI",
502
515
  "card.nicknameSep": ":{nickname}"
503
516
  };
504
517
  const COPY = {
@@ -538,6 +551,10 @@ window.__ModuleLoader__.load({
538
551
  "card.action.downloading": "Downloading…",
539
552
  "card.action.place": "Place in editor",
540
553
  "card.action.placing": "Placing…",
554
+ "card.action.openInEda": "Open in EDA",
555
+ "card.action.openingInEda": "Importing…",
556
+ "card.import.done": "Design opened in EDA ({id})",
557
+ "card.import.failed": "Failed to open in EDA: {detail}",
541
558
  "card.place.done": "Placed {count} file(s) into the editor",
542
559
  "card.place.partial": "Placed {placed}, {failed} failed: ",
543
560
  "card.place.failed": "Placement failed: ",
@@ -550,6 +567,8 @@ window.__ModuleLoader__.load({
550
567
  "card.auth.desc": "Tool \"{tool}\" requires a Huaqiu EDA AI login. Complete the login below (or use the 华秋EDA AI sidebar button); then reply \"I have logged in, please retry\" so the assistant can retry the tool.",
551
568
  "card.auth.loggedIn": "✓ Logged in{nickname} — reply \"I have logged in, please retry\" and the assistant will retry.",
552
569
  "card.auth.loggedOut": "Not logged in — complete the login above, or use the 华秋EDA AI sidebar button.",
570
+ "card.auth.descHost": "Tool \"{tool}\" requires a Huaqiu EDA AI account. Click the button below — EDA (KiCad) will open its login dialog. Complete the login there, then reply \"I have logged in, please retry\" so the assistant can retry the tool.",
571
+ "card.auth.loginBtn": "Sign in to Huaqiu EDA AI",
553
572
  "card.nicknameSep": ": {nickname}"
554
573
  }
555
574
  };
@@ -105626,6 +105645,20 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105626
105645
  ] });
105627
105646
  }
105628
105647
  //#endregion
105648
+ //#region src/client/b64.ts
105649
+ /**
105650
+ * Base64-encode a byte array for transport inside JSON (the edge-bridge proxy
105651
+ * serializes object bodies to JSON, so binary must travel base64). Chunked to
105652
+ * avoid call-stack limits on large zips. Pure module — no DOM/ecad imports —
105653
+ * so node-side tests can import it directly.
105654
+ */
105655
+ function bytesToBase64(bytes) {
105656
+ let bin = "";
105657
+ const chunkSize = 32768;
105658
+ for (let i = 0; i < bytes.length; i += chunkSize) bin += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
105659
+ return btoa(bin);
105660
+ }
105661
+ //#endregion
105629
105662
  //#region src/client/hit-card.tsx
105630
105663
  /**
105631
105664
  * Keyed `tool.call.toolview` HIT card for schematic generation (both
@@ -105745,7 +105778,7 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105745
105778
  });
105746
105779
  if (cancelled || canvas !== canvasRef.current) return;
105747
105780
  sizeCanvasFor(canvas);
105748
- if (payload.kind === "system" && payload.bytes) disposeViewer = await renderProjectZipToCanvas(payload.bytes, canvas);
105781
+ if (payload.bytes) disposeViewer = await renderProjectZipToCanvas(payload.bytes, canvas);
105749
105782
  else if (payload.source) disposeViewer = await renderSheetToCanvas(payload.source, canvas);
105750
105783
  else throw new Error("no preview source");
105751
105784
  if (cancelled || canvas !== canvasRef.current) return;
@@ -105790,15 +105823,22 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105790
105823
  }), overlay]
105791
105824
  });
105792
105825
  }
105793
- function LoginCard({ toolName, authState, t }) {
105826
+ function LoginCard({ toolName, authState, t, getAuth }) {
105794
105827
  const dark = useTheme();
105795
105828
  const locale = useLocale();
105829
+ const authClient = getAuth?.()?.auth;
105830
+ const hostMode = authClient?.isHostMode?.() ?? false;
105796
105831
  const src = (0, react.useMemo)(() => buildLoginUrl({
105797
105832
  lang: locale,
105798
105833
  theme: dark ? "dark" : "light"
105799
105834
  }), [locale, dark]);
105800
105835
  const remountKey = `${locale}|${dark ? "d" : "l"}`;
105801
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105836
+ const statusLine = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
105837
+ className: "hq-sch__login-status",
105838
+ style: { color: authState?.authenticated ? "#1677ff" : "#d4380d" },
105839
+ children: authState?.authenticated ? t("card.auth.loggedIn", { nickname: authState.nickname ? t("card.nicknameSep", { nickname: authState.nickname }) : "" }) : t("card.auth.loggedOut")
105840
+ });
105841
+ if (hostMode) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105802
105842
  className: "hq-sch",
105803
105843
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105804
105844
  className: "hq-sch__header",
@@ -105814,13 +105854,42 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105814
105854
  children: [
105815
105855
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
105816
105856
  className: "hq-sch__login-desc",
105817
- children: t("card.auth.desc", { tool: toolName })
105857
+ children: t("card.auth.descHost", { tool: toolName })
105818
105858
  }),
105859
+ statusLine,
105860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
105861
+ type: "button",
105862
+ className: "hq-sch__login-btn",
105863
+ onClick: () => {
105864
+ authClient?.login?.({
105865
+ lang: locale,
105866
+ theme: dark ? "dark" : "light"
105867
+ }).catch(() => {});
105868
+ },
105869
+ children: t("card.auth.loginBtn")
105870
+ })
105871
+ ]
105872
+ })]
105873
+ });
105874
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105875
+ className: "hq-sch",
105876
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105877
+ className: "hq-sch__header",
105878
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
105879
+ className: "hq-sch__icon",
105880
+ children: "⇶"
105881
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
105882
+ className: "hq-sch__title",
105883
+ children: t("card.auth.title")
105884
+ })]
105885
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
105886
+ className: "hq-sch__login",
105887
+ children: [
105819
105888
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
105820
- className: "hq-sch__login-status",
105821
- style: { color: authState?.authenticated ? "#1677ff" : "#d4380d" },
105822
- children: authState?.authenticated ? t("card.auth.loggedIn", { nickname: authState.nickname ? t("card.nicknameSep", { nickname: authState.nickname }) : "" }) : t("card.auth.loggedOut")
105889
+ className: "hq-sch__login-desc",
105890
+ children: t("card.auth.desc", { tool: toolName })
105823
105891
  }),
105892
+ statusLine,
105824
105893
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
105825
105894
  src,
105826
105895
  title: t("card.auth.title"),
@@ -105856,7 +105925,7 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105856
105925
  });
105857
105926
  (async () => {
105858
105927
  try {
105859
- if ((result.kind ?? "schematic") === "system") {
105928
+ if ((result?.artifact?.type ?? (result?.kind === "system" ? "zip" : "schematic")) === "zip") {
105860
105929
  const art = await resolveArtifactBytes(artifactKey);
105861
105930
  if (cancelled) return;
105862
105931
  setPayload({
@@ -105896,18 +105965,50 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105896
105965
  }, [artifactKey, state.phase]);
105897
105966
  const [busy, setBusy] = (0, react.useState)(null);
105898
105967
  const [placeStatus, setPlaceStatus] = (0, react.useState)(null);
105968
+ const [importStatus, setImportStatus] = (0, react.useState)(null);
105899
105969
  function onDownload() {
105900
105970
  if (busy || payload.phase !== "ready") return;
105901
- const kind = result?.kind ?? "schematic";
105902
- const filename = downloadFilenameFor(kind, result?.artifact ?? null, result?.designName ?? null);
105971
+ const filename = downloadFilenameFor(result?.kind ?? "schematic", result?.artifact ?? null, result?.designName ?? null);
105903
105972
  setBusy("download");
105904
105973
  try {
105905
- if (kind === "system" && payload.bytes) downloadBytes(filename, payload.bytes);
105974
+ if (payload.bytes) downloadBytes(filename, payload.bytes);
105906
105975
  else if (payload.source != null) downloadText(filename, payload.source);
105907
105976
  } finally {
105908
105977
  setBusy(null);
105909
105978
  }
105910
105979
  }
105980
+ /**
105981
+ * Open the generated project in the EDA editor. Host mode only: the browser
105982
+ * half posts the project zip (base64 in JSON — the edge-bridge proxy cannot
105983
+ * carry multipart) to hq-edge `POST /api/v1/import/kicad-b64`, which runs
105984
+ * the ImportDesign pipeline (extract → gRPC → EDA opens the design).
105985
+ */
105986
+ async function onOpenInEda() {
105987
+ if (busy || payload.phase !== "ready" || !payload.bytes) return;
105988
+ const api = props.getHqEdge?.()?.api;
105989
+ if (!api || typeof api.request !== "function") return;
105990
+ setBusy("open-in-eda");
105991
+ setImportStatus(null);
105992
+ try {
105993
+ const body = await api.request({
105994
+ method: "POST",
105995
+ path: "/api/v1/import/kicad-b64",
105996
+ body: {
105997
+ zip_b64: bytesToBase64(payload.bytes),
105998
+ filename: payload.filename ?? "design.zip",
105999
+ project_name: result?.designName ?? "schematic",
106000
+ source_vendor: "circuit_agent",
106001
+ source_format: "kicad"
106002
+ }
106003
+ }) ?? {};
106004
+ setImportStatus(t("card.import.done", { id: body.design_id ?? body.status ?? "ok" }));
106005
+ } catch (e) {
106006
+ console.warn("[hq-schematic-gen] open-in-eda failed", e);
106007
+ setImportStatus(t("card.import.failed", { detail: String(e?.message || e) }));
106008
+ } finally {
106009
+ setBusy(null);
106010
+ }
106011
+ }
105911
106012
  function onRegenerate() {
105912
106013
  if (busy) return;
105913
106014
  setBusy("regenerate");
@@ -105966,7 +106067,8 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
105966
106067
  if (state.phase === "needs_auth") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginCard, {
105967
106068
  toolName: props.toolName,
105968
106069
  authState: props.authState,
105969
- t
106070
+ t,
106071
+ getAuth: props.getAuth
105970
106072
  });
105971
106073
  const headerKind = result?.kind ?? kindOf(props.toolName);
105972
106074
  if (state.phase === "generating") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -106038,6 +106140,7 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
106038
106140
  const placeSupport = placeSupportOf(props.getHqEdge, "schematic");
106039
106141
  const placeableCount = result.artifacts.filter((a) => a.uri).length;
106040
106142
  const canPlace = !!placeSupport?.() && placeableCount > 0;
106143
+ const canOpenInEda = payload.phase === "ready" && payload.bytes != null && !!props.getHqEdge?.()?.api?.request;
106041
106144
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
106042
106145
  className: "hq-sch",
106043
106146
  children: [
@@ -106058,6 +106161,13 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
106058
106161
  disabled: !canDownload || busy === "download",
106059
106162
  children: ["⭳ ", busy === "download" ? t("card.action.downloading") : t("card.action.download")]
106060
106163
  }),
106164
+ canOpenInEda ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
106165
+ type: "button",
106166
+ className: "hq-sch__act",
106167
+ onClick: onOpenInEda,
106168
+ disabled: busy === "open-in-eda",
106169
+ children: ["⇱ ", busy === "open-in-eda" ? t("card.action.openingInEda") : t("card.action.openInEda")]
106170
+ }) : null,
106061
106171
  canPlace ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
106062
106172
  type: "button",
106063
106173
  className: "hq-sch__act",
@@ -106080,6 +106190,10 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
106080
106190
  }) : null
106081
106191
  ]
106082
106192
  }),
106193
+ importStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
106194
+ className: "hq-sch__note",
106195
+ children: importStatus
106196
+ }) : null,
106083
106197
  placeStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
106084
106198
  className: "hq-sch__note",
106085
106199
  children: placeStatus
@@ -106164,7 +106278,8 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
106164
106278
  }
106165
106279
  function apply(ctx) {
106166
106280
  const disposers = [];
106167
- const useAuthState = createUseAuthState(ctx.get("huaqiuAuth")?.auth);
106281
+ const authService = ctx.get("huaqiuAuth");
106282
+ const useAuthState = createUseAuthState(authService?.auth);
106168
106283
  const getHqEdge = () => ctx.get("hqEdge");
106169
106284
  const sendPrompt = (sessionId, message) => {
106170
106285
  const sessions = ctx.get("sessions");
@@ -106186,7 +106301,8 @@ while (n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3] && n3 === a3[++i3]
106186
106301
  callId: props.callId,
106187
106302
  sendPrompt,
106188
106303
  authState: useAuthState(),
106189
- getHqEdge
106304
+ getHqEdge,
106305
+ getAuth: () => authService
106190
106306
  });
106191
106307
  for (const toolName of TOOLVIEW_KEYS) disposers.push(slots.inject("tool.call.toolview", () => slots.register({
106192
106308
  name: "tool.call.toolview",