mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 05:10:45 +08:00
Fix knowledge-base browse feedback and accept MANIFEST.json paths.
Browse no longer fails silently, falls back to Host directoryPicker RPC, and resolve treats a MANIFEST.json file path as its parent package root. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
de7c6a0c4b
commit
f64f1279e7
9 changed files with 246 additions and 49 deletions
|
|
@ -621,7 +621,10 @@ async function fetchKbStatus(call, signal) {
|
|||
return unconfiguredKbSnapshot();
|
||||
}
|
||||
async function resolveKbPath(call, path, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL2, KB_RESOLVE_ENDPOINT, { path }, signal);
|
||||
let result = await call(NETXOPS_RPC_CHANNEL2, KB_RESOLVE_ENDPOINT, { path }, signal);
|
||||
if (!(result !== null && typeof result === "object" && result.ok === true)) {
|
||||
result = await call(NETXOPS_RPC_CHANNEL2, KB_RESOLVE_ENDPOINT, { args: { path } }, signal);
|
||||
}
|
||||
if (result !== null && typeof result === "object" && result.ok === true) {
|
||||
return asKbSnapshot(result.value);
|
||||
}
|
||||
|
|
@ -1376,7 +1379,7 @@ function NetxopsCard(props) {
|
|||
/* @__PURE__ */ jsx_runtime.jsx("button", {
|
||||
type: "button",
|
||||
className: "dsh-nx-btn",
|
||||
disabled: disabled || !state.kbDirectoryPickerReady,
|
||||
disabled,
|
||||
onClick: () => {
|
||||
props.browseKbRoot();
|
||||
},
|
||||
|
|
@ -1388,6 +1391,15 @@ function NetxopsCard(props) {
|
|||
className: state.kbRoot.invalid ? "dsh-nx-invalid" : "dsh-nx-hint",
|
||||
children: state.kbRoot.invalid ? t("invalid") : state.kbDirectoryPickerReady ? t("kbRootHint") : t("kbBrowseUnavailable")
|
||||
}),
|
||||
/* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
className: "dsh-nx-hint",
|
||||
children: t("kbSaveHint")
|
||||
}),
|
||||
state.kbUiError ? /* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
className: "dsh-nx-invalid",
|
||||
role: "status",
|
||||
children: fillTemplate(t("kbBrowseFailed"), { detail: state.kbUiError })
|
||||
}) : null,
|
||||
state.kbStatus?.status === "error" && state.kbStatus.errorMessage ? /* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||
className: "dsh-nx-invalid",
|
||||
role: "status",
|
||||
|
|
@ -1806,6 +1818,7 @@ class NetxopsCardController {
|
|||
kbDirectoryPicker;
|
||||
kbBrowseInFlight = false;
|
||||
kbStatusInFlight = false;
|
||||
kbUiError = null;
|
||||
imDeliveryCatalog = { ...EMPTY_IM_DELIVERY_CATALOG };
|
||||
sessionsExportStatus = null;
|
||||
sessionsExportBusy = false;
|
||||
|
|
@ -2002,17 +2015,57 @@ class NetxopsCardController {
|
|||
} catch {}
|
||||
}
|
||||
browseKbRoot() {
|
||||
const picker = this.kbDirectoryPicker;
|
||||
if (picker === undefined || this.kbBrowseInFlight)
|
||||
if (this.kbBrowseInFlight)
|
||||
return;
|
||||
this.kbBrowseInFlight = true;
|
||||
picker.pick().then((path) => {
|
||||
if (typeof path === "string" && path.trim() !== "") {
|
||||
this.form.actions().edit("kbRoot", path);
|
||||
this.refreshKbPreview();
|
||||
this.kbUiError = null;
|
||||
this.store.set(this.projection());
|
||||
const applyPath = (path) => {
|
||||
if (typeof path !== "string" || path.trim() === "") {
|
||||
return;
|
||||
}
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.form.actions().edit("kbRoot", path.trim());
|
||||
this.refreshKbPreview();
|
||||
};
|
||||
const fail = (error) => {
|
||||
this.kbUiError = error instanceof Error ? error.message : String(error);
|
||||
this.store.set(this.projection());
|
||||
};
|
||||
const picker = this.kbDirectoryPicker;
|
||||
if (picker !== undefined) {
|
||||
picker.pick().then(applyPath).catch(fail).finally(() => {
|
||||
this.kbBrowseInFlight = false;
|
||||
this.store.set(this.projection());
|
||||
});
|
||||
return;
|
||||
}
|
||||
const call = this.rpcCall;
|
||||
if (call === undefined) {
|
||||
this.kbBrowseInFlight = false;
|
||||
this.kbUiError = "directory picker unavailable — paste an absolute folder path and Save";
|
||||
this.store.set(this.projection());
|
||||
return;
|
||||
}
|
||||
call("/api", "directoryPicker/pick", { args: {} }).then((result) => {
|
||||
if (typeof result === "string" || result === null) {
|
||||
applyPath(result);
|
||||
return;
|
||||
}
|
||||
if (result !== null && typeof result === "object") {
|
||||
const row = result;
|
||||
if (row.ok === false) {
|
||||
fail(row.error?.message || "directoryPicker/pick failed");
|
||||
return;
|
||||
}
|
||||
if ("value" in row) {
|
||||
applyPath(row.value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
applyPath(result);
|
||||
}).catch(fail).finally(() => {
|
||||
this.kbBrowseInFlight = false;
|
||||
this.store.set(this.projection());
|
||||
});
|
||||
}
|
||||
exportAllSessions() {
|
||||
|
|
@ -2071,7 +2124,8 @@ class NetxopsCardController {
|
|||
apiTokenRemoteReady: this.credential.remoteReady,
|
||||
alarmPushStatus: this.alarmPushStatus,
|
||||
kbStatus: this.kbPreview ?? this.kbStatus,
|
||||
kbDirectoryPickerReady: this.kbDirectoryPicker !== undefined,
|
||||
kbDirectoryPickerReady: this.kbDirectoryPicker !== undefined || this.rpcCall !== undefined,
|
||||
kbUiError: this.kbUiError,
|
||||
imDeliveryCatalog: this.imDeliveryCatalog,
|
||||
sessionsExportStatus: this.sessionsExportStatus,
|
||||
sessionsExportBusy: this.sessionsExportBusy,
|
||||
|
|
@ -2185,7 +2239,9 @@ var en = {
|
|||
kbRoot: "Package root",
|
||||
kbRootHint: "Folder with MANIFEST.json (operator-subset v1.0). Empty = pure netx.",
|
||||
kbBrowse: "Browse…",
|
||||
kbBrowseUnavailable: "Directory picker unavailable — paste an absolute path.",
|
||||
kbBrowseUnavailable: "Directory picker unavailable — paste an absolute folder path, then Save.",
|
||||
kbBrowseFailed: "Browse failed: {detail}",
|
||||
kbSaveHint: "Click Save after choosing a path to apply the knowledge base.",
|
||||
kbStatusConfigured: "Knowledge base: {operator} ({country}) v{version}",
|
||||
kbStatusUnconfigured: "Knowledge base: not configured (pure netx)",
|
||||
kbStatusError: "Knowledge base: error — {detail}",
|
||||
|
|
@ -2255,7 +2311,9 @@ var zh = {
|
|||
kbRoot: "知识包根目录",
|
||||
kbRootHint: "含 MANIFEST.json 的运营商子集包(v1.0)。留空=纯 netx。",
|
||||
kbBrowse: "浏览…",
|
||||
kbBrowseUnavailable: "目录选择器不可用 — 请粘贴绝对路径。",
|
||||
kbBrowseUnavailable: "目录选择器不可用 — 请粘贴绝对目录路径后点保存。",
|
||||
kbBrowseFailed: "浏览失败:{detail}",
|
||||
kbSaveHint: "选好路径后请点「保存」,知识库才会生效。",
|
||||
kbStatusConfigured: "知识库: {operator}({country}) v{version}",
|
||||
kbStatusUnconfigured: "知识库: 未配置(纯 netx)",
|
||||
kbStatusError: "知识库: 错误 — {detail}",
|
||||
|
|
@ -2350,13 +2408,16 @@ function apply(ctx) {
|
|||
card.setAlarmPushRpc(undefined);
|
||||
}, "netxops: clear alarm-push rpc");
|
||||
});
|
||||
ctx.inject(["remote.directoryPicker"], (dpCtx) => {
|
||||
const picker = dpCtx.remote?.directoryPicker;
|
||||
if (!picker || typeof picker.pick !== "function") {
|
||||
dpCtx.logger.warn("netxops: remote.directoryPicker.pick unavailable — browse button disabled");
|
||||
const bindDirectoryPicker = (picker) => {
|
||||
if (!picker || typeof picker.pick !== "function")
|
||||
return;
|
||||
}
|
||||
card.setDirectoryPicker(picker);
|
||||
};
|
||||
bindDirectoryPicker(ctx.remote?.directoryPicker);
|
||||
ctx.inject(["remote.directoryPicker"], (dpCtx) => {
|
||||
const viaGet = typeof dpCtx.get === "function" ? dpCtx.get("remote.directoryPicker") : undefined;
|
||||
const viaNested = dpCtx.remote?.directoryPicker;
|
||||
bindDirectoryPicker(viaGet ?? viaNested);
|
||||
dpCtx.effect(() => () => {
|
||||
card.setDirectoryPicker(undefined);
|
||||
}, "netxops: clear directory picker");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue