@wingsky-1/dsh-decision-gateway 0.2.6
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/LICENSE +21 -0
- package/README.en.md +56 -0
- package/README.md +56 -0
- package/cordis.patch.yml +7 -0
- package/lib/THIRD-PARTY-LICENSES +35 -0
- package/lib/client/api/contract.d.ts +37 -0
- package/lib/client/api/interface.d.ts +9 -0
- package/lib/client/api/routes.d.ts +11 -0
- package/lib/client/index.d.ts +8 -0
- package/lib/client/locale.d.ts +31 -0
- package/lib/client/locales.d.ts +122 -0
- package/lib/client/settings/card.d.ts +7 -0
- package/lib/client/settings/connection.d.ts +10 -0
- package/lib/client/settings/format.d.ts +4 -0
- package/lib/client/settings/history.d.ts +9 -0
- package/lib/client/settings/presets.d.ts +9 -0
- package/lib/client/settings/prob.d.ts +10 -0
- package/lib/client.js +1518 -0
- package/lib/index.d.ts +32 -0
- package/lib/index.js +3142 -0
- package/lib/server/api/deps.d.ts +53 -0
- package/lib/server/api/impl/handlers.d.ts +11 -0
- package/lib/server/api/impl/route.d.ts +28 -0
- package/lib/server/api/impl/service.d.ts +4 -0
- package/lib/server/api/interface.d.ts +3 -0
- package/lib/server/config/deps.d.ts +32 -0
- package/lib/server/config/impl/envelope.d.ts +9 -0
- package/lib/server/config/impl/model.d.ts +54 -0
- package/lib/server/config/impl/paths.d.ts +12 -0
- package/lib/server/config/impl/service.d.ts +37 -0
- package/lib/server/config/interface.d.ts +12 -0
- package/lib/server/history/deps.d.ts +25 -0
- package/lib/server/history/impl/entry.d.ts +29 -0
- package/lib/server/history/impl/hash.d.ts +8 -0
- package/lib/server/history/impl/paths.d.ts +2 -0
- package/lib/server/history/impl/redact.d.ts +2 -0
- package/lib/server/history/impl/service.d.ts +30 -0
- package/lib/server/history/interface.d.ts +4 -0
- package/lib/server/store/deps.d.ts +6 -0
- package/lib/server/store/impl/io.d.ts +24 -0
- package/lib/server/store/interface.d.ts +4 -0
- package/lib/server/tools/deps.d.ts +83 -0
- package/lib/server/tools/impl/client.d.ts +62 -0
- package/lib/server/tools/impl/define.d.ts +30 -0
- package/lib/server/tools/impl/precheck.d.ts +8 -0
- package/lib/server/tools/impl/probe.d.ts +24 -0
- package/lib/server/tools/impl/semaphore.d.ts +4 -0
- package/lib/server/tools/impl/service.d.ts +18 -0
- package/lib/server/tools/impl/validate.d.ts +17 -0
- package/lib/server/tools/interface.d.ts +7 -0
- package/lib/server/upgrade/deps.d.ts +37 -0
- package/lib/server/upgrade/impl/service.d.ts +13 -0
- package/lib/server/upgrade/impl/steps.d.ts +3 -0
- package/lib/server/upgrade/interface.d.ts +6 -0
- package/lib/shared/contract.d.ts +184 -0
- package/lib/shared/interface.d.ts +7 -0
- package/package.json +99 -0
- package/shared/client/ensure-style.d.ts +21 -0
- package/shared/client/i18n.d.ts +15 -0
- package/shared/dsh-home.d.ts +15 -0
- package/shared/host-utils.d.ts +65 -0
- package/shared/loopback.d.ts +21 -0
- package/shared/paths.d.ts +12 -0
- package/shared/settings-namespace.d.ts +82 -0
- package/shared/sse-hub.d.ts +76 -0
- package/shared/upgrade-tick.d.ts +11 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1518 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// 契约外壳(scripts/build-client.ts 生成):external 依赖(React 等)经 factory 注入的 require 解析
|
|
3
|
+
window.__ModuleLoader__.load({
|
|
4
|
+
id: "@wingsky-1/dsh-decision-gateway",
|
|
5
|
+
factory: function (require) {
|
|
6
|
+
var module = { exports: {} }
|
|
7
|
+
var exports = module.exports
|
|
8
|
+
"use strict";
|
|
9
|
+
"use strict";
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
|
|
38
|
+
// src/client/index.tsx
|
|
39
|
+
var index_exports = {};
|
|
40
|
+
__export(index_exports, {
|
|
41
|
+
apply: () => apply,
|
|
42
|
+
inject: () => inject
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// <define:__DSH_ROUTES__>
|
|
47
|
+
var define_DSH_ROUTES_default = { health: "/api/dsh-decision-gateway/health", config: "/api/dsh-decision-gateway/config", presets: "/api/dsh-decision-gateway/presets", history: "/api/dsh-decision-gateway/history", testConnection: "/api/dsh-decision-gateway/test-connection" };
|
|
48
|
+
|
|
49
|
+
// src/client/style.css
|
|
50
|
+
var style_default = '/* dsh-decision-gateway — 客户端样式(独立 .css 文件,#940 Apple 语言极简克制优雅)。\n *\n * 由 build-client 的 .css text-loader 构建期内联进 client.js(产物仍自包含单文件、\n * 零运行时依赖、无独立请求)。ensureStyle(shared/client/ensure-style.js)注入\n * <style id=dsh-decision-gateway-style>,version 变化重建。\n * 前缀 dj-(防与 shell 冲突);颜色只走 --dsw-alias-* 主题变量 + 浅色回退,\n * 明暗自适应(变量随主题切换;@media 暗色段只改回退,不引入新色板)。\n * Liquid Glass:半透底 + backdrop-blur-md;边界只用 0.5px subtle border,禁粗分割线;\n * 动效 cubic-bezier(0.16,1,0.3,1) 极轻缩放淡入,可点击 active 缩放 0.97;\n * 中文系统栈行高 1.7+、数字 tabular-nums、禁斜体;概率条细条 + 阈值线 + 数字。 */\n\n.dj-card {\n list-style: none;\n font-family:\n -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Hiragino Sans GB",\n "Microsoft YaHei", "Noto Sans SC", sans-serif;\n font-style: normal;\n border: 0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n border-radius: 14px;\n background: var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.8));\n -webkit-backdrop-filter: blur(12px) saturate(1.2);\n backdrop-filter: blur(12px) saturate(1.2);\n overflow: hidden;\n transition:\n border-color 0.25s cubic-bezier(0.16, 1, 0.3, 1),\n background 0.25s cubic-bezier(0.16, 1, 0.3, 1),\n transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n animation: dj-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n}\n@keyframes dj-in {\n from {\n opacity: 0;\n transform: scale(0.99);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n}\n.dj-card:hover {\n border-color: var(--dsw-alias-label-dimmed, rgba(0, 0, 0, 0.14));\n}\n.dj-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n padding: 16px 18px 12px;\n}\n.dj-headText {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n.dj-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.7;\n color: var(--dsw-alias-label-primary, #1f2329);\n}\n.dj-desc {\n font-size: 13px;\n line-height: 1.75;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n}\n.dj-status {\n flex: none;\n font-size: 11px;\n line-height: 1.7;\n padding: 2px 10px;\n border-radius: 999px;\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n color: var(--dsw-alias-label-secondary, #5f6672);\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.6));\n font-variant-numeric: tabular-nums;\n}\n.dj-statusOk {\n color: var(--dsw-alias-state-success-primary, #16a34a);\n border-color: var(--dsw-alias-state-success-border, rgba(22, 163, 74, 0.3));\n}\n.dj-statusErr {\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n border-color: var(--dsw-alias-state-danger-border, rgba(220, 38, 38, 0.3));\n}\n.dj-tabs {\n display: flex;\n gap: 4px;\n padding: 0 14px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n}\n.dj-tab {\n border: none;\n background: none;\n cursor: pointer;\n font: inherit;\n font-style: normal;\n font-size: 13px;\n line-height: 1.7;\n padding: 9px 13px;\n color: var(--dsw-alias-label-secondary, #5f6672);\n border-bottom: 1px solid transparent;\n margin-bottom: -0.5px;\n border-radius: 8px 8px 0 0;\n transition:\n color 0.2s cubic-bezier(0.16, 1, 0.3, 1),\n background 0.2s cubic-bezier(0.16, 1, 0.3, 1),\n transform 0.15s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-tab:hover {\n color: var(--dsw-alias-label-primary, #1f2329);\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n}\n.dj-tab:active {\n transform: scale(0.97);\n}\n.dj-tabActive {\n color: var(--dsw-alias-label-primary, #1f2329);\n border-bottom-color: var(--dsw-alias-state-info-primary, #3b82f6);\n font-weight: 600;\n}\n.dj-body {\n padding: 16px 18px 18px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.dj-pane {\n display: flex;\n flex-direction: column;\n gap: 12px;\n animation: dj-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.dj-row label {\n font-size: 12.5px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n flex: none;\n}\n.dj-field {\n display: flex;\n flex-direction: column;\n gap: 7px;\n}\n.dj-field label {\n font-size: 12.5px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n}\n.dj-input,\n.dj-select {\n width: 100%;\n max-width: 100%;\n padding: 7px 11px;\n font-size: 12.5px;\n line-height: 1.7;\n font-family: inherit;\n font-style: normal;\n color: var(--dsw-alias-label-primary, #1f2329);\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.7));\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));\n border-radius: 8px;\n transition: border-color 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-input:focus,\n.dj-select:focus {\n outline: none;\n border-color: var(--dsw-alias-state-info-primary, #3b82f6);\n}\n.dj-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n.dj-checkRow {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n}\n.dj-hint {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n line-height: 1.7;\n}\n.dj-fold {\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n border-radius: 10px;\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.5));\n}\n.dj-foldHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n width: 100%;\n padding: 10px 13px;\n border: none;\n background: none;\n cursor: pointer;\n font: inherit;\n font-style: normal;\n font-size: 12.5px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n text-align: left;\n transition:\n background 0.2s cubic-bezier(0.16, 1, 0.3, 1),\n transform 0.15s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-foldHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n}\n.dj-foldHead:active {\n transform: scale(0.97);\n}\n.dj-foldBody {\n padding: 12px 13px 14px;\n border-top: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n/* hidden 折叠:display:flex 会覆盖 UA 的 [hidden] display:none,必须显式补回(明文/高级/历史过滤三处同治)。 */\n.dj-foldBody[hidden] {\n display: none;\n}\n.dj-badge {\n display: inline-block;\n font-size: 11px;\n line-height: 1.7;\n padding: 1px 9px;\n border-radius: 999px;\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));\n color: var(--dsw-alias-label-secondary, #5f6672);\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.6));\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n}\n.dj-badgeOn {\n color: var(--dsw-alias-state-success-primary, #16a34a);\n border-color: var(--dsw-alias-state-success-border, rgba(22, 163, 74, 0.3));\n}\n.dj-badgeWarn {\n color: var(--dsw-alias-state-warning-primary, #d97706);\n border-color: var(--dsw-alias-state-warning-border, rgba(217, 119, 6, 0.3));\n}\n.dj-badgeDanger {\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n border-color: var(--dsw-alias-state-danger-border, rgba(220, 38, 38, 0.3));\n}\n.dj-badgeInfo {\n color: var(--dsw-alias-state-info-primary, #3b82f6);\n border-color: var(--dsw-alias-state-info-border, rgba(59, 130, 246, 0.3));\n}\n.dj-btn {\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.7));\n color: var(--dsw-alias-label-primary, #1f2329);\n border-radius: 8px;\n padding: 6px 15px;\n font-size: 12.5px;\n line-height: 1.7;\n font-family: inherit;\n font-style: normal;\n cursor: pointer;\n white-space: nowrap;\n transition:\n background 0.2s cubic-bezier(0.16, 1, 0.3, 1),\n transform 0.15s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06));\n}\n.dj-btn:active {\n transform: scale(0.97);\n}\n.dj-btn:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n transform: none;\n}\n.dj-btnSmall {\n padding: 4px 11px;\n font-size: 12px;\n}\n.dj-btnDanger {\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n border-color: var(--dsw-alias-state-danger-border, rgba(220, 38, 38, 0.3));\n}\n.dj-btnPrimary {\n color: #fff;\n background: var(--dsw-alias-state-info-primary, #3b82f6);\n border-color: var(--dsw-alias-state-info-primary, #3b82f6);\n}\n.dj-foot {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n flex-wrap: wrap;\n}\n.dj-note {\n font-size: 12px;\n line-height: 1.75;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n}\n.dj-ok {\n font-size: 12px;\n line-height: 1.75;\n color: var(--dsw-alias-state-success-primary, #16a34a);\n}\n.dj-error {\n font-size: 12px;\n line-height: 1.75;\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n border: 0.5px solid var(--dsw-alias-state-danger-border, rgba(220, 38, 38, 0.3));\n border-radius: 8px;\n padding: 8px 10px;\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.6));\n overflow-wrap: anywhere;\n}\n.dj-tools {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.dj-filters {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n.dj-filters .dj-field {\n flex: 1;\n min-width: 140px;\n}\n.dj-preset {\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n border-radius: 10px;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.5));\n}\n.dj-presetHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n.dj-presetId {\n font-size: 13px;\n font-weight: 600;\n line-height: 1.7;\n color: var(--dsw-alias-label-primary, #1f2329);\n overflow-wrap: anywhere;\n}\n.dj-switch {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n cursor: pointer;\n}\n.dj-rangeRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.dj-rangeRow input[type="range"] {\n flex: 1;\n min-width: 0;\n accent-color: var(--dsw-alias-state-info-primary, #3b82f6);\n}\n.dj-rangeVal {\n flex: none;\n min-width: 48px;\n text-align: right;\n font-size: 12px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n font-variant-numeric: tabular-nums;\n}\n.dj-hist {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.dj-histItem {\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n border-radius: 10px;\n padding: 11px 13px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n background: var(--dsw-alias-bg-layer-1, rgba(255, 255, 255, 0.55));\n}\n.dj-histHead {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n font-size: 11px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n}\n.dj-histTime {\n font-variant-numeric: tabular-nums;\n}\n.dj-opts {\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin: 0;\n padding: 0;\n}\n.dj-opt {\n border: 0.5px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));\n border-radius: 8px;\n padding: 7px 10px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dj-optHead {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary, #1f2329);\n}\n.dj-histSnippet {\n font-size: 12.5px;\n line-height: 1.75;\n color: var(--dsw-alias-label-primary, #1f2329);\n overflow-wrap: anywhere;\n white-space: pre-wrap;\n}\n/* 概率条:细条 + 阈值线 + 数字(禁重色块;无运行时图表库,手写 div)。 */\n.dj-probRow {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dj-bar {\n position: relative;\n flex: 1;\n min-width: 0;\n height: 4px;\n border-radius: 999px;\n background: var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.1));\n overflow: visible;\n}\n.dj-barFill {\n height: 100%;\n border-radius: 999px;\n background: var(--dsw-alias-state-info-primary, #3b82f6);\n transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.dj-barFillHigh {\n background: var(--dsw-alias-state-success-primary, #16a34a);\n}\n.dj-barFillLow {\n background: var(--dsw-alias-state-warning-primary, #d97706);\n}\n.dj-barThreshold {\n position: absolute;\n top: -2px;\n bottom: -2px;\n width: 1px;\n background: var(--dsw-alias-label-tertiary, #8a919c);\n opacity: 0.8;\n}\n.dj-probNum {\n flex: none;\n min-width: 44px;\n text-align: right;\n font-size: 11px;\n line-height: 1.7;\n color: var(--dsw-alias-label-secondary, #5f6672);\n font-variant-numeric: tabular-nums;\n}\n.dj-meta {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n align-items: center;\n font-size: 11px;\n line-height: 1.7;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n font-variant-numeric: tabular-nums;\n}\n.dj-errLine {\n font-size: 11px;\n line-height: 1.7;\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n overflow-wrap: anywhere;\n}\n@media (max-width: 480px) {\n .dj-row {\n flex-direction: column;\n align-items: stretch;\n }\n .dj-foot {\n justify-content: stretch;\n }\n .dj-foot .dj-btn {\n flex: 1;\n }\n}\n/* 暗色:只改回退(变量已随主题切换;此处保证变量缺席时仍是深底浅字)。 */\n@media (prefers-color-scheme: dark) {\n .dj-card {\n background: var(--dsw-alias-bg-layer-3, rgba(24, 24, 27, 0.8));\n border-color: var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.1));\n }\n .dj-status,\n .dj-badge,\n .dj-input,\n .dj-select,\n .dj-btn,\n .dj-fold,\n .dj-preset,\n .dj-histItem {\n background: var(--dsw-alias-bg-layer-1, rgba(24, 24, 27, 0.6));\n border-color: var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.1));\n }\n}\n';
|
|
51
|
+
|
|
52
|
+
// ../../shared/client/ensure-style.js
|
|
53
|
+
function ensureStyle(options) {
|
|
54
|
+
const opts = options || {};
|
|
55
|
+
const id = opts.id;
|
|
56
|
+
const cssText = opts.cssText;
|
|
57
|
+
const version = opts.version;
|
|
58
|
+
if (typeof id !== "string" || id === "" || typeof cssText !== "string") {
|
|
59
|
+
throw new TypeError("ensureStyle: { id, cssText } 为必填且须为字符串(id 非空)");
|
|
60
|
+
}
|
|
61
|
+
const dispose = function() {
|
|
62
|
+
const node = document.getElementById(id);
|
|
63
|
+
if (node !== null) node.remove();
|
|
64
|
+
};
|
|
65
|
+
if (document.head == null) return dispose;
|
|
66
|
+
const existing = document.getElementById(id);
|
|
67
|
+
if (existing !== null) {
|
|
68
|
+
if (version === void 0) return dispose;
|
|
69
|
+
if (existing.dataset.version === version) return dispose;
|
|
70
|
+
existing.remove();
|
|
71
|
+
}
|
|
72
|
+
const style = document.createElement("style");
|
|
73
|
+
style.id = id;
|
|
74
|
+
if (version !== void 0) style.dataset.version = version;
|
|
75
|
+
style.textContent = cssText;
|
|
76
|
+
document.head.appendChild(style);
|
|
77
|
+
return dispose;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/client/index.tsx
|
|
81
|
+
var React6 = __toESM(require("react"), 1);
|
|
82
|
+
|
|
83
|
+
// src/client/locales.ts
|
|
84
|
+
var zh = {
|
|
85
|
+
tabLabel: "JEV 决策",
|
|
86
|
+
cardName: "JEV 决策(dsh-decision-gateway)",
|
|
87
|
+
cardDesc: "连接 / 模板库 / 历史:密钥掩码显示,原文永不回显。",
|
|
88
|
+
tabsAria: "JEV 决策设置",
|
|
89
|
+
tabConnection: "连接",
|
|
90
|
+
tabPresets: "模板库",
|
|
91
|
+
tabHistory: "历史",
|
|
92
|
+
healthChecking: "自检中…",
|
|
93
|
+
healthOk: "服务可用",
|
|
94
|
+
healthBad: "服务异常 ",
|
|
95
|
+
healthUnreachable: "服务不可达",
|
|
96
|
+
notLoaded: "未加载",
|
|
97
|
+
loading: "加载中…",
|
|
98
|
+
save: "保存",
|
|
99
|
+
retry: "重试",
|
|
100
|
+
exportJson: "导出 JSON",
|
|
101
|
+
detail: "详情",
|
|
102
|
+
copyCustom: "复制为自定义",
|
|
103
|
+
copyCustomNote: "复制为自定义走新增接口(P1),当前仅展示规范。",
|
|
104
|
+
refresh: "刷新",
|
|
105
|
+
clearSession: "清空本会话",
|
|
106
|
+
testOffline: "离线自检",
|
|
107
|
+
testing: "自检中…",
|
|
108
|
+
testOk: "自检通过",
|
|
109
|
+
testFail: "自检失败:",
|
|
110
|
+
enabledOn: "已启用",
|
|
111
|
+
enabledOff: "已关闭",
|
|
112
|
+
enablePresetAria: "启用预设 ",
|
|
113
|
+
emptyPresets: "暂无预设。",
|
|
114
|
+
emptyHistory: "暂无历史。",
|
|
115
|
+
emptySpec: "暂无规范。",
|
|
116
|
+
truncBadge: "截断",
|
|
117
|
+
errPrefix: "错误:",
|
|
118
|
+
noProb: "无概率",
|
|
119
|
+
highLine: "high 线 80%",
|
|
120
|
+
capNone: "none(仅人工)",
|
|
121
|
+
capLabel: "自动化上限",
|
|
122
|
+
tplVersion: "模板版本 v",
|
|
123
|
+
frozenNote: "(frozen,恒为 1)",
|
|
124
|
+
countEntries: "共 {n} 条",
|
|
125
|
+
keyMode: "密钥方式(二选一,互斥)",
|
|
126
|
+
keyEnv: "ENV 引用",
|
|
127
|
+
keyPlain: "明文密钥",
|
|
128
|
+
modeEnvNote: "当前 ENV 引用:明文侧已隐藏。",
|
|
129
|
+
modePlainNote: "当前明文密钥:ENV 侧已隐藏。保存明文将清除已存的 ENV 引用(切轨);切回 ENV 即清空明文。",
|
|
130
|
+
maskDotsAria: "已配置明文密钥掩码(定长八点,与原文长度无关)",
|
|
131
|
+
envLabel: "ENV 变量名(apiKeyRef)",
|
|
132
|
+
envAria: "ENV 变量名",
|
|
133
|
+
envPlaceholder: "大写字母开头,如 JEV_API_KEY(留空不改)",
|
|
134
|
+
envRule: "规则:^[A-Z][A-Z0-9_]{1,63}$;与明文密钥互斥,只提交其一。",
|
|
135
|
+
envBad: "ENV 名非法:须满足 ^[A-Z][A-Z0-9_]{1,63}$(如 JEV_API_KEY)。",
|
|
136
|
+
plainAria: "明文密钥",
|
|
137
|
+
plainPlaceholder: "输入新的明文密钥以替换(留空不改)",
|
|
138
|
+
plainFoldTitle: "输入明文密钥…",
|
|
139
|
+
plainNote: "密钥原文永不回显;已配置仅显示“已配置”。占位符不含任何真密钥示例。",
|
|
140
|
+
maskOn: "密钥:已配置",
|
|
141
|
+
maskOff: "密钥:未配置",
|
|
142
|
+
maskEnv: "ENV:",
|
|
143
|
+
loadedNote: "已加载配置 v1。明文框恒空:已配置仅显示“已配置”。",
|
|
144
|
+
loadFail: "加载失败:",
|
|
145
|
+
loadFailShort: "加载失败",
|
|
146
|
+
mutualError: "明文与 ENV 引用互斥:请先清空 ENV 输入框(留空)再提交明文。",
|
|
147
|
+
nanError: "高级参数须为数字。",
|
|
148
|
+
saving: "保存中…(明文仅本次提交,不回显)",
|
|
149
|
+
savedReload: "已保存。重新加载掩码态…",
|
|
150
|
+
saveFail: "保存失败:",
|
|
151
|
+
advTitle: "高级…",
|
|
152
|
+
advTimeout: "请求超时 timeoutMs",
|
|
153
|
+
advTimeoutHint: "默认 8000(毫秒,上限 120000)",
|
|
154
|
+
advConcurrency: "最大并发 maxConcurrency",
|
|
155
|
+
advConcurrencyHint: "默认 4",
|
|
156
|
+
advTrunc: "截断预算 truncBudget",
|
|
157
|
+
advTruncHint: "默认 32000(字符)",
|
|
158
|
+
advPerSession: "单会话历史 perSession",
|
|
159
|
+
advPerSessionHint: "默认 200(上限 1000)",
|
|
160
|
+
advTotalSessions: "总会话数 totalSessions",
|
|
161
|
+
advTotalSessionsHint: "默认 50(上限 200)",
|
|
162
|
+
filterTitle: "过滤…",
|
|
163
|
+
wsLabel: "工作目录",
|
|
164
|
+
wsFilterAria: "工作目录过滤",
|
|
165
|
+
sessLabel: "会话",
|
|
166
|
+
sessFilterAria: "会话过滤",
|
|
167
|
+
allWs: "全部工作目录",
|
|
168
|
+
allSess: "全部会话",
|
|
169
|
+
historyOrderNote: "倒序排列(含概率条 + tier + 截断徽标 + 错误行)。",
|
|
170
|
+
clearing: "清空中…(仅单会话)",
|
|
171
|
+
clearOk: "已清空本会话。重新加载…",
|
|
172
|
+
clearFail: "清空失败:",
|
|
173
|
+
confirmClear: "确认清空本会话?",
|
|
174
|
+
confirmClearAgain: "再次点击确认清空当前会话历史(仅单会话 DELETE)。",
|
|
175
|
+
clearScopeNote: "仅清空当前选中会话(DELETE 单会话),无批量清空",
|
|
176
|
+
presetsMergedNote: "目录与开关位已合并。阈值微调即 automationCap 三档(0=none/1=low/2=high)。",
|
|
177
|
+
presetsCount: "预设 {n} 个",
|
|
178
|
+
presetsExported: "已导出 {n} 个预设(JSON 下载)。",
|
|
179
|
+
presetsSaved: "已保存 {n} 个预设开关与阈值。",
|
|
180
|
+
noImportNote: "无导入按钮:模板库只出不进。secret-leak 默认关闭(frozen 模板)。",
|
|
181
|
+
customBadge: "自建",
|
|
182
|
+
addCustom: "新建自建预设",
|
|
183
|
+
deleteCustom: "删除",
|
|
184
|
+
confirmDelete: "确认删除?",
|
|
185
|
+
confirmDeleteCustom: "再次点击确认删除该自建预设(仅删本条)。",
|
|
186
|
+
customIdLabel: "自建 id(小写字母/数字/连字符,不可与 frozen 重名)",
|
|
187
|
+
customLabelLabel: "展示名(英文)",
|
|
188
|
+
customDescLabel: "出题规范(英文自由文本,可抄模板写法)",
|
|
189
|
+
customDraftNote: "保存后默认关闭,需手动启用;题目永随调用传入,不存模板。",
|
|
190
|
+
cancelCustom: "取消",
|
|
191
|
+
createCustom: "创建",
|
|
192
|
+
customSaved: "自建预设已保存。",
|
|
193
|
+
customFormInvalid: "id/展示名/规范均不能为空。"
|
|
194
|
+
};
|
|
195
|
+
var en = {
|
|
196
|
+
tabLabel: "JEV Decide",
|
|
197
|
+
cardName: "JEV Decide (dsh-decision-gateway)",
|
|
198
|
+
cardDesc: "Connection / Templates / History: keys are masked, never echoed.",
|
|
199
|
+
tabsAria: "JEV Decide settings",
|
|
200
|
+
tabConnection: "Connection",
|
|
201
|
+
tabPresets: "Templates",
|
|
202
|
+
tabHistory: "History",
|
|
203
|
+
healthChecking: "Checking…",
|
|
204
|
+
healthOk: "Service up",
|
|
205
|
+
healthBad: "Service error ",
|
|
206
|
+
healthUnreachable: "Service unreachable",
|
|
207
|
+
notLoaded: "Not loaded",
|
|
208
|
+
loading: "Loading…",
|
|
209
|
+
save: "Save",
|
|
210
|
+
retry: "Retry",
|
|
211
|
+
exportJson: "Export JSON",
|
|
212
|
+
detail: "Details",
|
|
213
|
+
copyCustom: "Copy as custom",
|
|
214
|
+
copyCustomNote: "Copy-as-custom needs a new API (P1); spec view only for now.",
|
|
215
|
+
refresh: "Refresh",
|
|
216
|
+
clearSession: "Clear this session",
|
|
217
|
+
testOffline: "Self-check",
|
|
218
|
+
testing: "Checking…",
|
|
219
|
+
testOk: "Self-check passed",
|
|
220
|
+
testFail: "Self-check failed: ",
|
|
221
|
+
enabledOn: "Enabled",
|
|
222
|
+
enabledOff: "Off",
|
|
223
|
+
enablePresetAria: "Enable preset ",
|
|
224
|
+
emptyPresets: "No presets.",
|
|
225
|
+
emptyHistory: "No history.",
|
|
226
|
+
emptySpec: "No guide yet.",
|
|
227
|
+
truncBadge: "truncated",
|
|
228
|
+
errPrefix: "Error: ",
|
|
229
|
+
noProb: "no probability",
|
|
230
|
+
highLine: "high line 80%",
|
|
231
|
+
capNone: "none (manual only)",
|
|
232
|
+
capLabel: "Automation cap",
|
|
233
|
+
tplVersion: "Template v",
|
|
234
|
+
frozenNote: " (frozen, always 1)",
|
|
235
|
+
countEntries: "{n} entries",
|
|
236
|
+
keyMode: "Key mode (pick one, mutually exclusive)",
|
|
237
|
+
keyEnv: "ENV reference",
|
|
238
|
+
keyPlain: "Plaintext key",
|
|
239
|
+
modeEnvNote: "ENV reference active: plaintext side hidden.",
|
|
240
|
+
modePlainNote: "Plaintext active: ENV side hidden. Saving plaintext clears the stored ENV reference (track switch); switching back to ENV clears plaintext.",
|
|
241
|
+
maskDotsAria: "Masked configured plaintext key (fixed eight dots, unrelated to key length)",
|
|
242
|
+
envLabel: "ENV variable name (apiKeyRef)",
|
|
243
|
+
envAria: "ENV variable name",
|
|
244
|
+
envPlaceholder: "Uppercase-leading, e.g. JEV_API_KEY (blank keeps current)",
|
|
245
|
+
envRule: "Rule: ^[A-Z][A-Z0-9_]{1,63}$; mutually exclusive with plaintext, only one is submitted.",
|
|
246
|
+
envBad: "Bad ENV name: must match ^[A-Z][A-Z0-9_]{1,63}$ (e.g. JEV_API_KEY).",
|
|
247
|
+
plainAria: "Plaintext key",
|
|
248
|
+
plainPlaceholder: "Enter a new plaintext key to replace (blank keeps current)",
|
|
249
|
+
plainFoldTitle: "Enter plaintext key…",
|
|
250
|
+
plainNote: 'Key text is never echoed; configured state shows as "configured" only. Placeholders carry no real key examples.',
|
|
251
|
+
maskOn: "Key: configured",
|
|
252
|
+
maskOff: "Key: missing",
|
|
253
|
+
maskEnv: "ENV: ",
|
|
254
|
+
loadedNote: 'Config v1 loaded. Plaintext box stays empty: configured state shows as "configured" only.',
|
|
255
|
+
loadFail: "Load failed: ",
|
|
256
|
+
loadFailShort: "Load failed",
|
|
257
|
+
mutualError: "Plaintext and ENV reference are mutually exclusive: clear the ENV box (leave blank) before submitting plaintext.",
|
|
258
|
+
nanError: "Advanced values must be numbers.",
|
|
259
|
+
saving: "Saving… (plaintext in this submit only, never echoed)",
|
|
260
|
+
savedReload: "Saved. Reloading masked state…",
|
|
261
|
+
saveFail: "Save failed: ",
|
|
262
|
+
advTitle: "Advanced…",
|
|
263
|
+
advTimeout: "Request timeout timeoutMs",
|
|
264
|
+
advTimeoutHint: "Default 8000 (ms, max 120000)",
|
|
265
|
+
advConcurrency: "Max concurrency maxConcurrency",
|
|
266
|
+
advConcurrencyHint: "Default 4",
|
|
267
|
+
advTrunc: "Truncation budget truncBudget",
|
|
268
|
+
advTruncHint: "Default 32000 (chars)",
|
|
269
|
+
advPerSession: "History per session perSession",
|
|
270
|
+
advPerSessionHint: "Default 200 (max 1000)",
|
|
271
|
+
advTotalSessions: "Total sessions totalSessions",
|
|
272
|
+
advTotalSessionsHint: "Default 50 (max 200)",
|
|
273
|
+
filterTitle: "Filter…",
|
|
274
|
+
wsLabel: "Workspace",
|
|
275
|
+
wsFilterAria: "Workspace filter",
|
|
276
|
+
sessLabel: "Session",
|
|
277
|
+
sessFilterAria: "Session filter",
|
|
278
|
+
allWs: "All workspaces",
|
|
279
|
+
allSess: "All sessions",
|
|
280
|
+
historyOrderNote: "Newest first (probability bars + tier + truncated badges + error lines).",
|
|
281
|
+
clearing: "Clearing… (single session only)",
|
|
282
|
+
clearOk: "Session cleared. Reloading…",
|
|
283
|
+
clearFail: "Clear failed: ",
|
|
284
|
+
confirmClear: "Clear this session?",
|
|
285
|
+
confirmClearAgain: "Click again to confirm clearing this session (single-session DELETE only).",
|
|
286
|
+
clearScopeNote: "Only the selected session is cleared (single-session DELETE), no bulk clear",
|
|
287
|
+
presetsMergedNote: "Catalog and switches merged. Cap tuning is automationCap in 0=none/1=low/2=high.",
|
|
288
|
+
presetsCount: "{n} presets",
|
|
289
|
+
presetsExported: "Exported {n} presets (JSON download).",
|
|
290
|
+
presetsSaved: "Saved {n} preset switches and caps.",
|
|
291
|
+
noImportNote: "No import button: templates export only. secret-leak stays off by default (frozen template).",
|
|
292
|
+
customBadge: "custom",
|
|
293
|
+
addCustom: "New custom preset",
|
|
294
|
+
deleteCustom: "Delete",
|
|
295
|
+
confirmDelete: "Confirm delete?",
|
|
296
|
+
confirmDeleteCustom: "Click again to delete this custom preset (only this one).",
|
|
297
|
+
customIdLabel: "Custom id (lowercase/digits/hyphen, must not collide with frozen)",
|
|
298
|
+
customLabelLabel: "Display name (English)",
|
|
299
|
+
customDescLabel: "Asking guide (free-form English, may follow template style)",
|
|
300
|
+
customDraftNote: "Saved drafts start disabled; enable manually. Questions travel per call, never stored.",
|
|
301
|
+
cancelCustom: "Cancel",
|
|
302
|
+
createCustom: "Create",
|
|
303
|
+
customSaved: "Custom preset saved.",
|
|
304
|
+
customFormInvalid: "Id, display name and guide must all be non-empty."
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/client/locale.ts
|
|
308
|
+
var current = resolveLang();
|
|
309
|
+
var bound = null;
|
|
310
|
+
function bindTranslate(next) {
|
|
311
|
+
if (typeof next !== "function") return;
|
|
312
|
+
bound = next;
|
|
313
|
+
}
|
|
314
|
+
function unbindTranslate() {
|
|
315
|
+
bound = null;
|
|
316
|
+
}
|
|
317
|
+
function resolveLang() {
|
|
318
|
+
try {
|
|
319
|
+
const nav = globalThis.navigator;
|
|
320
|
+
const lang = nav?.language;
|
|
321
|
+
if (typeof lang === "string" && lang.toLowerCase().startsWith("zh")) return "zh";
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
return "en";
|
|
325
|
+
}
|
|
326
|
+
function t(key, params) {
|
|
327
|
+
if (bound !== null) return bound(key, params === void 0 ? void 0 : { n: params.n });
|
|
328
|
+
const raw = current === "zh" ? zh[key] : en[key];
|
|
329
|
+
if (params?.n === void 0) return raw;
|
|
330
|
+
return raw.replaceAll("{n}", String(params.n));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/client/settings/card.tsx
|
|
334
|
+
var React5 = __toESM(require("react"), 1);
|
|
335
|
+
|
|
336
|
+
// src/shared/contract.ts
|
|
337
|
+
var JEV_BASE_URL = "https://api.typesafe.ai/v1/systemone";
|
|
338
|
+
if (JEV_BASE_URL !== "https://api.typesafe.ai/v1/systemone") {
|
|
339
|
+
throw new Error("dsh-decision-gateway: JEV_BASE_URL 被篡改,拒绝加载");
|
|
340
|
+
}
|
|
341
|
+
var JEV_MODEL = "jev-latest";
|
|
342
|
+
if (JEV_MODEL !== "jev-latest") {
|
|
343
|
+
throw new Error("dsh-decision-gateway: JEV_MODEL 被篡改,拒绝加载");
|
|
344
|
+
}
|
|
345
|
+
var ROUTES = {
|
|
346
|
+
health: "/api/dsh-decision-gateway/health",
|
|
347
|
+
config: "/api/dsh-decision-gateway/config",
|
|
348
|
+
presets: "/api/dsh-decision-gateway/presets",
|
|
349
|
+
history: "/api/dsh-decision-gateway/history",
|
|
350
|
+
testConnection: "/api/dsh-decision-gateway/test-connection"
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/client/api/routes.ts
|
|
354
|
+
function injected(key, fallback) {
|
|
355
|
+
if (typeof define_DSH_ROUTES_default !== "undefined") {
|
|
356
|
+
const value = define_DSH_ROUTES_default[key];
|
|
357
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
358
|
+
}
|
|
359
|
+
return fallback;
|
|
360
|
+
}
|
|
361
|
+
var APP_ROUTES = {
|
|
362
|
+
health: injected("health", ROUTES.health),
|
|
363
|
+
config: injected("config", ROUTES.config),
|
|
364
|
+
presets: injected("presets", ROUTES.presets),
|
|
365
|
+
history: injected("history", ROUTES.history),
|
|
366
|
+
testConnection: injected("testConnection", ROUTES.testConnection)
|
|
367
|
+
};
|
|
368
|
+
var CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
369
|
+
function fetchTimeout(url, init, timeoutMs = CLIENT_FETCH_TIMEOUT_MS) {
|
|
370
|
+
if (init?.signal !== void 0) return fetch(url, init);
|
|
371
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/client/api/contract.ts
|
|
375
|
+
function pickCategory(body) {
|
|
376
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
377
|
+
const rec = body;
|
|
378
|
+
const nested = rec["error"];
|
|
379
|
+
if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
|
|
380
|
+
const nrec = nested;
|
|
381
|
+
for (const key of ["category", "errorCode", "code"]) {
|
|
382
|
+
const v = nrec[key];
|
|
383
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (const key of ["category", "errorCode", "error", "code"]) {
|
|
387
|
+
const v = rec[key];
|
|
388
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
389
|
+
}
|
|
390
|
+
const msg = rec["message"];
|
|
391
|
+
if (typeof msg === "string" && msg.length > 0) return msg;
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
function failureCategory(status, body) {
|
|
395
|
+
const hit = pickCategory(body);
|
|
396
|
+
if (hit !== null) return hit;
|
|
397
|
+
if (status === 403) return "forbidden-non-loopback";
|
|
398
|
+
if (status === 405) return "method-not-allowed";
|
|
399
|
+
if (status === 400) return "bad-request";
|
|
400
|
+
return "http-" + status;
|
|
401
|
+
}
|
|
402
|
+
function isRecord(v) {
|
|
403
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
404
|
+
}
|
|
405
|
+
function asNumber(v, fallback) {
|
|
406
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
407
|
+
}
|
|
408
|
+
function normalizeCap(v) {
|
|
409
|
+
if (v === 1 || v === 2) return v;
|
|
410
|
+
if (v === 0) return 0;
|
|
411
|
+
if (typeof v === "string") {
|
|
412
|
+
if (v === "high" || v === "2") return 2;
|
|
413
|
+
if (v === "low" || v === "1") return 1;
|
|
414
|
+
}
|
|
415
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
416
|
+
if (v >= 2) return 2;
|
|
417
|
+
if (v >= 1) return 1;
|
|
418
|
+
}
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
function capLabel(cap) {
|
|
422
|
+
if (cap === 2) return "high";
|
|
423
|
+
if (cap === 1) return "low";
|
|
424
|
+
return t("capNone");
|
|
425
|
+
}
|
|
426
|
+
function parseConfigPayload(payload) {
|
|
427
|
+
const raw = isRecord(payload) && "config" in payload ? payload["config"] : isRecord(payload) && "data" in payload && isRecord(payload["data"]) ? payload["data"] : payload;
|
|
428
|
+
if (!isRecord(raw)) return null;
|
|
429
|
+
if (raw["version"] !== 1) return null;
|
|
430
|
+
const conn = isRecord(raw["connection"]) ? raw["connection"] : null;
|
|
431
|
+
const hist = isRecord(raw["history"]) ? raw["history"] : null;
|
|
432
|
+
const presetsRaw = Array.isArray(raw["presets"]) ? raw["presets"] : null;
|
|
433
|
+
if (conn === null || hist === null || presetsRaw === null) return null;
|
|
434
|
+
const presets = [];
|
|
435
|
+
for (const item of presetsRaw) {
|
|
436
|
+
if (!isRecord(item) || typeof item["id"] !== "string") continue;
|
|
437
|
+
presets.push({
|
|
438
|
+
id: item["id"],
|
|
439
|
+
enabled: item["enabled"] === true,
|
|
440
|
+
automationCap: normalizeCap(item["automationCap"])
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
const apiKeyRef = conn["apiKeyRef"];
|
|
444
|
+
return {
|
|
445
|
+
version: 1,
|
|
446
|
+
connection: {
|
|
447
|
+
apiKeyRef: typeof apiKeyRef === "string" ? apiKeyRef : void 0,
|
|
448
|
+
hasPlaintextKey: conn["hasPlaintextKey"] === true,
|
|
449
|
+
timeoutMs: asNumber(conn["timeoutMs"], 8e3),
|
|
450
|
+
maxConcurrency: asNumber(conn["maxConcurrency"], 4),
|
|
451
|
+
truncBudget: asNumber(conn["truncBudget"], 32e3)
|
|
452
|
+
},
|
|
453
|
+
presets,
|
|
454
|
+
history: {
|
|
455
|
+
perSession: asNumber(hist["perSession"], 200),
|
|
456
|
+
totalSessions: asNumber(hist["totalSessions"], 50)
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function parsePresetsPayload(payload) {
|
|
461
|
+
const list = isRecord(payload) && Array.isArray(payload["presets"]) ? payload["presets"] : Array.isArray(payload) ? payload : isRecord(payload) && Array.isArray(payload["data"]) ? payload["data"] : [];
|
|
462
|
+
const out = [];
|
|
463
|
+
for (const item of list) {
|
|
464
|
+
if (!isRecord(item) || typeof item["id"] !== "string") continue;
|
|
465
|
+
const tv = item["templateVersion"];
|
|
466
|
+
const label = item["label"];
|
|
467
|
+
const desc = item["description"];
|
|
468
|
+
const custom = item["custom"];
|
|
469
|
+
out.push({
|
|
470
|
+
id: item["id"],
|
|
471
|
+
templateVersion: typeof tv === "number" && Number.isFinite(tv) ? tv : void 0,
|
|
472
|
+
label: typeof label === "string" ? label : void 0,
|
|
473
|
+
description: typeof desc === "string" ? desc : void 0,
|
|
474
|
+
custom: custom === true ? true : void 0
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
function normalizeAutomation(v) {
|
|
480
|
+
if (v === "assisted" || v === "auto" || v === "manual") return v;
|
|
481
|
+
return "manual";
|
|
482
|
+
}
|
|
483
|
+
function parseQuestions(raw) {
|
|
484
|
+
if (!Array.isArray(raw)) return void 0;
|
|
485
|
+
const out = [];
|
|
486
|
+
for (const item of raw) {
|
|
487
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) return void 0;
|
|
488
|
+
const rec = item;
|
|
489
|
+
if (typeof rec["id"] !== "string" || typeof rec["text"] !== "string") return void 0;
|
|
490
|
+
const kind = rec["kind"];
|
|
491
|
+
if (kind !== "choice" && kind !== "score") return void 0;
|
|
492
|
+
const options = rec["options"];
|
|
493
|
+
const levels = rec["levels"];
|
|
494
|
+
out.push({
|
|
495
|
+
id: rec["id"],
|
|
496
|
+
text: rec["text"],
|
|
497
|
+
kind,
|
|
498
|
+
...Array.isArray(options) && options.every((o) => typeof o === "string") ? { options: options.slice() } : {},
|
|
499
|
+
...Array.isArray(levels) && levels.every((o) => typeof o === "string") ? { levels: levels.slice() } : {}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
var str = (v, fb = "") => typeof v === "string" ? v : fb;
|
|
505
|
+
var optStr = (v) => typeof v === "string" ? v : void 0;
|
|
506
|
+
var num = (v, fb) => asNumber(v, fb);
|
|
507
|
+
var enumOf = (v, xs, fb) => xs.includes(v) ? v : fb;
|
|
508
|
+
var optScore = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
509
|
+
var F = [
|
|
510
|
+
["rootHash", str],
|
|
511
|
+
["rootDisplay", str],
|
|
512
|
+
["presetId", str],
|
|
513
|
+
["templateVersion", (v) => num(v, 1)],
|
|
514
|
+
["stateHash", str],
|
|
515
|
+
["snippetRedacted", str],
|
|
516
|
+
["resultKind", str],
|
|
517
|
+
["tier", (v) => enumOf(v, ["high", "low"], "none")],
|
|
518
|
+
["lang", (v) => enumOf(v, ["en", "zh"], "unknown")],
|
|
519
|
+
["automation", (v) => normalizeAutomation(v)],
|
|
520
|
+
["truncated", (v) => v === true],
|
|
521
|
+
["originalLength", (v) => num(v, 0)],
|
|
522
|
+
["confidence", (v) => num(v, 0)],
|
|
523
|
+
["latencyMs", (v) => num(v, 0)],
|
|
524
|
+
["choice", optStr],
|
|
525
|
+
["score", optScore],
|
|
526
|
+
["errorCode", optStr]
|
|
527
|
+
];
|
|
528
|
+
function toHistoryEntry(it) {
|
|
529
|
+
if (!isRecord(it)) return null;
|
|
530
|
+
if (typeof it.ts !== "number" || typeof it.sessionId !== "string") return null;
|
|
531
|
+
const o = { ts: it.ts, sessionId: it.sessionId, provider: "official" };
|
|
532
|
+
for (const [k, f] of F) o[k] = f(it[k]);
|
|
533
|
+
o.presetTitle = optStr(it.presetTitle);
|
|
534
|
+
if (typeof it.sessionTitle === "string" && it.sessionTitle.length > 0)
|
|
535
|
+
o.sessionTitle = it.sessionTitle;
|
|
536
|
+
const q = parseQuestions(it.questions);
|
|
537
|
+
if (q !== void 0) o.questions = q;
|
|
538
|
+
return o;
|
|
539
|
+
}
|
|
540
|
+
function parseHistoryPayload(payload) {
|
|
541
|
+
let list = [];
|
|
542
|
+
if (Array.isArray(payload)) list = payload;
|
|
543
|
+
else if (isRecord(payload)) {
|
|
544
|
+
for (const key of ["entries", "items", "history", "data"]) {
|
|
545
|
+
const v = payload[key];
|
|
546
|
+
if (Array.isArray(v)) {
|
|
547
|
+
list = v;
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const out = [];
|
|
553
|
+
for (const item of list) {
|
|
554
|
+
const entry = toHistoryEntry(item);
|
|
555
|
+
if (entry !== null) out.push(entry);
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
558
|
+
}
|
|
559
|
+
function validApiKeyRef(name) {
|
|
560
|
+
return /^[A-Z][A-Z0-9_]{1,63}$/.test(name);
|
|
561
|
+
}
|
|
562
|
+
function clamp01(v) {
|
|
563
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return 0;
|
|
564
|
+
if (v < 0) return 0;
|
|
565
|
+
if (v > 1) return 1;
|
|
566
|
+
return v;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/client/settings/connection.tsx
|
|
570
|
+
var React = __toESM(require("react"), 1);
|
|
571
|
+
function ConnectionPane() {
|
|
572
|
+
const [snapshot, setSnapshot] = React.useState(null);
|
|
573
|
+
const [keyMode, setKeyMode] = React.useState("env");
|
|
574
|
+
const [env, setEnv] = React.useState("");
|
|
575
|
+
const [plain, setPlain] = React.useState("");
|
|
576
|
+
const [timeoutMs, setTimeoutMs] = React.useState("8000");
|
|
577
|
+
const [maxConcurrency, setMaxConcurrency] = React.useState("4");
|
|
578
|
+
const [truncBudget, setTruncBudget] = React.useState("32000");
|
|
579
|
+
const [perSession, setPerSession] = React.useState("200");
|
|
580
|
+
const [totalSessions, setTotalSessions] = React.useState("50");
|
|
581
|
+
const [msg, setMsg] = React.useState(null);
|
|
582
|
+
const [testOut, setTestOut] = React.useState("");
|
|
583
|
+
const [testing, setTesting] = React.useState(false);
|
|
584
|
+
const [saving, setSaving] = React.useState(false);
|
|
585
|
+
const [failed, setFailed] = React.useState(false);
|
|
586
|
+
const [plainOpen, setPlainOpen] = React.useState(false);
|
|
587
|
+
const [advOpen, setAdvOpen] = React.useState(false);
|
|
588
|
+
const alive = React.useRef(true);
|
|
589
|
+
React.useEffect(() => {
|
|
590
|
+
alive.current = true;
|
|
591
|
+
return () => {
|
|
592
|
+
alive.current = false;
|
|
593
|
+
};
|
|
594
|
+
}, []);
|
|
595
|
+
const load = React.useCallback(() => {
|
|
596
|
+
setFailed(false);
|
|
597
|
+
setMsg({ kind: "info", text: t("loading") });
|
|
598
|
+
void fetchTimeout(APP_ROUTES.config, { headers: { accept: "application/json" } }).then(async (res) => {
|
|
599
|
+
if (!alive.current) return;
|
|
600
|
+
if (!res.ok) {
|
|
601
|
+
let cat = "http-" + res.status;
|
|
602
|
+
try {
|
|
603
|
+
cat = failureCategory(res.status, await res.json());
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
throw new Error(cat);
|
|
607
|
+
}
|
|
608
|
+
const cfg = parseConfigPayload(await res.json());
|
|
609
|
+
if (cfg === null) throw new Error("config-shape-unknown");
|
|
610
|
+
setSnapshot(cfg);
|
|
611
|
+
setEnv(cfg.connection.apiKeyRef ?? "");
|
|
612
|
+
setPlain("");
|
|
613
|
+
setKeyMode(
|
|
614
|
+
cfg.connection.apiKeyRef ? "env" : cfg.connection.hasPlaintextKey ? "plain" : "env"
|
|
615
|
+
);
|
|
616
|
+
setTimeoutMs(String(cfg.connection.timeoutMs));
|
|
617
|
+
setMaxConcurrency(String(cfg.connection.maxConcurrency));
|
|
618
|
+
setTruncBudget(String(cfg.connection.truncBudget));
|
|
619
|
+
setPerSession(String(cfg.history.perSession));
|
|
620
|
+
setTotalSessions(String(cfg.history.totalSessions));
|
|
621
|
+
setMsg({ kind: "info", text: t("loadedNote") });
|
|
622
|
+
}).catch((e) => {
|
|
623
|
+
if (!alive.current) return;
|
|
624
|
+
setSnapshot(null);
|
|
625
|
+
setFailed(true);
|
|
626
|
+
setMsg({
|
|
627
|
+
kind: "error",
|
|
628
|
+
text: t("loadFail") + (e instanceof Error ? e.message : String(e))
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
}, []);
|
|
632
|
+
React.useEffect(load, [load]);
|
|
633
|
+
const useEnv = keyMode === "env";
|
|
634
|
+
const conn = snapshot?.connection;
|
|
635
|
+
const mask = conn === void 0 ? t("notLoaded") : conn.hasPlaintextKey ? t("maskOn") : t("maskOff");
|
|
636
|
+
const save = () => {
|
|
637
|
+
if (snapshot === null || saving) return;
|
|
638
|
+
const envName = useEnv ? env.trim() : "";
|
|
639
|
+
const plainValue = !useEnv ? plain : "";
|
|
640
|
+
if (plainValue !== "" && envName !== "") {
|
|
641
|
+
setMsg({ kind: "error", text: t("mutualError") });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (envName !== "" && !validApiKeyRef(envName)) {
|
|
645
|
+
setMsg({ kind: "error", text: t("envBad") });
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const nums = [timeoutMs, maxConcurrency, truncBudget, perSession, totalSessions].map(Number);
|
|
649
|
+
if (!nums.every((n) => Number.isFinite(n))) {
|
|
650
|
+
setMsg({ kind: "error", text: t("nanError") });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const body = {
|
|
654
|
+
version: 1,
|
|
655
|
+
connection: {
|
|
656
|
+
timeoutMs: nums[0],
|
|
657
|
+
maxConcurrency: nums[1],
|
|
658
|
+
truncBudget: nums[2],
|
|
659
|
+
...plainValue !== "" ? {} : envName !== "" ? { apiKeyRef: envName } : {}
|
|
660
|
+
},
|
|
661
|
+
presets: snapshot.presets.map((p) => ({
|
|
662
|
+
id: p.id,
|
|
663
|
+
enabled: p.enabled,
|
|
664
|
+
automationCap: p.automationCap
|
|
665
|
+
})),
|
|
666
|
+
history: { perSession: nums[3], totalSessions: nums[4] },
|
|
667
|
+
...plainValue !== "" ? { apiKeyPlaintext: plainValue } : {},
|
|
668
|
+
// 切 ENV→明文:附 apiKeyRef:null 清掉已存引用,否则服务端互斥 400。
|
|
669
|
+
...plainValue !== "" && snapshot.connection.apiKeyRef ? { apiKeyRef: null } : {}
|
|
670
|
+
};
|
|
671
|
+
setSaving(true);
|
|
672
|
+
setMsg({ kind: "info", text: t("saving") });
|
|
673
|
+
void fetchTimeout(APP_ROUTES.config, {
|
|
674
|
+
method: "PUT",
|
|
675
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
676
|
+
body: JSON.stringify(body)
|
|
677
|
+
}).then(async (res) => {
|
|
678
|
+
if (!alive.current) return;
|
|
679
|
+
if (!res.ok) {
|
|
680
|
+
let cat = "http-" + res.status;
|
|
681
|
+
try {
|
|
682
|
+
const b = await res.json();
|
|
683
|
+
if (b !== null && typeof b === "object") {
|
|
684
|
+
const rec = b;
|
|
685
|
+
const nested = rec["error"];
|
|
686
|
+
if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
|
|
687
|
+
const v = nested["category"] ?? nested["errorCode"];
|
|
688
|
+
if (typeof v === "string" && v.length > 0) cat = v;
|
|
689
|
+
} else {
|
|
690
|
+
const v = rec["category"] ?? rec["errorCode"] ?? rec["error"] ?? rec["code"];
|
|
691
|
+
if (typeof v === "string" && v.length > 0) cat = v;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
throw new Error(cat);
|
|
697
|
+
}
|
|
698
|
+
setPlain("");
|
|
699
|
+
setMsg({ kind: "ok", text: t("savedReload") });
|
|
700
|
+
load();
|
|
701
|
+
}).catch((e) => {
|
|
702
|
+
if (!alive.current) return;
|
|
703
|
+
setMsg({
|
|
704
|
+
kind: "error",
|
|
705
|
+
text: t("saveFail") + (e instanceof Error ? e.message : String(e))
|
|
706
|
+
});
|
|
707
|
+
}).finally(() => {
|
|
708
|
+
if (alive.current) setSaving(false);
|
|
709
|
+
});
|
|
710
|
+
};
|
|
711
|
+
const selfCheck = () => {
|
|
712
|
+
if (testing) return;
|
|
713
|
+
setTesting(true);
|
|
714
|
+
setTestOut(t("testing"));
|
|
715
|
+
void fetchTimeout(APP_ROUTES.testConnection, {
|
|
716
|
+
method: "POST",
|
|
717
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
718
|
+
body: "{}"
|
|
719
|
+
}).then(async (res) => {
|
|
720
|
+
if (!alive.current) return;
|
|
721
|
+
let category = "";
|
|
722
|
+
try {
|
|
723
|
+
const body = await res.json();
|
|
724
|
+
const hit = failureCategory(res.status, body);
|
|
725
|
+
if (hit !== "http-" + res.status) category = hit;
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
setTestOut(
|
|
729
|
+
res.ok ? category !== "" ? t("testOk") + "(" + category + ")" : t("testOk") : t("testFail") + (category !== "" ? category : "http-" + res.status)
|
|
730
|
+
);
|
|
731
|
+
}).catch((e) => {
|
|
732
|
+
if (!alive.current) return;
|
|
733
|
+
setTestOut(t("testFail") + (e instanceof Error ? e.message : String(e)));
|
|
734
|
+
}).finally(() => {
|
|
735
|
+
if (alive.current) setTesting(false);
|
|
736
|
+
});
|
|
737
|
+
};
|
|
738
|
+
return /* @__PURE__ */ React.createElement("div", { className: "dj-pane", "data-tab": "connection" }, /* @__PURE__ */ React.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React.createElement("span", { className: conn?.hasPlaintextKey ? "dj-badge dj-badgeOn" : "dj-badge" }, mask), conn?.hasPlaintextKey === true && /* @__PURE__ */ React.createElement("span", { className: "dj-note", "aria-label": t("maskDotsAria") }, "••••••••"), conn?.apiKeyRef !== void 0 && conn.apiKeyRef !== "" && /* @__PURE__ */ React.createElement("span", { className: "dj-badge" }, t("maskEnv"), conn.apiKeyRef)), msg !== null && /* @__PURE__ */ React.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React.createElement("span", { className: msg.kind === "error" ? "dj-errLine" : "dj-note" }, msg.text)), /* @__PURE__ */ React.createElement("div", { className: "dj-field", role: "radiogroup", "aria-label": t("keyMode") }, /* @__PURE__ */ React.createElement("label", null, t("keyMode")), /* @__PURE__ */ React.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement(
|
|
739
|
+
"input",
|
|
740
|
+
{
|
|
741
|
+
type: "radio",
|
|
742
|
+
name: "dj-keymode",
|
|
743
|
+
value: "env",
|
|
744
|
+
checked: useEnv,
|
|
745
|
+
onChange: () => setKeyMode("env")
|
|
746
|
+
}
|
|
747
|
+
), t("keyEnv")), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement(
|
|
748
|
+
"input",
|
|
749
|
+
{
|
|
750
|
+
type: "radio",
|
|
751
|
+
name: "dj-keymode",
|
|
752
|
+
value: "plain",
|
|
753
|
+
checked: !useEnv,
|
|
754
|
+
onChange: () => setKeyMode("plain")
|
|
755
|
+
}
|
|
756
|
+
), t("keyPlain"))), /* @__PURE__ */ React.createElement("span", { className: "dj-note" }, useEnv ? t("modeEnvNote") : t("modePlainNote"))), useEnv ? /* @__PURE__ */ React.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React.createElement("label", null, t("envLabel")), /* @__PURE__ */ React.createElement(
|
|
757
|
+
"input",
|
|
758
|
+
{
|
|
759
|
+
className: "dj-input",
|
|
760
|
+
"aria-label": t("envAria"),
|
|
761
|
+
inputMode: "text",
|
|
762
|
+
autoComplete: "off",
|
|
763
|
+
value: env,
|
|
764
|
+
placeholder: t("envPlaceholder"),
|
|
765
|
+
onChange: (e) => setEnv(e.target.value)
|
|
766
|
+
}
|
|
767
|
+
), /* @__PURE__ */ React.createElement("span", { className: "dj-note" }, t("envRule"))) : /* @__PURE__ */ React.createElement("div", { className: "dj-fold" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "dj-foldHead", onClick: () => setPlainOpen((v) => !v) }, /* @__PURE__ */ React.createElement("span", null, t("plainFoldTitle")), /* @__PURE__ */ React.createElement("span", null, plainOpen ? "▾" : "▸")), /* @__PURE__ */ React.createElement("div", { className: "dj-foldBody", hidden: !plainOpen }, /* @__PURE__ */ React.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React.createElement(
|
|
768
|
+
"input",
|
|
769
|
+
{
|
|
770
|
+
className: "dj-input",
|
|
771
|
+
type: "password",
|
|
772
|
+
"aria-label": t("plainAria"),
|
|
773
|
+
autoComplete: "off",
|
|
774
|
+
value: plain,
|
|
775
|
+
placeholder: t("plainPlaceholder"),
|
|
776
|
+
onChange: (e) => setPlain(e.target.value)
|
|
777
|
+
}
|
|
778
|
+
), /* @__PURE__ */ React.createElement("span", { className: "dj-note" }, t("plainNote"))))), /* @__PURE__ */ React.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "dj-btn dj-btnSmall", disabled: testing, onClick: selfCheck }, t("testOffline")), /* @__PURE__ */ React.createElement("span", { className: "dj-note" }, testOut)), /* @__PURE__ */ React.createElement("div", { className: "dj-fold" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "dj-foldHead", onClick: () => setAdvOpen((v) => !v) }, /* @__PURE__ */ React.createElement("span", null, t("advTitle")), /* @__PURE__ */ React.createElement("span", null, advOpen ? "▾" : "▸")), /* @__PURE__ */ React.createElement("div", { className: "dj-foldBody", hidden: !advOpen }, [
|
|
779
|
+
[t("advTimeout"), timeoutMs, setTimeoutMs, t("advTimeoutHint")],
|
|
780
|
+
[t("advConcurrency"), maxConcurrency, setMaxConcurrency, t("advConcurrencyHint")],
|
|
781
|
+
[t("advTrunc"), truncBudget, setTruncBudget, t("advTruncHint")],
|
|
782
|
+
[t("advPerSession"), perSession, setPerSession, t("advPerSessionHint")],
|
|
783
|
+
[t("advTotalSessions"), totalSessions, setTotalSessions, t("advTotalSessionsHint")]
|
|
784
|
+
].map(([label, value, set, hint]) => /* @__PURE__ */ React.createElement("div", { className: "dj-field", key: label }, /* @__PURE__ */ React.createElement("label", null, label), /* @__PURE__ */ React.createElement(
|
|
785
|
+
"input",
|
|
786
|
+
{
|
|
787
|
+
className: "dj-input",
|
|
788
|
+
inputMode: "numeric",
|
|
789
|
+
autoComplete: "off",
|
|
790
|
+
value,
|
|
791
|
+
onChange: (e) => set(e.target.value)
|
|
792
|
+
}
|
|
793
|
+
), /* @__PURE__ */ React.createElement("span", { className: "dj-note" }, hint))))), /* @__PURE__ */ React.createElement("div", { className: "dj-foot" }, failed && /* @__PURE__ */ React.createElement("button", { type: "button", className: "dj-btn dj-btnSmall", onClick: load }, t("retry")), /* @__PURE__ */ React.createElement(
|
|
794
|
+
"button",
|
|
795
|
+
{
|
|
796
|
+
type: "button",
|
|
797
|
+
className: "dj-btn dj-btnPrimary",
|
|
798
|
+
disabled: saving || snapshot === null,
|
|
799
|
+
onClick: save
|
|
800
|
+
},
|
|
801
|
+
t("save")
|
|
802
|
+
)));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/client/settings/history.tsx
|
|
806
|
+
var React3 = __toESM(require("react"), 1);
|
|
807
|
+
|
|
808
|
+
// src/client/settings/format.ts
|
|
809
|
+
function fmtTime(ts) {
|
|
810
|
+
if (typeof ts !== "number" || !Number.isFinite(ts)) return "";
|
|
811
|
+
try {
|
|
812
|
+
const d = new Date(ts);
|
|
813
|
+
const pad = (n) => n < 10 ? "0" + n : String(n);
|
|
814
|
+
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
|
|
815
|
+
} catch {
|
|
816
|
+
return "";
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function shortId(id, len = 8) {
|
|
820
|
+
if (id.length <= len) return id;
|
|
821
|
+
return id.slice(0, len);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/client/settings/prob.tsx
|
|
825
|
+
var React2 = __toESM(require("react"), 1);
|
|
826
|
+
var HIGH_LINE = 0.8;
|
|
827
|
+
function tierBadge(tier) {
|
|
828
|
+
if (tier === "high") return /* @__PURE__ */ React2.createElement("span", { className: "dj-badge dj-badgeOn" }, "high");
|
|
829
|
+
if (tier === "low") return /* @__PURE__ */ React2.createElement("span", { className: "dj-badge dj-badgeWarn" }, "low");
|
|
830
|
+
return /* @__PURE__ */ React2.createElement("span", { className: "dj-badge" }, "none");
|
|
831
|
+
}
|
|
832
|
+
function probPercent(entry) {
|
|
833
|
+
const hasConf = entry.confidence !== 0;
|
|
834
|
+
const p = hasConf ? clamp01(entry.confidence) : entry.score !== void 0 ? clamp01(entry.score / 5) : 0;
|
|
835
|
+
return Math.round(p * 100);
|
|
836
|
+
}
|
|
837
|
+
function ProbBar({ entry }) {
|
|
838
|
+
const hasConf = entry.confidence !== 0;
|
|
839
|
+
const pct = probPercent(entry);
|
|
840
|
+
const title = hasConf ? "confidence " + entry.confidence + (entry.score !== void 0 ? " / score " + entry.score + "/5" : "") : entry.score !== void 0 ? "score " + entry.score + "/5" : t("noProb");
|
|
841
|
+
const fillCls = "dj-barFill" + (entry.tier === "high" ? " dj-barFillHigh" : entry.tier === "low" ? " dj-barFillLow" : "");
|
|
842
|
+
return /* @__PURE__ */ React2.createElement("div", { className: "dj-probRow" }, /* @__PURE__ */ React2.createElement("div", { className: "dj-bar" }, /* @__PURE__ */ React2.createElement("div", { className: fillCls, style: { width: pct + "%" } }), /* @__PURE__ */ React2.createElement(
|
|
843
|
+
"div",
|
|
844
|
+
{
|
|
845
|
+
className: "dj-barThreshold",
|
|
846
|
+
style: { left: Math.round(HIGH_LINE * 100) + "%" },
|
|
847
|
+
title: t("highLine")
|
|
848
|
+
}
|
|
849
|
+
)), /* @__PURE__ */ React2.createElement("span", { className: "dj-probNum", title }, pct + "%"));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/client/settings/history.tsx
|
|
853
|
+
var PAGE_LIMIT = 200;
|
|
854
|
+
function EntryItem({ entry }) {
|
|
855
|
+
const meta = [];
|
|
856
|
+
if (entry.resultKind !== "") meta.push(entry.resultKind);
|
|
857
|
+
if (entry.choice !== void 0 && entry.choice !== "") meta.push("choice=" + entry.choice);
|
|
858
|
+
meta.push("orig=" + entry.originalLength);
|
|
859
|
+
meta.push(entry.automation);
|
|
860
|
+
meta.push(entry.provider);
|
|
861
|
+
meta.push(entry.latencyMs + "ms");
|
|
862
|
+
return /* @__PURE__ */ React3.createElement("li", { className: "dj-histItem" }, /* @__PURE__ */ React3.createElement("div", { className: "dj-histHead" }, /* @__PURE__ */ React3.createElement("span", { className: "dj-histTime" }, fmtTime(entry.ts)), entry.rootDisplay !== "" && /* @__PURE__ */ React3.createElement("span", { className: "dj-badge" }, entry.rootDisplay), /* @__PURE__ */ React3.createElement("span", { className: "dj-badge", title: entry.sessionId }, entry.sessionTitle !== void 0 && entry.sessionTitle !== "" ? entry.sessionTitle : shortId(entry.sessionId)), entry.presetId !== "" && /* @__PURE__ */ React3.createElement("span", { className: "dj-badge", title: entry.presetId }, (entry.presetTitle ?? entry.presetId) + (entry.templateVersion > 0 ? " v" + entry.templateVersion : "")), /* @__PURE__ */ React3.createElement("span", { className: "dj-badge" }, entry.lang), entry.truncated && /* @__PURE__ */ React3.createElement("span", { className: "dj-badge dj-badgeWarn" }, t("truncBadge")), tierBadge(entry.tier)), entry.snippetRedacted !== "" && /* @__PURE__ */ React3.createElement("div", { className: "dj-histSnippet" }, entry.snippetRedacted), /* @__PURE__ */ React3.createElement(ProbBar, { entry }), /* @__PURE__ */ React3.createElement("div", { className: "dj-meta" }, meta.join(" · ")), entry.questions !== void 0 && entry.questions.length > 0 && /* @__PURE__ */ React3.createElement("ul", { className: "dj-opts" }, entry.questions.map((q) => /* @__PURE__ */ React3.createElement("li", { className: "dj-opt", key: q.id }, /* @__PURE__ */ React3.createElement("div", { className: "dj-optHead" }, /* @__PURE__ */ React3.createElement("span", null, q.text)), q.options !== void 0 && /* @__PURE__ */ React3.createElement("div", { className: "dj-meta" }, q.options.join(" / "))))), entry.errorCode !== void 0 && entry.errorCode !== "" && /* @__PURE__ */ React3.createElement("div", { className: "dj-errLine" }, t("errPrefix"), entry.errorCode));
|
|
863
|
+
}
|
|
864
|
+
function HistoryPane() {
|
|
865
|
+
const [entries, setEntries] = React3.useState([]);
|
|
866
|
+
const [root, setRoot] = React3.useState("");
|
|
867
|
+
const [sessionId, setSessionId] = React3.useState("");
|
|
868
|
+
const [msg, setMsg] = React3.useState(null);
|
|
869
|
+
const [failed, setFailed] = React3.useState(false);
|
|
870
|
+
const [armed, setArmed] = React3.useState(false);
|
|
871
|
+
const [clearing, setClearing] = React3.useState(false);
|
|
872
|
+
const [filterOpen, setFilterOpen] = React3.useState(false);
|
|
873
|
+
const alive = React3.useRef(true);
|
|
874
|
+
React3.useEffect(() => {
|
|
875
|
+
alive.current = true;
|
|
876
|
+
return () => {
|
|
877
|
+
alive.current = false;
|
|
878
|
+
};
|
|
879
|
+
}, []);
|
|
880
|
+
const fetchList = React3.useCallback((wantRoot, wantSession) => {
|
|
881
|
+
setArmed(false);
|
|
882
|
+
setFailed(false);
|
|
883
|
+
setMsg({ kind: "info", text: t("loading") });
|
|
884
|
+
const q = new URLSearchParams();
|
|
885
|
+
if (wantRoot !== "") q.set("root", wantRoot);
|
|
886
|
+
if (wantSession !== "") q.set("sessionId", wantSession);
|
|
887
|
+
q.set("limit", String(PAGE_LIMIT));
|
|
888
|
+
void fetchTimeout(APP_ROUTES.history + "?" + q.toString(), {
|
|
889
|
+
headers: { accept: "application/json" }
|
|
890
|
+
}).then(async (res) => {
|
|
891
|
+
if (!alive.current) return;
|
|
892
|
+
if (!res.ok) {
|
|
893
|
+
let cat = "http-" + res.status;
|
|
894
|
+
try {
|
|
895
|
+
cat = failureCategory(res.status, await res.json());
|
|
896
|
+
} catch {
|
|
897
|
+
}
|
|
898
|
+
throw new Error(cat);
|
|
899
|
+
}
|
|
900
|
+
const list = parseHistoryPayload(await res.json());
|
|
901
|
+
list.sort((a, b) => b.ts - a.ts);
|
|
902
|
+
setEntries(list);
|
|
903
|
+
setMsg({ kind: "info", text: t("historyOrderNote") });
|
|
904
|
+
}).catch((e) => {
|
|
905
|
+
if (!alive.current) return;
|
|
906
|
+
setEntries([]);
|
|
907
|
+
setFailed(true);
|
|
908
|
+
setMsg({
|
|
909
|
+
kind: "error",
|
|
910
|
+
text: t("loadFail") + (e instanceof Error ? e.message : String(e))
|
|
911
|
+
});
|
|
912
|
+
});
|
|
913
|
+
}, []);
|
|
914
|
+
React3.useEffect(() => {
|
|
915
|
+
fetchList(root, sessionId);
|
|
916
|
+
}, [root, sessionId, fetchList]);
|
|
917
|
+
const roots = React3.useMemo(() => {
|
|
918
|
+
const m = /* @__PURE__ */ new Map();
|
|
919
|
+
for (const e of entries) {
|
|
920
|
+
if (e.rootDisplay !== "" && !m.has(e.rootDisplay)) m.set(e.rootDisplay, e.rootHash);
|
|
921
|
+
}
|
|
922
|
+
return [...m.keys()].sort();
|
|
923
|
+
}, [entries]);
|
|
924
|
+
const sessions = React3.useMemo(() => {
|
|
925
|
+
const s = /* @__PURE__ */ new Set();
|
|
926
|
+
for (const e of entries) if (e.sessionId !== "") s.add(e.sessionId);
|
|
927
|
+
return [...s].sort();
|
|
928
|
+
}, [entries]);
|
|
929
|
+
const clear = () => {
|
|
930
|
+
if (sessionId === "" || clearing) return;
|
|
931
|
+
if (!armed) {
|
|
932
|
+
setArmed(true);
|
|
933
|
+
setMsg({ kind: "info", text: t("confirmClearAgain") });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
setClearing(true);
|
|
937
|
+
setMsg({ kind: "info", text: t("clearing") });
|
|
938
|
+
const q = new URLSearchParams();
|
|
939
|
+
if (root !== "") q.set("root", root);
|
|
940
|
+
q.set("sessionId", sessionId);
|
|
941
|
+
void fetchTimeout(APP_ROUTES.history + "?" + q.toString(), {
|
|
942
|
+
method: "DELETE",
|
|
943
|
+
headers: { accept: "application/json" }
|
|
944
|
+
}).then(async (res) => {
|
|
945
|
+
if (!alive.current) return;
|
|
946
|
+
if (!res.ok) {
|
|
947
|
+
let cat = "http-" + res.status;
|
|
948
|
+
try {
|
|
949
|
+
cat = failureCategory(res.status, await res.json());
|
|
950
|
+
} catch {
|
|
951
|
+
}
|
|
952
|
+
throw new Error(cat);
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
const b = await res.json();
|
|
956
|
+
if (b !== null && typeof b === "object" && b["deleted"] === false) {
|
|
957
|
+
throw new Error("delete-not-confirmed");
|
|
958
|
+
}
|
|
959
|
+
} catch (e) {
|
|
960
|
+
if (e instanceof Error && e.message === "delete-not-confirmed") throw e;
|
|
961
|
+
}
|
|
962
|
+
setArmed(false);
|
|
963
|
+
setMsg({ kind: "info", text: t("clearOk") });
|
|
964
|
+
fetchList(root, sessionId);
|
|
965
|
+
}).catch((e) => {
|
|
966
|
+
if (!alive.current) return;
|
|
967
|
+
setArmed(false);
|
|
968
|
+
setMsg({
|
|
969
|
+
kind: "error",
|
|
970
|
+
text: t("clearFail") + (e instanceof Error ? e.message : String(e))
|
|
971
|
+
});
|
|
972
|
+
}).finally(() => {
|
|
973
|
+
if (alive.current) setClearing(false);
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
return /* @__PURE__ */ React3.createElement("div", { className: "dj-pane", "data-tab": "history" }, /* @__PURE__ */ React3.createElement("div", { className: "dj-fold" }, /* @__PURE__ */ React3.createElement("button", { type: "button", className: "dj-foldHead", onClick: () => setFilterOpen((v) => !v) }, /* @__PURE__ */ React3.createElement("span", null, t("filterTitle")), /* @__PURE__ */ React3.createElement("span", null, filterOpen ? "▾" : "▸")), /* @__PURE__ */ React3.createElement("div", { className: "dj-foldBody", hidden: !filterOpen }, /* @__PURE__ */ React3.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React3.createElement("label", null, t("wsLabel")), /* @__PURE__ */ React3.createElement(
|
|
977
|
+
"select",
|
|
978
|
+
{
|
|
979
|
+
className: "dj-select",
|
|
980
|
+
autoComplete: "off",
|
|
981
|
+
"aria-label": t("wsFilterAria"),
|
|
982
|
+
value: root,
|
|
983
|
+
onChange: (e) => setRoot(e.target.value)
|
|
984
|
+
},
|
|
985
|
+
/* @__PURE__ */ React3.createElement("option", { value: "" }, t("allWs")),
|
|
986
|
+
roots.map((name) => /* @__PURE__ */ React3.createElement("option", { key: name, value: name }, name))
|
|
987
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React3.createElement("label", null, t("sessLabel")), /* @__PURE__ */ React3.createElement(
|
|
988
|
+
"select",
|
|
989
|
+
{
|
|
990
|
+
className: "dj-select",
|
|
991
|
+
autoComplete: "off",
|
|
992
|
+
"aria-label": t("sessFilterAria"),
|
|
993
|
+
value: sessionId,
|
|
994
|
+
onChange: (e) => setSessionId(e.target.value)
|
|
995
|
+
},
|
|
996
|
+
/* @__PURE__ */ React3.createElement("option", { value: "" }, t("allSess")),
|
|
997
|
+
sessions.map((id) => /* @__PURE__ */ React3.createElement("option", { key: id, value: id, title: id }, shortId(id, 12) + (id.length > 12 ? "…" : "")))
|
|
998
|
+
)))), /* @__PURE__ */ React3.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React3.createElement("span", { className: "dj-badge" }, t("countEntries", { n: entries.length })), /* @__PURE__ */ React3.createElement(
|
|
999
|
+
"button",
|
|
1000
|
+
{
|
|
1001
|
+
type: "button",
|
|
1002
|
+
className: "dj-btn dj-btnSmall",
|
|
1003
|
+
onClick: () => fetchList(root, sessionId)
|
|
1004
|
+
},
|
|
1005
|
+
t("refresh")
|
|
1006
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1007
|
+
"button",
|
|
1008
|
+
{
|
|
1009
|
+
type: "button",
|
|
1010
|
+
className: "dj-btn dj-btnSmall dj-btnDanger",
|
|
1011
|
+
title: t("clearScopeNote"),
|
|
1012
|
+
disabled: sessionId === "" || clearing,
|
|
1013
|
+
onClick: clear
|
|
1014
|
+
},
|
|
1015
|
+
armed ? t("confirmClear") : t("clearSession")
|
|
1016
|
+
)), msg !== null && /* @__PURE__ */ React3.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React3.createElement("span", { className: msg.kind === "error" ? "dj-errLine" : "dj-note" }, msg.text)), failed && /* @__PURE__ */ React3.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React3.createElement(
|
|
1017
|
+
"button",
|
|
1018
|
+
{
|
|
1019
|
+
type: "button",
|
|
1020
|
+
className: "dj-btn dj-btnSmall",
|
|
1021
|
+
onClick: () => fetchList(root, sessionId)
|
|
1022
|
+
},
|
|
1023
|
+
t("retry")
|
|
1024
|
+
)), /* @__PURE__ */ React3.createElement("ul", { className: "dj-hist" }, entries.length === 0 && !failed ? /* @__PURE__ */ React3.createElement("div", { className: "dj-note" }, t("emptyHistory")) : entries.map((e, i) => /* @__PURE__ */ React3.createElement(EntryItem, { key: e.rootHash + "/" + e.sessionId + "/" + e.ts + "/" + i, entry: e }))));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/client/settings/presets.tsx
|
|
1028
|
+
var React4 = __toESM(require("react"), 1);
|
|
1029
|
+
function PresetsPane() {
|
|
1030
|
+
const [snapshot, setSnapshot] = React4.useState(null);
|
|
1031
|
+
const [rows, setRows] = React4.useState([]);
|
|
1032
|
+
const [openDetail, setOpenDetail] = React4.useState(null);
|
|
1033
|
+
const [msg, setMsg] = React4.useState(null);
|
|
1034
|
+
const [failed, setFailed] = React4.useState(false);
|
|
1035
|
+
const [saving, setSaving] = React4.useState(false);
|
|
1036
|
+
const [formOpen, setFormOpen] = React4.useState(false);
|
|
1037
|
+
const [draftId, setDraftId] = React4.useState("");
|
|
1038
|
+
const [draftLabel, setDraftLabel] = React4.useState("");
|
|
1039
|
+
const [draftDesc, setDraftDesc] = React4.useState("");
|
|
1040
|
+
const [draftCap, setDraftCap] = React4.useState(1);
|
|
1041
|
+
const [armedDelete, setArmedDelete] = React4.useState(null);
|
|
1042
|
+
const alive = React4.useRef(true);
|
|
1043
|
+
React4.useEffect(() => {
|
|
1044
|
+
alive.current = true;
|
|
1045
|
+
return () => {
|
|
1046
|
+
alive.current = false;
|
|
1047
|
+
};
|
|
1048
|
+
}, []);
|
|
1049
|
+
const load = React4.useCallback(() => {
|
|
1050
|
+
setFailed(false);
|
|
1051
|
+
setMsg({ kind: "info", text: t("loading") });
|
|
1052
|
+
void Promise.all([
|
|
1053
|
+
fetchTimeout(APP_ROUTES.presets, { headers: { accept: "application/json" } }).then(
|
|
1054
|
+
async (res) => {
|
|
1055
|
+
if (!res.ok) throw new Error("presets-http-" + res.status);
|
|
1056
|
+
return parsePresetsPayload(await res.json());
|
|
1057
|
+
}
|
|
1058
|
+
),
|
|
1059
|
+
fetchTimeout(APP_ROUTES.config, { headers: { accept: "application/json" } }).then(
|
|
1060
|
+
async (res) => {
|
|
1061
|
+
if (!res.ok) throw new Error("config-http-" + res.status);
|
|
1062
|
+
const cfg = parseConfigPayload(await res.json());
|
|
1063
|
+
if (cfg === null) throw new Error("config-shape-unknown");
|
|
1064
|
+
return cfg;
|
|
1065
|
+
}
|
|
1066
|
+
)
|
|
1067
|
+
]).then(([infos, cfg]) => {
|
|
1068
|
+
if (!alive.current) return;
|
|
1069
|
+
setSnapshot(cfg);
|
|
1070
|
+
const byCfg = new Map(cfg.presets.map((p) => [p.id, p]));
|
|
1071
|
+
const byInfo = new Map(infos.map((i) => [i.id, i]));
|
|
1072
|
+
const ids = infos.length > 0 ? infos.map((i) => i.id) : cfg.presets.map((p) => p.id);
|
|
1073
|
+
setRows(
|
|
1074
|
+
ids.map((id) => {
|
|
1075
|
+
const info = byInfo.get(id);
|
|
1076
|
+
const cfgRow = byCfg.get(id);
|
|
1077
|
+
return {
|
|
1078
|
+
id,
|
|
1079
|
+
custom: info?.custom === true,
|
|
1080
|
+
label: info?.label ?? id,
|
|
1081
|
+
templateVersion: info?.templateVersion,
|
|
1082
|
+
desc: info?.description ?? info?.label,
|
|
1083
|
+
enabled: cfgRow?.enabled ?? false,
|
|
1084
|
+
cap: normalizeCap(cfgRow?.automationCap ?? 0)
|
|
1085
|
+
};
|
|
1086
|
+
})
|
|
1087
|
+
);
|
|
1088
|
+
setMsg({ kind: "info", text: t("presetsMergedNote") });
|
|
1089
|
+
}).catch((e) => {
|
|
1090
|
+
if (!alive.current) return;
|
|
1091
|
+
setSnapshot(null);
|
|
1092
|
+
setRows([]);
|
|
1093
|
+
setFailed(true);
|
|
1094
|
+
setMsg({
|
|
1095
|
+
kind: "error",
|
|
1096
|
+
text: t("loadFail") + (e instanceof Error ? e.message : String(e))
|
|
1097
|
+
});
|
|
1098
|
+
});
|
|
1099
|
+
}, []);
|
|
1100
|
+
React4.useEffect(load, [load]);
|
|
1101
|
+
const patchRow = (id, patch) => {
|
|
1102
|
+
setRows((prev) => prev.map((r) => r.id === id ? { ...r, ...patch } : r));
|
|
1103
|
+
};
|
|
1104
|
+
const doExport = () => {
|
|
1105
|
+
if (rows.length === 0) return;
|
|
1106
|
+
const doc = {
|
|
1107
|
+
plugin: "dsh-decision-gateway",
|
|
1108
|
+
version: 1,
|
|
1109
|
+
exportedAt: Date.now(),
|
|
1110
|
+
presets: rows.map((r) => ({
|
|
1111
|
+
id: r.id,
|
|
1112
|
+
enabled: r.enabled,
|
|
1113
|
+
automationCap: r.cap,
|
|
1114
|
+
...r.templateVersion !== void 0 ? { templateVersion: r.templateVersion } : {}
|
|
1115
|
+
}))
|
|
1116
|
+
};
|
|
1117
|
+
try {
|
|
1118
|
+
const blob = new Blob([JSON.stringify(doc, null, 2)], { type: "application/json" });
|
|
1119
|
+
const url = URL.createObjectURL(blob);
|
|
1120
|
+
const a = document.createElement("a");
|
|
1121
|
+
a.href = url;
|
|
1122
|
+
a.download = "dsh-decision-gateway-presets.json";
|
|
1123
|
+
document.body.appendChild(a);
|
|
1124
|
+
a.click();
|
|
1125
|
+
a.remove();
|
|
1126
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
1127
|
+
setMsg({ kind: "ok", text: t("presetsExported", { n: rows.length }) });
|
|
1128
|
+
} catch (e) {
|
|
1129
|
+
setMsg({ kind: "error", text: t("saveFail") + (e instanceof Error ? e.message : String(e)) });
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
const putCustoms = (customs) => {
|
|
1133
|
+
setSaving(true);
|
|
1134
|
+
setMsg({ kind: "info", text: t("saving") });
|
|
1135
|
+
void fetchTimeout(APP_ROUTES.config, {
|
|
1136
|
+
method: "PUT",
|
|
1137
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
1138
|
+
body: JSON.stringify({
|
|
1139
|
+
customPresets: customs.map((c) => ({
|
|
1140
|
+
id: c.id,
|
|
1141
|
+
label: c.label,
|
|
1142
|
+
description: c.description,
|
|
1143
|
+
enabled: c.enabled,
|
|
1144
|
+
automationCap: c.cap
|
|
1145
|
+
}))
|
|
1146
|
+
})
|
|
1147
|
+
}).then(async (res) => {
|
|
1148
|
+
if (!alive.current) return;
|
|
1149
|
+
if (!res.ok) {
|
|
1150
|
+
let cat = "http-" + res.status;
|
|
1151
|
+
try {
|
|
1152
|
+
cat = failureCategory(res.status, await res.json());
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
throw new Error(cat);
|
|
1156
|
+
}
|
|
1157
|
+
setFormOpen(false);
|
|
1158
|
+
setMsg({ kind: "ok", text: t("customSaved") });
|
|
1159
|
+
load();
|
|
1160
|
+
}).catch((e) => {
|
|
1161
|
+
if (!alive.current) return;
|
|
1162
|
+
setMsg({
|
|
1163
|
+
kind: "error",
|
|
1164
|
+
text: t("saveFail") + (e instanceof Error ? e.message : String(e))
|
|
1165
|
+
});
|
|
1166
|
+
}).finally(() => {
|
|
1167
|
+
if (alive.current) setSaving(false);
|
|
1168
|
+
});
|
|
1169
|
+
};
|
|
1170
|
+
const createCustom = () => {
|
|
1171
|
+
const id = draftId.trim();
|
|
1172
|
+
if (id === "" || draftLabel.trim() === "" || draftDesc.trim() === "") {
|
|
1173
|
+
setMsg({ kind: "error", text: t("customFormInvalid") });
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const next = rows.filter((r) => r.custom).map((r) => ({
|
|
1177
|
+
id: r.id,
|
|
1178
|
+
label: r.label,
|
|
1179
|
+
description: r.desc ?? r.id,
|
|
1180
|
+
enabled: r.enabled,
|
|
1181
|
+
cap: r.cap
|
|
1182
|
+
})).filter((r) => r.id !== id);
|
|
1183
|
+
next.push({
|
|
1184
|
+
id,
|
|
1185
|
+
label: draftLabel.trim(),
|
|
1186
|
+
description: draftDesc.trim(),
|
|
1187
|
+
enabled: false,
|
|
1188
|
+
cap: draftCap
|
|
1189
|
+
});
|
|
1190
|
+
putCustoms(next);
|
|
1191
|
+
};
|
|
1192
|
+
const save = () => {
|
|
1193
|
+
if (snapshot === null || saving) return;
|
|
1194
|
+
const presets = rows.filter((r) => !r.custom).map((r) => ({ id: r.id, enabled: r.enabled, automationCap: r.cap }));
|
|
1195
|
+
const customs = rows.filter((r) => r.custom).map((r) => ({
|
|
1196
|
+
id: r.id,
|
|
1197
|
+
label: r.label,
|
|
1198
|
+
description: r.desc ?? r.id,
|
|
1199
|
+
enabled: r.enabled,
|
|
1200
|
+
automationCap: r.cap
|
|
1201
|
+
}));
|
|
1202
|
+
const conn = snapshot.connection;
|
|
1203
|
+
const body = {
|
|
1204
|
+
version: 1,
|
|
1205
|
+
connection: {
|
|
1206
|
+
timeoutMs: conn.timeoutMs,
|
|
1207
|
+
maxConcurrency: conn.maxConcurrency,
|
|
1208
|
+
truncBudget: conn.truncBudget,
|
|
1209
|
+
...conn.apiKeyRef !== void 0 && conn.apiKeyRef !== "" ? { apiKeyRef: conn.apiKeyRef } : {}
|
|
1210
|
+
},
|
|
1211
|
+
presets,
|
|
1212
|
+
customPresets: customs,
|
|
1213
|
+
history: {
|
|
1214
|
+
perSession: snapshot.history.perSession,
|
|
1215
|
+
totalSessions: snapshot.history.totalSessions
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
setSaving(true);
|
|
1219
|
+
setMsg({ kind: "info", text: t("saving") });
|
|
1220
|
+
void fetchTimeout(APP_ROUTES.config, {
|
|
1221
|
+
method: "PUT",
|
|
1222
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
1223
|
+
body: JSON.stringify(body)
|
|
1224
|
+
}).then(async (res) => {
|
|
1225
|
+
if (!alive.current) return;
|
|
1226
|
+
if (!res.ok) {
|
|
1227
|
+
let cat = "http-" + res.status;
|
|
1228
|
+
try {
|
|
1229
|
+
cat = failureCategory(res.status, await res.json());
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
throw new Error(cat);
|
|
1233
|
+
}
|
|
1234
|
+
setMsg({ kind: "ok", text: t("presetsSaved", { n: presets.length }) });
|
|
1235
|
+
load();
|
|
1236
|
+
}).catch((e) => {
|
|
1237
|
+
if (!alive.current) return;
|
|
1238
|
+
setMsg({
|
|
1239
|
+
kind: "error",
|
|
1240
|
+
text: t("saveFail") + (e instanceof Error ? e.message : String(e))
|
|
1241
|
+
});
|
|
1242
|
+
}).finally(() => {
|
|
1243
|
+
if (alive.current) setSaving(false);
|
|
1244
|
+
});
|
|
1245
|
+
};
|
|
1246
|
+
return /* @__PURE__ */ React4.createElement("div", { className: "dj-pane", "data-tab": "presets" }, /* @__PURE__ */ React4.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React4.createElement("span", { className: "dj-badge" }, rows.length === 0 ? t("loading") : t("presetsCount", { n: rows.length }))), msg !== null && /* @__PURE__ */ React4.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React4.createElement("span", { className: msg.kind === "error" ? "dj-errLine" : "dj-note" }, msg.text)), /* @__PURE__ */ React4.createElement("div", { className: "dj-field" }, rows.length === 0 && !failed ? /* @__PURE__ */ React4.createElement("div", { className: "dj-note" }, t("emptyPresets")) : rows.map((row) => {
|
|
1247
|
+
const open = openDetail === row.id;
|
|
1248
|
+
return /* @__PURE__ */ React4.createElement("div", { className: "dj-preset", key: row.id }, /* @__PURE__ */ React4.createElement("div", { className: "dj-presetHead" }, /* @__PURE__ */ React4.createElement("span", { className: "dj-presetId" }, row.id), row.custom && /* @__PURE__ */ React4.createElement("span", { className: "dj-badge dj-badgeInfo" }, t("customBadge")), /* @__PURE__ */ React4.createElement("label", { className: "dj-switch" }, /* @__PURE__ */ React4.createElement(
|
|
1249
|
+
"input",
|
|
1250
|
+
{
|
|
1251
|
+
type: "checkbox",
|
|
1252
|
+
role: "switch",
|
|
1253
|
+
"aria-label": t("enablePresetAria") + row.id,
|
|
1254
|
+
checked: row.enabled,
|
|
1255
|
+
onChange: (e) => patchRow(row.id, { enabled: e.target.checked })
|
|
1256
|
+
}
|
|
1257
|
+
), row.enabled ? t("enabledOn") : t("enabledOff"))), row.templateVersion !== void 0 && /* @__PURE__ */ React4.createElement("div", { className: "dj-note" }, t("tplVersion"), row.templateVersion, t("frozenNote")), /* @__PURE__ */ React4.createElement("div", { className: "dj-rangeRow" }, /* @__PURE__ */ React4.createElement("label", null, t("capLabel")), /* @__PURE__ */ React4.createElement(
|
|
1258
|
+
"select",
|
|
1259
|
+
{
|
|
1260
|
+
className: "dj-select",
|
|
1261
|
+
autoComplete: "off",
|
|
1262
|
+
"aria-label": t("capLabel") + " " + row.id,
|
|
1263
|
+
value: String(row.cap),
|
|
1264
|
+
onChange: (e) => patchRow(row.id, { cap: normalizeCap(Number(e.target.value)) })
|
|
1265
|
+
},
|
|
1266
|
+
/* @__PURE__ */ React4.createElement("option", { value: "0" }, t("capNone")),
|
|
1267
|
+
/* @__PURE__ */ React4.createElement("option", { value: "1" }, "low"),
|
|
1268
|
+
/* @__PURE__ */ React4.createElement("option", { value: "2" }, "high")
|
|
1269
|
+
), /* @__PURE__ */ React4.createElement("span", { className: "dj-rangeVal" }, capLabel(row.cap))), /* @__PURE__ */ React4.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React4.createElement(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
type: "button",
|
|
1273
|
+
className: "dj-btn dj-btnSmall",
|
|
1274
|
+
"aria-expanded": open ? "true" : "false",
|
|
1275
|
+
"aria-controls": "dj-detail-" + row.id,
|
|
1276
|
+
onClick: () => setOpenDetail(open ? null : row.id)
|
|
1277
|
+
},
|
|
1278
|
+
t("detail")
|
|
1279
|
+
)), open && /* @__PURE__ */ React4.createElement("div", { className: "dj-field", id: "dj-detail-" + row.id }, /* @__PURE__ */ React4.createElement("div", { className: "dj-note" }, row.desc ?? t("emptySpec")), /* @__PURE__ */ React4.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React4.createElement(
|
|
1280
|
+
"button",
|
|
1281
|
+
{
|
|
1282
|
+
type: "button",
|
|
1283
|
+
className: "dj-btn dj-btnSmall",
|
|
1284
|
+
onClick: () => {
|
|
1285
|
+
setDraftId(row.id + "-copy");
|
|
1286
|
+
setDraftLabel(row.label);
|
|
1287
|
+
setDraftDesc(row.desc ?? "");
|
|
1288
|
+
setDraftCap(row.cap);
|
|
1289
|
+
setFormOpen(true);
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
t("copyCustom")
|
|
1293
|
+
), row.custom && /* @__PURE__ */ React4.createElement(
|
|
1294
|
+
"button",
|
|
1295
|
+
{
|
|
1296
|
+
type: "button",
|
|
1297
|
+
className: "dj-btn dj-btnSmall dj-btnDanger",
|
|
1298
|
+
onClick: () => {
|
|
1299
|
+
if (armedDelete === row.id) {
|
|
1300
|
+
setArmedDelete(null);
|
|
1301
|
+
void putCustoms(
|
|
1302
|
+
rows.filter((r) => r.custom && r.id !== row.id).map((r) => ({
|
|
1303
|
+
id: r.id,
|
|
1304
|
+
label: r.label,
|
|
1305
|
+
description: r.desc ?? r.id,
|
|
1306
|
+
enabled: r.enabled,
|
|
1307
|
+
cap: r.cap
|
|
1308
|
+
}))
|
|
1309
|
+
);
|
|
1310
|
+
} else {
|
|
1311
|
+
setArmedDelete(row.id);
|
|
1312
|
+
setMsg({ kind: "info", text: t("confirmDeleteCustom") });
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
armedDelete === row.id ? t("confirmDelete") : t("deleteCustom")
|
|
1317
|
+
))));
|
|
1318
|
+
})), /* @__PURE__ */ React4.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React4.createElement("button", { type: "button", className: "dj-btn dj-btnSmall", onClick: () => setFormOpen((v) => !v) }, t("addCustom"))), formOpen && /* @__PURE__ */ React4.createElement("div", { className: "dj-field" }, /* @__PURE__ */ React4.createElement("label", null, t("customIdLabel")), /* @__PURE__ */ React4.createElement(
|
|
1319
|
+
"input",
|
|
1320
|
+
{
|
|
1321
|
+
className: "dj-input",
|
|
1322
|
+
autoComplete: "off",
|
|
1323
|
+
"aria-label": t("customIdLabel"),
|
|
1324
|
+
value: draftId,
|
|
1325
|
+
placeholder: "my-board",
|
|
1326
|
+
onChange: (e) => setDraftId(e.target.value)
|
|
1327
|
+
}
|
|
1328
|
+
), /* @__PURE__ */ React4.createElement("label", null, t("customLabelLabel")), /* @__PURE__ */ React4.createElement(
|
|
1329
|
+
"input",
|
|
1330
|
+
{
|
|
1331
|
+
className: "dj-input",
|
|
1332
|
+
autoComplete: "off",
|
|
1333
|
+
"aria-label": t("customLabelLabel"),
|
|
1334
|
+
value: draftLabel,
|
|
1335
|
+
onChange: (e) => setDraftLabel(e.target.value)
|
|
1336
|
+
}
|
|
1337
|
+
), /* @__PURE__ */ React4.createElement("label", null, t("customDescLabel")), /* @__PURE__ */ React4.createElement(
|
|
1338
|
+
"textarea",
|
|
1339
|
+
{
|
|
1340
|
+
className: "dj-input",
|
|
1341
|
+
rows: 3,
|
|
1342
|
+
"aria-label": t("customDescLabel"),
|
|
1343
|
+
value: draftDesc,
|
|
1344
|
+
onChange: (e) => setDraftDesc(e.target.value)
|
|
1345
|
+
}
|
|
1346
|
+
), /* @__PURE__ */ React4.createElement("label", null, t("capLabel")), /* @__PURE__ */ React4.createElement(
|
|
1347
|
+
"select",
|
|
1348
|
+
{
|
|
1349
|
+
className: "dj-select",
|
|
1350
|
+
autoComplete: "off",
|
|
1351
|
+
value: String(draftCap),
|
|
1352
|
+
onChange: (e) => setDraftCap(normalizeCap(Number(e.target.value)))
|
|
1353
|
+
},
|
|
1354
|
+
/* @__PURE__ */ React4.createElement("option", { value: "0" }, t("capNone")),
|
|
1355
|
+
/* @__PURE__ */ React4.createElement("option", { value: "1" }, "low"),
|
|
1356
|
+
/* @__PURE__ */ React4.createElement("option", { value: "2" }, "high")
|
|
1357
|
+
), /* @__PURE__ */ React4.createElement("span", { className: "dj-note" }, t("customDraftNote")), /* @__PURE__ */ React4.createElement("div", { className: "dj-tools" }, /* @__PURE__ */ React4.createElement("button", { type: "button", className: "dj-btn dj-btnSmall", onClick: () => setFormOpen(false) }, t("cancelCustom")), /* @__PURE__ */ React4.createElement(
|
|
1358
|
+
"button",
|
|
1359
|
+
{
|
|
1360
|
+
type: "button",
|
|
1361
|
+
className: "dj-btn dj-btnPrimary",
|
|
1362
|
+
disabled: saving,
|
|
1363
|
+
onClick: createCustom
|
|
1364
|
+
},
|
|
1365
|
+
t("createCustom")
|
|
1366
|
+
))), /* @__PURE__ */ React4.createElement("div", { className: "dj-foot" }, failed && /* @__PURE__ */ React4.createElement("button", { type: "button", className: "dj-btn dj-btnSmall", onClick: load }, t("retry")), /* @__PURE__ */ React4.createElement(
|
|
1367
|
+
"button",
|
|
1368
|
+
{
|
|
1369
|
+
type: "button",
|
|
1370
|
+
className: "dj-btn dj-btnSmall",
|
|
1371
|
+
disabled: rows.length === 0,
|
|
1372
|
+
onClick: doExport
|
|
1373
|
+
},
|
|
1374
|
+
t("exportJson")
|
|
1375
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1376
|
+
"button",
|
|
1377
|
+
{
|
|
1378
|
+
type: "button",
|
|
1379
|
+
className: "dj-btn dj-btnPrimary",
|
|
1380
|
+
disabled: saving || snapshot === null,
|
|
1381
|
+
onClick: save
|
|
1382
|
+
},
|
|
1383
|
+
t("save")
|
|
1384
|
+
)), /* @__PURE__ */ React4.createElement("div", { className: "dj-note" }, t("noImportNote")));
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// src/client/settings/card.tsx
|
|
1388
|
+
var TABS = [
|
|
1389
|
+
{ key: "connection", textKey: "tabConnection" },
|
|
1390
|
+
{ key: "presets", textKey: "tabPresets" },
|
|
1391
|
+
{ key: "history", textKey: "tabHistory" }
|
|
1392
|
+
];
|
|
1393
|
+
function DecisionCard() {
|
|
1394
|
+
const [active, setActive] = React5.useState("connection");
|
|
1395
|
+
const [healthText, setHealthText] = React5.useState(t("healthChecking"));
|
|
1396
|
+
const [healthCls, setHealthCls] = React5.useState("dj-badge");
|
|
1397
|
+
const alive = React5.useRef(true);
|
|
1398
|
+
React5.useEffect(() => {
|
|
1399
|
+
alive.current = true;
|
|
1400
|
+
void fetchTimeout(APP_ROUTES.health, { headers: { accept: "application/json" } }).then((res) => {
|
|
1401
|
+
if (!alive.current) return;
|
|
1402
|
+
setHealthText(res.ok ? t("healthOk") : t("healthBad") + res.status);
|
|
1403
|
+
setHealthCls(res.ok ? "dj-badge dj-statusOk" : "dj-badge dj-statusErr");
|
|
1404
|
+
}).catch(() => {
|
|
1405
|
+
if (!alive.current) return;
|
|
1406
|
+
setHealthText(t("healthUnreachable"));
|
|
1407
|
+
setHealthCls("dj-badge dj-statusErr");
|
|
1408
|
+
});
|
|
1409
|
+
return () => {
|
|
1410
|
+
alive.current = false;
|
|
1411
|
+
};
|
|
1412
|
+
}, []);
|
|
1413
|
+
return /* @__PURE__ */ React5.createElement("section", { className: "dj-card", "data-plugin": "dsh-decision-gateway" }, /* @__PURE__ */ React5.createElement("div", { className: "dj-head" }, /* @__PURE__ */ React5.createElement("span", { className: healthCls }, healthText)), /* @__PURE__ */ React5.createElement("div", { className: "dj-tabs", role: "group", "aria-label": t("tabsAria") }, TABS.map((tab) => /* @__PURE__ */ React5.createElement(
|
|
1414
|
+
"button",
|
|
1415
|
+
{
|
|
1416
|
+
key: tab.key,
|
|
1417
|
+
type: "button",
|
|
1418
|
+
className: tab.key === active ? "dj-tab dj-tabActive" : "dj-tab",
|
|
1419
|
+
"aria-selected": tab.key === active ? "true" : "false",
|
|
1420
|
+
onClick: () => setActive(tab.key)
|
|
1421
|
+
},
|
|
1422
|
+
t(tab.textKey)
|
|
1423
|
+
))), /* @__PURE__ */ React5.createElement("div", { className: "dj-body" }, active === "connection" && /* @__PURE__ */ React5.createElement(ConnectionPane, null), active === "presets" && /* @__PURE__ */ React5.createElement(PresetsPane, null), active === "history" && /* @__PURE__ */ React5.createElement(HistoryPane, null)));
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/client/index.tsx
|
|
1427
|
+
var STYLE_ID = "dsh-decision-gateway-style";
|
|
1428
|
+
var CSS_VERSION = "1";
|
|
1429
|
+
var NS = "decision-gateway";
|
|
1430
|
+
function apply(ctx) {
|
|
1431
|
+
try {
|
|
1432
|
+
const disposeStyle = ensureStyle({ id: STYLE_ID, cssText: style_default, version: CSS_VERSION });
|
|
1433
|
+
let styleDisposed = false;
|
|
1434
|
+
const slots = ctx.get("slots");
|
|
1435
|
+
if (slots === null || slots === void 0 || typeof slots.inject !== "function") {
|
|
1436
|
+
console.warn("[dsh-decision-gateway] 缺少 slots 服务,设置 tab 未挂载");
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
let unsubLocale;
|
|
1440
|
+
const locale = ctx.get("locale");
|
|
1441
|
+
if (locale !== null && locale !== void 0 && typeof locale.register === "function") {
|
|
1442
|
+
const localeService = locale;
|
|
1443
|
+
const rebind = () => {
|
|
1444
|
+
let next;
|
|
1445
|
+
try {
|
|
1446
|
+
next = localeService.bind(NS);
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
console.warn("[dsh-decision-gateway] locale 重绑失败:", e);
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
if (typeof next !== "function") {
|
|
1452
|
+
console.warn("[dsh-decision-gateway] locale bind 返回非函数,已回落本地字典");
|
|
1453
|
+
return false;
|
|
1454
|
+
}
|
|
1455
|
+
bindTranslate(next);
|
|
1456
|
+
return true;
|
|
1457
|
+
};
|
|
1458
|
+
try {
|
|
1459
|
+
localeService.register(NS, { zh, en });
|
|
1460
|
+
const boundOk = rebind();
|
|
1461
|
+
if (boundOk && typeof localeService.subscribe === "function" && typeof localeService.getSnapshot === "function") {
|
|
1462
|
+
unsubLocale = localeService.subscribe(function() {
|
|
1463
|
+
rebind();
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
} catch (e) {
|
|
1467
|
+
console.warn("[dsh-decision-gateway] locale 注册失败:", e);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
try {
|
|
1471
|
+
slots.inject("settings.section", function() {
|
|
1472
|
+
return slots.register(
|
|
1473
|
+
{
|
|
1474
|
+
name: "settings.section",
|
|
1475
|
+
id: "dsh-decision-gateway",
|
|
1476
|
+
order: 80,
|
|
1477
|
+
label: () => t("tabLabel")
|
|
1478
|
+
},
|
|
1479
|
+
function() {
|
|
1480
|
+
return /* @__PURE__ */ React6.createElement(DecisionCard, null);
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
});
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
console.warn("[dsh-decision-gateway] 设置 tab 注册失败:", e);
|
|
1486
|
+
}
|
|
1487
|
+
if (unsubLocale !== void 0) {
|
|
1488
|
+
const unsubscribe = unsubLocale;
|
|
1489
|
+
ctx.effect(function() {
|
|
1490
|
+
return function() {
|
|
1491
|
+
try {
|
|
1492
|
+
unsubscribe();
|
|
1493
|
+
} catch {
|
|
1494
|
+
}
|
|
1495
|
+
unbindTranslate();
|
|
1496
|
+
};
|
|
1497
|
+
}, "dsh-decision-gateway-locale");
|
|
1498
|
+
}
|
|
1499
|
+
ctx.effect(function() {
|
|
1500
|
+
return function() {
|
|
1501
|
+
if (styleDisposed) return;
|
|
1502
|
+
styleDisposed = true;
|
|
1503
|
+
try {
|
|
1504
|
+
disposeStyle();
|
|
1505
|
+
} catch {
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}, "dsh-decision-gateway");
|
|
1509
|
+
} catch (e) {
|
|
1510
|
+
console.warn("[dsh-decision-gateway] 挂载失败:", e);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
var inject = ["slots", "locale"];
|
|
1514
|
+
|
|
1515
|
+
Object.defineProperty(module.exports, Symbol.toStringTag, { value: 'Module' })
|
|
1516
|
+
return module.exports
|
|
1517
|
+
}
|
|
1518
|
+
})
|