@jayyuen66/dsh-ocr-review 0.1.8 → 0.1.10
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/CHANGELOG.md +63 -0
- package/LICENSE +1 -1
- package/README.md +18 -17
- package/README.zh-CN.md +17 -16
- package/THIRD_PARTY_NOTICES.md +30 -2
- package/client.js +1013 -586
- package/cordis.patch.yml +1 -1
- package/docs/DETAIL.md +28 -0
- package/docs/DETAIL.zh.md +28 -0
- package/host.js +750 -333
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +62 -43
- package/scripts/get-cred.mjs +124 -59
package/client.js
CHANGED
|
@@ -7,18 +7,20 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let exports = module.exports;
|
|
8
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
9
|
let react = require("react");
|
|
10
|
+
let zod = require("zod");
|
|
10
11
|
// src/ui-messages.ts
|
|
11
12
|
const UI_MESSAGES = {
|
|
12
13
|
zh: {
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
modelSection: "模型",
|
|
15
|
+
reviewSection: "审查参数",
|
|
16
|
+
actionsLabel: "动作",
|
|
17
|
+
outOfRange: "超出允许范围,未保存",
|
|
15
18
|
statusReadOnly: "当前作用域只读",
|
|
16
19
|
statusDirty: "有未保存的修改,点「保存」生效",
|
|
17
20
|
statusClean: "无未保存的修改",
|
|
18
21
|
save: "保存",
|
|
19
22
|
saving: "保存中…",
|
|
20
23
|
revert: "撤销",
|
|
21
|
-
saveFailed: "保存失败:",
|
|
22
24
|
saveRefused: "未写入(宿主拒收或跳过):{fields}",
|
|
23
25
|
loadProvidersFailed: "加载 providers 失败:{error}(远程面未就绪?重启 dsh 后重试)",
|
|
24
26
|
remoteUnavailable: "远程服务不可用:宿主没有挂上本插件的设置通道。",
|
|
@@ -59,8 +61,6 @@ const UI_MESSAGES = {
|
|
|
59
61
|
languageHint: "OCR 评论输出语言(写入 config.language)",
|
|
60
62
|
autoVerifyLabel: "应用后自动验证",
|
|
61
63
|
autoVerifyHint: "选择 provider/model 后自动跑 ocr llm test",
|
|
62
|
-
onLabel: "开启",
|
|
63
|
-
offLabel: "关闭",
|
|
64
64
|
maxCommentsLabel: "摘要评论条数 (maxComments)",
|
|
65
65
|
maxCommentsHint: "返回给模型的 top 评论条数;0 = 全部(含未锚定行,输出大)(默认见本行设置项的初值)",
|
|
66
66
|
timeoutLabel: "运行超时 (timeoutMinutes)",
|
|
@@ -73,6 +73,7 @@ const UI_MESSAGES = {
|
|
|
73
73
|
mcpSaving: "保存中…",
|
|
74
74
|
mcpSaved: "MCP servers 已保存",
|
|
75
75
|
mcpLoadFailed: "读取 MCP 配置失败:",
|
|
76
|
+
mcpNotLoaded: "还没读到当前 MCP 配置,先等读取完成(失败时先修好读取)再保存",
|
|
76
77
|
chatGptTitle: "ChatGPT(OCR auth)",
|
|
77
78
|
chatGptHint: "登录态由 ocr auth 管理;本卡片只读查询状态,并在登录后把 provider 切到 chatgpt。",
|
|
78
79
|
chatGptCheck: "检查登录态",
|
|
@@ -90,7 +91,7 @@ const UI_MESSAGES = {
|
|
|
90
91
|
binaryUnknown: "状态未取到:刷新本页,或确认 dsh 的 web 服务已起来",
|
|
91
92
|
binaryBundled: "随本插件分发的兜底入口已就位:{path}({channel})",
|
|
92
93
|
binaryPath: "用 PATH 上的全局安装:{path}({channel})",
|
|
93
|
-
binaryNone: "未找到 ocr:PATH
|
|
94
|
+
binaryNone: "未找到 ocr:PATH 与随包兜底两条路都没有可执行候选",
|
|
94
95
|
channelBrew: "brew formula",
|
|
95
96
|
channelNpm: "npm 全局包",
|
|
96
97
|
channelUnknown: "装法未识别",
|
|
@@ -111,15 +112,16 @@ const UI_MESSAGES = {
|
|
|
111
112
|
binaryGuideDismiss: "知道了"
|
|
112
113
|
},
|
|
113
114
|
en: {
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
modelSection: "Model",
|
|
116
|
+
reviewSection: "Review parameters",
|
|
117
|
+
actionsLabel: "Actions",
|
|
118
|
+
outOfRange: "Out of the allowed range — not saved",
|
|
116
119
|
statusReadOnly: "This scope is read-only",
|
|
117
120
|
statusDirty: "Unsaved changes — press Save to apply",
|
|
118
121
|
statusClean: "No unsaved changes",
|
|
119
122
|
save: "Save",
|
|
120
123
|
saving: "Saving…",
|
|
121
124
|
revert: "Revert",
|
|
122
|
-
saveFailed: "save failed: ",
|
|
123
125
|
saveRefused: "not written (host refused or skipped): {fields}",
|
|
124
126
|
loadProvidersFailed: "failed to load providers: {error} (remote face not ready? restart dsh and retry)",
|
|
125
127
|
remoteUnavailable: "remote service unavailable: the host did not mount this plugin's settings channel",
|
|
@@ -160,8 +162,6 @@ const UI_MESSAGES = {
|
|
|
160
162
|
languageHint: "Output language of OCR comments (written into config.language)",
|
|
161
163
|
autoVerifyLabel: "Verify after applying",
|
|
162
164
|
autoVerifyHint: "Run `ocr llm test` automatically after picking a provider/model",
|
|
163
|
-
onLabel: "On",
|
|
164
|
-
offLabel: "Off",
|
|
165
165
|
maxCommentsLabel: "Summary comment cap (maxComments)",
|
|
166
166
|
maxCommentsHint: "Top comments returned to the model; 0 = all (including unanchored lines, large output) (default: the row's initial value)",
|
|
167
167
|
timeoutLabel: "Run timeout (timeoutMinutes)",
|
|
@@ -174,6 +174,7 @@ const UI_MESSAGES = {
|
|
|
174
174
|
mcpSaving: "Saving…",
|
|
175
175
|
mcpSaved: "MCP servers saved",
|
|
176
176
|
mcpLoadFailed: "Failed to read the MCP config: ",
|
|
177
|
+
mcpNotLoaded: "The current MCP config has not loaded yet; wait for the read (or fix it) before saving",
|
|
177
178
|
chatGptTitle: "ChatGPT (OCR auth)",
|
|
178
179
|
chatGptHint: "Sign-in is managed by ocr auth; this card only reports the status and switches OCR's provider to chatgpt after you sign in.",
|
|
179
180
|
chatGptCheck: "Check sign-in",
|
|
@@ -191,7 +192,7 @@ const UI_MESSAGES = {
|
|
|
191
192
|
binaryUnknown: "status not reported: reload this page, or check that the dsh web server is up",
|
|
192
193
|
binaryBundled: "bundled fallback entry in place: {path} ({channel})",
|
|
193
194
|
binaryPath: "using the global install on PATH: {path} ({channel})",
|
|
194
|
-
binaryNone: "ocr not found: neither PATH nor the bundled fallback has an executable candidate
|
|
195
|
+
binaryNone: "ocr not found: neither PATH nor the bundled fallback has an executable candidate",
|
|
195
196
|
channelBrew: "brew formula",
|
|
196
197
|
channelNpm: "npm global package",
|
|
197
198
|
channelUnknown: "install method unrecognised",
|
|
@@ -200,7 +201,7 @@ const UI_MESSAGES = {
|
|
|
200
201
|
versionDiverged: "this machine reports {actual}, which is not the verified {expected}: comment wording and severity may drift between versions",
|
|
201
202
|
badgeDiverged: "ocr version differs",
|
|
202
203
|
detectCli: "Detect version",
|
|
203
|
-
detecting: "Detecting
|
|
204
|
+
detecting: "Detecting…",
|
|
204
205
|
probeVersion: "ocr version: {version}",
|
|
205
206
|
probeReason: "no version read: {reason}",
|
|
206
207
|
badgeBundled: "ocr from bundled fallback",
|
|
@@ -213,59 +214,591 @@ const UI_MESSAGES = {
|
|
|
213
214
|
}
|
|
214
215
|
};
|
|
215
216
|
// node_modules/@jayyuen66/dsh-plugin-shared/dist/record.js
|
|
216
|
-
function e$
|
|
217
|
+
function e$3(e) {
|
|
217
218
|
return typeof e == `object` && !!e && !Array.isArray(e);
|
|
218
219
|
}
|
|
219
|
-
const t$
|
|
220
|
-
function n$
|
|
220
|
+
const t$3 = { absent: !0 };
|
|
221
|
+
function n$2(e, n) {
|
|
221
222
|
try {
|
|
222
223
|
return {
|
|
223
224
|
absent: !1,
|
|
224
225
|
value: e[n]
|
|
225
226
|
};
|
|
226
227
|
} catch {
|
|
227
|
-
return t$
|
|
228
|
+
return t$3;
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
|
-
function i(r, i) {
|
|
231
|
-
let a = e$
|
|
231
|
+
function i$1(r, i) {
|
|
232
|
+
let a = e$3(r) ? n$2(r, i) : t$3;
|
|
232
233
|
return a.absent ? void 0 : a.value;
|
|
233
234
|
}
|
|
234
235
|
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-apply.js
|
|
235
|
-
const e$
|
|
236
|
-
function t$
|
|
236
|
+
const e$2 = (e) => e.length > 0 && !(e in Object.prototype);
|
|
237
|
+
function t$2(e, t, n) {
|
|
237
238
|
n === void 0 ? e[t] = void 0 : Object.defineProperty(e, t, n);
|
|
238
239
|
}
|
|
239
|
-
function n(n, r, i) {
|
|
240
|
-
if (!e$
|
|
240
|
+
function n$1(n, r, i) {
|
|
241
|
+
if (!e$2(r)) throw Error(`claimApply 的 flag 不得为空、也不得命中 Object.prototype 的成员(__proto__ 一类会让占位静默失灵或遮蔽全局成员):${r}`);
|
|
241
242
|
let a = globalThis, o = Object.hasOwn(a, r) ? a[r] : void 0;
|
|
242
243
|
if (o === !0 || typeof o == `symbol`) return !1;
|
|
243
244
|
let s = Object.getOwnPropertyDescriptor(a, r), c = Symbol(r);
|
|
244
245
|
a[r] = c;
|
|
245
246
|
try {
|
|
246
247
|
n.effect(() => () => {
|
|
247
|
-
a[r] === c && t$
|
|
248
|
+
a[r] === c && t$2(a, r, s);
|
|
248
249
|
}, i);
|
|
249
250
|
} catch (e) {
|
|
250
|
-
throw a[r] === c && t$
|
|
251
|
+
throw a[r] === c && t$2(a, r, s), e;
|
|
251
252
|
}
|
|
252
253
|
return !0;
|
|
253
254
|
}
|
|
254
255
|
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
|
|
255
|
-
function e(t, n, r) {
|
|
256
|
+
function e$1(t, n, r) {
|
|
256
257
|
if (t == null || n == null) return (t ?? null) === (n ?? null);
|
|
257
258
|
if (Object.is(t, n)) return !0;
|
|
258
259
|
if (typeof t != `object` || typeof n != `object`) return !1;
|
|
259
260
|
if (r.get(t) === n) return !0;
|
|
260
|
-
if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e(t, n[i], r));
|
|
261
|
+
if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e$1(t, n[i], r));
|
|
261
262
|
let i = Object.keys(t), a = Object.keys(n);
|
|
262
|
-
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
263
|
+
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e$1(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
263
264
|
}
|
|
264
|
-
function t(t, n) {
|
|
265
|
+
function t$1(t, n) {
|
|
265
266
|
let r = [];
|
|
266
|
-
for (let i of Object.keys(t)) e(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
267
|
+
for (let i of Object.keys(t)) e$1(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
267
268
|
return r;
|
|
268
269
|
}
|
|
270
|
+
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-ui.js
|
|
271
|
+
const c = `
|
|
272
|
+
.dsu-form{display:flex;flex-direction:column}
|
|
273
|
+
.dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
|
|
274
|
+
.dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
|
|
275
|
+
.dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
|
|
276
|
+
.dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
277
|
+
.dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
278
|
+
.dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
279
|
+
.dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
280
|
+
.dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
|
|
281
|
+
.dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
282
|
+
.dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
283
|
+
.dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
284
|
+
.dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
|
|
285
|
+
.dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
286
|
+
.dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
|
|
287
|
+
.dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
|
|
288
|
+
.dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}
|
|
289
|
+
.dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
|
|
290
|
+
.dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
291
|
+
.dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}
|
|
292
|
+
.dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
293
|
+
.dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
294
|
+
.dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
|
|
295
|
+
.dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
296
|
+
.dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
297
|
+
.dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
|
|
298
|
+
.dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
299
|
+
.dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
|
|
300
|
+
.dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
|
|
301
|
+
.dsu-number .dsu-input{min-width:0}
|
|
302
|
+
.dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
303
|
+
.dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
|
|
304
|
+
.dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}
|
|
305
|
+
.dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
|
|
306
|
+
.dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
|
|
307
|
+
.dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
|
|
308
|
+
.dsu-switch:disabled{opacity:.4;cursor:not-allowed}
|
|
309
|
+
.dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
310
|
+
.dsu-selectWrap{position:relative;width:100%;min-width:0}
|
|
311
|
+
.dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
|
|
312
|
+
.dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
|
|
313
|
+
.dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
314
|
+
.dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
|
|
315
|
+
.dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
|
|
316
|
+
.dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
|
|
317
|
+
.dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
|
|
318
|
+
.dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
|
|
319
|
+
.dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
|
|
320
|
+
.dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
|
|
321
|
+
.dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
|
|
322
|
+
.dsu-check{opacity:0;flex:none}
|
|
323
|
+
.dsu-option[aria-selected="true"] .dsu-check{opacity:1}
|
|
324
|
+
.dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}
|
|
325
|
+
.dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
|
|
326
|
+
.dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}
|
|
327
|
+
.dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
|
|
328
|
+
.dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
|
|
329
|
+
.dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
330
|
+
.dsu-btn:disabled{opacity:.4;cursor:not-allowed}
|
|
331
|
+
.dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
332
|
+
.dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
|
|
333
|
+
.dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
|
|
334
|
+
.dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
|
|
335
|
+
.dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}
|
|
336
|
+
.dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
337
|
+
.dsu-statusError{color:var(--dsw-alias-state-error-primary)}
|
|
338
|
+
.dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
|
|
339
|
+
.dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}
|
|
340
|
+
.dsu-grid>.dsu-field .dsu-control{width:100%}
|
|
341
|
+
@media (max-width:640px){
|
|
342
|
+
.dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
343
|
+
.dsu-control{width:100%;max-width:720px}
|
|
344
|
+
.dsu-section{margin-top:20px;padding-top:20px}
|
|
345
|
+
}
|
|
346
|
+
@media (prefers-reduced-motion:reduce){
|
|
347
|
+
.dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
|
|
348
|
+
}
|
|
349
|
+
`;
|
|
350
|
+
function l(e, t = 30) {
|
|
351
|
+
return e.length <= t ? { hint: e } : { detail: e };
|
|
352
|
+
}
|
|
353
|
+
function u(t) {
|
|
354
|
+
return (0, react.createElement)(`div`, { className: `dsu-form` }, t.children);
|
|
355
|
+
}
|
|
356
|
+
function d(t) {
|
|
357
|
+
return (0, react.createElement)(`section`, { className: `dsu-section` }, (0, react.createElement)(`h2`, { className: `dsu-sectionTitle` }, t.title), t.hint === void 0 ? null : (0, react.createElement)(`p`, { className: `dsu-sectionHint` }, t.hint), t.children);
|
|
358
|
+
}
|
|
359
|
+
function m(a) {
|
|
360
|
+
let c = (0, react.useId)(), [l, u] = (0, react.useState)(!1), [d, f] = (0, react.useState)(!1), p = (0, react.useRef)(!1), m = (0, react.useRef)(!1), h = (0, react.useRef)(!1), g = (0, react.useRef)(null), _ = (0, react.useRef)(null), v = (0, react.useRef)(null), y = (0, react.useCallback)(() => {
|
|
361
|
+
v.current !== null && (clearTimeout(v.current), v.current = null);
|
|
362
|
+
}, []), b = (0, react.useCallback)(() => {
|
|
363
|
+
let e = g.current, t = _.current;
|
|
364
|
+
/* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
|
|
365
|
+
if (e === null || t === null) return;
|
|
366
|
+
let n = e.getBoundingClientRect(), r = t.offsetWidth, i = t.offsetHeight, { left: a } = n, o = n.bottom + 8;
|
|
367
|
+
o + i > window.innerHeight - 8 && (o = n.top - i - 8), a + r > window.innerWidth - 8 && (a = window.innerWidth - r - 8);
|
|
368
|
+
let s = Math.max(8, window.innerWidth - r - 8), c = Math.max(8, window.innerHeight - i - 8);
|
|
369
|
+
t.style.left = `${Math.min(Math.max(8, a), s)}px`, t.style.top = `${Math.min(Math.max(8, o), c)}px`;
|
|
370
|
+
}, []), x = (0, react.useCallback)(() => {
|
|
371
|
+
y(), h.current = !1, f(!1), u(!1);
|
|
372
|
+
}, [y]), S = (0, react.useCallback)(() => {
|
|
373
|
+
y(), u(!0);
|
|
374
|
+
}, [y]), C = (0, react.useCallback)(() => {
|
|
375
|
+
y(), !(p.current || m.current || h.current) && (v.current = setTimeout(() => {
|
|
376
|
+
/* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
|
|
377
|
+
v.current = null, p.current || m.current || h.current || u(!1);
|
|
378
|
+
}, 120));
|
|
379
|
+
}, [y]), w = (0, react.useCallback)((e) => {
|
|
380
|
+
h.current = e, f(e);
|
|
381
|
+
}, []);
|
|
382
|
+
(0, react.useLayoutEffect)(() => {
|
|
383
|
+
if (!l) return () => {};
|
|
384
|
+
b();
|
|
385
|
+
let e = (e) => {
|
|
386
|
+
let { target: t } = e;
|
|
387
|
+
t instanceof Node && _.current?.contains(t) === !0 || (h.current ? b() : x());
|
|
388
|
+
}, t = () => {
|
|
389
|
+
b();
|
|
390
|
+
};
|
|
391
|
+
return window.addEventListener(`scroll`, e, !0), window.addEventListener(`resize`, t), () => {
|
|
392
|
+
window.removeEventListener(`scroll`, e, !0), window.removeEventListener(`resize`, t);
|
|
393
|
+
};
|
|
394
|
+
}, [
|
|
395
|
+
l,
|
|
396
|
+
a.detail,
|
|
397
|
+
b,
|
|
398
|
+
x
|
|
399
|
+
]), (0, react.useEffect)(() => {
|
|
400
|
+
if (!l) return () => {};
|
|
401
|
+
let e = (e) => {
|
|
402
|
+
e.key === `Escape` && x();
|
|
403
|
+
};
|
|
404
|
+
return document.addEventListener(`keydown`, e), () => {
|
|
405
|
+
document.removeEventListener(`keydown`, e);
|
|
406
|
+
};
|
|
407
|
+
}, [l, x]), (0, react.useEffect)(() => y, [y]);
|
|
408
|
+
let T = (0, react.createElement)(`button`, {
|
|
409
|
+
ref: g,
|
|
410
|
+
type: `button`,
|
|
411
|
+
className: `dsu-helpBtn`,
|
|
412
|
+
"aria-label": `${a.label} 说明`,
|
|
413
|
+
"aria-describedby": c,
|
|
414
|
+
"aria-pressed": d,
|
|
415
|
+
onClick: () => {
|
|
416
|
+
h.current ? x() : (w(!0), S());
|
|
417
|
+
}
|
|
418
|
+
}, (0, react.createElement)(`svg`, {
|
|
419
|
+
width: 12,
|
|
420
|
+
height: 12,
|
|
421
|
+
viewBox: `0 0 14 14`,
|
|
422
|
+
"aria-hidden": !0
|
|
423
|
+
}, (0, react.createElement)(`circle`, {
|
|
424
|
+
cx: 7,
|
|
425
|
+
cy: 7,
|
|
426
|
+
r: 6,
|
|
427
|
+
fill: `none`,
|
|
428
|
+
stroke: `currentColor`,
|
|
429
|
+
strokeWidth: 1.2
|
|
430
|
+
}), (0, react.createElement)(`path`, {
|
|
431
|
+
d: `M7 6.2v4`,
|
|
432
|
+
stroke: `currentColor`,
|
|
433
|
+
strokeWidth: 1.3,
|
|
434
|
+
strokeLinecap: `round`
|
|
435
|
+
}), (0, react.createElement)(`circle`, {
|
|
436
|
+
cx: 7,
|
|
437
|
+
cy: 4.3,
|
|
438
|
+
r: .8,
|
|
439
|
+
fill: `currentColor`
|
|
440
|
+
}))), E = (0, react.createElement)(`div`, {
|
|
441
|
+
ref: _,
|
|
442
|
+
id: c,
|
|
443
|
+
role: `tooltip`,
|
|
444
|
+
className: `dsu-tip`,
|
|
445
|
+
hidden: !l
|
|
446
|
+
}, a.detail), D = a.showLabel === !1 ? null : (0, react.createElement)(`span`, { className: `dsu-label${a.compact === !0 ? ` dsu-labelMono` : ``}` }, a.label);
|
|
447
|
+
return (0, react.createElement)(`span`, {
|
|
448
|
+
className: `dsu-help`,
|
|
449
|
+
onMouseEnter: () => {
|
|
450
|
+
p.current = !0, S();
|
|
451
|
+
},
|
|
452
|
+
onMouseLeave: () => {
|
|
453
|
+
p.current = !1, C();
|
|
454
|
+
},
|
|
455
|
+
onFocus: () => {
|
|
456
|
+
m.current = !0, S();
|
|
457
|
+
},
|
|
458
|
+
onBlur: (e) => {
|
|
459
|
+
let t = e.relatedTarget;
|
|
460
|
+
t instanceof Node && e.currentTarget.contains(t) || (m.current = !1, C());
|
|
461
|
+
}
|
|
462
|
+
}, D, T, E);
|
|
463
|
+
}
|
|
464
|
+
function h(t) {
|
|
465
|
+
let n = `dsu-label${t.compact === !0 ? ` dsu-labelMono` : ``}`, r = t.controlId === void 0 ? (0, react.createElement)(`span`, { className: n }, t.label) : (0, react.createElement)(`label`, {
|
|
466
|
+
className: n,
|
|
467
|
+
htmlFor: t.controlId
|
|
468
|
+
}, t.label);
|
|
469
|
+
return (0, react.createElement)(`div`, { className: `dsu-labelRow` }, r, t.detail === void 0 ? null : (0, react.createElement)(m, {
|
|
470
|
+
label: t.label,
|
|
471
|
+
detail: t.detail,
|
|
472
|
+
showLabel: !1
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
function g(t) {
|
|
476
|
+
let n = t.controlId === void 0 ? void 0 : `${t.controlId}-hint`;
|
|
477
|
+
return (0, react.createElement)(`div`, { className: `dsu-field${t.wide === !0 ? ` dsu-fieldWide` : ``}` }, (0, react.createElement)(`div`, { className: `dsu-fieldMeta` }, (0, react.createElement)(h, {
|
|
478
|
+
label: t.label,
|
|
479
|
+
detail: t.detail,
|
|
480
|
+
compact: t.compact,
|
|
481
|
+
controlId: t.controlId
|
|
482
|
+
}), t.hint === void 0 ? null : (0, react.createElement)(`p`, {
|
|
483
|
+
className: `dsu-hint`,
|
|
484
|
+
id: n
|
|
485
|
+
}, t.hint)), (0, react.createElement)(`div`, { className: `dsu-control` }, t.children));
|
|
486
|
+
}
|
|
487
|
+
function _(t) {
|
|
488
|
+
let n = (0, react.useId)(), i = t.hint === void 0 ? void 0 : `${n}-hint`;
|
|
489
|
+
return (0, react.createElement)(g, {
|
|
490
|
+
...t,
|
|
491
|
+
controlId: n
|
|
492
|
+
}, (0, react.createElement)(`button`, {
|
|
493
|
+
type: `button`,
|
|
494
|
+
role: `switch`,
|
|
495
|
+
id: n,
|
|
496
|
+
className: `dsu-switch`,
|
|
497
|
+
"aria-checked": t.checked,
|
|
498
|
+
"aria-describedby": i,
|
|
499
|
+
"data-field": t.field,
|
|
500
|
+
disabled: t.disabled === !0,
|
|
501
|
+
onClick: () => {
|
|
502
|
+
t.onChange(!t.checked);
|
|
503
|
+
}
|
|
504
|
+
}, (0, react.createElement)(`span`, { className: `dsu-knob` })));
|
|
505
|
+
}
|
|
506
|
+
function v(t) {
|
|
507
|
+
let i = typeof t.value == `number` && Number.isFinite(t.value) ? String(t.value) : ``, [a, c] = (0, react.useState)(i), [l, u] = (0, react.useState)(!1), d = (0, react.useRef)(void 0), f = (0, react.useId)(), p = (0, react.useId)(), m = [t.hint === void 0 ? void 0 : `${f}-hint`, l ? p : void 0].filter((e) => e !== void 0), h = m.length === 0 ? void 0 : m.join(` `);
|
|
508
|
+
(0, react.useEffect)(() => {
|
|
509
|
+
t.value !== d.current && (d.current = t.value, c(i), u(!1));
|
|
510
|
+
}, [t.value, i]);
|
|
511
|
+
let _ = (e) => {
|
|
512
|
+
let n = t.min ?? -1 / 0, r = t.max ?? 1 / 0;
|
|
513
|
+
if (!Number.isFinite(e) || e < n || e > r) return !1;
|
|
514
|
+
let { step: i } = t;
|
|
515
|
+
if (i === void 0 || i <= 0) return !0;
|
|
516
|
+
let a = (e - (t.min ?? 0)) / i;
|
|
517
|
+
return Math.abs(a - Math.round(a)) < 1e-9;
|
|
518
|
+
}, v = (e) => {
|
|
519
|
+
if (c(e), e === ``) {
|
|
520
|
+
u(!1), d.current = void 0, t.onChange(void 0);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
let n = Number(e);
|
|
524
|
+
_(n) ? (u(!1), d.current = n, t.onChange(n)) : u(!0);
|
|
525
|
+
}, y = () => {
|
|
526
|
+
a === `` || l || c(String(Number(a)));
|
|
527
|
+
};
|
|
528
|
+
return (0, react.createElement)(g, {
|
|
529
|
+
...t,
|
|
530
|
+
controlId: f
|
|
531
|
+
}, (0, react.createElement)(`div`, { className: `dsu-numberWrap` }, (0, react.createElement)(`div`, { className: `dsu-number` }, (0, react.createElement)(`input`, {
|
|
532
|
+
className: `dsu-input`,
|
|
533
|
+
type: `number`,
|
|
534
|
+
inputMode: `numeric`,
|
|
535
|
+
id: f,
|
|
536
|
+
value: a,
|
|
537
|
+
min: t.min,
|
|
538
|
+
max: t.max,
|
|
539
|
+
step: t.step,
|
|
540
|
+
placeholder: t.placeholder,
|
|
541
|
+
"aria-invalid": l || void 0,
|
|
542
|
+
"aria-describedby": h,
|
|
543
|
+
"data-field": t.field,
|
|
544
|
+
disabled: t.disabled === !0,
|
|
545
|
+
onChange: (e) => {
|
|
546
|
+
v(e.target.value);
|
|
547
|
+
},
|
|
548
|
+
onBlur: y
|
|
549
|
+
}), t.unit === void 0 ? null : (0, react.createElement)(`span`, { className: `dsu-unit` }, t.unit)), l ? (0, react.createElement)(`p`, {
|
|
550
|
+
className: `dsu-error`,
|
|
551
|
+
id: p
|
|
552
|
+
}, t.invalidHint) : null));
|
|
553
|
+
}
|
|
554
|
+
function y(t) {
|
|
555
|
+
let [i, a] = (0, react.useState)(t.value), [o, c] = (0, react.useState)(!1), l = (0, react.useId)(), u = t.hint === void 0 ? void 0 : `${l}-hint`;
|
|
556
|
+
(0, react.useEffect)(() => {
|
|
557
|
+
o || a(t.value);
|
|
558
|
+
}, [t.value, o]);
|
|
559
|
+
let d = {
|
|
560
|
+
id: l,
|
|
561
|
+
className: `dsu-input${t.multiline === !0 ? ` dsu-textarea` : ``}${t.monospace === !0 ? ` dsu-mono` : ``}`,
|
|
562
|
+
value: i,
|
|
563
|
+
placeholder: t.placeholder,
|
|
564
|
+
"aria-describedby": u,
|
|
565
|
+
"data-field": t.field,
|
|
566
|
+
disabled: t.disabled === !0,
|
|
567
|
+
onFocus: () => {
|
|
568
|
+
c(!0);
|
|
569
|
+
},
|
|
570
|
+
onBlur: () => {
|
|
571
|
+
c(!1);
|
|
572
|
+
},
|
|
573
|
+
onChange: (e) => {
|
|
574
|
+
a(e.target.value), t.onChange(e.target.value);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
return (0, react.createElement)(g, {
|
|
578
|
+
...t,
|
|
579
|
+
controlId: l
|
|
580
|
+
}, t.multiline === !0 ? (0, react.createElement)(`textarea`, d) : (0, react.createElement)(`input`, d));
|
|
581
|
+
}
|
|
582
|
+
function x(c) {
|
|
583
|
+
let [l, u] = (0, react.useState)(!1), [d, f] = (0, react.useState)(0), p = (0, react.useRef)(null), m = (0, react.useRef)(null), h = (0, react.useId)(), _ = (0, react.useId)(), v = c.hint === void 0 ? void 0 : `${_}-hint`, { disabled: y, value: b, options: x, onChange: S } = c, C = (0, react.useMemo)(() => x.map((e, t) => ({
|
|
584
|
+
option: e,
|
|
585
|
+
index: t
|
|
586
|
+
})).filter((e) => e.option.disabled !== !0), [x]), w = x.find((e) => e.value === b)?.label ?? b, T = x[d], E = T !== void 0 && T.disabled !== !0, D = (0, react.useCallback)(() => {
|
|
587
|
+
let e = p.current, t = m.current;
|
|
588
|
+
/* v8 ignore next 3 -- place 只在 open 的 layout effect 里调用;refs 为空只可能是卸载竞态 */
|
|
589
|
+
if (e === null || t === null) return;
|
|
590
|
+
let n = e.getBoundingClientRect(), r = Math.min(n.width, window.innerWidth - 16), { left: i } = n;
|
|
591
|
+
i + r > window.innerWidth - 8 && (i = window.innerWidth - r - 8);
|
|
592
|
+
let a = t.offsetHeight, o = n.bottom + 6;
|
|
593
|
+
o + a > window.innerHeight - 8 && (o = n.top - a - 6);
|
|
594
|
+
let s = Math.max(8, window.innerWidth - r - 8), c = Math.max(8, window.innerHeight - a - 8);
|
|
595
|
+
t.style.width = `${r}px`, t.style.left = `${Math.min(Math.max(8, i), s)}px`, t.style.top = `${Math.min(Math.max(8, o), c)}px`;
|
|
596
|
+
}, []), O = (0, react.useCallback)(() => {
|
|
597
|
+
u(!1);
|
|
598
|
+
}, []), k = (0, react.useCallback)(() => {
|
|
599
|
+
if (y === !0) return;
|
|
600
|
+
let e = C.find((e) => e.option.value === b) ?? C.at(0);
|
|
601
|
+
e !== void 0 && (f(e.index), u(!0));
|
|
602
|
+
}, [
|
|
603
|
+
y,
|
|
604
|
+
b,
|
|
605
|
+
C
|
|
606
|
+
]), A = (0, react.useCallback)((e) => {
|
|
607
|
+
/* v8 ignore next 3 -- 浮层只能经由 openMenu 打开;组件禁用时打不开 */
|
|
608
|
+
if (y === !0) return;
|
|
609
|
+
let t = x[e];
|
|
610
|
+
/* v8 ignore next 3 -- disabled 选项在浏览器里不派发 click;越界下标由调用面保证 */
|
|
611
|
+
t !== void 0 && t.disabled !== !0 && (S(t.value), O(), p.current?.focus());
|
|
612
|
+
}, [
|
|
613
|
+
y,
|
|
614
|
+
x,
|
|
615
|
+
S,
|
|
616
|
+
O
|
|
617
|
+
]), j = (0, react.useCallback)((e) => {
|
|
618
|
+
let t = C.findIndex((e) => e.index === d);
|
|
619
|
+
/* v8 ignore next 5 -- open 时归一 effect 已保证 enabled 非空且 active 落在可用项上 */
|
|
620
|
+
if (t === -1 || C.length === 0) return;
|
|
621
|
+
let n = (t + e + C.length) % C.length, r = C[n];
|
|
622
|
+
/* v8 ignore next 3 -- next 由取模保证在界内 */
|
|
623
|
+
r !== void 0 && f(r.index);
|
|
624
|
+
}, [d, C]);
|
|
625
|
+
(0, react.useLayoutEffect)(() => {
|
|
626
|
+
if (!l) return () => {};
|
|
627
|
+
D();
|
|
628
|
+
let e = (e) => {
|
|
629
|
+
let t = e.target;
|
|
630
|
+
m.current?.contains(t) !== !0 && p.current?.contains(t) !== !0 && O();
|
|
631
|
+
}, t = (e) => {
|
|
632
|
+
let { target: t } = e;
|
|
633
|
+
t instanceof Node && m.current?.contains(t) === !0 || O();
|
|
634
|
+
}, n = () => {
|
|
635
|
+
D();
|
|
636
|
+
};
|
|
637
|
+
return document.addEventListener(`pointerdown`, e, !0), window.addEventListener(`scroll`, t, !0), window.addEventListener(`resize`, n), () => {
|
|
638
|
+
document.removeEventListener(`pointerdown`, e, !0), window.removeEventListener(`scroll`, t, !0), window.removeEventListener(`resize`, n);
|
|
639
|
+
};
|
|
640
|
+
}, [
|
|
641
|
+
l,
|
|
642
|
+
c.options,
|
|
643
|
+
D,
|
|
644
|
+
O
|
|
645
|
+
]), (0, react.useEffect)(() => {
|
|
646
|
+
if (!l) return;
|
|
647
|
+
if (y === !0 || C.length === 0) {
|
|
648
|
+
O();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (C.some((e) => e.index === d)) return;
|
|
652
|
+
let e = C.find((e) => e.option.value === b) ?? C.at(0);
|
|
653
|
+
/* v8 ignore next 3 -- enabled 非空在上一分支已保证 */
|
|
654
|
+
e !== void 0 && f(e.index);
|
|
655
|
+
}, [
|
|
656
|
+
l,
|
|
657
|
+
y,
|
|
658
|
+
b,
|
|
659
|
+
x,
|
|
660
|
+
d,
|
|
661
|
+
O,
|
|
662
|
+
C
|
|
663
|
+
]), (0, react.useLayoutEffect)(() => {
|
|
664
|
+
if (!l) return;
|
|
665
|
+
let e = m.current;
|
|
666
|
+
/* v8 ignore next 2 -- 同上,menu 在 open 的 commit 之后必然已挂上 */
|
|
667
|
+
if (e === null) return;
|
|
668
|
+
let t = e.querySelector(`[data-active="true"]`);
|
|
669
|
+
/* v8 ignore next 2 -- active 已由归一 effect 保证落在可用项上 */
|
|
670
|
+
t !== null && (t.offsetTop < e.scrollTop ? e.scrollTop = t.offsetTop : t.offsetTop + t.offsetHeight > e.scrollTop + e.clientHeight && (e.scrollTop = t.offsetTop + t.offsetHeight - e.clientHeight));
|
|
671
|
+
}, [
|
|
672
|
+
l,
|
|
673
|
+
d,
|
|
674
|
+
x
|
|
675
|
+
]);
|
|
676
|
+
let M = (e) => {
|
|
677
|
+
if (e.key === `ArrowDown` || e.key === `ArrowUp`) {
|
|
678
|
+
if (e.preventDefault(), !l) {
|
|
679
|
+
k();
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
j(e.key === `ArrowDown` ? 1 : -1);
|
|
683
|
+
} else if (e.key === `Home` || e.key === `End`) {
|
|
684
|
+
if (e.preventDefault(), l || k(), C.length === 0) return;
|
|
685
|
+
let t = e.key === `Home` ? C.at(0) : C.at(-1);
|
|
686
|
+
/* v8 ignore next 3 -- enabled 为空已在上一条返回 */
|
|
687
|
+
if (t === void 0) return;
|
|
688
|
+
f(t.index);
|
|
689
|
+
} else if (e.key === `Enter` || e.key === ` `) {
|
|
690
|
+
if (e.preventDefault(), !l) {
|
|
691
|
+
k();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
A(d);
|
|
695
|
+
} else (e.key === `Tab` || e.key === `Escape`) && O();
|
|
696
|
+
}, N = [], P;
|
|
697
|
+
for (let [t, n] of x.entries()) n.group !== P && (n.group !== void 0 && N.push((0, react.createElement)(`div`, {
|
|
698
|
+
key: `group-${t}-${n.group}`,
|
|
699
|
+
className: `dsu-optionGroup`,
|
|
700
|
+
role: `presentation`
|
|
701
|
+
}, n.group)), P = n.group), N.push((0, react.createElement)(`button`, {
|
|
702
|
+
key: `option-${n.value}`,
|
|
703
|
+
type: `button`,
|
|
704
|
+
role: `option`,
|
|
705
|
+
id: `${h}-${t}`,
|
|
706
|
+
className: `dsu-option`,
|
|
707
|
+
tabIndex: -1,
|
|
708
|
+
"aria-selected": n.value === c.value,
|
|
709
|
+
"data-active": t === d,
|
|
710
|
+
disabled: n.disabled === !0,
|
|
711
|
+
onMouseDown: (e) => {
|
|
712
|
+
e.preventDefault();
|
|
713
|
+
},
|
|
714
|
+
onClick: () => {
|
|
715
|
+
A(t);
|
|
716
|
+
},
|
|
717
|
+
onMouseEnter: () => {
|
|
718
|
+
/* v8 ignore next -- happy-dom 对 disabled 元素不派发 mouse 事件;浏览器实现各有差异,保留禁用检查 */
|
|
719
|
+
n.disabled !== !0 && f(t);
|
|
720
|
+
}
|
|
721
|
+
}, (0, react.createElement)(`span`, null, n.label), (0, react.createElement)(`svg`, {
|
|
722
|
+
className: `dsu-check`,
|
|
723
|
+
width: 14,
|
|
724
|
+
height: 14,
|
|
725
|
+
viewBox: `0 0 14 14`,
|
|
726
|
+
"aria-hidden": !0
|
|
727
|
+
}, (0, react.createElement)(`path`, {
|
|
728
|
+
d: `M2.5 7.5l3 3 6-7`,
|
|
729
|
+
fill: `none`,
|
|
730
|
+
stroke: `currentColor`,
|
|
731
|
+
strokeWidth: 1.5,
|
|
732
|
+
strokeLinecap: `round`,
|
|
733
|
+
strokeLinejoin: `round`
|
|
734
|
+
}))));
|
|
735
|
+
return (0, react.createElement)(g, {
|
|
736
|
+
...c,
|
|
737
|
+
controlId: _
|
|
738
|
+
}, (0, react.createElement)(`div`, {
|
|
739
|
+
className: `dsu-selectWrap`,
|
|
740
|
+
onBlur: (e) => {
|
|
741
|
+
let t = e.relatedTarget;
|
|
742
|
+
t instanceof Node && e.currentTarget.contains(t) || O();
|
|
743
|
+
}
|
|
744
|
+
}, (0, react.createElement)(`button`, {
|
|
745
|
+
ref: p,
|
|
746
|
+
type: `button`,
|
|
747
|
+
id: _,
|
|
748
|
+
className: `dsu-select dsu-input`,
|
|
749
|
+
role: `combobox`,
|
|
750
|
+
"aria-haspopup": `listbox`,
|
|
751
|
+
"aria-expanded": l,
|
|
752
|
+
"aria-controls": `${h}-list`,
|
|
753
|
+
"aria-activedescendant": l && E ? `${h}-${d}` : void 0,
|
|
754
|
+
"aria-describedby": v,
|
|
755
|
+
"data-field": c.field,
|
|
756
|
+
disabled: y === !0 || x.length === 0,
|
|
757
|
+
onClick: () => {
|
|
758
|
+
l ? O() : k();
|
|
759
|
+
},
|
|
760
|
+
onKeyDown: M
|
|
761
|
+
}, (0, react.createElement)(`span`, { className: `dsu-selectValue` }, w), (0, react.createElement)(`svg`, {
|
|
762
|
+
className: `dsu-chevron`,
|
|
763
|
+
width: 14,
|
|
764
|
+
height: 14,
|
|
765
|
+
viewBox: `0 0 14 14`,
|
|
766
|
+
"aria-hidden": !0
|
|
767
|
+
}, (0, react.createElement)(`path`, {
|
|
768
|
+
d: `M3 5l4 4 4-4`,
|
|
769
|
+
fill: `none`,
|
|
770
|
+
stroke: `currentColor`,
|
|
771
|
+
strokeWidth: 1.5,
|
|
772
|
+
strokeLinecap: `round`,
|
|
773
|
+
strokeLinejoin: `round`
|
|
774
|
+
}))), (0, react.createElement)(`div`, {
|
|
775
|
+
ref: m,
|
|
776
|
+
id: `${h}-list`,
|
|
777
|
+
role: `listbox`,
|
|
778
|
+
className: `dsu-menu`,
|
|
779
|
+
"aria-label": c.label,
|
|
780
|
+
hidden: !l
|
|
781
|
+
}, ...N)));
|
|
782
|
+
}
|
|
783
|
+
function S(t) {
|
|
784
|
+
let n = !t.writable || t.busy || !t.dirty, r;
|
|
785
|
+
return r = t.error === null ? t.writable ? t.dirty ? t.dirtyLabel : t.cleanLabel : t.readOnlyLabel : t.error, (0, react.createElement)(`div`, {
|
|
786
|
+
className: `dsu-savebar`,
|
|
787
|
+
"data-dirty": t.dirty
|
|
788
|
+
}, (0, react.createElement)(`button`, {
|
|
789
|
+
type: `button`,
|
|
790
|
+
className: `dsu-btn dsu-btnPrimary`,
|
|
791
|
+
"data-field": `save`,
|
|
792
|
+
disabled: n,
|
|
793
|
+
onClick: t.onSave
|
|
794
|
+
}, t.busy ? t.savingLabel : t.saveLabel), (0, react.createElement)(`button`, {
|
|
795
|
+
type: `button`,
|
|
796
|
+
className: `dsu-btn`,
|
|
797
|
+
"data-field": `discard`,
|
|
798
|
+
disabled: n,
|
|
799
|
+
onClick: t.onDiscard
|
|
800
|
+
}, t.revertLabel), (0, react.createElement)(`span`, { className: t.error === null ? `dsu-status` : `dsu-status dsu-statusError` }, r));
|
|
801
|
+
}
|
|
269
802
|
// lib/remote-shape.ts
|
|
270
803
|
/** 宿主服务键 = 客户端线上命名空间(两侧共用的唯一来源)。 */
|
|
271
804
|
const OCR_REMOTE_NAMESPACE = "ocrReview";
|
|
@@ -282,6 +815,24 @@ const OCR_SERVICE_KEY = `remote.${OCR_REMOTE_NAMESPACE}`;
|
|
|
282
815
|
/** 卡片所在 bundle 的包名(`$mount` 的 `package` 位与槽位键同源;唯一定义在
|
|
283
816
|
* lib/remote-shape.ts,client-entry.ts 的槽位键引的是同一个常量)。 */
|
|
284
817
|
const PACKAGE_NAME = OCR_BUNDLE_PACKAGE;
|
|
818
|
+
/** strict codec 的统一出口:失败交给 zod 的 `prettifyError`,读到的是一份带字段路径的说明。 */
|
|
819
|
+
function parseOrThrow(schema, value) {
|
|
820
|
+
const parsed = schema.safeParse(value);
|
|
821
|
+
if (!parsed.success) throw new TypeError(zod.z.prettifyError(parsed.error));
|
|
822
|
+
return parsed.data;
|
|
823
|
+
}
|
|
824
|
+
const SELECT_MESSAGE = "select request requires string provider and model";
|
|
825
|
+
const SELECT_SCHEMA = zod.z.object({
|
|
826
|
+
provider: zod.z.string({ error: SELECT_MESSAGE }),
|
|
827
|
+
model: zod.z.string({ error: SELECT_MESSAGE })
|
|
828
|
+
}, { error: "select request must be an object" });
|
|
829
|
+
const MCP_SAVE_MESSAGE = "mcp save request requires a string json";
|
|
830
|
+
const MCP_SAVE_SCHEMA = zod.z.object({
|
|
831
|
+
json: zod.z.string({ error: MCP_SAVE_MESSAGE }),
|
|
832
|
+
revision: zod.z.string().optional()
|
|
833
|
+
}, { error: MCP_SAVE_MESSAGE });
|
|
834
|
+
const CHAT_GPT_MESSAGE = "chatgpt request requires a string model";
|
|
835
|
+
const CHAT_GPT_SCHEMA = zod.z.object({ model: zod.z.string({ error: CHAT_GPT_MESSAGE }) }, { error: CHAT_GPT_MESSAGE });
|
|
285
836
|
/** 把宿主那九个方法挂到客户端远程面上的描述符(`id` 的形状照生成器:`<包>#<命名空间>/<方法>`)。
|
|
286
837
|
* 导出它只为一件事:`test/remote.test.ts` 拿它与宿主侧 `remoteMethods()` 的标记对质
|
|
287
838
|
* —— 两侧分叉时卡片不会报错,只会"读不到",所以那条比较必须在门禁里。 */
|
|
@@ -309,13 +860,7 @@ const OCR_DESCRIPTORS = [
|
|
|
309
860
|
mode: "strict",
|
|
310
861
|
typeSymbol: `${PACKAGE_NAME}#select-request`,
|
|
311
862
|
create: () => ({ parse(value) {
|
|
312
|
-
|
|
313
|
-
const { provider, model } = value;
|
|
314
|
-
if (typeof provider !== "string" || typeof model !== "string") throw new TypeError("select request requires string provider and model");
|
|
315
|
-
return {
|
|
316
|
-
provider,
|
|
317
|
-
model
|
|
318
|
-
};
|
|
863
|
+
return parseOrThrow(SELECT_SCHEMA, value);
|
|
319
864
|
} })
|
|
320
865
|
}
|
|
321
866
|
}],
|
|
@@ -371,8 +916,7 @@ const OCR_DESCRIPTORS = [
|
|
|
371
916
|
mode: "strict",
|
|
372
917
|
typeSymbol: `${PACKAGE_NAME}#mcp-save-request`,
|
|
373
918
|
create: () => ({ parse(value) {
|
|
374
|
-
|
|
375
|
-
return { json: value["json"] };
|
|
919
|
+
return parseOrThrow(MCP_SAVE_SCHEMA, value);
|
|
376
920
|
} })
|
|
377
921
|
}
|
|
378
922
|
}],
|
|
@@ -401,8 +945,7 @@ const OCR_DESCRIPTORS = [
|
|
|
401
945
|
mode: "strict",
|
|
402
946
|
typeSymbol: `${PACKAGE_NAME}#chatgpt-request`,
|
|
403
947
|
create: () => ({ parse(value) {
|
|
404
|
-
|
|
405
|
-
return { model: value["model"] };
|
|
948
|
+
return parseOrThrow(CHAT_GPT_SCHEMA, value);
|
|
406
949
|
} })
|
|
407
950
|
}
|
|
408
951
|
}],
|
|
@@ -410,12 +953,13 @@ const OCR_DESCRIPTORS = [
|
|
|
410
953
|
}
|
|
411
954
|
];
|
|
412
955
|
/** 这个值能当远程面用吗(只核我们要调的那一位;服务来自别的实现或版本时保守当作没有)。 */
|
|
413
|
-
const isRemoteMounter = (value) => e$
|
|
956
|
+
const isRemoteMounter = (value) => e$3(value) && typeof value["$mount"] === "function";
|
|
414
957
|
/** 同上:命名空间服务(挂载之后才有,缺它说明网关没认这枚贡献)。 */
|
|
415
|
-
const isOcrNamespace = (value) => e$
|
|
416
|
-
/** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。
|
|
958
|
+
const isOcrNamespace = (value) => e$3(value) && typeof value["providers"] === "function" && typeof value["select"] === "function" && typeof value["migrate"] === "function" && typeof value["test"] === "function" && typeof value["probe"] === "function" && typeof value["mcp"] === "function" && typeof value["saveMcp"] === "function" && typeof value["auth"] === "function" && typeof value["chatGpt"] === "function";
|
|
959
|
+
/** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。
|
|
960
|
+
* 机器码放 `cause`:卡片只展示 message,`REMOTE_...` 这类内部标识不是给用户看的。 */
|
|
417
961
|
function unwrap(result) {
|
|
418
|
-
if (!result.ok) throw new Error(
|
|
962
|
+
if (!result.ok) throw new Error(result.error.message, { cause: { code: result.error.code } });
|
|
419
963
|
return result.value;
|
|
420
964
|
}
|
|
421
965
|
/**
|
|
@@ -431,7 +975,7 @@ function buildOcrReader(ctx) {
|
|
|
431
975
|
let mounted;
|
|
432
976
|
const remoteOf = () => {
|
|
433
977
|
const remote = ctx.get?.("remote", false);
|
|
434
|
-
if (!isRemoteMounter(remote)) throw new Error("remote
|
|
978
|
+
if (!isRemoteMounter(remote)) throw new Error("remote 未提供:当前部署没有远程读面");
|
|
435
979
|
return remote;
|
|
436
980
|
};
|
|
437
981
|
const namespaceOf = async () => {
|
|
@@ -447,7 +991,10 @@ function buildOcrReader(ctx) {
|
|
|
447
991
|
throw error;
|
|
448
992
|
}
|
|
449
993
|
const namespace = ctx.get?.(OCR_SERVICE_KEY, false);
|
|
450
|
-
if (!isOcrNamespace(namespace))
|
|
994
|
+
if (!isOcrNamespace(namespace)) {
|
|
995
|
+
mounted = void 0;
|
|
996
|
+
throw new Error(`${OCR_SERVICE_KEY} 未提供:宿主网关没有挂上这枚命名空间`);
|
|
997
|
+
}
|
|
451
998
|
return namespace;
|
|
452
999
|
};
|
|
453
1000
|
return {
|
|
@@ -472,8 +1019,11 @@ function buildOcrReader(ctx) {
|
|
|
472
1019
|
async mcp() {
|
|
473
1020
|
return unwrap(await (await namespaceOf()).mcp());
|
|
474
1021
|
},
|
|
475
|
-
async saveMcp(json) {
|
|
476
|
-
return unwrap(await (await namespaceOf()).saveMcp({ json }
|
|
1022
|
+
async saveMcp(json, revision) {
|
|
1023
|
+
return unwrap(await (await namespaceOf()).saveMcp(revision === void 0 ? { json } : {
|
|
1024
|
+
json,
|
|
1025
|
+
revision
|
|
1026
|
+
}));
|
|
477
1027
|
},
|
|
478
1028
|
async auth() {
|
|
479
1029
|
return unwrap(await (await namespaceOf()).auth());
|
|
@@ -524,35 +1074,65 @@ const BUNDLE_PKG = OCR_BUNDLE_PACKAGE;
|
|
|
524
1074
|
function strOf(value) {
|
|
525
1075
|
return typeof value === "string" ? value : "";
|
|
526
1076
|
}
|
|
527
|
-
/**
|
|
528
|
-
|
|
529
|
-
|
|
1077
|
+
/** 设置行的取值域:枚举/计数都交给 zod 声明,界面只消费解析后的类型。 */
|
|
1078
|
+
const EFFORT_SCHEMA = zod.z.enum([
|
|
1079
|
+
"auto",
|
|
1080
|
+
"low",
|
|
1081
|
+
"medium",
|
|
1082
|
+
"high"
|
|
1083
|
+
]);
|
|
1084
|
+
const LANGUAGE_SCHEMA = zod.z.enum(["中文", "English"]);
|
|
1085
|
+
const COUNT_SCHEMA = zod.z.int().min(0);
|
|
1086
|
+
/** 未知值 → 安全数字(非整数/越界给 fallback)。 */
|
|
1087
|
+
function numOf(value, fallback, schema) {
|
|
1088
|
+
const parsed = schema.safeParse(value);
|
|
1089
|
+
return parsed.success ? parsed.data : fallback;
|
|
1090
|
+
}
|
|
1091
|
+
/** 未知值 → 枚举里的一个取值(域外给 fallback)。 */
|
|
1092
|
+
function textOf(value, schema, fallback) {
|
|
1093
|
+
const parsed = schema.safeParse(value);
|
|
1094
|
+
return parsed.success ? parsed.data : fallback;
|
|
1095
|
+
}
|
|
1096
|
+
/** 未知值 → 布尔(非布尔给 fallback)。 */
|
|
1097
|
+
function booleanOf(value, fallback) {
|
|
1098
|
+
const parsed = zod.z.boolean().safeParse(value);
|
|
1099
|
+
return parsed.success ? parsed.data : fallback;
|
|
530
1100
|
}
|
|
531
1101
|
/** 错误摘要(非 Error 抛出也要可读;catch 分支共用)。 */
|
|
532
1102
|
function errorSummary(error) {
|
|
533
1103
|
return String(error instanceof Error ? error.message : error);
|
|
534
1104
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1105
|
+
const STRING_OR_EMPTY = zod.z.preprocess((value) => typeof value === "string" ? value : "", zod.z.string());
|
|
1106
|
+
const NULLABLE_STRING = zod.z.preprocess((value) => typeof value === "string" ? value : null, zod.z.string().nullable());
|
|
1107
|
+
const TRUE_ONLY = zod.z.preprocess((value) => value === true, zod.z.boolean());
|
|
1108
|
+
const MODEL_SCHEMA = zod.z.object({
|
|
1109
|
+
id: STRING_OR_EMPTY,
|
|
1110
|
+
name: STRING_OR_EMPTY
|
|
1111
|
+
});
|
|
1112
|
+
const PROVIDER_SCHEMA = zod.z.object({
|
|
1113
|
+
name: STRING_OR_EMPTY,
|
|
1114
|
+
displayName: STRING_OR_EMPTY,
|
|
1115
|
+
apiKeyEnv: NULLABLE_STRING,
|
|
1116
|
+
baseURL: STRING_OR_EMPTY,
|
|
1117
|
+
models: zod.z.preprocess((value) => Array.isArray(value) ? value.map((item) => e$3(item) ? item : {}) : [], zod.z.array(MODEL_SCHEMA)),
|
|
1118
|
+
hasKey: TRUE_ONLY
|
|
1119
|
+
});
|
|
1120
|
+
const CURRENT_SCHEMA = zod.z.object({
|
|
1121
|
+
provider: STRING_OR_EMPTY,
|
|
1122
|
+
model: STRING_OR_EMPTY,
|
|
1123
|
+
keyIsDynamic: TRUE_ONLY,
|
|
1124
|
+
url: STRING_OR_EMPTY
|
|
1125
|
+
});
|
|
1126
|
+
const SOURCE_SCHEMA = zod.z.object({
|
|
1127
|
+
status: STRING_OR_EMPTY,
|
|
1128
|
+
message: STRING_OR_EMPTY
|
|
1129
|
+
});
|
|
1130
|
+
const ENDPOINT_VERIFY_SCHEMA = zod.z.object({
|
|
1131
|
+
ok: TRUE_ONLY,
|
|
1132
|
+
message: STRING_OR_EMPTY
|
|
1133
|
+
});
|
|
554
1134
|
/** 可得性来源的合法域(与 host 侧 `OcrBinarySource` 同一份取值)。形状不合即「没报」。 */
|
|
555
|
-
const
|
|
1135
|
+
const BINARY_SOURCE_SCHEMA = zod.z.enum([
|
|
556
1136
|
"bundled",
|
|
557
1137
|
"path",
|
|
558
1138
|
"none"
|
|
@@ -565,22 +1145,25 @@ const BINARY_SOURCES = /* @__PURE__ */ new Set([
|
|
|
565
1145
|
* 作废——「宿主报了 PATH 却没报版本」仍然是一条用户要看的事实。`measuredVersion` 例外,
|
|
566
1146
|
* 它恒在(null = 还没测过),因为「没测过」与「旧宿主不带这字段」在卡片上是同一句话。
|
|
567
1147
|
*/
|
|
1148
|
+
const BINARY_SCHEMA = zod.z.object({
|
|
1149
|
+
source: BINARY_SOURCE_SCHEMA,
|
|
1150
|
+
path: STRING_OR_EMPTY,
|
|
1151
|
+
channel: STRING_OR_EMPTY,
|
|
1152
|
+
expectedVersion: STRING_OR_EMPTY,
|
|
1153
|
+
installHint: STRING_OR_EMPTY,
|
|
1154
|
+
measuredVersion: NULLABLE_STRING
|
|
1155
|
+
});
|
|
568
1156
|
function binaryOf(raw) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const path = strOf(raw["path"]);
|
|
573
|
-
const channel = strOf(raw["channel"]);
|
|
574
|
-
const expectedVersion = strOf(raw["expectedVersion"]);
|
|
575
|
-
const installHint = strOf(raw["installHint"]);
|
|
576
|
-
const measured = raw["measuredVersion"];
|
|
1157
|
+
const parsed = BINARY_SCHEMA.safeParse(raw);
|
|
1158
|
+
if (!parsed.success) return null;
|
|
1159
|
+
const { source, path, channel, expectedVersion, installHint, measuredVersion } = parsed.data;
|
|
577
1160
|
return {
|
|
578
1161
|
source,
|
|
579
1162
|
...path === "" ? {} : { path },
|
|
580
1163
|
...channel === "" ? {} : { channel },
|
|
581
1164
|
...expectedVersion === "" ? {} : { expectedVersion },
|
|
582
1165
|
...installHint === "" ? {} : { installHint },
|
|
583
|
-
measuredVersion
|
|
1166
|
+
measuredVersion
|
|
584
1167
|
};
|
|
585
1168
|
}
|
|
586
1169
|
/**
|
|
@@ -591,67 +1174,55 @@ function binaryOf(raw) {
|
|
|
591
1174
|
async function loadCliStatus(reader) {
|
|
592
1175
|
if (reader === void 0) return null;
|
|
593
1176
|
try {
|
|
594
|
-
return binaryOf(i(await reader.providers(), "ocrBinary"));
|
|
1177
|
+
return binaryOf(i$1(await reader.providers(), "ocrBinary"));
|
|
595
1178
|
} catch {
|
|
596
1179
|
return null;
|
|
597
1180
|
}
|
|
598
1181
|
}
|
|
1182
|
+
const EMPTY_PAYLOAD = {
|
|
1183
|
+
providers: [],
|
|
1184
|
+
current: {
|
|
1185
|
+
provider: "",
|
|
1186
|
+
model: "",
|
|
1187
|
+
keyIsDynamic: false,
|
|
1188
|
+
url: ""
|
|
1189
|
+
},
|
|
1190
|
+
getCredReady: false,
|
|
1191
|
+
source: {
|
|
1192
|
+
status: "",
|
|
1193
|
+
message: ""
|
|
1194
|
+
},
|
|
1195
|
+
endpointVerify: null,
|
|
1196
|
+
ocrBinary: null
|
|
1197
|
+
};
|
|
1198
|
+
const PROVIDERS_PAYLOAD_SCHEMA = zod.z.object({
|
|
1199
|
+
providers: zod.z.preprocess((value) => Array.isArray(value) ? value.map((item) => e$3(item) ? item : {}) : [], zod.z.array(PROVIDER_SCHEMA)),
|
|
1200
|
+
current: zod.z.preprocess((value) => e$3(value) ? value : {}, CURRENT_SCHEMA),
|
|
1201
|
+
getCredReady: TRUE_ONLY,
|
|
1202
|
+
source: zod.z.preprocess((value) => e$3(value) ? value : {}, SOURCE_SCHEMA),
|
|
1203
|
+
endpointVerify: zod.z.preprocess((value) => {
|
|
1204
|
+
const parsed = ENDPOINT_VERIFY_SCHEMA.safeParse(value);
|
|
1205
|
+
return parsed.success ? parsed.data : null;
|
|
1206
|
+
}, ENDPOINT_VERIFY_SCHEMA.nullable()),
|
|
1207
|
+
ocrBinary: zod.z.unknown().transform((value) => binaryOf(value))
|
|
1208
|
+
});
|
|
599
1209
|
function providersPayloadOf(json) {
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
const currentRaw = i(json, "current");
|
|
603
|
-
const current = {
|
|
604
|
-
provider: strOf(i(currentRaw, "provider")),
|
|
605
|
-
model: strOf(i(currentRaw, "model")),
|
|
606
|
-
keyIsDynamic: i(currentRaw, "keyIsDynamic") === true,
|
|
607
|
-
url: strOf(i(currentRaw, "url"))
|
|
608
|
-
};
|
|
609
|
-
const sourceRaw = i(json, "source");
|
|
610
|
-
const verifyRaw = i(json, "endpointVerify");
|
|
611
|
-
const endpointVerify = verifyRaw === null || verifyRaw === void 0 ? null : {
|
|
612
|
-
ok: i(verifyRaw, "ok") === true,
|
|
613
|
-
message: strOf(i(verifyRaw, "message"))
|
|
614
|
-
};
|
|
615
|
-
return {
|
|
616
|
-
providers,
|
|
617
|
-
current,
|
|
618
|
-
getCredReady: i(json, "getCredReady") === true,
|
|
619
|
-
source: {
|
|
620
|
-
status: strOf(i(sourceRaw, "status")),
|
|
621
|
-
message: strOf(i(sourceRaw, "message"))
|
|
622
|
-
},
|
|
623
|
-
endpointVerify,
|
|
624
|
-
ocrBinary: binaryOf(i(json, "ocrBinary"))
|
|
625
|
-
};
|
|
1210
|
+
const parsed = PROVIDERS_PAYLOAD_SCHEMA.safeParse(json);
|
|
1211
|
+
return parsed.success ? parsed.data : EMPTY_PAYLOAD;
|
|
626
1212
|
}
|
|
627
|
-
/**
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const FORM_CONTROL_CLASS = "ocr-select";
|
|
631
|
-
/** 小节标题的类名(卡片头、CLI 状态行、详情页区块共用;sonarjs 的重复字面量门禁)。 */
|
|
1213
|
+
/** 动作按钮行的类名(共享组件的行内按钮组;多处共用,见 sonarjs 的重复字面量门禁)。 */
|
|
1214
|
+
const ACTIONS_CLASS = "ocr-actions";
|
|
1215
|
+
/** 小节标题的类名(详情页区块共用;sonarjs 的重复字面量门禁)。 */
|
|
632
1216
|
const CARD_NAME_CLASS = "ocr-card-name";
|
|
633
1217
|
const CARD_CSS = [
|
|
634
|
-
".ocr-card{
|
|
635
|
-
".ocr-
|
|
636
|
-
".ocr-
|
|
637
|
-
".ocr-
|
|
638
|
-
".ocr-
|
|
639
|
-
".ocr-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
|
|
640
|
-
".ocr-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
|
|
641
|
-
".ocr-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
|
|
642
|
-
".ocr-card-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}",
|
|
643
|
-
".ocr-card-chevron-open{transform:rotate(180deg)}",
|
|
644
|
-
".ocr-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:8px 0 12px;font-size:13px;line-height:1.5}",
|
|
645
|
-
".ocr-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0}",
|
|
646
|
-
".ocr-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);margin-top:2px}",
|
|
647
|
-
".ocr-select{flex:none;width:min(46%,240px);box-sizing:border-box;font:inherit;font-size:12px;color:var(--dsw-alias-label-primary,inherit);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,#d8dbe2);border-radius:8px;padding:5px 8px}",
|
|
648
|
-
".ocr-btn{appearance:auto;font:inherit;font-size:12px;cursor:pointer;border-radius:6px;padding:4px 12px;border:1px solid var(--dsw-alias-border-l2,#d8dbe2);background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,inherit)}",
|
|
649
|
-
".ocr-btn:disabled{opacity:.5;cursor:not-allowed}",
|
|
650
|
-
".ocr-btn-primary{background:var(--dsw-alias-brand-primary,#2f6fed);border-color:var(--dsw-alias-brand-primary,#2f6fed);color:var(--dsw-alias-label-primary-foreground,#fff)}",
|
|
651
|
-
".ocr-status{font-size:12px;margin-top:6px;white-space:pre-wrap;word-break:break-word}",
|
|
652
|
-
".ocr-err{color:#c4483f}",
|
|
1218
|
+
".ocr-card-name{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.4}",
|
|
1219
|
+
".ocr-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);line-height:1.5}",
|
|
1220
|
+
".ocr-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
|
|
1221
|
+
".ocr-status{font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;margin:8px 0 0}",
|
|
1222
|
+
".ocr-err{color:var(--dsw-alias-state-error-primary)}",
|
|
653
1223
|
".ocr-ok{color:var(--dsw-alias-label-primary,inherit)}",
|
|
654
|
-
".ocr-muted{color:var(--dsw-alias-label-tertiary,#8a8f99)}"
|
|
1224
|
+
".ocr-muted{color:var(--dsw-alias-label-tertiary,#8a8f99)}",
|
|
1225
|
+
".ocr-adminTextarea{min-height:160px}"
|
|
655
1226
|
].join("\n");
|
|
656
1227
|
function cardStore(scope) {
|
|
657
1228
|
let cachedSnap = null;
|
|
@@ -679,190 +1250,6 @@ function cardStore(scope) {
|
|
|
679
1250
|
}
|
|
680
1251
|
};
|
|
681
1252
|
}
|
|
682
|
-
function SaveBar(props) {
|
|
683
|
-
const { t } = props;
|
|
684
|
-
const disabled = !props.writable || props.busy;
|
|
685
|
-
const btnStyle = {
|
|
686
|
-
appearance: "auto",
|
|
687
|
-
font: "inherit",
|
|
688
|
-
fontSize: "12px",
|
|
689
|
-
cursor: disabled || !props.dirty ? "not-allowed" : "pointer",
|
|
690
|
-
borderRadius: "6px",
|
|
691
|
-
padding: "4px 12px",
|
|
692
|
-
border: "1px solid var(--dsw-alias-border-l2,#d8dbe2)",
|
|
693
|
-
background: "var(--dsw-alias-bg-layer-1,transparent)",
|
|
694
|
-
color: "var(--dsw-alias-label-primary,inherit)",
|
|
695
|
-
opacity: disabled ? "0.5" : "1"
|
|
696
|
-
};
|
|
697
|
-
const saveBtn = (0, react.createElement)("button", {
|
|
698
|
-
type: "button",
|
|
699
|
-
"data-field": "save",
|
|
700
|
-
disabled: disabled || !props.dirty,
|
|
701
|
-
onClick: props.onSave,
|
|
702
|
-
style: {
|
|
703
|
-
...btnStyle,
|
|
704
|
-
background: "var(--dsw-alias-brand-primary,#2f6fed)",
|
|
705
|
-
borderColor: "var(--dsw-alias-brand-primary,#2f6fed)",
|
|
706
|
-
color: "var(--dsw-alias-label-primary-foreground,#fff)"
|
|
707
|
-
}
|
|
708
|
-
}, props.busy ? t("saving") : t("save"));
|
|
709
|
-
const discardBtn = (0, react.createElement)("button", {
|
|
710
|
-
type: "button",
|
|
711
|
-
"data-field": "discard",
|
|
712
|
-
disabled: disabled || !props.dirty,
|
|
713
|
-
onClick: props.onDiscard,
|
|
714
|
-
style: btnStyle
|
|
715
|
-
}, t("revert"));
|
|
716
|
-
const dirtyText = props.dirty ? t("statusDirty") : t("statusClean");
|
|
717
|
-
let statusNode;
|
|
718
|
-
if (props.error !== null) statusNode = (0, react.createElement)("span", { style: {
|
|
719
|
-
fontSize: 12,
|
|
720
|
-
color: "#c4483f"
|
|
721
|
-
} }, props.error);
|
|
722
|
-
else if (props.writable) statusNode = (0, react.createElement)("span", { style: {
|
|
723
|
-
fontSize: 12,
|
|
724
|
-
color: "var(--dsw-alias-label-tertiary,#8a8f99)"
|
|
725
|
-
} }, dirtyText);
|
|
726
|
-
else statusNode = (0, react.createElement)("span", { style: {
|
|
727
|
-
fontSize: 12,
|
|
728
|
-
color: "var(--dsw-alias-label-tertiary,#8a8f99)"
|
|
729
|
-
} }, t("statusReadOnly"));
|
|
730
|
-
return (0, react.createElement)("div", { style: {
|
|
731
|
-
display: "flex",
|
|
732
|
-
gap: 8,
|
|
733
|
-
alignItems: "center",
|
|
734
|
-
padding: "10px 0 2px",
|
|
735
|
-
borderTop: "1px dashed var(--dsw-alias-border-l2,#d8dbe2)",
|
|
736
|
-
marginTop: 6,
|
|
737
|
-
flexWrap: "wrap"
|
|
738
|
-
} }, saveBtn, discardBtn, statusNode);
|
|
739
|
-
}
|
|
740
|
-
/** 设置卡外壳:复刻同款自研卡片(边框圆角卡片 + 可折叠 header + chevron)。 */
|
|
741
|
-
function OcrPluginCard(props) {
|
|
742
|
-
const { title, description, children } = props;
|
|
743
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
744
|
-
const pathKey = "d";
|
|
745
|
-
const headRow = (0, react.createElement)("span", { className: "ocr-card-head" }, (0, react.createElement)("span", { className: CARD_NAME_CLASS }, title), (0, react.createElement)("span", { className: "ocr-card-desc" }, description));
|
|
746
|
-
const chevron = (0, react.createElement)("svg", {
|
|
747
|
-
width: 14,
|
|
748
|
-
height: 14,
|
|
749
|
-
viewBox: "0 0 14 14",
|
|
750
|
-
"aria-hidden": true,
|
|
751
|
-
className: `ocr-card-chevron${open ? " ocr-card-chevron-open" : ""}`
|
|
752
|
-
}, (0, react.createElement)("path", {
|
|
753
|
-
[pathKey]: "M3 5l4 4 4-4",
|
|
754
|
-
fill: "none",
|
|
755
|
-
stroke: "currentColor",
|
|
756
|
-
strokeWidth: 1.5,
|
|
757
|
-
strokeLinecap: "round",
|
|
758
|
-
strokeLinejoin: "round"
|
|
759
|
-
}));
|
|
760
|
-
const header = (0, react.createElement)("button", {
|
|
761
|
-
type: "button",
|
|
762
|
-
className: "ocr-card-header",
|
|
763
|
-
"aria-expanded": open,
|
|
764
|
-
onClick: () => {
|
|
765
|
-
setOpen(!open);
|
|
766
|
-
}
|
|
767
|
-
}, headRow, chevron);
|
|
768
|
-
const body = open ? (0, react.createElement)("div", { className: "ocr-card-body" }, children) : null;
|
|
769
|
-
return (0, react.createElement)("div", { className: `ocr-card${open ? " ocr-card-open" : ""}` }, header, body);
|
|
770
|
-
}
|
|
771
|
-
/** 整数数字输入行(保存条模式:合法输入上报草稿,不直写)。 */
|
|
772
|
-
function NumberRow(props) {
|
|
773
|
-
const { label, hint, value, min, max, disabled, onCommit } = props;
|
|
774
|
-
const [draft, setDraft] = (0, react.useState)(String(value));
|
|
775
|
-
(0, react.useEffect)(() => {
|
|
776
|
-
setDraft(String(value));
|
|
777
|
-
}, [value]);
|
|
778
|
-
/** 用**本次输入的值**上报,不读 `draft`:state 更新是异步的,`onChange` 里
|
|
779
|
-
* `setDraft(x)` 之后立刻读 `draft` 拿到的还是上一个字符,草稿会永远滞后一格。 */
|
|
780
|
-
const stage = (text) => {
|
|
781
|
-
const parsed = Number(text);
|
|
782
|
-
if (Number.isInteger(parsed) && parsed >= min && (max === void 0 || parsed <= max)) onCommit(parsed);
|
|
783
|
-
};
|
|
784
|
-
return (0, react.createElement)("div", { className: "ocr-row" }, (0, react.createElement)("div", { style: {
|
|
785
|
-
minWidth: 0,
|
|
786
|
-
marginRight: 12
|
|
787
|
-
} }, (0, react.createElement)("div", {}, label), (0, react.createElement)("div", { className: "ocr-hint" }, hint)), (0, react.createElement)("input", {
|
|
788
|
-
type: "number",
|
|
789
|
-
"data-field": props.field,
|
|
790
|
-
value: draft,
|
|
791
|
-
min,
|
|
792
|
-
max,
|
|
793
|
-
disabled,
|
|
794
|
-
"aria-label": label,
|
|
795
|
-
onChange: (ev) => {
|
|
796
|
-
setDraft(ev.target.value);
|
|
797
|
-
stage(ev.target.value);
|
|
798
|
-
},
|
|
799
|
-
onKeyDown: (ev) => {
|
|
800
|
-
if (ev.key === "Enter") {
|
|
801
|
-
ev.preventDefault();
|
|
802
|
-
stage(draft);
|
|
803
|
-
}
|
|
804
|
-
},
|
|
805
|
-
className: FORM_CONTROL_CLASS
|
|
806
|
-
}));
|
|
807
|
-
}
|
|
808
|
-
/** 文本输入行(保存条模式:改动只上报草稿)。空串是合法取值(= 用宿主默认位置),
|
|
809
|
-
* 所以不像 NumberRow 那样要求「解析成功」才上报。 */
|
|
810
|
-
function TextRow(props) {
|
|
811
|
-
const { label, hint, value, placeholder, disabled, onCommit } = props;
|
|
812
|
-
const [draft, setDraft] = (0, react.useState)(value);
|
|
813
|
-
(0, react.useEffect)(() => {
|
|
814
|
-
setDraft(value);
|
|
815
|
-
}, [value]);
|
|
816
|
-
/** 同 NumberRow:读参数而不是 `draft`,否则草稿永远滞后一格。 */
|
|
817
|
-
const stage = (text) => {
|
|
818
|
-
onCommit(text.trim());
|
|
819
|
-
};
|
|
820
|
-
return (0, react.createElement)("div", { className: "ocr-row" }, (0, react.createElement)("div", { style: {
|
|
821
|
-
minWidth: 0,
|
|
822
|
-
marginRight: 12
|
|
823
|
-
} }, (0, react.createElement)("div", {}, label), (0, react.createElement)("div", { className: "ocr-hint" }, hint)), (0, react.createElement)("input", {
|
|
824
|
-
type: "text",
|
|
825
|
-
"data-field": props.field,
|
|
826
|
-
value: draft,
|
|
827
|
-
placeholder,
|
|
828
|
-
disabled,
|
|
829
|
-
"aria-label": label,
|
|
830
|
-
className: FORM_CONTROL_CLASS,
|
|
831
|
-
style: { width: "min(60%,320px)" },
|
|
832
|
-
onChange: (ev) => {
|
|
833
|
-
setDraft(ev.target.value);
|
|
834
|
-
stage(ev.target.value);
|
|
835
|
-
},
|
|
836
|
-
onKeyDown: (ev) => {
|
|
837
|
-
if (ev.key === "Enter") {
|
|
838
|
-
ev.preventDefault();
|
|
839
|
-
stage(draft);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}));
|
|
843
|
-
}
|
|
844
|
-
function SelectRow(props) {
|
|
845
|
-
const current = typeof props.current === "string" ? props.current : "";
|
|
846
|
-
const options = props.options.map((option) => (0, react.createElement)("option", {
|
|
847
|
-
key: option.value,
|
|
848
|
-
value: option.value,
|
|
849
|
-
disabled: option.disabled === true
|
|
850
|
-
}, option.label));
|
|
851
|
-
const labelBlock = (0, react.createElement)("div", { style: {
|
|
852
|
-
minWidth: 0,
|
|
853
|
-
marginRight: 12
|
|
854
|
-
} }, (0, react.createElement)("div", {}, props.label), (0, react.createElement)("div", { className: "ocr-hint" }, props.hint));
|
|
855
|
-
const select = (0, react.createElement)("select", {
|
|
856
|
-
className: FORM_CONTROL_CLASS,
|
|
857
|
-
"aria-label": props.label,
|
|
858
|
-
value: current,
|
|
859
|
-
disabled: props.disabled,
|
|
860
|
-
onChange: (ev) => {
|
|
861
|
-
props.onChange(props.field, ev.target.value);
|
|
862
|
-
}
|
|
863
|
-
}, options);
|
|
864
|
-
return (0, react.createElement)("div", { className: "ocr-row" }, labelBlock, select);
|
|
865
|
-
}
|
|
866
1253
|
/** provider 下拉项文本(没有 key 的加后缀并被禁用)。提成模块级函数是为了不把
|
|
867
1254
|
* 带插值的 t() 嵌进 createElement(unicorn/max-nested-calls 上限 3)。 */
|
|
868
1255
|
function providerOptionLabel(t, provider) {
|
|
@@ -876,14 +1263,13 @@ function currentConfigText(t, current) {
|
|
|
876
1263
|
keyPart: t(current.keyIsDynamic ? "dynamicKey" : "plainKeyWarning")
|
|
877
1264
|
});
|
|
878
1265
|
}
|
|
879
|
-
/**
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
npm: "channelNpm"
|
|
883
|
-
};
|
|
884
|
-
/** 那份 CLI 是怎么装上的,翻成人话。判不出来源(含 host 没报这一档)不猜。 */
|
|
1266
|
+
/** 那份 CLI 是怎么装上的,翻成人话。判不出来源(含 host 没报这一档)不猜。
|
|
1267
|
+
* 逐个比对已知值而不是查一张普通对象表:`channel` 来自 wire,只校验了「是字符串」,
|
|
1268
|
+
* 用属性查表时 `"toString"`、`"constructor"` 一类会命中原型上的成员,把函数当文案键传下去。 */
|
|
885
1269
|
function channelText(t, channel) {
|
|
886
|
-
|
|
1270
|
+
if (channel === "brew") return t("channelBrew");
|
|
1271
|
+
if (channel === "npm") return t("channelNpm");
|
|
1272
|
+
return t("channelUnknown");
|
|
887
1273
|
}
|
|
888
1274
|
/**
|
|
889
1275
|
* 外部 CLI 可得性那一行的文本。三档来源与「宿主没报」四种说法,判据全在 host 侧
|
|
@@ -939,35 +1325,30 @@ function statusClass(ok) {
|
|
|
939
1325
|
*/
|
|
940
1326
|
function buildCliStatusRow(deps) {
|
|
941
1327
|
const { t } = deps;
|
|
942
|
-
const label = (0, react.createElement)("div", { className: CARD_NAME_CLASS }, t("binaryTitle"));
|
|
943
1328
|
const button = (0, react.createElement)("button", {
|
|
944
|
-
className: "
|
|
1329
|
+
className: "dsu-btn",
|
|
945
1330
|
type: "button",
|
|
946
1331
|
disabled: deps.busy !== null,
|
|
947
1332
|
onClick: deps.onProbe,
|
|
948
1333
|
"data-field": "probe"
|
|
949
1334
|
}, deps.busy === "probe" ? t("detecting") : t("detectCli"));
|
|
950
|
-
const
|
|
951
|
-
className:
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
}
|
|
956
|
-
}, label, button);
|
|
957
|
-
const statusText = binaryStatusText(t, deps.binary);
|
|
958
|
-
const status = (0, react.createElement)("div", { className: "ocr-hint" }, statusText);
|
|
1335
|
+
const controls = (0, react.createElement)("div", {
|
|
1336
|
+
className: ACTIONS_CLASS,
|
|
1337
|
+
"data-field": "cli-status"
|
|
1338
|
+
}, button);
|
|
1339
|
+
const status = (0, react.createElement)("p", { className: "dsu-hint" }, binaryStatusText(t, deps.binary));
|
|
959
1340
|
const versionText = cliVersionText(t, deps.binary);
|
|
960
|
-
const versionLine = versionText === "" ? null : (0, react.createElement)("
|
|
1341
|
+
const versionLine = versionText === "" ? null : (0, react.createElement)("p", { className: "dsu-hint" }, versionText);
|
|
961
1342
|
const hintText = cliInstallText(deps.binary);
|
|
962
|
-
const installLine = hintText === "" ? null : (0, react.createElement)("
|
|
1343
|
+
const installLine = hintText === "" ? null : (0, react.createElement)("p", { className: "dsu-hint" }, hintText);
|
|
963
1344
|
const probe = deps.probeMsg ? (0, react.createElement)("div", {
|
|
964
1345
|
className: statusClass(deps.probeMsg.ok),
|
|
965
1346
|
"data-field": "probe-result"
|
|
966
1347
|
}, deps.probeMsg.text) : null;
|
|
967
|
-
return (0, react.createElement)(
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
},
|
|
1348
|
+
return (0, react.createElement)(d, {
|
|
1349
|
+
title: t("binaryTitle"),
|
|
1350
|
+
hint: t("binaryProbeHint")
|
|
1351
|
+
}, status, versionLine, installLine, controls, probe);
|
|
971
1352
|
}
|
|
972
1353
|
/**
|
|
973
1354
|
* 徽标用的短文案(标题旁那只标签放不下整句装法)。与 `binaryStatusText` 同一判据、
|
|
@@ -1095,100 +1476,165 @@ function OcrCliActivation(props) {
|
|
|
1095
1476
|
if (!mine || status?.source !== "none") return null;
|
|
1096
1477
|
return buildCliActivation(props.t, props, cliInstallText(status));
|
|
1097
1478
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
|
|
1100
|
-
* 的降级支本就不是同一件事。 */
|
|
1101
|
-
function buildReadyProviderSection(deps, data) {
|
|
1479
|
+
/** 数据齐时的动作行:三个按钮共用一只在飞旗标,按钮文案只跟**自己**那条走。 */
|
|
1480
|
+
function buildActionField(deps) {
|
|
1102
1481
|
const { t } = deps;
|
|
1103
|
-
const providerOptions = data.providers.map((provider) => ({
|
|
1104
|
-
value: provider.name,
|
|
1105
|
-
label: providerOptionLabel(t, provider),
|
|
1106
|
-
disabled: !provider.hasKey
|
|
1107
|
-
}));
|
|
1108
|
-
const fallbackModelName = deps.effectiveModel === "" ? t("noModel") : deps.effectiveModel;
|
|
1109
|
-
const modelOptions = (deps.modelOptions.length > 0 ? deps.modelOptions : [{
|
|
1110
|
-
id: deps.effectiveModel,
|
|
1111
|
-
name: fallbackModelName
|
|
1112
|
-
}]).map((model) => ({
|
|
1113
|
-
value: model.id,
|
|
1114
|
-
label: model.name
|
|
1115
|
-
}));
|
|
1116
|
-
const providerRow = (0, react.createElement)(SelectRow, {
|
|
1117
|
-
label: "Provider",
|
|
1118
|
-
hint: t("providerHint"),
|
|
1119
|
-
field: "ocrProvider",
|
|
1120
|
-
current: deps.effectiveProvider,
|
|
1121
|
-
disabled: data.providers.length === 0,
|
|
1122
|
-
options: providerOptions,
|
|
1123
|
-
onChange: (field, value) => {
|
|
1124
|
-
deps.onPickProvider(value);
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1127
|
-
const modelRow = (0, react.createElement)(SelectRow, {
|
|
1128
|
-
label: "Model",
|
|
1129
|
-
hint: deps.chosenProvider ? deps.chosenProvider.baseURL : "",
|
|
1130
|
-
field: "ocrModel",
|
|
1131
|
-
current: deps.effectiveModel,
|
|
1132
|
-
disabled: !deps.chosenProvider || deps.modelOptions.length === 0,
|
|
1133
|
-
options: modelOptions,
|
|
1134
|
-
onChange: (field, value) => {
|
|
1135
|
-
deps.onPickModel(value);
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
1482
|
const applyBtn = (0, react.createElement)("button", {
|
|
1139
|
-
className: "
|
|
1483
|
+
className: "dsu-btn dsu-btnPrimary",
|
|
1140
1484
|
type: "button",
|
|
1141
1485
|
disabled: deps.busy !== null,
|
|
1142
1486
|
onClick: deps.onApply,
|
|
1143
1487
|
"data-field": "apply"
|
|
1144
1488
|
}, deps.busy === "apply" ? t("applying") : t("applyToOcr"));
|
|
1145
1489
|
const testBtn = (0, react.createElement)("button", {
|
|
1146
|
-
className: "
|
|
1490
|
+
className: "dsu-btn",
|
|
1147
1491
|
type: "button",
|
|
1148
1492
|
disabled: deps.busy !== null,
|
|
1149
1493
|
onClick: deps.onTest,
|
|
1150
1494
|
"data-field": "test"
|
|
1151
1495
|
}, t("testConnection"));
|
|
1152
1496
|
const migrateBtn = (0, react.createElement)("button", {
|
|
1153
|
-
className: "
|
|
1497
|
+
className: "dsu-btn",
|
|
1154
1498
|
type: "button",
|
|
1155
1499
|
disabled: deps.busy !== null,
|
|
1156
1500
|
onClick: deps.onMigrate,
|
|
1157
1501
|
"data-field": "migrate"
|
|
1158
1502
|
}, t("migrateKeys"));
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1503
|
+
return (0, react.createElement)(g, {
|
|
1504
|
+
label: t("actionsLabel"),
|
|
1505
|
+
wide: true
|
|
1506
|
+
}, (0, react.createElement)("div", { className: ACTIONS_CLASS }, applyBtn, testBtn, migrateBtn));
|
|
1507
|
+
}
|
|
1508
|
+
/** 当前配置 / key / 装载期自检三行提示(自检失败只作提示,不挡表单)。 */
|
|
1509
|
+
function buildProviderInfo(deps, data) {
|
|
1510
|
+
const { t } = deps;
|
|
1166
1511
|
const cur = data.current;
|
|
1167
1512
|
const currentHint = cur.provider === "" ? t("noProvider") : currentConfigText(t, cur);
|
|
1168
|
-
const currentInfo = (0, react.createElement)("
|
|
1169
|
-
const keyInfo = (0, react.createElement)("
|
|
1513
|
+
const currentInfo = (0, react.createElement)("p", { className: "dsu-hint" }, currentHint);
|
|
1514
|
+
const keyInfo = (0, react.createElement)("p", { className: "dsu-hint" }, deps.keyHint);
|
|
1170
1515
|
const verify = data.endpointVerify ?? null;
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1516
|
+
return [
|
|
1517
|
+
verify !== null && !verify.ok ? (0, react.createElement)("p", {
|
|
1518
|
+
className: "dsu-error",
|
|
1519
|
+
"data-field": "endpoint-verify"
|
|
1520
|
+
}, t("endpointVerifyFailed", { reason: verify.message })) : null,
|
|
1521
|
+
currentInfo,
|
|
1522
|
+
keyInfo
|
|
1523
|
+
];
|
|
1524
|
+
}
|
|
1525
|
+
/** 数据齐(source.status === "ready")那一支:模型四个字段 + 动作行 + 三行提示。 */
|
|
1526
|
+
function buildReadyProviderSection(deps, data) {
|
|
1527
|
+
const { t } = deps;
|
|
1528
|
+
const providerOptions = data.providers.map((provider) => ({
|
|
1529
|
+
value: provider.name,
|
|
1530
|
+
label: providerOptionLabel(t, provider),
|
|
1531
|
+
disabled: !provider.hasKey
|
|
1532
|
+
}));
|
|
1533
|
+
const fallbackModelName = deps.effectiveModel === "" ? t("noModel") : deps.effectiveModel;
|
|
1534
|
+
const modelOptions = (deps.modelOptions.length > 0 ? deps.modelOptions : [{
|
|
1535
|
+
id: deps.effectiveModel,
|
|
1536
|
+
name: fallbackModelName
|
|
1537
|
+
}]).map((model) => ({
|
|
1538
|
+
value: model.id,
|
|
1539
|
+
label: model.name
|
|
1540
|
+
}));
|
|
1541
|
+
const effortOptions = [
|
|
1542
|
+
{
|
|
1543
|
+
value: "auto",
|
|
1544
|
+
label: t("effortAuto")
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
value: "low",
|
|
1548
|
+
label: t("effortLow")
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
value: "medium",
|
|
1552
|
+
label: t("effortMedium")
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
value: "high",
|
|
1556
|
+
label: t("effortHigh")
|
|
1557
|
+
}
|
|
1558
|
+
];
|
|
1559
|
+
const languageOptions = [{
|
|
1560
|
+
value: "中文",
|
|
1561
|
+
label: "中文"
|
|
1562
|
+
}, {
|
|
1563
|
+
value: "English",
|
|
1564
|
+
label: "English"
|
|
1565
|
+
}];
|
|
1566
|
+
const modelUrl = deps.chosenProvider?.baseURL ?? "";
|
|
1567
|
+
return [
|
|
1568
|
+
(0, react.createElement)(x, {
|
|
1569
|
+
label: "Provider",
|
|
1570
|
+
...l(t("providerHint")),
|
|
1571
|
+
field: "ocrProvider",
|
|
1572
|
+
value: deps.effectiveProvider,
|
|
1573
|
+
options: providerOptions,
|
|
1574
|
+
disabled: data.providers.length === 0,
|
|
1575
|
+
onChange: deps.onPickProvider
|
|
1576
|
+
}),
|
|
1577
|
+
(0, react.createElement)(x, {
|
|
1578
|
+
label: "Model",
|
|
1579
|
+
...modelUrl === "" ? {} : { hint: modelUrl },
|
|
1580
|
+
field: "ocrModel",
|
|
1581
|
+
value: deps.effectiveModel,
|
|
1582
|
+
options: modelOptions,
|
|
1583
|
+
disabled: !deps.chosenProvider || deps.modelOptions.length === 0,
|
|
1584
|
+
onChange: deps.onPickModel
|
|
1585
|
+
}),
|
|
1586
|
+
(0, react.createElement)(x, {
|
|
1587
|
+
label: t("effortLabel"),
|
|
1588
|
+
...l(t("effortHint")),
|
|
1589
|
+
field: "effort",
|
|
1590
|
+
value: deps.effortValue,
|
|
1591
|
+
options: effortOptions,
|
|
1592
|
+
disabled: !deps.writable,
|
|
1593
|
+
onChange: (next) => {
|
|
1594
|
+
deps.onField("effort", next);
|
|
1595
|
+
}
|
|
1596
|
+
}),
|
|
1597
|
+
(0, react.createElement)(x, {
|
|
1598
|
+
label: t("languageLabel"),
|
|
1599
|
+
...l(t("languageHint")),
|
|
1600
|
+
field: "language",
|
|
1601
|
+
value: deps.languageValue,
|
|
1602
|
+
options: languageOptions,
|
|
1603
|
+
disabled: !deps.writable,
|
|
1604
|
+
onChange: (next) => {
|
|
1605
|
+
deps.onField("language", next);
|
|
1606
|
+
}
|
|
1607
|
+
}),
|
|
1608
|
+
(0, react.createElement)(_, {
|
|
1609
|
+
label: t("autoVerifyLabel"),
|
|
1610
|
+
...l(t("autoVerifyHint")),
|
|
1611
|
+
field: "autoVerify",
|
|
1612
|
+
checked: deps.autoVerifyValue,
|
|
1613
|
+
disabled: !deps.writable,
|
|
1614
|
+
onChange: (next) => {
|
|
1615
|
+
deps.onField("autoVerify", next);
|
|
1616
|
+
}
|
|
1617
|
+
}),
|
|
1618
|
+
buildActionField(deps),
|
|
1619
|
+
...buildProviderInfo(deps, data)
|
|
1620
|
+
];
|
|
1176
1621
|
}
|
|
1177
1622
|
/** Provider / Model 选择与应用区:三条降级各出一行提示(数据未就绪/读取失败/
|
|
1178
1623
|
* 数据源不可用),只有 source.status === "ready" 才交给 buildReadyProviderSection 画表单。 */
|
|
1179
1624
|
function buildProviderSection(deps) {
|
|
1180
1625
|
const { t } = deps;
|
|
1626
|
+
let body;
|
|
1181
1627
|
if (deps.fetchError !== null) {
|
|
1182
1628
|
const reason = t("loadProvidersFailed", { error: deps.fetchError });
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
if (deps.data ===
|
|
1186
|
-
|
|
1629
|
+
body = (0, react.createElement)("p", { className: "dsu-error" }, reason);
|
|
1630
|
+
} else if (deps.data === null) body = (0, react.createElement)("p", { className: "dsu-hint" }, t("loadingProviders"));
|
|
1631
|
+
else if (deps.data.source.status === "ready") body = buildReadyProviderSection(deps, deps.data);
|
|
1632
|
+
else {
|
|
1187
1633
|
const sourceMessage = deps.data.source.message;
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1634
|
+
const reason = sourceMessage === "" ? t("noReason") : sourceMessage;
|
|
1635
|
+
body = (0, react.createElement)("p", { className: "dsu-error" }, t("readProvidersFailed", { reason }));
|
|
1190
1636
|
}
|
|
1191
|
-
return
|
|
1637
|
+
return (0, react.createElement)(d, { title: t("modelSection") }, body);
|
|
1192
1638
|
}
|
|
1193
1639
|
/** provider 预选:显式选择 > 当前 OCR 配置(仍存在)> 第一个有 key 的 provider。 */
|
|
1194
1640
|
function resolveBackend(providerList, current, selProvider) {
|
|
@@ -1218,157 +1664,95 @@ function resolveSelection(providerList, current, selProvider, selModel) {
|
|
|
1218
1664
|
modelOptions: model.modelOptions
|
|
1219
1665
|
};
|
|
1220
1666
|
}
|
|
1221
|
-
/**
|
|
1667
|
+
/** 审查参数小节:评论条数、运行超时、配置文件路径 + 保存条。 */
|
|
1222
1668
|
function buildSettingsSection(deps, writable, setField) {
|
|
1223
1669
|
const { t } = deps;
|
|
1224
|
-
const
|
|
1225
|
-
label: t("effortLabel"),
|
|
1226
|
-
hint: t("effortHint"),
|
|
1227
|
-
field: "effort",
|
|
1228
|
-
current: deps.effortValue,
|
|
1229
|
-
disabled: !writable,
|
|
1230
|
-
options: [
|
|
1231
|
-
{
|
|
1232
|
-
value: "auto",
|
|
1233
|
-
label: t("effortAuto")
|
|
1234
|
-
},
|
|
1235
|
-
{
|
|
1236
|
-
value: "low",
|
|
1237
|
-
label: t("effortLow")
|
|
1238
|
-
},
|
|
1239
|
-
{
|
|
1240
|
-
value: "medium",
|
|
1241
|
-
label: t("effortMedium")
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
value: "high",
|
|
1245
|
-
label: t("effortHigh")
|
|
1246
|
-
}
|
|
1247
|
-
],
|
|
1248
|
-
onChange: (field, draft) => {
|
|
1249
|
-
setField(field, draft);
|
|
1250
|
-
}
|
|
1251
|
-
});
|
|
1252
|
-
const languageRow = (0, react.createElement)(SelectRow, {
|
|
1253
|
-
label: t("languageLabel"),
|
|
1254
|
-
hint: t("languageHint"),
|
|
1255
|
-
field: "language",
|
|
1256
|
-
current: deps.languageValue,
|
|
1257
|
-
disabled: !writable,
|
|
1258
|
-
options: [{
|
|
1259
|
-
value: "中文",
|
|
1260
|
-
label: "中文"
|
|
1261
|
-
}, {
|
|
1262
|
-
value: "English",
|
|
1263
|
-
label: "English"
|
|
1264
|
-
}],
|
|
1265
|
-
onChange: (field, draft) => {
|
|
1266
|
-
setField(field, draft);
|
|
1267
|
-
}
|
|
1268
|
-
});
|
|
1269
|
-
const verifyRow = (0, react.createElement)(SelectRow, {
|
|
1270
|
-
label: t("autoVerifyLabel"),
|
|
1271
|
-
hint: t("autoVerifyHint"),
|
|
1272
|
-
field: "autoVerify",
|
|
1273
|
-
current: deps.autoVerifyValue === true ? "on" : "off",
|
|
1274
|
-
disabled: !writable,
|
|
1275
|
-
options: [{
|
|
1276
|
-
value: "on",
|
|
1277
|
-
label: t("onLabel")
|
|
1278
|
-
}, {
|
|
1279
|
-
value: "off",
|
|
1280
|
-
label: t("offLabel")
|
|
1281
|
-
}],
|
|
1282
|
-
onChange: (field, draft) => {
|
|
1283
|
-
setField(field, draft === "on");
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1286
|
-
const maxCommentsRow = (0, react.createElement)(NumberRow, {
|
|
1670
|
+
const maxCommentsRow = (0, react.createElement)(v, {
|
|
1287
1671
|
label: t("maxCommentsLabel"),
|
|
1288
|
-
|
|
1672
|
+
...l(t("maxCommentsHint")),
|
|
1289
1673
|
field: "maxComments",
|
|
1290
1674
|
value: deps.maxCommentsValue,
|
|
1291
|
-
min:
|
|
1675
|
+
...COUNT_SCHEMA.minValue === null ? {} : { min: COUNT_SCHEMA.minValue },
|
|
1676
|
+
step: 1,
|
|
1677
|
+
invalidHint: t("outOfRange"),
|
|
1292
1678
|
disabled: !writable,
|
|
1293
|
-
|
|
1294
|
-
setField("maxComments",
|
|
1679
|
+
onChange: (next) => {
|
|
1680
|
+
setField("maxComments", next);
|
|
1295
1681
|
}
|
|
1296
1682
|
});
|
|
1297
|
-
const timeoutRow = (0, react.createElement)(
|
|
1683
|
+
const timeoutRow = (0, react.createElement)(v, {
|
|
1298
1684
|
label: t("timeoutLabel"),
|
|
1299
|
-
|
|
1685
|
+
...l(t("timeoutHint")),
|
|
1300
1686
|
field: "timeoutMinutes",
|
|
1301
1687
|
value: deps.timeoutMinutesValue,
|
|
1302
|
-
min:
|
|
1688
|
+
...COUNT_SCHEMA.minValue === null ? {} : { min: COUNT_SCHEMA.minValue },
|
|
1689
|
+
step: 1,
|
|
1690
|
+
invalidHint: t("outOfRange"),
|
|
1303
1691
|
disabled: !writable,
|
|
1304
|
-
|
|
1305
|
-
setField("timeoutMinutes",
|
|
1692
|
+
onChange: (next) => {
|
|
1693
|
+
setField("timeoutMinutes", next);
|
|
1306
1694
|
}
|
|
1307
1695
|
});
|
|
1308
|
-
const configPathRow = (0, react.createElement)(
|
|
1696
|
+
const configPathRow = (0, react.createElement)(y, {
|
|
1309
1697
|
label: t("configPathLabel"),
|
|
1310
|
-
|
|
1698
|
+
...l(t("configPathHint")),
|
|
1311
1699
|
field: "ocrConfigPath",
|
|
1312
1700
|
value: deps.ocrConfigPathValue,
|
|
1313
1701
|
placeholder: "~/.opencodereview/config.json",
|
|
1702
|
+
monospace: true,
|
|
1703
|
+
wide: true,
|
|
1314
1704
|
disabled: !writable,
|
|
1315
|
-
|
|
1316
|
-
setField("ocrConfigPath",
|
|
1705
|
+
onChange: (next) => {
|
|
1706
|
+
setField("ocrConfigPath", next.trim());
|
|
1317
1707
|
}
|
|
1318
1708
|
});
|
|
1319
|
-
const saveRow = (0, react.createElement)(
|
|
1320
|
-
t,
|
|
1709
|
+
const saveRow = (0, react.createElement)(S, {
|
|
1321
1710
|
dirty: deps.dirty,
|
|
1322
1711
|
writable,
|
|
1323
1712
|
busy: deps.settingsBusy,
|
|
1324
1713
|
error: deps.saveError,
|
|
1714
|
+
saveLabel: t("save"),
|
|
1715
|
+
savingLabel: t("saving"),
|
|
1716
|
+
revertLabel: t("revert"),
|
|
1717
|
+
dirtyLabel: t("statusDirty"),
|
|
1718
|
+
cleanLabel: t("statusClean"),
|
|
1719
|
+
readOnlyLabel: t("statusReadOnly"),
|
|
1325
1720
|
onSave: deps.onSave,
|
|
1326
1721
|
onDiscard: deps.onDiscard
|
|
1327
1722
|
});
|
|
1328
|
-
const statusHintNode = deps.statusHint !== null && deps.statusHint !== "" ? (0, react.createElement)("
|
|
1329
|
-
return (0, react.createElement)(
|
|
1723
|
+
const statusHintNode = deps.statusHint !== null && deps.statusHint !== "" ? (0, react.createElement)("p", { className: "dsu-hint" }, deps.statusHint) : null;
|
|
1724
|
+
return (0, react.createElement)(d, { title: t("reviewSection") }, statusHintNode, maxCommentsRow, timeoutRow, configPathRow, saveRow);
|
|
1330
1725
|
}
|
|
1331
|
-
const sectionStyle = { padding: "6px 0" };
|
|
1332
|
-
const titleStyle = {
|
|
1333
|
-
fontWeight: "600",
|
|
1334
|
-
fontSize: "13px"
|
|
1335
|
-
};
|
|
1336
|
-
const hintStyle = {
|
|
1337
|
-
fontSize: "12px",
|
|
1338
|
-
opacity: "0.8",
|
|
1339
|
-
margin: "2px 0 6px"
|
|
1340
|
-
};
|
|
1341
|
-
const textareaStyle = {
|
|
1342
|
-
width: "100%",
|
|
1343
|
-
minHeight: "120px",
|
|
1344
|
-
fontFamily: "monospace",
|
|
1345
|
-
fontSize: "12px",
|
|
1346
|
-
boxSizing: "border-box"
|
|
1347
|
-
};
|
|
1348
1726
|
function buildMcpAdmin(props) {
|
|
1349
1727
|
const { t } = props;
|
|
1350
1728
|
const textarea = (0, react.createElement)("textarea", {
|
|
1351
1729
|
"data-field": "mcpJson",
|
|
1352
1730
|
value: props.mcpJson,
|
|
1353
|
-
disabled: !props.writable || props.mcpBusy || !props.available,
|
|
1731
|
+
disabled: !props.writable || props.mcpBusy || !props.available || !props.mcpReady,
|
|
1732
|
+
"aria-label": t("mcpTitle"),
|
|
1733
|
+
className: "dsu-input dsu-textarea dsu-mono ocr-adminTextarea",
|
|
1354
1734
|
onChange: (event) => {
|
|
1355
1735
|
props.onMcpChange(event.target.value);
|
|
1356
|
-
}
|
|
1357
|
-
style: textareaStyle
|
|
1736
|
+
}
|
|
1358
1737
|
});
|
|
1359
1738
|
const button = (0, react.createElement)("button", {
|
|
1360
1739
|
type: "button",
|
|
1740
|
+
className: "dsu-btn",
|
|
1361
1741
|
"data-field": "mcpSave",
|
|
1362
|
-
disabled: !props.writable || props.mcpBusy || !props.available,
|
|
1742
|
+
disabled: !props.writable || props.mcpBusy || !props.available || !props.mcpReady,
|
|
1363
1743
|
onClick: props.onMcpSave
|
|
1364
1744
|
}, props.mcpBusy ? t("mcpSaving") : t("mcpSave"));
|
|
1365
|
-
const message = props.mcpMsg === null ? null : (0, react.createElement)("
|
|
1366
|
-
return (0, react.createElement)(
|
|
1745
|
+
const message = props.mcpMsg === null ? null : (0, react.createElement)("p", { className: "dsu-hint" }, props.mcpMsg);
|
|
1746
|
+
return (0, react.createElement)(d, {
|
|
1747
|
+
title: t("mcpTitle"),
|
|
1748
|
+
hint: t("mcpHint")
|
|
1749
|
+
}, props.available ? textarea : (0, react.createElement)("p", { className: "dsu-hint" }, t("adminUnavailable")), (0, react.createElement)("div", { className: ACTIONS_CLASS }, button), message);
|
|
1367
1750
|
}
|
|
1368
1751
|
function buildAuthAdmin(props) {
|
|
1369
1752
|
const { t } = props;
|
|
1370
1753
|
const check = (0, react.createElement)("button", {
|
|
1371
1754
|
type: "button",
|
|
1755
|
+
className: "dsu-btn",
|
|
1372
1756
|
"data-field": "authCheck",
|
|
1373
1757
|
disabled: props.authBusy || !props.available,
|
|
1374
1758
|
onClick: props.onCheckAuth
|
|
@@ -1377,23 +1761,29 @@ function buildAuthAdmin(props) {
|
|
|
1377
1761
|
"data-field": "chatGptModel",
|
|
1378
1762
|
value: props.authModel,
|
|
1379
1763
|
disabled: !props.writable || props.authBusy || !props.available,
|
|
1764
|
+
"aria-label": t("chatGptModelLabel"),
|
|
1765
|
+
className: "dsu-input",
|
|
1380
1766
|
onChange: (event) => {
|
|
1381
1767
|
props.onAuthModelChange(event.target.value);
|
|
1382
1768
|
}
|
|
1383
1769
|
});
|
|
1384
1770
|
const use = (0, react.createElement)("button", {
|
|
1385
1771
|
type: "button",
|
|
1772
|
+
className: "dsu-btn",
|
|
1386
1773
|
"data-field": "chatGptUse",
|
|
1387
1774
|
disabled: !props.writable || props.authBusy || !props.available,
|
|
1388
1775
|
onClick: props.onUseChatGpt
|
|
1389
1776
|
}, props.authBusy ? t("chatGptUsing") : t("chatGptUse"));
|
|
1390
|
-
const output = props.authOutput === null ? null : (0, react.createElement)("pre", {
|
|
1391
|
-
const message = props.authMsg === null ? null : (0, react.createElement)("
|
|
1392
|
-
return (0, react.createElement)(
|
|
1777
|
+
const output = props.authOutput === null ? null : (0, react.createElement)("pre", { className: "ocr-status dsu-mono" }, props.authOutput);
|
|
1778
|
+
const message = props.authMsg === null ? null : (0, react.createElement)("p", { className: "dsu-hint" }, props.authMsg);
|
|
1779
|
+
return (0, react.createElement)(d, {
|
|
1780
|
+
title: t("chatGptTitle"),
|
|
1781
|
+
hint: t("chatGptHint")
|
|
1782
|
+
}, (0, react.createElement)("div", { className: ACTIONS_CLASS }, check), (0, react.createElement)("p", { className: "dsu-hint" }, `${t("chatGptLoginHint")} ${props.loginCommand}`), output, (0, react.createElement)("p", { className: "dsu-hint" }, t("chatGptModelLabel")), model, (0, react.createElement)("div", { className: ACTIONS_CLASS }, use), message);
|
|
1393
1783
|
}
|
|
1394
1784
|
/** MCP 配置 + ChatGPT 登录态/切换:都写 OCR 自己的配置,放在设置卡下方。 */
|
|
1395
1785
|
function buildAdminSection(props) {
|
|
1396
|
-
return (0, react.createElement)("div",
|
|
1786
|
+
return (0, react.createElement)("div", null, buildMcpAdmin(props), buildAuthAdmin(props));
|
|
1397
1787
|
}
|
|
1398
1788
|
/**
|
|
1399
1789
|
* 读设置页载荷(卡片唯一的数据入口):失败只落 fetchError 文本,不抛穿渲染。
|
|
@@ -1405,8 +1795,8 @@ async function refreshProviders(reader, t, sinks) {
|
|
|
1405
1795
|
try {
|
|
1406
1796
|
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1407
1797
|
const json = await reader.providers();
|
|
1408
|
-
if (i(json, "ok") === false) {
|
|
1409
|
-
const detail = strOf(i(json, "error"));
|
|
1798
|
+
if (i$1(json, "ok") === false) {
|
|
1799
|
+
const detail = strOf(i$1(json, "error"));
|
|
1410
1800
|
throw new Error(detail === "" ? t("remoteUnavailable") : detail);
|
|
1411
1801
|
}
|
|
1412
1802
|
const payload = providersPayloadOf(json);
|
|
@@ -1423,8 +1813,8 @@ function readerPost(reader, t) {
|
|
|
1423
1813
|
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1424
1814
|
switch (action) {
|
|
1425
1815
|
case "select": {
|
|
1426
|
-
const provider = strOf(i(body, "provider"));
|
|
1427
|
-
const model = strOf(i(body, "model"));
|
|
1816
|
+
const provider = strOf(i$1(body, "provider"));
|
|
1817
|
+
const model = strOf(i$1(body, "model"));
|
|
1428
1818
|
const reply = await reader.select(provider, model);
|
|
1429
1819
|
return { json: () => Promise.resolve(reply) };
|
|
1430
1820
|
}
|
|
@@ -1444,7 +1834,8 @@ function readerPost(reader, t) {
|
|
|
1444
1834
|
}
|
|
1445
1835
|
};
|
|
1446
1836
|
}
|
|
1447
|
-
/** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。
|
|
1837
|
+
/** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。
|
|
1838
|
+
* 导出与 `runProbe` 同因:卡片上的动作以纯函数形态可测(回执着色与文案都在这里定)。 */
|
|
1448
1839
|
async function runApplySelection(deps) {
|
|
1449
1840
|
const { t, effectiveProvider, effectiveModel } = deps;
|
|
1450
1841
|
if (!effectiveProvider || !effectiveModel) {
|
|
@@ -1461,12 +1852,14 @@ async function runApplySelection(deps) {
|
|
|
1461
1852
|
provider: effectiveProvider,
|
|
1462
1853
|
model: effectiveModel
|
|
1463
1854
|
})).json();
|
|
1464
|
-
if (i(selJson, "ok") === true) {
|
|
1855
|
+
if (i$1(selJson, "ok") === true) {
|
|
1465
1856
|
let testText;
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1857
|
+
let verified = true;
|
|
1858
|
+
const llmTestRaw = i$1(selJson, "llmTest");
|
|
1859
|
+
if (e$3(llmTestRaw)) {
|
|
1860
|
+
const testOutput = strOf(i$1(llmTestRaw, "output")).slice(0, 400);
|
|
1861
|
+
verified = i$1(llmTestRaw, "ok") === true;
|
|
1862
|
+
testText = verified ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
|
|
1470
1863
|
} else testText = t("notVerified");
|
|
1471
1864
|
const appliedText = t("applied", {
|
|
1472
1865
|
provider: effectiveProvider,
|
|
@@ -1474,12 +1867,12 @@ async function runApplySelection(deps) {
|
|
|
1474
1867
|
test: testText
|
|
1475
1868
|
});
|
|
1476
1869
|
deps.setActionMsg({
|
|
1477
|
-
ok:
|
|
1870
|
+
ok: verified,
|
|
1478
1871
|
text: appliedText
|
|
1479
1872
|
});
|
|
1480
1873
|
deps.refresh();
|
|
1481
1874
|
} else {
|
|
1482
|
-
const failure = strOf(i(selJson, "error"));
|
|
1875
|
+
const failure = strOf(i$1(selJson, "error"));
|
|
1483
1876
|
deps.setActionMsg({
|
|
1484
1877
|
ok: false,
|
|
1485
1878
|
text: failure === "" ? t("applyFailed") : failure
|
|
@@ -1502,12 +1895,14 @@ async function runMigrateKeys(deps) {
|
|
|
1502
1895
|
deps.setActionMsg(null);
|
|
1503
1896
|
try {
|
|
1504
1897
|
const json = await (await deps.post("migrate")).json();
|
|
1505
|
-
const migratedVal = i(json, "migrated");
|
|
1506
|
-
const skippedVal = i(json, "skippedUnknown");
|
|
1898
|
+
const migratedVal = i$1(json, "migrated");
|
|
1899
|
+
const skippedVal = i$1(json, "skippedUnknown");
|
|
1507
1900
|
const migrated = Array.isArray(migratedVal) ? migratedVal.filter((item) => typeof item === "string") : [];
|
|
1508
1901
|
const skipped = Array.isArray(skippedVal) ? skippedVal.filter((item) => typeof item === "string") : [];
|
|
1509
|
-
const ok = i(json, "ok") === true;
|
|
1510
|
-
const
|
|
1902
|
+
const ok = i$1(json, "ok") === true;
|
|
1903
|
+
const migratedText = migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey");
|
|
1904
|
+
const skippedText = skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "";
|
|
1905
|
+
const head = ok ? migratedText + skippedText : t("migrateFailed");
|
|
1511
1906
|
deps.setActionMsg({
|
|
1512
1907
|
ok,
|
|
1513
1908
|
text: head
|
|
@@ -1530,8 +1925,8 @@ async function runTestConnection(deps) {
|
|
|
1530
1925
|
deps.setActionMsg(null);
|
|
1531
1926
|
try {
|
|
1532
1927
|
const json = await (await deps.post("test")).json();
|
|
1533
|
-
const ok = i(json, "ok") === true;
|
|
1534
|
-
const testOutput = strOf(i(json, "output"));
|
|
1928
|
+
const ok = i$1(json, "ok") === true;
|
|
1929
|
+
const testOutput = strOf(i$1(json, "output"));
|
|
1535
1930
|
deps.setActionMsg({
|
|
1536
1931
|
ok,
|
|
1537
1932
|
text: (ok ? t("llmTestPassed") : t("llmTestFailed")) + testOutput
|
|
@@ -1558,12 +1953,12 @@ async function runProbe(deps) {
|
|
|
1558
1953
|
deps.setProbeMsg(null);
|
|
1559
1954
|
try {
|
|
1560
1955
|
const json = await (await deps.post("probe")).json();
|
|
1561
|
-
const ok = i(json, "ok") === true;
|
|
1562
|
-
const head = ok ? t("probeVersion", { version: strOf(i(json, "version")) }) : t("probeReason", { reason: strOf(i(json, "reason")) });
|
|
1563
|
-
const output = strOf(i(json, "output"));
|
|
1956
|
+
const ok = i$1(json, "ok") === true;
|
|
1957
|
+
const head = ok ? t("probeVersion", { version: strOf(i$1(json, "version")) }) : t("probeReason", { reason: strOf(i$1(json, "reason")) });
|
|
1958
|
+
const output = strOf(i$1(json, "output"));
|
|
1564
1959
|
const lines = [
|
|
1565
1960
|
head,
|
|
1566
|
-
strOf(i(json, "changeText")),
|
|
1961
|
+
strOf(i$1(json, "changeText")),
|
|
1567
1962
|
output
|
|
1568
1963
|
].filter((line) => line !== "");
|
|
1569
1964
|
deps.setProbeMsg({
|
|
@@ -1585,8 +1980,8 @@ async function runProbe(deps) {
|
|
|
1585
1980
|
* 这时不能清 touched、也不能报成功——否则用户看到「已保存」而配置原封未动。
|
|
1586
1981
|
* 传输失败仍以 rejection 表达。 */
|
|
1587
1982
|
async function saveSettingsDraft(deps) {
|
|
1588
|
-
const { t
|
|
1589
|
-
const keys = t(touched, value);
|
|
1983
|
+
const { t, value, touched } = deps;
|
|
1984
|
+
const keys = t$1(touched, value);
|
|
1590
1985
|
if (keys.length === 0) return;
|
|
1591
1986
|
deps.setSettingsBusy(true);
|
|
1592
1987
|
deps.setSaveError(null);
|
|
@@ -1612,7 +2007,7 @@ async function saveSettingsDraft(deps) {
|
|
|
1612
2007
|
for (const field of refused) kept[field] = touched[field];
|
|
1613
2008
|
deps.setTouched(kept);
|
|
1614
2009
|
const reason = failures.length > 0 ? `(${errorSummary(failures[0])})` : "";
|
|
1615
|
-
deps.setSaveError(`${t
|
|
2010
|
+
deps.setSaveError(`${t("saveRefused", { fields: refused.join(", ") })}${reason}`);
|
|
1616
2011
|
return;
|
|
1617
2012
|
}
|
|
1618
2013
|
deps.setTouched({});
|
|
@@ -1620,11 +2015,11 @@ async function saveSettingsDraft(deps) {
|
|
|
1620
2015
|
function draftFieldsOf(touched, value) {
|
|
1621
2016
|
const eff = (field, def) => field in touched ? touched[field] : value[field] ?? def;
|
|
1622
2017
|
return {
|
|
1623
|
-
effortValue: eff("effort", "auto"),
|
|
1624
|
-
languageValue: eff("language", "中文"),
|
|
1625
|
-
autoVerifyValue: eff("autoVerify", true),
|
|
1626
|
-
maxCommentsValue: numOf(eff("maxComments", 12), 12),
|
|
1627
|
-
timeoutMinutesValue: numOf(eff("timeoutMinutes", 0), 0),
|
|
2018
|
+
effortValue: textOf(eff("effort", "auto"), EFFORT_SCHEMA, "auto"),
|
|
2019
|
+
languageValue: textOf(eff("language", "中文"), LANGUAGE_SCHEMA, "中文"),
|
|
2020
|
+
autoVerifyValue: booleanOf(eff("autoVerify", true), true),
|
|
2021
|
+
maxCommentsValue: numOf(eff("maxComments", 12), 12, COUNT_SCHEMA),
|
|
2022
|
+
timeoutMinutesValue: numOf(eff("timeoutMinutes", 0), 0, COUNT_SCHEMA),
|
|
1628
2023
|
ocrConfigPathValue: strOf(eff("ocrConfigPath", ""))
|
|
1629
2024
|
};
|
|
1630
2025
|
}
|
|
@@ -1643,20 +2038,16 @@ function cardHints(t, ready, status, data) {
|
|
|
1643
2038
|
/** Provider 区 = provider/model 表单 + 最近一次动作的回执条。 */
|
|
1644
2039
|
function buildProviderPanel(deps) {
|
|
1645
2040
|
const { actionMsg } = deps;
|
|
1646
|
-
|
|
1647
|
-
const actionMsgNode = actionMsg ? (0, react.createElement)("div", { className: statusClass(actionMsg.ok) }, actionMsg.text) : null;
|
|
1648
|
-
return (0, react.createElement)("div", { style: { padding: "2px 0" } }, providerSection, actionMsgNode);
|
|
2041
|
+
return [buildProviderSection(deps), actionMsg ? (0, react.createElement)("p", { className: statusClass(actionMsg.ok) }, actionMsg.text) : null];
|
|
1649
2042
|
}
|
|
1650
|
-
/**
|
|
1651
|
-
function
|
|
2043
|
+
/** MCP 配置编辑器的状态与动作:读、写,以及「没读成功不许写」的判据。 */
|
|
2044
|
+
function useMcpPanel(reader, t) {
|
|
1652
2045
|
const [mcpJson, setMcpJson] = (0, react.useState)("{}");
|
|
1653
2046
|
const [mcpBusy, setMcpBusy] = (0, react.useState)(false);
|
|
1654
2047
|
const [mcpMsg, setMcpMsg] = (0, react.useState)(null);
|
|
1655
|
-
const [
|
|
1656
|
-
const
|
|
1657
|
-
const
|
|
1658
|
-
const [authModel, setAuthModel] = (0, react.useState)("");
|
|
1659
|
-
const [loginCommand, setLoginCommand] = (0, react.useState)("ocr auth login --provider chatgpt");
|
|
2048
|
+
const [mcpReady, setMcpReady] = (0, react.useState)(false);
|
|
2049
|
+
const mcpDirtyRef = (0, react.useRef)(false);
|
|
2050
|
+
const mcpRevisionRef = (0, react.useRef)(void 0);
|
|
1660
2051
|
const loadMcp = (0, react.useCallback)(async () => {
|
|
1661
2052
|
if (reader === void 0) {
|
|
1662
2053
|
setMcpMsg(t("adminUnavailable"));
|
|
@@ -1664,10 +2055,17 @@ function useAdminPanel(reader, t) {
|
|
|
1664
2055
|
}
|
|
1665
2056
|
try {
|
|
1666
2057
|
const reply = await reader.mcp();
|
|
1667
|
-
if ("status" in reply)
|
|
1668
|
-
|
|
2058
|
+
if ("status" in reply) {
|
|
2059
|
+
setMcpMsg(reply.error);
|
|
2060
|
+
setMcpReady(false);
|
|
2061
|
+
} else {
|
|
2062
|
+
if (!mcpDirtyRef.current) setMcpJson(reply.json);
|
|
2063
|
+
mcpRevisionRef.current = reply.revision;
|
|
2064
|
+
setMcpReady(true);
|
|
2065
|
+
}
|
|
1669
2066
|
} catch (error) {
|
|
1670
2067
|
setMcpMsg(`${t("mcpLoadFailed")}${String(error)}`);
|
|
2068
|
+
setMcpReady(false);
|
|
1671
2069
|
}
|
|
1672
2070
|
}, [reader, t]);
|
|
1673
2071
|
(0, react.useEffect)(() => {
|
|
@@ -1678,17 +2076,48 @@ function useAdminPanel(reader, t) {
|
|
|
1678
2076
|
setMcpMsg(t("adminUnavailable"));
|
|
1679
2077
|
return;
|
|
1680
2078
|
}
|
|
2079
|
+
if (!mcpReady) {
|
|
2080
|
+
setMcpMsg(t("mcpNotLoaded"));
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
1681
2083
|
setMcpBusy(true);
|
|
1682
2084
|
setMcpMsg(null);
|
|
1683
2085
|
try {
|
|
1684
|
-
const reply = await reader.saveMcp(mcpJson);
|
|
1685
|
-
|
|
2086
|
+
const reply = await reader.saveMcp(mcpJson, mcpRevisionRef.current);
|
|
2087
|
+
if ("status" in reply) setMcpMsg(reply.error);
|
|
2088
|
+
else {
|
|
2089
|
+
mcpDirtyRef.current = false;
|
|
2090
|
+
mcpRevisionRef.current = reply.revision;
|
|
2091
|
+
setMcpMsg(t("mcpSaved"));
|
|
2092
|
+
}
|
|
1686
2093
|
} catch (error) {
|
|
1687
2094
|
setMcpMsg(String(error));
|
|
1688
2095
|
} finally {
|
|
1689
2096
|
setMcpBusy(false);
|
|
1690
2097
|
}
|
|
1691
2098
|
}
|
|
2099
|
+
return {
|
|
2100
|
+
mcpJson,
|
|
2101
|
+
mcpReady,
|
|
2102
|
+
mcpBusy,
|
|
2103
|
+
mcpMsg,
|
|
2104
|
+
onMcpChange: (value) => {
|
|
2105
|
+
mcpDirtyRef.current = true;
|
|
2106
|
+
setMcpJson(value);
|
|
2107
|
+
},
|
|
2108
|
+
onMcpSave: () => {
|
|
2109
|
+
saveMcp();
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
/** 管理区的状态与动作(MCP 配置 + ChatGPT 登录态/切换);OcrCard 只负责摆位置。 */
|
|
2114
|
+
function useAdminPanel(reader, t) {
|
|
2115
|
+
const mcp = useMcpPanel(reader, t);
|
|
2116
|
+
const [authOutput, setAuthOutput] = (0, react.useState)(null);
|
|
2117
|
+
const [authBusy, setAuthBusy] = (0, react.useState)(false);
|
|
2118
|
+
const [authMsg, setAuthMsg] = (0, react.useState)(null);
|
|
2119
|
+
const [authModel, setAuthModel] = (0, react.useState)("");
|
|
2120
|
+
const [loginCommand, setLoginCommand] = (0, react.useState)("ocr auth login --provider chatgpt");
|
|
1692
2121
|
async function checkAuth() {
|
|
1693
2122
|
if (reader === void 0) {
|
|
1694
2123
|
setAuthMsg(t("adminUnavailable"));
|
|
@@ -1728,18 +2157,12 @@ function useAdminPanel(reader, t) {
|
|
|
1728
2157
|
}
|
|
1729
2158
|
return {
|
|
1730
2159
|
available: reader !== void 0,
|
|
1731
|
-
|
|
1732
|
-
mcpBusy,
|
|
1733
|
-
mcpMsg,
|
|
2160
|
+
...mcp,
|
|
1734
2161
|
authOutput,
|
|
1735
2162
|
authBusy,
|
|
1736
2163
|
authMsg,
|
|
1737
2164
|
authModel,
|
|
1738
2165
|
loginCommand,
|
|
1739
|
-
onMcpChange: setMcpJson,
|
|
1740
|
-
onMcpSave: () => {
|
|
1741
|
-
saveMcp();
|
|
1742
|
-
},
|
|
1743
2166
|
onAuthModelChange: setAuthModel,
|
|
1744
2167
|
onCheckAuth: () => {
|
|
1745
2168
|
checkAuth();
|
|
@@ -1750,25 +2173,34 @@ function useAdminPanel(reader, t) {
|
|
|
1750
2173
|
};
|
|
1751
2174
|
}
|
|
1752
2175
|
function OcrCard(props) {
|
|
1753
|
-
const { t
|
|
2176
|
+
const { t } = props;
|
|
1754
2177
|
const snap = props.useCard((state) => state);
|
|
1755
2178
|
const { value } = snap;
|
|
1756
2179
|
const ready = snap.status === "ready";
|
|
1757
2180
|
const writable = snap.writable && ready;
|
|
1758
|
-
const adminState = useAdminPanel(props.reader, t
|
|
2181
|
+
const adminState = useAdminPanel(props.reader, t);
|
|
1759
2182
|
const [data, setData] = (0, react.useState)(null);
|
|
1760
2183
|
const [fetchError, setFetchError] = (0, react.useState)(null);
|
|
1761
2184
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
1762
2185
|
const [actionMsg, setActionMsg] = (0, react.useState)(null);
|
|
1763
2186
|
const [probeMsg, setProbeMsg] = (0, react.useState)(null);
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2187
|
+
const refreshSeq = (0, react.useRef)(0);
|
|
2188
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
2189
|
+
refreshSeq.current += 1;
|
|
2190
|
+
const seq = refreshSeq.current;
|
|
2191
|
+
return refreshProviders(props.reader, t, {
|
|
2192
|
+
setData: (payload) => {
|
|
2193
|
+
if (seq === refreshSeq.current) setData(payload);
|
|
2194
|
+
},
|
|
2195
|
+
setFetchError: (message) => {
|
|
2196
|
+
if (seq === refreshSeq.current) setFetchError(message);
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
}, [props.reader, t]);
|
|
1768
2200
|
(0, react.useEffect)(() => {
|
|
1769
2201
|
refresh();
|
|
1770
2202
|
}, [refresh]);
|
|
1771
|
-
const post = readerPost(props.reader, t
|
|
2203
|
+
const post = readerPost(props.reader, t);
|
|
1772
2204
|
const [selProvider, setSelProvider] = (0, react.useState)("");
|
|
1773
2205
|
const [selModel, setSelModel] = (0, react.useState)("");
|
|
1774
2206
|
const { effectiveProvider, effectiveModel, chosenProvider, modelOptions } = resolveSelection(data?.providers ?? [], data?.current, selProvider, selModel);
|
|
@@ -1779,7 +2211,7 @@ function OcrCard(props) {
|
|
|
1779
2211
|
setSelModel(first);
|
|
1780
2212
|
}
|
|
1781
2213
|
const actions = {
|
|
1782
|
-
t
|
|
2214
|
+
t,
|
|
1783
2215
|
post,
|
|
1784
2216
|
refresh,
|
|
1785
2217
|
setBusy,
|
|
@@ -1790,7 +2222,7 @@ function OcrCard(props) {
|
|
|
1790
2222
|
const [touched, setTouched] = (0, react.useState)({});
|
|
1791
2223
|
const [settingsBusy, setSettingsBusy] = (0, react.useState)(false);
|
|
1792
2224
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
1793
|
-
const dirty = t(touched, value).length > 0;
|
|
2225
|
+
const dirty = t$1(touched, value).length > 0;
|
|
1794
2226
|
const setField = (field, draft) => {
|
|
1795
2227
|
setTouched((prev) => ({
|
|
1796
2228
|
...prev,
|
|
@@ -1802,7 +2234,7 @@ function OcrCard(props) {
|
|
|
1802
2234
|
setSaveError(null);
|
|
1803
2235
|
};
|
|
1804
2236
|
const deps = {
|
|
1805
|
-
t
|
|
2237
|
+
t,
|
|
1806
2238
|
data,
|
|
1807
2239
|
fetchError,
|
|
1808
2240
|
busy,
|
|
@@ -1813,7 +2245,7 @@ function OcrCard(props) {
|
|
|
1813
2245
|
effectiveModel,
|
|
1814
2246
|
chosenProvider,
|
|
1815
2247
|
modelOptions,
|
|
1816
|
-
...cardHints(t
|
|
2248
|
+
...cardHints(t, ready, snap.status, data),
|
|
1817
2249
|
binary: data?.ocrBinary ?? null,
|
|
1818
2250
|
probeMsg,
|
|
1819
2251
|
...draftFieldsOf(touched, value),
|
|
@@ -1833,7 +2265,7 @@ function OcrCard(props) {
|
|
|
1833
2265
|
},
|
|
1834
2266
|
onProbe: () => {
|
|
1835
2267
|
runProbe({
|
|
1836
|
-
t
|
|
2268
|
+
t,
|
|
1837
2269
|
post,
|
|
1838
2270
|
refresh,
|
|
1839
2271
|
setBusy,
|
|
@@ -1843,7 +2275,7 @@ function OcrCard(props) {
|
|
|
1843
2275
|
onField: setField,
|
|
1844
2276
|
onSave: () => {
|
|
1845
2277
|
saveSettingsDraft({
|
|
1846
|
-
t
|
|
2278
|
+
t,
|
|
1847
2279
|
value,
|
|
1848
2280
|
touched,
|
|
1849
2281
|
setTouched,
|
|
@@ -1859,15 +2291,10 @@ function OcrCard(props) {
|
|
|
1859
2291
|
const settingsSection = buildSettingsSection(deps, writable, setField);
|
|
1860
2292
|
const adminSection = buildAdminSection({
|
|
1861
2293
|
...adminState,
|
|
1862
|
-
t
|
|
2294
|
+
t,
|
|
1863
2295
|
writable
|
|
1864
2296
|
});
|
|
1865
|
-
|
|
1866
|
-
const headDesc = t$4("cardDescription");
|
|
1867
|
-
return (0, react.createElement)(OcrPluginCard, {
|
|
1868
|
-
title: headTitle,
|
|
1869
|
-
description: headDesc
|
|
1870
|
-
}, cliStatus, providerPanel, settingsSection, adminSection);
|
|
2297
|
+
return (0, react.createElement)(u, null, cliStatus, providerPanel, settingsSection, adminSection);
|
|
1871
2298
|
}
|
|
1872
2299
|
const inject = [
|
|
1873
2300
|
"slots",
|
|
@@ -1875,11 +2302,11 @@ const inject = [
|
|
|
1875
2302
|
"locale"
|
|
1876
2303
|
];
|
|
1877
2304
|
function apply(ctx) {
|
|
1878
|
-
if (!n(ctx, "ocrReviewCardApplied", "ocr-review-card: apply claim")) return;
|
|
2305
|
+
if (!n$1(ctx, "ocrReviewCardApplied", "ocr-review-card: apply claim")) return;
|
|
1879
2306
|
ctx.effect(() => {
|
|
1880
2307
|
const tag = document.createElement("style");
|
|
1881
2308
|
tag.id = "ocr-review-card-css";
|
|
1882
|
-
tag.textContent = CARD_CSS
|
|
2309
|
+
tag.textContent = `${CARD_CSS}\n${c}`;
|
|
1883
2310
|
document.head.append(tag);
|
|
1884
2311
|
return () => {
|
|
1885
2312
|
tag.remove();
|
|
@@ -1930,7 +2357,6 @@ exports.OcrCard = OcrCard;
|
|
|
1930
2357
|
exports.OcrCliActivation = OcrCliActivation;
|
|
1931
2358
|
exports.OcrCliBadge = OcrCliBadge;
|
|
1932
2359
|
exports.OcrCliSection = OcrCliSection;
|
|
1933
|
-
exports.SaveBar = SaveBar;
|
|
1934
2360
|
exports.apply = apply;
|
|
1935
2361
|
exports.binaryOf = binaryOf;
|
|
1936
2362
|
exports.binaryStatusText = binaryStatusText;
|
|
@@ -1950,6 +2376,7 @@ exports.inject = inject;
|
|
|
1950
2376
|
exports.isOwnSubject = isOwnSubject;
|
|
1951
2377
|
exports.loadCliStatus = loadCliStatus;
|
|
1952
2378
|
exports.providerOptionLabel = providerOptionLabel;
|
|
2379
|
+
exports.runApplySelection = runApplySelection;
|
|
1953
2380
|
exports.runProbe = runProbe;
|
|
1954
2381
|
exports.saveSettingsDraft = saveSettingsDraft;
|
|
1955
2382
|
return module.exports;
|