@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 +131 -15
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +87 -8
- package/package.json +7 -7
- package/src/client/b64.ts +14 -0
- package/src/client/hit-card.tsx +129 -13
- package/src/client/i18n.ts +12 -0
- package/src/client/index.ts +1 -0
- package/src/client/parse.ts +8 -1
- package/src/client/theme.ts +2 -0
- package/src/tools.ts +101 -12
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.
|
|
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
|
-
|
|
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.
|
|
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-
|
|
105821
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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",
|