diff --git a/docs/PUBLICATION-REVIEW.zh.md b/docs/PUBLICATION-REVIEW.zh.md index 3f89325..13b891a 100644 --- a/docs/PUBLICATION-REVIEW.zh.md +++ b/docs/PUBLICATION-REVIEW.zh.md @@ -78,6 +78,10 @@ ## 验证与维护 +设置页已精简重复的“自动保存”“即时挂载/卸载”和“已覆盖”提示。发布范围使用简短标签,挂载解释与明细集中在折叠的诊断信息中;模型语言独立分组,使用本地化选项。连接字段在宽面板并排、窄面板单列,保存/丢弃栏仅在待处理修改、保存中或保存失败时出现。实际错误仍显示在对应区域。 + +本次界面验证覆盖运行中的桌面深色设置页、390px 窄屏布局、中英文文案、预设列表展开与选择,以及手工草稿保存后收起操作栏。预览凭据服务为内存模拟,不读取或写入真实 token。预览截图已更新为精简后的界面。 + 验证命令: ```powershell diff --git a/docs/assets/publication-controls.jpg b/docs/assets/publication-controls.jpg index a67bd49..062c33a 100644 Binary files a/docs/assets/publication-controls.jpg and b/docs/assets/publication-controls.jpg differ diff --git a/docs/assets/publication-preview.jpg b/docs/assets/publication-preview.jpg index fc63aa9..924ed82 100644 Binary files a/docs/assets/publication-preview.jpg and b/docs/assets/publication-preview.jpg differ diff --git a/examples/local-debug/ui-preview.tsx b/examples/local-debug/ui-preview.tsx index 3b674f4..ff5a8ad 100644 --- a/examples/local-debug/ui-preview.tsx +++ b/examples/local-debug/ui-preview.tsx @@ -23,7 +23,15 @@ const scope = { async set(field: string, value: unknown) { user[field] = value; for (const fn of listeners) fn() }, async unset(field: string) { delete user[field]; for (const fn of listeners) fn() }, } -const controller = new NetxopsCardController(scope, { get: () => undefined } as never) +const previewCredentials = { + async describe(refs: string[]) { + return { ok: true, value: Object.fromEntries(refs.map(ref => [ref, { configured: true, writable: true }])) } + }, + async set() { /* Preview only; no credentials are stored. */ }, +} +const controller = new NetxopsCardController(scope, { + get: key => key === 'remote.credentials' ? previewCredentials : undefined, +} as never) controller.setAlarmPushRpc(async (_channel, endpoint) => { const value = scope.getSnapshot().value if (endpoint === 'presets.catalog') return { ok: true, value: { presets: [ diff --git a/lib/client.js b/lib/client.js index 737727a..2fe685b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -50,9 +50,6 @@ __export(exports_client, { }); module.exports = __toCommonJS(exports_client); -// src/client/NetxopsCard.tsx -var import_react = require("react"); - // src/client/alarm-push-status-view.ts var NETXOPS_RPC_CHANNEL = "/netxops"; var ALARM_PUSH_STATUS_ENDPOINT = "alarm-push.status"; @@ -768,17 +765,21 @@ function toolsMountTone(view) { // src/client/styles.ts var STYLE_ID = "dsh-netxops-settings-css"; var CSS = ` -.dsh-nx-settings{box-sizing:border-box;display:flex;flex-direction:column;gap:20px;min-height:0;padding:4px 4px 24px;color:var(--dsw-alias-label-primary,#1f2329)} +.dsh-nx-settings{box-sizing:border-box;display:flex;flex-direction:column;gap:16px;min-height:0;padding:4px 4px 24px;color:var(--dsw-alias-label-primary,#1f2329)} .dsh-nx-settings>header{display:flex;flex-direction:column;gap:4px} .dsh-nx-settings h2{margin:0;font-size:20px;font-weight:600;line-height:28px} -.dsh-nx-settings-intro{margin:0;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary,#646a73)} .dsh-nx-settings-card{border:1px solid var(--dsw-alias-border-l2,#dee0e3);border-radius:12px;padding:16px;background:var(--dsw-alias-bg-layer-1,transparent)} .dsh-nx-settings-card h3{margin:0 0 12px;font-size:15px;font-weight:600;line-height:22px} +.dsh-nx-card-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-bottom:14px} +.dsh-nx-card-head h3{margin:0} .dsh-nx-settings-field{display:flex;flex-direction:column;gap:4px;margin-bottom:12px} .dsh-nx-settings-field:last-child{margin-bottom:0} -.dsh-nx-settings-field>label,.dsh-nx-field-label{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#646a73)} +.dsh-nx-settings-field>label,.dsh-nx-field-label,.dsh-nx-field-head>label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,#646a73)} .dsh-nx-field-head{display:flex;align-items:center;justify-content:space-between;gap:8px} .dsh-nx-field-head>label,.dsh-nx-field-head>.dsh-nx-field-label{flex:1;min-width:0} +.dsh-nx-fieldGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr));gap:12px 20px} +.dsh-nx-fieldGrid>.dsh-nx-settings-field{min-width:0;margin:0} +.dsh-nx-toggleField>.dsh-nx-field-head{min-height:28px} .dsh-nx-settings-field input[type=text], .dsh-nx-settings-field input[type=password], .dsh-nx-settings-field input:not([type]), @@ -790,12 +791,12 @@ var CSS = ` .dsh-nx-settings-field input:disabled, .dsh-nx-settings-field select:disabled{opacity:.55;cursor:default} .dsh-nx-inputInvalid{border-color:var(--dsw-alias-state-error-primary,#d54941)!important} -.dsh-nx-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#8f959e)} -.dsh-nx-invalid{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary,#d54941)} +.dsh-nx-hint{margin:0;font-size:12px;line-height:1.5;overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary,#8f959e)} +.dsh-nx-invalid{margin:0;font-size:12px;line-height:1.5;overflow-wrap:anywhere;color:var(--dsw-alias-state-error-primary,#d54941)} .dsh-nx-badges{display:inline-flex;align-items:center;gap:8px;flex:none} .dsh-nx-badge{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;white-space:nowrap;font-weight:500;background:var(--dsw-alias-bg-module-platform,#f4f5f7);color:var(--dsw-alias-label-secondary,#646a73)} .dsh-nx-badgeMuted{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;white-space:nowrap;color:var(--dsw-alias-label-tertiary,#8f959e)} -.dsh-nx-status{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;font-weight:500;white-space:nowrap;background:var(--dsw-alias-bg-module-platform,#f4f5f7);color:var(--dsw-alias-label-tertiary,#8f959e)} +.dsh-nx-status{border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px;font-weight:500;max-width:100%;overflow-wrap:anywhere;background:var(--dsw-alias-bg-module-platform,#f4f5f7);color:var(--dsw-alias-label-tertiary,#8f959e)} .dsh-nx-statusOk{color:var(--dsw-alias-state-success-primary,#20a162);background:rgba(32,161,98,.12)} .dsh-nx-statusWarn{color:var(--dsw-alias-state-warn-primary,#d97706);background:rgba(217,119,6,.12)} .dsh-nx-statusErr{color:var(--dsw-alias-state-error-primary,#d54941);background:rgba(213,73,65,.12)} @@ -803,22 +804,26 @@ var CSS = ` .dsh-nx-reset:hover:not(:disabled){opacity:.85} .dsh-nx-reset:disabled{opacity:.4;cursor:default} .dsh-nx-checkRow{display:flex;align-items:flex-start;gap:10px;margin:0;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2329);cursor:pointer} -.dsh-nx-checkRow input{margin-top:3px;flex:none} -.dsh-nx-groupBlock{display:flex;flex-direction:column;gap:8px;padding:4px 0 8px} -.dsh-nx-groupBlock+.dsh-nx-groupBlock{border-top:1px solid var(--dsw-alias-border-l1,#eef0f3);padding-top:12px} -.dsh-nx-groupTitle{font-size:13px;font-weight:600;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2329)} -.dsh-nx-groupChecks{display:flex;flex-direction:column;gap:8px;padding-left:2px} -.dsh-nx-targets{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2,#dfe1e5)} +.dsh-nx-checkRow input{margin:3px 0 0;flex:none} +.dsh-nx-settings input[type=checkbox]{accent-color:var(--dsw-alias-state-business-primary,#3370ff)} +.dsh-nx-groupBlock{display:flex;flex-direction:column;gap:10px;padding:12px 0} +.dsh-nx-groupBlock+.dsh-nx-groupBlock{border-top:1px solid var(--dsw-alias-border-l1,#eef0f3)} +.dsh-nx-groupTitle{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2329)} +.dsh-nx-groupChecks{display:flex;flex-wrap:wrap;gap:8px 24px} +.dsh-nx-targets{margin-top:0} +.dsh-nx-diagnostics{padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1,#eef0f3)} +.dsh-nx-diagnostics[open] summary{margin-bottom:8px} +.dsh-nx-diagnostics p+p{margin-top:4px} .dsh-nx-settings summary{cursor:pointer;font-size:12px;font-weight:500;line-height:1.6} .dsh-nx-targets[open] .dsh-nx-checkRow{padding:5px 0;flex-wrap:wrap} .dsh-nx-settings input:focus-visible,.dsh-nx-settings button:focus-visible,.dsh-nx-settings summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#3370ff);outline-offset:2px} .dsh-nx-settings>.dsh-nx-settings-actions{position:sticky;bottom:0;padding:12px;background:var(--dsw-alias-bg-base,#fff);border-top:1px solid var(--dsw-alias-border-l2,#dfe1e5);flex-wrap:wrap} +.dsh-nx-settings-actions{display:flex;align-items:center;gap:8px} .dsh-nx-imTargetList{display:flex;flex-direction:column;gap:8px;padding:4px 0 2px} .dsh-nx-pathRow{display:flex;gap:8px;align-items:stretch} .dsh-nx-pathRow>input{flex:1;min-width:0} .dsh-nx-pathRow>.dsh-nx-btn{flex:none;align-self:stretch} .dsh-nx-settings-msg{font-size:12px;color:var(--dsw-alias-label-secondary,#646a73)} -.dsh-nx-settings-msg.ok{color:var(--dsw-alias-state-success-primary,#20a162)} .dsh-nx-settings-msg.err{color:var(--dsw-alias-state-error-primary,#d54941)} .dsh-nx-settings-empty{padding:24px;text-align:center;color:var(--dsw-alias-label-tertiary,#8f959e);font-size:13px} .dsh-nx-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2,#dfe1e5);border-radius:4px;padding:8px 12px;font:inherit;font-size:13px;line-height:20px;cursor:pointer;background:var(--dsw-alias-bg-module-platform,#f4f5f7);color:var(--dsw-alias-label-primary,#1f2329)} @@ -831,8 +836,12 @@ var CSS = ` function ensureStyles() { if (typeof document === "undefined") return; - if (document.getElementById(STYLE_ID)) + const existing = document.getElementById(STYLE_ID); + if (existing) { + if (existing.textContent !== CSS) + existing.textContent = CSS; return; + } const tag = document.createElement("style"); tag.id = STYLE_ID; tag.textContent = CSS; @@ -1087,7 +1096,7 @@ function kbBadgeLabel(t, snapshot) { return t("kbStatusUnconfigured"); } if (snapshot.status === "error") { - return fillTemplate(t("kbStatusError"), { detail: snapshot.errorMessage || "error" }); + return t("kbStatusError"); } return fillTemplate(t("kbStatusConfigured"), { operator: snapshot.operatorName, @@ -1104,12 +1113,11 @@ function toolsBadgeLabel(t, view) { if (!view.toolsEnabled) return t("toolsDisabled"); if (view.error) { - return fillTemplate(t("toolsStatusError"), { detail: view.error }); + return t("toolsStatusError"); } if (view.count > 0) { return fillTemplate(t("toolsStatusMounted"), { - count: String(view.count), - version: view.version || "?" + count: String(view.count) }); } return t("toolsStatusEmpty"); @@ -1147,21 +1155,15 @@ function ValueField(props) { htmlFor: props.id, children: props.label }), - props.field.overridden ? /* @__PURE__ */ jsx_runtime.jsxs("span", { + props.field.overridden ? /* @__PURE__ */ jsx_runtime.jsx("span", { className: "dsh-nx-badges", - children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-badge", - children: props.overriddenLabel - }), - /* @__PURE__ */ jsx_runtime.jsx("button", { - type: "button", - className: "dsh-nx-reset", - disabled: props.disabled, - onClick: props.onReset, - children: props.resetLabel - }) - ] + children: /* @__PURE__ */ jsx_runtime.jsx("button", { + type: "button", + className: "dsh-nx-reset", + disabled: props.disabled, + onClick: props.onReset, + children: props.resetLabel + }) }) : null ] }), @@ -1169,6 +1171,7 @@ function ValueField(props) { id: props.id, className: props.field.invalid ? "dsh-nx-inputInvalid" : undefined, type: "text", + placeholder: props.hint, value: props.field.text, disabled: props.disabled, "aria-invalid": props.field.invalid || undefined, @@ -1176,10 +1179,10 @@ function ValueField(props) { props.onEdit(event.target.value); } }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: props.field.invalid ? "dsh-nx-invalid" : "dsh-nx-hint", - children: props.field.invalid ? props.invalidLabel : props.hint - }) + props.field.invalid ? /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-invalid", + children: props.invalidLabel + }) : null ] }); } @@ -1195,21 +1198,15 @@ function SelectField(props) { htmlFor: props.id, children: props.label }), - props.field.overridden ? /* @__PURE__ */ jsx_runtime.jsxs("span", { + props.field.overridden ? /* @__PURE__ */ jsx_runtime.jsx("span", { className: "dsh-nx-badges", - children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-badge", - children: props.overriddenLabel - }), - /* @__PURE__ */ jsx_runtime.jsx("button", { - type: "button", - className: "dsh-nx-reset", - disabled: props.disabled, - onClick: props.onReset, - children: props.resetLabel - }) - ] + children: /* @__PURE__ */ jsx_runtime.jsx("button", { + type: "button", + className: "dsh-nx-reset", + disabled: props.disabled, + onClick: props.onReset, + children: props.resetLabel + }) }) : null ] }), @@ -1233,10 +1230,10 @@ function SelectField(props) { }, row.value)) ] }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: props.field.invalid ? "dsh-nx-invalid" : "dsh-nx-hint", - children: props.field.invalid ? props.invalidLabel : props.hint - }) + props.field.invalid ? /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-invalid", + children: props.invalidLabel + }) : null ] }); } @@ -1333,10 +1330,6 @@ function PresetTargets(props) { selected.length ] }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-hint", - children: props.t("publicationHint") - }), props.state.presetCatalogError ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-invalid", children: props.t("presetCatalogUnavailable") @@ -1375,10 +1368,20 @@ function PresetTargets(props) { function CapabilityGroupBlock(props) { return /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-groupBlock", + role: "group", + "aria-label": props.title, children: [ - /* @__PURE__ */ jsx_runtime.jsx("div", { + /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-groupTitle", - children: props.title + children: [ + /* @__PURE__ */ jsx_runtime.jsx("span", { + children: props.title + }), + props.toolCount !== undefined ? /* @__PURE__ */ jsx_runtime.jsx("span", { + className: "dsh-nx-badgeMuted", + children: props.toolCount + }) : null + ] }), /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-groupChecks", @@ -1423,7 +1426,7 @@ function CapabilityGroupBlock(props) { } function ToggleField(props) { return /* @__PURE__ */ jsx_runtime.jsxs("div", { - className: "dsh-nx-settings-field", + className: "dsh-nx-settings-field dsh-nx-toggleField", children: [ /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-field-head", @@ -1436,40 +1439,27 @@ function ToggleField(props) { className: "dsh-nx-badges", children: [ props.trailing, - props.overridden ? /* @__PURE__ */ jsx_runtime.jsxs(jsx_runtime.Fragment, { - children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-badge", - children: props.overriddenLabel - }), - /* @__PURE__ */ jsx_runtime.jsx("button", { - type: "button", - className: "dsh-nx-reset", - disabled: props.disabled, - onClick: props.onReset, - children: props.resetLabel - }) - ] + props.overridden ? /* @__PURE__ */ jsx_runtime.jsx("button", { + type: "button", + className: "dsh-nx-reset", + disabled: props.disabled, + onClick: props.onReset, + children: props.resetLabel }) : null ] + }), + /* @__PURE__ */ jsx_runtime.jsx("input", { + id: props.id, + type: "checkbox", + checked: props.checked, + disabled: props.disabled, + "aria-label": props.label, + onChange: (event) => { + props.onToggle(event.target.checked); + } }) ] }), - /* @__PURE__ */ jsx_runtime.jsx("label", { - className: "dsh-nx-checkRow", - htmlFor: props.id, - children: /* @__PURE__ */ jsx_runtime.jsx("input", { - id: props.id, - type: "checkbox", - checked: props.checked, - disabled: props.disabled, - "aria-label": props.label, - onChange: (event) => { - props.onToggle(event.target.checked); - } - }) - }), - props.hint, props.error ] }); @@ -1478,24 +1468,6 @@ function NetxopsCard(props) { ensureStyles(); const { t } = props; const state = props.useNetxopsCard((snapshot) => snapshot); - const [flash, setFlash] = import_react.useState(null); - const saveStarted = import_react.useRef(false); - import_react.useEffect(() => { - if (state.saving) { - saveStarted.current = true; - setFlash(null); - return; - } - if (!saveStarted.current) - return; - saveStarted.current = false; - if (state.failed) { - setFlash("err"); - return; - } - if (!state.dirty) - setFlash("ok"); - }, [state.dirty, state.failed, state.saving]); if (!state.available) { return /* @__PURE__ */ jsx_runtime.jsxs("section", { className: "dsh-nx-settings", @@ -1529,16 +1501,10 @@ function NetxopsCard(props) { className: "dsh-nx-settings", "aria-label": t("title"), children: [ - /* @__PURE__ */ jsx_runtime.jsxs("header", { - children: [ - /* @__PURE__ */ jsx_runtime.jsx("h2", { - children: t("title") - }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-settings-intro", - children: t("description") - }) - ] + /* @__PURE__ */ jsx_runtime.jsx("header", { + children: /* @__PURE__ */ jsx_runtime.jsx("h2", { + children: t("title") + }) }), !state.writable ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-hint", @@ -1551,45 +1517,6 @@ function NetxopsCard(props) { /* @__PURE__ */ jsx_runtime.jsx("h3", { children: t("sectionConnection") }), - /* @__PURE__ */ jsx_runtime.jsxs("div", { - className: "dsh-nx-settings-field", - children: [ - /* @__PURE__ */ jsx_runtime.jsxs("div", { - className: "dsh-nx-field-head", - children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - children: t("toolsStatus") - }), - /* @__PURE__ */ jsx_runtime.jsx(StatusBadge, { - tone: toolsMountTone(state.toolsStatus), - label: toolsBadgeLabel(t, state.toolsStatus) - }) - ] - }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-hint", - children: t("mountStatusHint") - }), - state.toolsStatus && state.toolsStatus.mounts.length > 0 ? /* @__PURE__ */ jsx_runtime.jsxs("details", { - children: [ - /* @__PURE__ */ jsx_runtime.jsx("summary", { - children: t("mountDetails") - }), - state.toolsStatus.mounts.map((mount, index) => /* @__PURE__ */ jsx_runtime.jsxs("p", { - className: mount.error ? "dsh-nx-invalid" : "dsh-nx-hint", - children: [ - mount.source, - " · ", - mount.count, - " · ", - mount.groups.join(", ") || "—", - mount.error ? ` · ${mount.error}` : "" - ] - }, `${mount.source}:${index}`)) - ] - }) : null - ] - }), /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-field", children: [ @@ -1613,16 +1540,17 @@ function NetxopsCard(props) { id: "netxops-api-token", type: "password", autoComplete: "off", + placeholder: t("apiTokenHint"), value: state.apiToken.text, disabled: !state.apiTokenWritable, onChange: (event) => { props.edit("apiToken", event.target.value); } }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-hint", - children: state.apiTokenRemoteReady ? t("apiTokenHint") : t("apiTokenUnavailable") - }) + !state.apiTokenRemoteReady ? /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-invalid", + children: t("apiTokenUnavailable") + }) : null ] }), /* @__PURE__ */ jsx_runtime.jsx(ValueField, { @@ -1630,7 +1558,6 @@ function NetxopsCard(props) { label: t("apiUrl"), hint: t("apiUrlHint"), field: state.apiUrl, - overriddenLabel: t("overridden"), resetLabel: t("reset"), invalidLabel: t("apiUrlInvalid"), disabled, @@ -1641,87 +1568,120 @@ function NetxopsCard(props) { props.resetField("apiUrl"); } }), - /* @__PURE__ */ jsx_runtime.jsx(ValueField, { - id: "netxops-lang", - label: t("lang"), - hint: t("langHint"), - field: state.lang, - overriddenLabel: t("overridden"), - resetLabel: t("reset"), - invalidLabel: t("invalid"), - disabled, - onEdit: (text) => { - props.edit("lang", text); - }, - onReset: () => { - props.resetField("lang"); - } - }), - /* @__PURE__ */ jsx_runtime.jsx(SelectField, { - id: "netxops-thinking-language", - label: t("thinkingLanguage"), - hint: t("thinkingLanguageHint"), - field: state.thinkingLanguage, - options: [ - { value: "auto", label: "auto" }, - { value: "zh-CN", label: "zh-CN" }, - { value: "en", label: "en" } - ], - overriddenLabel: t("overridden"), - resetLabel: t("reset"), - invalidLabel: t("invalid"), - disabled, - onEdit: (text) => { - props.edit("thinkingLanguage", text); - }, - onReset: () => { - props.resetField("thinkingLanguage"); - } - }), - /* @__PURE__ */ jsx_runtime.jsx(SelectField, { - id: "netxops-reply-language", - label: t("replyLanguage"), - hint: t("replyLanguageHint"), - field: state.replyLanguage, - options: [ - { value: "follow-user", label: "follow-user" }, - { value: "zh", label: "zh" }, - { value: "en", label: "en" } - ], - overriddenLabel: t("overridden"), - resetLabel: t("reset"), - invalidLabel: t("invalid"), - disabled, - onEdit: (text) => { - props.edit("replyLanguage", text); - }, - onReset: () => { - props.resetField("replyLanguage"); - } - }), - /* @__PURE__ */ jsx_runtime.jsx(ValueField, { - id: "netxops-nms-provider", - label: t("nmsProvider"), - hint: t("nmsProviderHint"), - field: state.nmsProvider, - overriddenLabel: t("overridden"), - resetLabel: t("reset"), - invalidLabel: t("invalid"), - disabled, - onEdit: (text) => { - props.edit("nmsProvider", text); - }, - onReset: () => { - props.resetField("nmsProvider"); - } + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-fieldGrid", + children: [ + /* @__PURE__ */ jsx_runtime.jsx(SelectField, { + id: "netxops-lang", + label: t("lang"), + field: state.lang, + options: [ + { value: "zh", label: t("languageChinese") }, + { value: "en", label: t("languageEnglish") } + ], + resetLabel: t("reset"), + invalidLabel: t("invalid"), + disabled, + onEdit: (text) => { + props.edit("lang", text); + }, + onReset: () => { + props.resetField("lang"); + } + }), + /* @__PURE__ */ jsx_runtime.jsx(SelectField, { + id: "netxops-nms-provider", + label: t("nmsProvider"), + field: state.nmsProvider, + options: [{ value: "zte-ume", label: "ZTE UME" }], + resetLabel: t("reset"), + invalidLabel: t("invalid"), + disabled, + onEdit: (text) => { + props.edit("nmsProvider", text); + }, + onReset: () => { + props.resetField("nmsProvider"); + } + }) + ] }) ] }), /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-card", children: [ - /* @__PURE__ */ jsx_runtime.jsx("h3", { - children: t("sectionCapabilities") + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-card-head", + children: [ + /* @__PURE__ */ jsx_runtime.jsx("h3", { + children: t("sectionLanguage") + }), + /* @__PURE__ */ jsx_runtime.jsx("span", { + className: "dsh-nx-hint", + children: t("languageScope") + }) + ] + }), + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-fieldGrid", + children: [ + /* @__PURE__ */ jsx_runtime.jsx(SelectField, { + id: "netxops-thinking-language", + label: t("thinkingLanguage"), + field: state.thinkingLanguage, + options: [ + { value: "auto", label: t("languageAuto") }, + { value: "zh-CN", label: t("languageChinese") }, + { value: "en", label: t("languageEnglish") } + ], + resetLabel: t("reset"), + invalidLabel: t("invalid"), + disabled, + onEdit: (text) => { + props.edit("thinkingLanguage", text); + }, + onReset: () => { + props.resetField("thinkingLanguage"); + } + }), + /* @__PURE__ */ jsx_runtime.jsx(SelectField, { + id: "netxops-reply-language", + label: t("replyLanguage"), + field: state.replyLanguage, + options: [ + { value: "follow-user", label: t("languageFollowUser") }, + { value: "zh", label: t("languageChinese") }, + { value: "en", label: t("languageEnglish") } + ], + resetLabel: t("reset"), + invalidLabel: t("invalid"), + disabled, + onEdit: (text) => { + props.edit("replyLanguage", text); + }, + onReset: () => { + props.resetField("replyLanguage"); + } + }) + ] + }) + ] + }), + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-settings-card", + children: [ + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-card-head", + children: [ + /* @__PURE__ */ jsx_runtime.jsx("h3", { + children: t("sectionCapabilities") + }), + /* @__PURE__ */ jsx_runtime.jsx(StatusBadge, { + tone: toolsMountTone(state.toolsStatus), + label: toolsBadgeLabel(t, state.toolsStatus) + }) + ] }), /* @__PURE__ */ jsx_runtime.jsx(ToggleField, { id: "netxops-tools-enabled", @@ -1729,7 +1689,6 @@ function NetxopsCard(props) { checked: state.toolsEnabled.text === "true", disabled, overridden: state.toolsEnabled.overridden, - overriddenLabel: t("overridden"), resetLabel: t("reset"), onToggle: (checked) => { props.edit("toolsEnabled", String(checked)); @@ -1737,13 +1696,15 @@ function NetxopsCard(props) { onReset: () => { props.resetField("toolsEnabled"); }, - hint: /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-hint", - children: t("toolsEnabledHint") - }) + error: state.toolsStatus?.error ? /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-invalid", + role: "status", + children: state.toolsStatus.error + }) : null }), /* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, { - title: `${t("groupOps")} · ${TOOLS_BY_GROUP.ops.length}`, + title: t("groupOps"), + toolCount: TOOLS_BY_GROUP.ops.length, targets: targets("groupOpsPresets", state.groupOpsPresets), inPresetLabel: t("groupInPreset"), publicLabel: t("groupPublic"), @@ -1758,7 +1719,8 @@ function NetxopsCard(props) { } }), /* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, { - title: `${t("groupTopology")} · ${TOOLS_BY_GROUP.topology.length}`, + title: t("groupTopology"), + toolCount: TOOLS_BY_GROUP.topology.length, targets: targets("groupTopologyPresets", state.groupTopologyPresets), inPresetLabel: t("groupInPreset"), publicLabel: t("groupPublic"), @@ -1773,7 +1735,8 @@ function NetxopsCard(props) { } }), /* @__PURE__ */ jsx_runtime.jsx(CapabilityGroupBlock, { - title: `${t("groupBizMonitor")} · ${TOOLS_BY_GROUP.bizMonitor.length}`, + title: t("groupBizMonitor"), + toolCount: TOOLS_BY_GROUP.bizMonitor.length, targets: targets("groupBizMonitorPresets", state.groupBizMonitorPresets), inPresetLabel: t("groupInPreset"), publicLabel: t("groupPublic"), @@ -1786,14 +1749,46 @@ function NetxopsCard(props) { onEditPublic: (checked) => { props.edit("groupBizMonitorPublic", checked ? "true" : "false"); } - }) + }), + state.toolsStatus && state.toolsStatus.mounts.length > 0 ? /* @__PURE__ */ jsx_runtime.jsxs("details", { + className: "dsh-nx-diagnostics", + children: [ + /* @__PURE__ */ jsx_runtime.jsx("summary", { + children: t("mountDetails") + }), + /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-hint", + children: t("mountStatusHint") + }), + state.toolsStatus.mounts.map((mount, index) => /* @__PURE__ */ jsx_runtime.jsxs("p", { + className: mount.error ? "dsh-nx-invalid" : "dsh-nx-hint", + children: [ + mount.source, + " · ", + mount.count, + " · ", + mount.groups.join(", ") || "—", + mount.error ? ` · ${mount.error}` : "" + ] + }, `${mount.source}:${index}`)) + ] + }) : null ] }), /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-card", children: [ - /* @__PURE__ */ jsx_runtime.jsx("h3", { - children: t("sectionKnowledge") + /* @__PURE__ */ jsx_runtime.jsxs("div", { + className: "dsh-nx-card-head", + children: [ + /* @__PURE__ */ jsx_runtime.jsx("h3", { + children: t("sectionKnowledge") + }), + /* @__PURE__ */ jsx_runtime.jsx(StatusBadge, { + tone: kbStatusTone(state.kbStatus?.status ?? "unconfigured"), + label: kbBadgeLabel(t, state.kbStatus) + }) + ] }), /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-field", @@ -1805,31 +1800,19 @@ function NetxopsCard(props) { htmlFor: "netxops-kb-root", children: t("kbRoot") }), - /* @__PURE__ */ jsx_runtime.jsxs("span", { + /* @__PURE__ */ jsx_runtime.jsx("span", { className: "dsh-nx-badges", - children: [ - /* @__PURE__ */ jsx_runtime.jsx(StatusBadge, { - tone: kbStatusTone(state.kbStatus?.status ?? "unconfigured"), - label: kbBadgeLabel(t, state.kbStatus) - }), - state.kbRoot.overridden ? /* @__PURE__ */ jsx_runtime.jsxs(jsx_runtime.Fragment, { - children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-badge", - children: t("overridden") - }), - /* @__PURE__ */ jsx_runtime.jsx("button", { - type: "button", - className: "dsh-nx-reset", - disabled, - onClick: () => { - props.resetField("kbRoot"); - }, - children: t("reset") - }) - ] - }) : null - ] + children: state.kbRoot.overridden ? /* @__PURE__ */ jsx_runtime.jsx(jsx_runtime.Fragment, { + children: /* @__PURE__ */ jsx_runtime.jsx("button", { + type: "button", + className: "dsh-nx-reset", + disabled, + onClick: () => { + props.resetField("kbRoot"); + }, + children: t("reset") + }) + }) : null }) ] }), @@ -1840,6 +1823,7 @@ function NetxopsCard(props) { id: "netxops-kb-root", className: state.kbRoot.invalid ? "dsh-nx-inputInvalid" : undefined, type: "text", + placeholder: t("kbRootHint"), value: state.kbRoot.text, disabled, "aria-invalid": state.kbRoot.invalid || undefined, @@ -1850,7 +1834,8 @@ function NetxopsCard(props) { /* @__PURE__ */ jsx_runtime.jsx("button", { type: "button", className: "dsh-nx-btn", - disabled, + disabled: disabled || !state.kbDirectoryPickerReady, + title: !state.kbDirectoryPickerReady ? t("kbBrowseUnavailable") : undefined, onClick: () => { props.browseKbRoot(); }, @@ -1858,14 +1843,10 @@ function NetxopsCard(props) { }) ] }), - /* @__PURE__ */ jsx_runtime.jsx("p", { - 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.kbRoot.invalid ? /* @__PURE__ */ jsx_runtime.jsx("p", { + className: "dsh-nx-invalid", + children: t("invalid") + }) : null, state.kbUiError ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-invalid", role: "status", @@ -1876,7 +1857,7 @@ function NetxopsCard(props) { role: "status", children: state.kbStatus.errorMessage }) : null, - state.kbStatus?.status === "configured" && state.kbStatus.realRoot ? /* @__PURE__ */ jsx_runtime.jsx("p", { + state.kbStatus?.status === "configured" && state.kbStatus.realRoot && state.kbStatus.realRoot !== state.kbRoot.text ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-hint", children: state.kbStatus.realRoot }) : null @@ -1911,7 +1892,6 @@ function NetxopsCard(props) { checked: state.alarmPushEnabled.text === "true", disabled, overridden: state.alarmPushEnabled.overridden, - overriddenLabel: t("overridden"), resetLabel: t("reset"), onToggle: (checked) => { props.edit("alarmPushEnabled", checked ? "true" : "false"); @@ -1921,11 +1901,7 @@ function NetxopsCard(props) { }, trailing: pushStatus ? /* @__PURE__ */ jsx_runtime.jsx(StatusBadge, { phase: pushStatus.phase, - label: `${t("alarmPushStatus")}: ${t(phaseLocaleKey(pushStatus.phase))}` - }) : null, - hint: pushStatus?.wsUrl ? /* @__PURE__ */ jsx_runtime.jsx("p", { - className: "dsh-nx-hint", - children: pushStatus.wsUrl + label: t(phaseLocaleKey(pushStatus.phase)) }) : null, error: pushStatus?.lastError ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-invalid", @@ -1939,7 +1915,6 @@ function NetxopsCard(props) { checked: state.alarmDeliverDsh.text === "true", disabled, overridden: state.alarmDeliverDsh.overridden, - overriddenLabel: t("overridden"), resetLabel: t("reset"), onToggle: (checked) => { props.edit("alarmDeliverDsh", checked ? "true" : "false"); @@ -1979,10 +1954,6 @@ function NetxopsCard(props) { /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-field", children: [ - /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-field-label", - children: t("sessionsExport") - }), state.sessionsExportStatus === null ? /* @__PURE__ */ jsx_runtime.jsx("p", { className: "dsh-nx-hint", children: t("sessionsExportUnavailable") @@ -2019,7 +1990,7 @@ function NetxopsCard(props) { }) ] }), - /* @__PURE__ */ jsx_runtime.jsxs("div", { + state.dirty || state.saving || state.failed ? /* @__PURE__ */ jsx_runtime.jsxs("div", { className: "dsh-nx-settings-actions", children: [ /* @__PURE__ */ jsx_runtime.jsx("button", { @@ -2033,27 +2004,20 @@ function NetxopsCard(props) { type: "button", className: "dsh-nx-btn dsh-nx-btn-ghost", disabled: !state.dirty || state.saving, - onClick: () => { - props.discard(); - setFlash(null); - }, + onClick: props.discard, children: t("discard") }), state.dirty && !state.saving ? /* @__PURE__ */ jsx_runtime.jsx("span", { className: "dsh-nx-settings-msg", children: t("unsaved") }) : null, - flash === "ok" && !state.dirty ? /* @__PURE__ */ jsx_runtime.jsx("span", { - className: "dsh-nx-settings-msg ok", - children: t("configSaved") - }) : null, - flash === "err" || state.failed ? /* @__PURE__ */ jsx_runtime.jsx("span", { + state.failed ? /* @__PURE__ */ jsx_runtime.jsx("span", { className: "dsh-nx-settings-msg err", role: "status", children: t("saveFailed") }) : null ] - }) + }) : null ] }); } @@ -2940,53 +2904,49 @@ function safeJson(value) { // src/client/locales.ts var en = { - settingsUnavailable: "Waiting for Host settings. Saving is disabled until connected. If this persists, check that the Host plugin is enabled.", - toolsEnabled: "Publish Netx capabilities", - toolsEnabledHint: "Auto-saves. Turn off to withdraw all Netx tools and skills; your group selections are retained.", - toolsDisabled: "All Netx tools and skills are off", - selectedPresets: "Selected other presets", - publicationHint: "Tools and their skills are published together. Changes auto-save and apply to the next model request in current and new sessions. Public capabilities are inherited by every preset, including Netx Ops.", - presetCatalogUnavailable: "Preset roster unavailable. Existing selections are retained and can be removed.", + settingsUnavailable: "Connecting to Host settings…", + toolsEnabled: "Enable tools and skills", + toolsDisabled: "Disabled", + selectedPresets: "Other presets", + presetCatalogUnavailable: "Cannot load presets. Existing selections are retained.", presetUnavailable: "Preset unavailable", - noOtherPresets: "No other presets available on this Host.", - mountStatusHint: "Actual mounts on this Host. Selected other presets mount when a session is active. This is registration status; API access is checked during tool calls.", - mountDetails: "Mount details", + noOtherPresets: "No other presets available.", + mountStatusHint: "Registered tools in active sessions; API access is verified when a tool runs.", + mountDetails: "Diagnostics", title: "Netx Ops", - description: "API, token, capabilities, knowledge base, and alarm delivery.", sectionConnection: "Connection", - sectionCapabilities: "Capability groups", + sectionLanguage: "Model language", + languageScope: "Applies to all presets", + languageAuto: "Automatic", + languageFollowUser: "Follow user", + languageChinese: "Chinese", + languageEnglish: "English", + sectionCapabilities: "Tools and skills", sectionKnowledge: "Knowledge base", sectionAlarms: "Key-alarm delivery", sectionExport: "Session export", apiUrl: "API URL", - apiUrlHint: "e.g. http://127.0.0.1:8890", - apiUrlInvalid: "Must be http(s) without username/password (no file:/javascript:)", + apiUrlHint: "http://127.0.0.1:8890", + apiUrlInvalid: "Enter an HTTP or HTTPS URL without credentials.", lang: "API / alarm language", - langHint: "zh / en — netx API query + alarm prompt copy only (not model reply)", thinkingLanguage: "Thinking language", - thinkingLanguageHint: "auto / zh-CN / en — model chain-of-thought for all presets", replyLanguage: "Reply language", - replyLanguageHint: "follow-user / zh / en — final answers for all presets", - capabilityGroups: "Capability groups", nmsProvider: "NMS provider", - nmsProviderHint: "zte-ume", groupOps: "Alarms, inventory and CLI", groupTopology: "Topology and layout", groupBizMonitor: "Cutover monitoring", groupKb: "Knowledge skills", - groupInPreset: "In Netx Ops preset (auto-saves; mount/unmount immediately)", - groupPublic: "Publish to every preset (auto-saves)", + groupInPreset: "Netx Ops", + groupPublic: "All presets", kbRoot: "Package root", - kbRootHint: "Folder with MANIFEST.json (operator-subset v1.0). Browse auto-saves. Pack skills load from paths.skills/_skills and paths.localSkills when hasSkills.", + kbRootHint: "Absolute path to a folder containing MANIFEST.json", kbBrowse: "Browse…", - kbBrowseUnavailable: "Directory picker unavailable — paste an absolute folder path, then Save.", + kbBrowseUnavailable: "Enter the folder path manually.", kbBrowseFailed: "Browse failed: {detail}", - kbSaveHint: "Browse writes and saves automatically, then reloads Host KB status. Manual paste still needs Save.", - kbStatusConfigured: "Knowledge base: {operator} ({country}) v{version}", - kbStatusUnconfigured: "Knowledge base: not configured (pure netx)", - kbStatusError: "Knowledge base: error — {detail}", - alarmPushEnabled: "Key-alarm push", - alarmPushStatus: "Status", + kbStatusConfigured: "{operator} ({country}) · v{version}", + kbStatusUnconfigured: "Not configured", + kbStatusError: "Load failed", + alarmPushEnabled: "Enable push", alarmPushPhaseDisabled: "Off", alarmPushPhaseIdle: "Idle", alarmPushPhaseConnecting: "Connecting…", @@ -2997,33 +2957,30 @@ var en = { alarmPushPhaseError: "Error", alarmDeliverDsh: "Deliver to DSH session", alarmDeliverIm: "Deliver to WhatsApp / IM", - imTargetNone: "No delivery targets yet — create one in IM delivery settings.", + imTargetNone: "No targets. Add one in IM settings.", imTargetSelectedCount: "{count} selected", - imCatalogUnavailable: "Cannot load IM targets (install/update dsh-im-ops).", + imCatalogUnavailable: "IM delivery unavailable", apiToken: "API token", - apiTokenHint: "Stored as NETX_API_TOKEN. Leave blank to keep current.", - apiTokenUnavailable: "remote.credentials unavailable — set NETX_API_TOKEN via script.", + apiTokenHint: "Leave blank to keep the current token", + apiTokenUnavailable: "Credential service unavailable", apiTokenSet: "Configured", apiTokenUnset: "Not set", - toolsStatus: "Agent tools", - toolsStatusMounted: "{count} netx__* tools (v{version})", - toolsStatusEmpty: "No active Netx tool mounts", - toolsStatusError: "Tool mount failed: {detail}", - toolsStatusUnknown: "Host status unavailable", - sessionsExport: "Export all sessions", + toolsStatusMounted: "{count} active tools", + toolsStatusEmpty: "No active tools", + toolsStatusError: "Publication failed", + toolsStatusUnknown: "Status unavailable", sessionsExportCount: "{count} sessions", sessionsExportUnavailable: "Export unavailable", - sessionsExportNoPersistence: "Session store unavailable (JSONL backend required).", - sessionsExportNoRawArtifacts: "This persistence backend cannot export raw session files.", + sessionsExportNoPersistence: "Session store unavailable.", + sessionsExportNoRawArtifacts: "This session store does not support export.", sessionsExportListFailed: "Failed to list sessions: {detail}", - sessionsExportRpcUnavailable: "Cannot reach Host RPC for export status.", - sessionsExportRpcFailed: "Export status RPC failed.", + sessionsExportRpcUnavailable: "Export service unavailable.", + sessionsExportRpcFailed: "Cannot load export status.", sessionsExportHttpFailed: "Export failed: HTTP {status}{detail}", sessionsExportEmpty: "Export failed: empty ZIP body.", sessionsExportButton: "Download ZIP", sessionsExportBusy: "Preparing…", sessionsExportDone: "Downloaded: {file}", - overridden: "Overridden", reset: "Reset", invalid: "Invalid", unsaved: "Unsaved changes", @@ -3031,57 +2988,52 @@ var en = { saveFailed: "Save failed", discard: "Discard", save: "Save", - saving: "Saving…", - configSaved: "Saved" + saving: "Saving…" }; var zh = { - settingsUnavailable: "正在等待 Host 设置,连接就绪前不能保存。若持续等待,请检查 Host 插件是否启用。", - toolsEnabled: "启用 Netx 能力发布", - toolsEnabledHint: "自动保存。关闭会撤销全部 Netx 工具和 skill,保留各组选择,便于随时恢复。", - toolsDisabled: "全部 Netx 工具和 skill 已关闭", - selectedPresets: "指定其他预设", - publicationHint: "工具与对应 skill 一起发布。选择自动保存,对当前及新会话的下一次模型请求生效。公开能力由所有预设继承,也包括 Netx Ops。", - presetCatalogUnavailable: "暂时无法读取预设列表,已有选择会保留,也可以取消。", + settingsUnavailable: "正在连接 Host 设置…", + toolsEnabled: "启用工具与技能", + toolsDisabled: "已关闭", + selectedPresets: "其他预设", + presetCatalogUnavailable: "无法加载预设列表,已有选择已保留。", presetUnavailable: "预设不可用", - noOtherPresets: "此 Host 暂无其他预设。", - mountStatusHint: "显示此 Host 的实际挂载。指定其他预设在会话活跃时挂载;此状态表示注册结果,API 权限在调用工具时验证。", - mountDetails: "查看实际挂载明细", + noOtherPresets: "暂无其他预设。", + mountStatusHint: "当前活跃会话中的工具注册情况;API 权限在调用时验证。", + mountDetails: "诊断信息", title: "Netx Ops", - description: "API、Token、能力组、知识库与告警投递。", sectionConnection: "连接", - sectionCapabilities: "能力组", + sectionLanguage: "模型语言", + languageScope: "适用于所有预设", + languageAuto: "自动", + languageFollowUser: "跟随用户", + languageChinese: "中文", + languageEnglish: "English", + sectionCapabilities: "工具与技能", sectionKnowledge: "知识库", sectionAlarms: "关键告警投递", sectionExport: "会话导出", apiUrl: "API 地址", - apiUrlHint: "例如 http://127.0.0.1:8890", - apiUrlInvalid: "须为无账号密码的 http(s) 地址(不支持 file:/javascript:)", + apiUrlHint: "http://127.0.0.1:8890", + apiUrlInvalid: "请输入不含账号密码的 HTTP 或 HTTPS 地址。", lang: "API / 告警语言", - langHint: "zh / en — 仅影响 netx API 与告警文案,不控制模型回复", thinkingLanguage: "思考语言", - thinkingLanguageHint: "auto / zh-CN / en — 全预设模型思考过程语言", replyLanguage: "回复语言", - replyLanguageHint: "follow-user / zh / en — 全预设最终回答语言", - capabilityGroups: "能力组", nmsProvider: "NMS 提供方", - nmsProviderHint: "zte-ume", groupOps: "告警、资产与 CLI 运维", groupTopology: "拓扑与布局", groupBizMonitor: "割接业务监控", groupKb: "知识库技能", - groupInPreset: "在 Netx Ops 预设中启用(自动保存,即时挂载/卸载)", - groupPublic: "对所有预设公开(自动保存)", + groupInPreset: "Netx Ops", + groupPublic: "所有预设", kbRoot: "知识包根目录", - kbRootHint: "含 MANIFEST.json 的运营商子集包(v1.0)。浏览选目录后会自动保存;hasSkills 时加载 paths.skills/_skills 与 paths.localSkills。", + kbRootHint: "含 MANIFEST.json 的知识包绝对路径", kbBrowse: "浏览…", - kbBrowseUnavailable: "目录选择器不可用 — 请粘贴绝对目录路径后点保存。", + kbBrowseUnavailable: "请手动输入目录路径。", kbBrowseFailed: "浏览失败:{detail}", - kbSaveHint: "点「浏览」选目录后会自动写入、保存,并刷新宿主知识库状态;若是手粘路径仍需点保存。", - kbStatusConfigured: "知识库: {operator}({country}) v{version}", - kbStatusUnconfigured: "知识库: 未配置(纯 netx)", - kbStatusError: "知识库: 错误 — {detail}", - alarmPushEnabled: "关键告警推送", - alarmPushStatus: "状态", + kbStatusConfigured: "{operator}({country})· v{version}", + kbStatusUnconfigured: "未配置", + kbStatusError: "加载失败", + alarmPushEnabled: "启用推送", alarmPushPhaseDisabled: "未开启", alarmPushPhaseIdle: "空闲", alarmPushPhaseConnecting: "连接中…", @@ -3092,33 +3044,30 @@ var zh = { alarmPushPhaseError: "异常", alarmDeliverDsh: "投递到 DSH 会话", alarmDeliverIm: "投递到 WhatsApp / IM", - imTargetNone: "暂无投递目标 — 请先在 IM「投递设置」新建。", + imTargetNone: "暂无目标,请在 IM 设置中添加。", imTargetSelectedCount: "已选 {count} 个", - imCatalogUnavailable: "无法加载投递目标(请安装/更新 dsh-im-ops)。", + imCatalogUnavailable: "IM 投递不可用", apiToken: "API Token", - apiTokenHint: "写入凭据 NETX_API_TOKEN;留空表示保留已有。", - apiTokenUnavailable: "未提供 remote.credentials — 请用脚本写入 NETX_API_TOKEN。", + apiTokenHint: "留空保留当前凭据", + apiTokenUnavailable: "凭据服务不可用", apiTokenSet: "已配置", apiTokenUnset: "未设置", - toolsStatus: "Agent 工具", - toolsStatusMounted: "已挂载 {count} 个 netx__*(v{version})", - toolsStatusEmpty: "当前没有活跃的 Netx 工具挂载", - toolsStatusError: "工具挂载失败:{detail}", - toolsStatusUnknown: "无法读取 Host 状态", - sessionsExport: "导出全部会话", + toolsStatusMounted: "{count} 个工具", + toolsStatusEmpty: "暂无活跃工具", + toolsStatusError: "发布异常", + toolsStatusUnknown: "状态不可用", sessionsExportCount: "{count} 个会话", sessionsExportUnavailable: "无法导出", - sessionsExportNoPersistence: "会话持久化不可用(需要 JSONL 后端)。", - sessionsExportNoRawArtifacts: "当前持久化后端不支持导出原始会话文件。", + sessionsExportNoPersistence: "会话存储不可用。", + sessionsExportNoRawArtifacts: "当前会话存储不支持导出。", sessionsExportListFailed: "列出会话失败:{detail}", - sessionsExportRpcUnavailable: "无法通过 Host RPC 查询导出状态。", - sessionsExportRpcFailed: "导出状态 RPC 失败。", + sessionsExportRpcUnavailable: "导出服务不可用。", + sessionsExportRpcFailed: "无法获取导出状态。", sessionsExportHttpFailed: "导出失败:HTTP {status}{detail}", sessionsExportEmpty: "导出失败:ZIP 为空。", sessionsExportButton: "下载 ZIP", sessionsExportBusy: "准备中…", sessionsExportDone: "已下载:{file}", - overridden: "已覆盖", reset: "重置", invalid: "无效", unsaved: "有未保存更改", @@ -3126,8 +3075,7 @@ var zh = { saveFailed: "保存失败", discard: "丢弃", save: "保存", - saving: "保存中…", - configSaved: "已保存" + saving: "保存中…" }; // src/client/settings-form.ts diff --git a/src/client/NetxopsCard.tsx b/src/client/NetxopsCard.tsx index 39d766f..0f96357 100644 --- a/src/client/NetxopsCard.tsx +++ b/src/client/NetxopsCard.tsx @@ -1,8 +1,6 @@ -/** - * Netx Ops settings section — uds-auth visual language (page header + cards). - */ +/** Netx Ops settings: connection, publication, knowledge and delivery. */ -import { useEffect, useRef, useState, type ReactNode } from 'react' +import type { ReactNode } from 'react' import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import type { NetxopsCardFace, NetxopsCardState } from './controller.ts' import type { NetxopsLocaleKey } from './locales.ts' @@ -66,7 +64,7 @@ function kbBadgeLabel( return t('kbStatusUnconfigured') } if (snapshot.status === 'error') { - return fillTemplate(t('kbStatusError'), { detail: snapshot.errorMessage || 'error' }) + return t('kbStatusError') } return fillTemplate(t('kbStatusConfigured'), { operator: snapshot.operatorName, @@ -88,12 +86,11 @@ function toolsBadgeLabel( if (!view) return t('toolsStatusUnknown') if (!view.toolsEnabled) return t('toolsDisabled') if (view.error) { - return fillTemplate(t('toolsStatusError'), { detail: view.error }) + return t('toolsStatusError') } if (view.count > 0) { return fillTemplate(t('toolsStatusMounted'), { count: String(view.count), - version: view.version || '?', }) } return t('toolsStatusEmpty') @@ -136,9 +133,8 @@ export type NetxopsCardProps = function ValueField(props: { id: string label: string - hint: string + hint?: string field: CardFieldState - overriddenLabel: string resetLabel: string invalidLabel: string disabled: boolean @@ -152,7 +148,6 @@ function ValueField(props: { {props.field.overridden ? ( - {props.overriddenLabel} @@ -164,14 +159,13 @@ function ValueField(props: { id={props.id} className={props.field.invalid ? 'dsh-nx-inputInvalid' : undefined} type="text" + placeholder={props.hint} value={props.field.text} disabled={props.disabled} aria-invalid={props.field.invalid || undefined} onChange={(event) => { props.onEdit(event.target.value) }} /> -

- {props.field.invalid ? props.invalidLabel : props.hint} -

+ {props.field.invalid ?

{props.invalidLabel}

: null} ) } @@ -179,10 +173,8 @@ function ValueField(props: { function SelectField(props: { id: string label: string - hint: string field: CardFieldState options: ReadonlyArray<{ value: string, label: string }> - overriddenLabel: string resetLabel: string invalidLabel: string disabled: boolean @@ -197,7 +189,6 @@ function SelectField(props: { {props.field.overridden ? ( - {props.overriddenLabel} @@ -220,9 +211,7 @@ function SelectField(props: { ))} -

- {props.field.invalid ? props.invalidLabel : props.hint} -

+ {props.field.invalid ?

{props.invalidLabel}

: null} ) } @@ -343,7 +332,6 @@ function PresetTargets(props: { return (
{props.t('selectedPresets')} · {selected.length} -

{props.t('publicationHint')}

{props.state.presetCatalogError ?

{props.t('presetCatalogUnavailable')}

: null} {[...rows, ...unknown.map(id => ({ id, name: undefined, broken: undefined }))].map(row => (