@jayyuen66/dsh-ocr-review 0.1.8 → 0.1.9
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 +36 -0
- package/README.md +9 -8
- package/README.zh-CN.md +9 -8
- package/THIRD_PARTY_NOTICES.md +2 -1
- package/client.js +916 -543
- package/cordis.patch.yml +1 -1
- package/docs/DETAIL.md +28 -0
- package/docs/DETAIL.zh.md +28 -0
- package/host.js +112 -41
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +50 -42
package/client.js
CHANGED
|
@@ -7,11 +7,14 @@ 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: "无未保存的修改",
|
|
@@ -59,8 +62,6 @@ const UI_MESSAGES = {
|
|
|
59
62
|
languageHint: "OCR 评论输出语言(写入 config.language)",
|
|
60
63
|
autoVerifyLabel: "应用后自动验证",
|
|
61
64
|
autoVerifyHint: "选择 provider/model 后自动跑 ocr llm test",
|
|
62
|
-
onLabel: "开启",
|
|
63
|
-
offLabel: "关闭",
|
|
64
65
|
maxCommentsLabel: "摘要评论条数 (maxComments)",
|
|
65
66
|
maxCommentsHint: "返回给模型的 top 评论条数;0 = 全部(含未锚定行,输出大)(默认见本行设置项的初值)",
|
|
66
67
|
timeoutLabel: "运行超时 (timeoutMinutes)",
|
|
@@ -111,8 +112,10 @@ 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",
|
|
@@ -160,8 +163,6 @@ const UI_MESSAGES = {
|
|
|
160
163
|
languageHint: "Output language of OCR comments (written into config.language)",
|
|
161
164
|
autoVerifyLabel: "Verify after applying",
|
|
162
165
|
autoVerifyHint: "Run `ocr llm test` automatically after picking a provider/model",
|
|
163
|
-
onLabel: "On",
|
|
164
|
-
offLabel: "Off",
|
|
165
166
|
maxCommentsLabel: "Summary comment cap (maxComments)",
|
|
166
167
|
maxCommentsHint: "Top comments returned to the model; 0 = all (including unanchored lines, large output) (default: the row's initial value)",
|
|
167
168
|
timeoutLabel: "Run timeout (timeoutMinutes)",
|
|
@@ -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,21 @@ 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({ json: zod.z.string({ error: MCP_SAVE_MESSAGE }) }, { error: MCP_SAVE_MESSAGE });
|
|
831
|
+
const CHAT_GPT_MESSAGE = "chatgpt request requires a string model";
|
|
832
|
+
const CHAT_GPT_SCHEMA = zod.z.object({ model: zod.z.string({ error: CHAT_GPT_MESSAGE }) }, { error: CHAT_GPT_MESSAGE });
|
|
285
833
|
/** 把宿主那九个方法挂到客户端远程面上的描述符(`id` 的形状照生成器:`<包>#<命名空间>/<方法>`)。
|
|
286
834
|
* 导出它只为一件事:`test/remote.test.ts` 拿它与宿主侧 `remoteMethods()` 的标记对质
|
|
287
835
|
* —— 两侧分叉时卡片不会报错,只会"读不到",所以那条比较必须在门禁里。 */
|
|
@@ -309,13 +857,7 @@ const OCR_DESCRIPTORS = [
|
|
|
309
857
|
mode: "strict",
|
|
310
858
|
typeSymbol: `${PACKAGE_NAME}#select-request`,
|
|
311
859
|
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
|
-
};
|
|
860
|
+
return parseOrThrow(SELECT_SCHEMA, value);
|
|
319
861
|
} })
|
|
320
862
|
}
|
|
321
863
|
}],
|
|
@@ -371,8 +913,7 @@ const OCR_DESCRIPTORS = [
|
|
|
371
913
|
mode: "strict",
|
|
372
914
|
typeSymbol: `${PACKAGE_NAME}#mcp-save-request`,
|
|
373
915
|
create: () => ({ parse(value) {
|
|
374
|
-
|
|
375
|
-
return { json: value["json"] };
|
|
916
|
+
return parseOrThrow(MCP_SAVE_SCHEMA, value);
|
|
376
917
|
} })
|
|
377
918
|
}
|
|
378
919
|
}],
|
|
@@ -401,8 +942,7 @@ const OCR_DESCRIPTORS = [
|
|
|
401
942
|
mode: "strict",
|
|
402
943
|
typeSymbol: `${PACKAGE_NAME}#chatgpt-request`,
|
|
403
944
|
create: () => ({ parse(value) {
|
|
404
|
-
|
|
405
|
-
return { model: value["model"] };
|
|
945
|
+
return parseOrThrow(CHAT_GPT_SCHEMA, value);
|
|
406
946
|
} })
|
|
407
947
|
}
|
|
408
948
|
}],
|
|
@@ -410,9 +950,9 @@ const OCR_DESCRIPTORS = [
|
|
|
410
950
|
}
|
|
411
951
|
];
|
|
412
952
|
/** 这个值能当远程面用吗(只核我们要调的那一位;服务来自别的实现或版本时保守当作没有)。 */
|
|
413
|
-
const isRemoteMounter = (value) => e$
|
|
953
|
+
const isRemoteMounter = (value) => e$3(value) && typeof value["$mount"] === "function";
|
|
414
954
|
/** 同上:命名空间服务(挂载之后才有,缺它说明网关没认这枚贡献)。 */
|
|
415
|
-
const isOcrNamespace = (value) => e$
|
|
955
|
+
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";
|
|
416
956
|
/** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。 */
|
|
417
957
|
function unwrap(result) {
|
|
418
958
|
if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
@@ -524,35 +1064,65 @@ const BUNDLE_PKG = OCR_BUNDLE_PACKAGE;
|
|
|
524
1064
|
function strOf(value) {
|
|
525
1065
|
return typeof value === "string" ? value : "";
|
|
526
1066
|
}
|
|
527
|
-
/**
|
|
528
|
-
|
|
529
|
-
|
|
1067
|
+
/** 设置行的取值域:枚举/计数都交给 zod 声明,界面只消费解析后的类型。 */
|
|
1068
|
+
const EFFORT_SCHEMA = zod.z.enum([
|
|
1069
|
+
"auto",
|
|
1070
|
+
"low",
|
|
1071
|
+
"medium",
|
|
1072
|
+
"high"
|
|
1073
|
+
]);
|
|
1074
|
+
const LANGUAGE_SCHEMA = zod.z.enum(["中文", "English"]);
|
|
1075
|
+
const COUNT_SCHEMA = zod.z.int().min(0);
|
|
1076
|
+
/** 未知值 → 安全数字(非整数/越界给 fallback)。 */
|
|
1077
|
+
function numOf(value, fallback, schema) {
|
|
1078
|
+
const parsed = schema.safeParse(value);
|
|
1079
|
+
return parsed.success ? parsed.data : fallback;
|
|
1080
|
+
}
|
|
1081
|
+
/** 未知值 → 枚举里的一个取值(域外给 fallback)。 */
|
|
1082
|
+
function textOf(value, schema, fallback) {
|
|
1083
|
+
const parsed = schema.safeParse(value);
|
|
1084
|
+
return parsed.success ? parsed.data : fallback;
|
|
1085
|
+
}
|
|
1086
|
+
/** 未知值 → 布尔(非布尔给 fallback)。 */
|
|
1087
|
+
function booleanOf(value, fallback) {
|
|
1088
|
+
const parsed = zod.z.boolean().safeParse(value);
|
|
1089
|
+
return parsed.success ? parsed.data : fallback;
|
|
530
1090
|
}
|
|
531
1091
|
/** 错误摘要(非 Error 抛出也要可读;catch 分支共用)。 */
|
|
532
1092
|
function errorSummary(error) {
|
|
533
1093
|
return String(error instanceof Error ? error.message : error);
|
|
534
1094
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1095
|
+
const STRING_OR_EMPTY = zod.z.preprocess((value) => typeof value === "string" ? value : "", zod.z.string());
|
|
1096
|
+
const NULLABLE_STRING = zod.z.preprocess((value) => typeof value === "string" ? value : null, zod.z.string().nullable());
|
|
1097
|
+
const TRUE_ONLY = zod.z.preprocess((value) => value === true, zod.z.boolean());
|
|
1098
|
+
const MODEL_SCHEMA = zod.z.object({
|
|
1099
|
+
id: STRING_OR_EMPTY,
|
|
1100
|
+
name: STRING_OR_EMPTY
|
|
1101
|
+
});
|
|
1102
|
+
const PROVIDER_SCHEMA = zod.z.object({
|
|
1103
|
+
name: STRING_OR_EMPTY,
|
|
1104
|
+
displayName: STRING_OR_EMPTY,
|
|
1105
|
+
apiKeyEnv: NULLABLE_STRING,
|
|
1106
|
+
baseURL: STRING_OR_EMPTY,
|
|
1107
|
+
models: zod.z.preprocess((value) => Array.isArray(value) ? value.map((item) => e$3(item) ? item : {}) : [], zod.z.array(MODEL_SCHEMA)),
|
|
1108
|
+
hasKey: TRUE_ONLY
|
|
1109
|
+
});
|
|
1110
|
+
const CURRENT_SCHEMA = zod.z.object({
|
|
1111
|
+
provider: STRING_OR_EMPTY,
|
|
1112
|
+
model: STRING_OR_EMPTY,
|
|
1113
|
+
keyIsDynamic: TRUE_ONLY,
|
|
1114
|
+
url: STRING_OR_EMPTY
|
|
1115
|
+
});
|
|
1116
|
+
const SOURCE_SCHEMA = zod.z.object({
|
|
1117
|
+
status: STRING_OR_EMPTY,
|
|
1118
|
+
message: STRING_OR_EMPTY
|
|
1119
|
+
});
|
|
1120
|
+
const ENDPOINT_VERIFY_SCHEMA = zod.z.object({
|
|
1121
|
+
ok: TRUE_ONLY,
|
|
1122
|
+
message: STRING_OR_EMPTY
|
|
1123
|
+
});
|
|
554
1124
|
/** 可得性来源的合法域(与 host 侧 `OcrBinarySource` 同一份取值)。形状不合即「没报」。 */
|
|
555
|
-
const
|
|
1125
|
+
const BINARY_SOURCE_SCHEMA = zod.z.enum([
|
|
556
1126
|
"bundled",
|
|
557
1127
|
"path",
|
|
558
1128
|
"none"
|
|
@@ -565,22 +1135,25 @@ const BINARY_SOURCES = /* @__PURE__ */ new Set([
|
|
|
565
1135
|
* 作废——「宿主报了 PATH 却没报版本」仍然是一条用户要看的事实。`measuredVersion` 例外,
|
|
566
1136
|
* 它恒在(null = 还没测过),因为「没测过」与「旧宿主不带这字段」在卡片上是同一句话。
|
|
567
1137
|
*/
|
|
1138
|
+
const BINARY_SCHEMA = zod.z.object({
|
|
1139
|
+
source: BINARY_SOURCE_SCHEMA,
|
|
1140
|
+
path: STRING_OR_EMPTY,
|
|
1141
|
+
channel: STRING_OR_EMPTY,
|
|
1142
|
+
expectedVersion: STRING_OR_EMPTY,
|
|
1143
|
+
installHint: STRING_OR_EMPTY,
|
|
1144
|
+
measuredVersion: NULLABLE_STRING
|
|
1145
|
+
});
|
|
568
1146
|
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"];
|
|
1147
|
+
const parsed = BINARY_SCHEMA.safeParse(raw);
|
|
1148
|
+
if (!parsed.success) return null;
|
|
1149
|
+
const { source, path, channel, expectedVersion, installHint, measuredVersion } = parsed.data;
|
|
577
1150
|
return {
|
|
578
1151
|
source,
|
|
579
1152
|
...path === "" ? {} : { path },
|
|
580
1153
|
...channel === "" ? {} : { channel },
|
|
581
1154
|
...expectedVersion === "" ? {} : { expectedVersion },
|
|
582
1155
|
...installHint === "" ? {} : { installHint },
|
|
583
|
-
measuredVersion
|
|
1156
|
+
measuredVersion
|
|
584
1157
|
};
|
|
585
1158
|
}
|
|
586
1159
|
/**
|
|
@@ -591,67 +1164,55 @@ function binaryOf(raw) {
|
|
|
591
1164
|
async function loadCliStatus(reader) {
|
|
592
1165
|
if (reader === void 0) return null;
|
|
593
1166
|
try {
|
|
594
|
-
return binaryOf(i(await reader.providers(), "ocrBinary"));
|
|
1167
|
+
return binaryOf(i$1(await reader.providers(), "ocrBinary"));
|
|
595
1168
|
} catch {
|
|
596
1169
|
return null;
|
|
597
1170
|
}
|
|
598
1171
|
}
|
|
1172
|
+
const EMPTY_PAYLOAD = {
|
|
1173
|
+
providers: [],
|
|
1174
|
+
current: {
|
|
1175
|
+
provider: "",
|
|
1176
|
+
model: "",
|
|
1177
|
+
keyIsDynamic: false,
|
|
1178
|
+
url: ""
|
|
1179
|
+
},
|
|
1180
|
+
getCredReady: false,
|
|
1181
|
+
source: {
|
|
1182
|
+
status: "",
|
|
1183
|
+
message: ""
|
|
1184
|
+
},
|
|
1185
|
+
endpointVerify: null,
|
|
1186
|
+
ocrBinary: null
|
|
1187
|
+
};
|
|
1188
|
+
const PROVIDERS_PAYLOAD_SCHEMA = zod.z.object({
|
|
1189
|
+
providers: zod.z.preprocess((value) => Array.isArray(value) ? value.map((item) => e$3(item) ? item : {}) : [], zod.z.array(PROVIDER_SCHEMA)),
|
|
1190
|
+
current: zod.z.preprocess((value) => e$3(value) ? value : {}, CURRENT_SCHEMA),
|
|
1191
|
+
getCredReady: TRUE_ONLY,
|
|
1192
|
+
source: zod.z.preprocess((value) => e$3(value) ? value : {}, SOURCE_SCHEMA),
|
|
1193
|
+
endpointVerify: zod.z.preprocess((value) => {
|
|
1194
|
+
const parsed = ENDPOINT_VERIFY_SCHEMA.safeParse(value);
|
|
1195
|
+
return parsed.success ? parsed.data : null;
|
|
1196
|
+
}, ENDPOINT_VERIFY_SCHEMA.nullable()),
|
|
1197
|
+
ocrBinary: zod.z.unknown().transform((value) => binaryOf(value))
|
|
1198
|
+
});
|
|
599
1199
|
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
|
-
};
|
|
1200
|
+
const parsed = PROVIDERS_PAYLOAD_SCHEMA.safeParse(json);
|
|
1201
|
+
return parsed.success ? parsed.data : EMPTY_PAYLOAD;
|
|
626
1202
|
}
|
|
627
|
-
/**
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const FORM_CONTROL_CLASS = "ocr-select";
|
|
631
|
-
/** 小节标题的类名(卡片头、CLI 状态行、详情页区块共用;sonarjs 的重复字面量门禁)。 */
|
|
1203
|
+
/** 动作按钮行的类名(共享组件的行内按钮组;多处共用,见 sonarjs 的重复字面量门禁)。 */
|
|
1204
|
+
const ACTIONS_CLASS = "ocr-actions";
|
|
1205
|
+
/** 小节标题的类名(详情页区块共用;sonarjs 的重复字面量门禁)。 */
|
|
632
1206
|
const CARD_NAME_CLASS = "ocr-card-name";
|
|
633
1207
|
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}",
|
|
1208
|
+
".ocr-card-name{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.4}",
|
|
1209
|
+
".ocr-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);line-height:1.5}",
|
|
1210
|
+
".ocr-actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
|
|
1211
|
+
".ocr-status{font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;margin:8px 0 0}",
|
|
1212
|
+
".ocr-err{color:var(--dsw-alias-state-error-primary)}",
|
|
653
1213
|
".ocr-ok{color:var(--dsw-alias-label-primary,inherit)}",
|
|
654
|
-
".ocr-muted{color:var(--dsw-alias-label-tertiary,#8a8f99)}"
|
|
1214
|
+
".ocr-muted{color:var(--dsw-alias-label-tertiary,#8a8f99)}",
|
|
1215
|
+
".ocr-adminTextarea{min-height:160px}"
|
|
655
1216
|
].join("\n");
|
|
656
1217
|
function cardStore(scope) {
|
|
657
1218
|
let cachedSnap = null;
|
|
@@ -679,190 +1240,6 @@ function cardStore(scope) {
|
|
|
679
1240
|
}
|
|
680
1241
|
};
|
|
681
1242
|
}
|
|
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
1243
|
/** provider 下拉项文本(没有 key 的加后缀并被禁用)。提成模块级函数是为了不把
|
|
867
1244
|
* 带插值的 t() 嵌进 createElement(unicorn/max-nested-calls 上限 3)。 */
|
|
868
1245
|
function providerOptionLabel(t, provider) {
|
|
@@ -939,35 +1316,30 @@ function statusClass(ok) {
|
|
|
939
1316
|
*/
|
|
940
1317
|
function buildCliStatusRow(deps) {
|
|
941
1318
|
const { t } = deps;
|
|
942
|
-
const label = (0, react.createElement)("div", { className: CARD_NAME_CLASS }, t("binaryTitle"));
|
|
943
1319
|
const button = (0, react.createElement)("button", {
|
|
944
|
-
className: "
|
|
1320
|
+
className: "dsu-btn",
|
|
945
1321
|
type: "button",
|
|
946
1322
|
disabled: deps.busy !== null,
|
|
947
1323
|
onClick: deps.onProbe,
|
|
948
1324
|
"data-field": "probe"
|
|
949
1325
|
}, 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);
|
|
1326
|
+
const controls = (0, react.createElement)("div", {
|
|
1327
|
+
className: ACTIONS_CLASS,
|
|
1328
|
+
"data-field": "cli-status"
|
|
1329
|
+
}, button);
|
|
1330
|
+
const status = (0, react.createElement)("p", { className: "dsu-hint" }, binaryStatusText(t, deps.binary));
|
|
959
1331
|
const versionText = cliVersionText(t, deps.binary);
|
|
960
|
-
const versionLine = versionText === "" ? null : (0, react.createElement)("
|
|
1332
|
+
const versionLine = versionText === "" ? null : (0, react.createElement)("p", { className: "dsu-hint" }, versionText);
|
|
961
1333
|
const hintText = cliInstallText(deps.binary);
|
|
962
|
-
const installLine = hintText === "" ? null : (0, react.createElement)("
|
|
1334
|
+
const installLine = hintText === "" ? null : (0, react.createElement)("p", { className: "dsu-hint" }, hintText);
|
|
963
1335
|
const probe = deps.probeMsg ? (0, react.createElement)("div", {
|
|
964
1336
|
className: statusClass(deps.probeMsg.ok),
|
|
965
1337
|
"data-field": "probe-result"
|
|
966
1338
|
}, deps.probeMsg.text) : null;
|
|
967
|
-
return (0, react.createElement)(
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
},
|
|
1339
|
+
return (0, react.createElement)(d, {
|
|
1340
|
+
title: t("binaryTitle"),
|
|
1341
|
+
hint: t("binaryProbeHint")
|
|
1342
|
+
}, status, versionLine, installLine, controls, probe);
|
|
971
1343
|
}
|
|
972
1344
|
/**
|
|
973
1345
|
* 徽标用的短文案(标题旁那只标签放不下整句装法)。与 `binaryStatusText` 同一判据、
|
|
@@ -1095,100 +1467,165 @@ function OcrCliActivation(props) {
|
|
|
1095
1467
|
if (!mine || status?.source !== "none") return null;
|
|
1096
1468
|
return buildCliActivation(props.t, props, cliInstallText(status));
|
|
1097
1469
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
|
|
1100
|
-
* 的降级支本就不是同一件事。 */
|
|
1101
|
-
function buildReadyProviderSection(deps, data) {
|
|
1470
|
+
/** 数据齐时的动作行:三个按钮共用一只在飞旗标,按钮文案只跟**自己**那条走。 */
|
|
1471
|
+
function buildActionField(deps) {
|
|
1102
1472
|
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
1473
|
const applyBtn = (0, react.createElement)("button", {
|
|
1139
|
-
className: "
|
|
1474
|
+
className: "dsu-btn dsu-btnPrimary",
|
|
1140
1475
|
type: "button",
|
|
1141
1476
|
disabled: deps.busy !== null,
|
|
1142
1477
|
onClick: deps.onApply,
|
|
1143
1478
|
"data-field": "apply"
|
|
1144
1479
|
}, deps.busy === "apply" ? t("applying") : t("applyToOcr"));
|
|
1145
1480
|
const testBtn = (0, react.createElement)("button", {
|
|
1146
|
-
className: "
|
|
1481
|
+
className: "dsu-btn",
|
|
1147
1482
|
type: "button",
|
|
1148
1483
|
disabled: deps.busy !== null,
|
|
1149
1484
|
onClick: deps.onTest,
|
|
1150
1485
|
"data-field": "test"
|
|
1151
1486
|
}, t("testConnection"));
|
|
1152
1487
|
const migrateBtn = (0, react.createElement)("button", {
|
|
1153
|
-
className: "
|
|
1488
|
+
className: "dsu-btn",
|
|
1154
1489
|
type: "button",
|
|
1155
1490
|
disabled: deps.busy !== null,
|
|
1156
1491
|
onClick: deps.onMigrate,
|
|
1157
1492
|
"data-field": "migrate"
|
|
1158
1493
|
}, t("migrateKeys"));
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1494
|
+
return (0, react.createElement)(g, {
|
|
1495
|
+
label: t("actionsLabel"),
|
|
1496
|
+
wide: true
|
|
1497
|
+
}, (0, react.createElement)("div", { className: ACTIONS_CLASS }, applyBtn, testBtn, migrateBtn));
|
|
1498
|
+
}
|
|
1499
|
+
/** 当前配置 / key / 装载期自检三行提示(自检失败只作提示,不挡表单)。 */
|
|
1500
|
+
function buildProviderInfo(deps, data) {
|
|
1501
|
+
const { t } = deps;
|
|
1166
1502
|
const cur = data.current;
|
|
1167
1503
|
const currentHint = cur.provider === "" ? t("noProvider") : currentConfigText(t, cur);
|
|
1168
|
-
const currentInfo = (0, react.createElement)("
|
|
1169
|
-
const keyInfo = (0, react.createElement)("
|
|
1504
|
+
const currentInfo = (0, react.createElement)("p", { className: "dsu-hint" }, currentHint);
|
|
1505
|
+
const keyInfo = (0, react.createElement)("p", { className: "dsu-hint" }, deps.keyHint);
|
|
1170
1506
|
const verify = data.endpointVerify ?? null;
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1507
|
+
return [
|
|
1508
|
+
verify !== null && !verify.ok ? (0, react.createElement)("p", {
|
|
1509
|
+
className: "dsu-error",
|
|
1510
|
+
"data-field": "endpoint-verify"
|
|
1511
|
+
}, t("endpointVerifyFailed", { reason: verify.message })) : null,
|
|
1512
|
+
currentInfo,
|
|
1513
|
+
keyInfo
|
|
1514
|
+
];
|
|
1515
|
+
}
|
|
1516
|
+
/** 数据齐(source.status === "ready")那一支:模型四个字段 + 动作行 + 三行提示。 */
|
|
1517
|
+
function buildReadyProviderSection(deps, data) {
|
|
1518
|
+
const { t } = deps;
|
|
1519
|
+
const providerOptions = data.providers.map((provider) => ({
|
|
1520
|
+
value: provider.name,
|
|
1521
|
+
label: providerOptionLabel(t, provider),
|
|
1522
|
+
disabled: !provider.hasKey
|
|
1523
|
+
}));
|
|
1524
|
+
const fallbackModelName = deps.effectiveModel === "" ? t("noModel") : deps.effectiveModel;
|
|
1525
|
+
const modelOptions = (deps.modelOptions.length > 0 ? deps.modelOptions : [{
|
|
1526
|
+
id: deps.effectiveModel,
|
|
1527
|
+
name: fallbackModelName
|
|
1528
|
+
}]).map((model) => ({
|
|
1529
|
+
value: model.id,
|
|
1530
|
+
label: model.name
|
|
1531
|
+
}));
|
|
1532
|
+
const effortOptions = [
|
|
1533
|
+
{
|
|
1534
|
+
value: "auto",
|
|
1535
|
+
label: t("effortAuto")
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
value: "low",
|
|
1539
|
+
label: t("effortLow")
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
value: "medium",
|
|
1543
|
+
label: t("effortMedium")
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
value: "high",
|
|
1547
|
+
label: t("effortHigh")
|
|
1548
|
+
}
|
|
1549
|
+
];
|
|
1550
|
+
const languageOptions = [{
|
|
1551
|
+
value: "中文",
|
|
1552
|
+
label: "中文"
|
|
1553
|
+
}, {
|
|
1554
|
+
value: "English",
|
|
1555
|
+
label: "English"
|
|
1556
|
+
}];
|
|
1557
|
+
const modelUrl = deps.chosenProvider?.baseURL ?? "";
|
|
1558
|
+
return [
|
|
1559
|
+
(0, react.createElement)(x, {
|
|
1560
|
+
label: "Provider",
|
|
1561
|
+
...l(t("providerHint")),
|
|
1562
|
+
field: "ocrProvider",
|
|
1563
|
+
value: deps.effectiveProvider,
|
|
1564
|
+
options: providerOptions,
|
|
1565
|
+
disabled: data.providers.length === 0,
|
|
1566
|
+
onChange: deps.onPickProvider
|
|
1567
|
+
}),
|
|
1568
|
+
(0, react.createElement)(x, {
|
|
1569
|
+
label: "Model",
|
|
1570
|
+
...modelUrl === "" ? {} : { hint: modelUrl },
|
|
1571
|
+
field: "ocrModel",
|
|
1572
|
+
value: deps.effectiveModel,
|
|
1573
|
+
options: modelOptions,
|
|
1574
|
+
disabled: !deps.chosenProvider || deps.modelOptions.length === 0,
|
|
1575
|
+
onChange: deps.onPickModel
|
|
1576
|
+
}),
|
|
1577
|
+
(0, react.createElement)(x, {
|
|
1578
|
+
label: t("effortLabel"),
|
|
1579
|
+
...l(t("effortHint")),
|
|
1580
|
+
field: "effort",
|
|
1581
|
+
value: deps.effortValue,
|
|
1582
|
+
options: effortOptions,
|
|
1583
|
+
disabled: !deps.writable,
|
|
1584
|
+
onChange: (next) => {
|
|
1585
|
+
deps.onField("effort", next);
|
|
1586
|
+
}
|
|
1587
|
+
}),
|
|
1588
|
+
(0, react.createElement)(x, {
|
|
1589
|
+
label: t("languageLabel"),
|
|
1590
|
+
...l(t("languageHint")),
|
|
1591
|
+
field: "language",
|
|
1592
|
+
value: deps.languageValue,
|
|
1593
|
+
options: languageOptions,
|
|
1594
|
+
disabled: !deps.writable,
|
|
1595
|
+
onChange: (next) => {
|
|
1596
|
+
deps.onField("language", next);
|
|
1597
|
+
}
|
|
1598
|
+
}),
|
|
1599
|
+
(0, react.createElement)(_, {
|
|
1600
|
+
label: t("autoVerifyLabel"),
|
|
1601
|
+
...l(t("autoVerifyHint")),
|
|
1602
|
+
field: "autoVerify",
|
|
1603
|
+
checked: deps.autoVerifyValue,
|
|
1604
|
+
disabled: !deps.writable,
|
|
1605
|
+
onChange: (next) => {
|
|
1606
|
+
deps.onField("autoVerify", next);
|
|
1607
|
+
}
|
|
1608
|
+
}),
|
|
1609
|
+
buildActionField(deps),
|
|
1610
|
+
...buildProviderInfo(deps, data)
|
|
1611
|
+
];
|
|
1176
1612
|
}
|
|
1177
1613
|
/** Provider / Model 选择与应用区:三条降级各出一行提示(数据未就绪/读取失败/
|
|
1178
1614
|
* 数据源不可用),只有 source.status === "ready" 才交给 buildReadyProviderSection 画表单。 */
|
|
1179
1615
|
function buildProviderSection(deps) {
|
|
1180
1616
|
const { t } = deps;
|
|
1617
|
+
let body;
|
|
1181
1618
|
if (deps.fetchError !== null) {
|
|
1182
1619
|
const reason = t("loadProvidersFailed", { error: deps.fetchError });
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
if (deps.data ===
|
|
1186
|
-
|
|
1620
|
+
body = (0, react.createElement)("p", { className: "dsu-error" }, reason);
|
|
1621
|
+
} else if (deps.data === null) body = (0, react.createElement)("p", { className: "dsu-hint" }, t("loadingProviders"));
|
|
1622
|
+
else if (deps.data.source.status === "ready") body = buildReadyProviderSection(deps, deps.data);
|
|
1623
|
+
else {
|
|
1187
1624
|
const sourceMessage = deps.data.source.message;
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1625
|
+
const reason = sourceMessage === "" ? t("noReason") : sourceMessage;
|
|
1626
|
+
body = (0, react.createElement)("p", { className: "dsu-error" }, t("readProvidersFailed", { reason }));
|
|
1190
1627
|
}
|
|
1191
|
-
return
|
|
1628
|
+
return (0, react.createElement)(d, { title: t("modelSection") }, body);
|
|
1192
1629
|
}
|
|
1193
1630
|
/** provider 预选:显式选择 > 当前 OCR 配置(仍存在)> 第一个有 key 的 provider。 */
|
|
1194
1631
|
function resolveBackend(providerList, current, selProvider) {
|
|
@@ -1218,157 +1655,95 @@ function resolveSelection(providerList, current, selProvider, selModel) {
|
|
|
1218
1655
|
modelOptions: model.modelOptions
|
|
1219
1656
|
};
|
|
1220
1657
|
}
|
|
1221
|
-
/**
|
|
1658
|
+
/** 审查参数小节:评论条数、运行超时、配置文件路径 + 保存条。 */
|
|
1222
1659
|
function buildSettingsSection(deps, writable, setField) {
|
|
1223
1660
|
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, {
|
|
1661
|
+
const maxCommentsRow = (0, react.createElement)(v, {
|
|
1287
1662
|
label: t("maxCommentsLabel"),
|
|
1288
|
-
|
|
1663
|
+
...l(t("maxCommentsHint")),
|
|
1289
1664
|
field: "maxComments",
|
|
1290
1665
|
value: deps.maxCommentsValue,
|
|
1291
|
-
min:
|
|
1666
|
+
...COUNT_SCHEMA.minValue === null ? {} : { min: COUNT_SCHEMA.minValue },
|
|
1667
|
+
step: 1,
|
|
1668
|
+
invalidHint: t("outOfRange"),
|
|
1292
1669
|
disabled: !writable,
|
|
1293
|
-
|
|
1294
|
-
setField("maxComments",
|
|
1670
|
+
onChange: (next) => {
|
|
1671
|
+
setField("maxComments", next);
|
|
1295
1672
|
}
|
|
1296
1673
|
});
|
|
1297
|
-
const timeoutRow = (0, react.createElement)(
|
|
1674
|
+
const timeoutRow = (0, react.createElement)(v, {
|
|
1298
1675
|
label: t("timeoutLabel"),
|
|
1299
|
-
|
|
1676
|
+
...l(t("timeoutHint")),
|
|
1300
1677
|
field: "timeoutMinutes",
|
|
1301
1678
|
value: deps.timeoutMinutesValue,
|
|
1302
|
-
min:
|
|
1679
|
+
...COUNT_SCHEMA.minValue === null ? {} : { min: COUNT_SCHEMA.minValue },
|
|
1680
|
+
step: 1,
|
|
1681
|
+
invalidHint: t("outOfRange"),
|
|
1303
1682
|
disabled: !writable,
|
|
1304
|
-
|
|
1305
|
-
setField("timeoutMinutes",
|
|
1683
|
+
onChange: (next) => {
|
|
1684
|
+
setField("timeoutMinutes", next);
|
|
1306
1685
|
}
|
|
1307
1686
|
});
|
|
1308
|
-
const configPathRow = (0, react.createElement)(
|
|
1687
|
+
const configPathRow = (0, react.createElement)(y, {
|
|
1309
1688
|
label: t("configPathLabel"),
|
|
1310
|
-
|
|
1689
|
+
...l(t("configPathHint")),
|
|
1311
1690
|
field: "ocrConfigPath",
|
|
1312
1691
|
value: deps.ocrConfigPathValue,
|
|
1313
1692
|
placeholder: "~/.opencodereview/config.json",
|
|
1693
|
+
monospace: true,
|
|
1694
|
+
wide: true,
|
|
1314
1695
|
disabled: !writable,
|
|
1315
|
-
|
|
1316
|
-
setField("ocrConfigPath",
|
|
1696
|
+
onChange: (next) => {
|
|
1697
|
+
setField("ocrConfigPath", next.trim());
|
|
1317
1698
|
}
|
|
1318
1699
|
});
|
|
1319
|
-
const saveRow = (0, react.createElement)(
|
|
1320
|
-
t,
|
|
1700
|
+
const saveRow = (0, react.createElement)(S, {
|
|
1321
1701
|
dirty: deps.dirty,
|
|
1322
1702
|
writable,
|
|
1323
1703
|
busy: deps.settingsBusy,
|
|
1324
1704
|
error: deps.saveError,
|
|
1705
|
+
saveLabel: t("save"),
|
|
1706
|
+
savingLabel: t("saving"),
|
|
1707
|
+
revertLabel: t("revert"),
|
|
1708
|
+
dirtyLabel: t("statusDirty"),
|
|
1709
|
+
cleanLabel: t("statusClean"),
|
|
1710
|
+
readOnlyLabel: t("statusReadOnly"),
|
|
1325
1711
|
onSave: deps.onSave,
|
|
1326
1712
|
onDiscard: deps.onDiscard
|
|
1327
1713
|
});
|
|
1328
|
-
const statusHintNode = deps.statusHint !== null && deps.statusHint !== "" ? (0, react.createElement)("
|
|
1329
|
-
return (0, react.createElement)(
|
|
1714
|
+
const statusHintNode = deps.statusHint !== null && deps.statusHint !== "" ? (0, react.createElement)("p", { className: "dsu-hint" }, deps.statusHint) : null;
|
|
1715
|
+
return (0, react.createElement)(d, { title: t("reviewSection") }, statusHintNode, maxCommentsRow, timeoutRow, configPathRow, saveRow);
|
|
1330
1716
|
}
|
|
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
1717
|
function buildMcpAdmin(props) {
|
|
1349
1718
|
const { t } = props;
|
|
1350
1719
|
const textarea = (0, react.createElement)("textarea", {
|
|
1351
1720
|
"data-field": "mcpJson",
|
|
1352
1721
|
value: props.mcpJson,
|
|
1353
1722
|
disabled: !props.writable || props.mcpBusy || !props.available,
|
|
1723
|
+
"aria-label": t("mcpTitle"),
|
|
1724
|
+
className: "dsu-input dsu-textarea dsu-mono ocr-adminTextarea",
|
|
1354
1725
|
onChange: (event) => {
|
|
1355
1726
|
props.onMcpChange(event.target.value);
|
|
1356
|
-
}
|
|
1357
|
-
style: textareaStyle
|
|
1727
|
+
}
|
|
1358
1728
|
});
|
|
1359
1729
|
const button = (0, react.createElement)("button", {
|
|
1360
1730
|
type: "button",
|
|
1731
|
+
className: "dsu-btn",
|
|
1361
1732
|
"data-field": "mcpSave",
|
|
1362
1733
|
disabled: !props.writable || props.mcpBusy || !props.available,
|
|
1363
1734
|
onClick: props.onMcpSave
|
|
1364
1735
|
}, props.mcpBusy ? t("mcpSaving") : t("mcpSave"));
|
|
1365
|
-
const message = props.mcpMsg === null ? null : (0, react.createElement)("
|
|
1366
|
-
return (0, react.createElement)(
|
|
1736
|
+
const message = props.mcpMsg === null ? null : (0, react.createElement)("p", { className: "dsu-hint" }, props.mcpMsg);
|
|
1737
|
+
return (0, react.createElement)(d, {
|
|
1738
|
+
title: t("mcpTitle"),
|
|
1739
|
+
hint: t("mcpHint")
|
|
1740
|
+
}, props.available ? textarea : (0, react.createElement)("p", { className: "dsu-hint" }, t("adminUnavailable")), (0, react.createElement)("div", { className: ACTIONS_CLASS }, button), message);
|
|
1367
1741
|
}
|
|
1368
1742
|
function buildAuthAdmin(props) {
|
|
1369
1743
|
const { t } = props;
|
|
1370
1744
|
const check = (0, react.createElement)("button", {
|
|
1371
1745
|
type: "button",
|
|
1746
|
+
className: "dsu-btn",
|
|
1372
1747
|
"data-field": "authCheck",
|
|
1373
1748
|
disabled: props.authBusy || !props.available,
|
|
1374
1749
|
onClick: props.onCheckAuth
|
|
@@ -1377,23 +1752,29 @@ function buildAuthAdmin(props) {
|
|
|
1377
1752
|
"data-field": "chatGptModel",
|
|
1378
1753
|
value: props.authModel,
|
|
1379
1754
|
disabled: !props.writable || props.authBusy || !props.available,
|
|
1755
|
+
"aria-label": t("chatGptModelLabel"),
|
|
1756
|
+
className: "dsu-input",
|
|
1380
1757
|
onChange: (event) => {
|
|
1381
1758
|
props.onAuthModelChange(event.target.value);
|
|
1382
1759
|
}
|
|
1383
1760
|
});
|
|
1384
1761
|
const use = (0, react.createElement)("button", {
|
|
1385
1762
|
type: "button",
|
|
1763
|
+
className: "dsu-btn",
|
|
1386
1764
|
"data-field": "chatGptUse",
|
|
1387
1765
|
disabled: !props.writable || props.authBusy || !props.available,
|
|
1388
1766
|
onClick: props.onUseChatGpt
|
|
1389
1767
|
}, 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)(
|
|
1768
|
+
const output = props.authOutput === null ? null : (0, react.createElement)("pre", { className: "ocr-status dsu-mono" }, props.authOutput);
|
|
1769
|
+
const message = props.authMsg === null ? null : (0, react.createElement)("p", { className: "dsu-hint" }, props.authMsg);
|
|
1770
|
+
return (0, react.createElement)(d, {
|
|
1771
|
+
title: t("chatGptTitle"),
|
|
1772
|
+
hint: t("chatGptHint")
|
|
1773
|
+
}, (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
1774
|
}
|
|
1394
1775
|
/** MCP 配置 + ChatGPT 登录态/切换:都写 OCR 自己的配置,放在设置卡下方。 */
|
|
1395
1776
|
function buildAdminSection(props) {
|
|
1396
|
-
return (0, react.createElement)("div",
|
|
1777
|
+
return (0, react.createElement)("div", null, buildMcpAdmin(props), buildAuthAdmin(props));
|
|
1397
1778
|
}
|
|
1398
1779
|
/**
|
|
1399
1780
|
* 读设置页载荷(卡片唯一的数据入口):失败只落 fetchError 文本,不抛穿渲染。
|
|
@@ -1405,8 +1786,8 @@ async function refreshProviders(reader, t, sinks) {
|
|
|
1405
1786
|
try {
|
|
1406
1787
|
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1407
1788
|
const json = await reader.providers();
|
|
1408
|
-
if (i(json, "ok") === false) {
|
|
1409
|
-
const detail = strOf(i(json, "error"));
|
|
1789
|
+
if (i$1(json, "ok") === false) {
|
|
1790
|
+
const detail = strOf(i$1(json, "error"));
|
|
1410
1791
|
throw new Error(detail === "" ? t("remoteUnavailable") : detail);
|
|
1411
1792
|
}
|
|
1412
1793
|
const payload = providersPayloadOf(json);
|
|
@@ -1423,8 +1804,8 @@ function readerPost(reader, t) {
|
|
|
1423
1804
|
if (reader === void 0) throw new Error(t("remoteUnavailable"));
|
|
1424
1805
|
switch (action) {
|
|
1425
1806
|
case "select": {
|
|
1426
|
-
const provider = strOf(i(body, "provider"));
|
|
1427
|
-
const model = strOf(i(body, "model"));
|
|
1807
|
+
const provider = strOf(i$1(body, "provider"));
|
|
1808
|
+
const model = strOf(i$1(body, "model"));
|
|
1428
1809
|
const reply = await reader.select(provider, model);
|
|
1429
1810
|
return { json: () => Promise.resolve(reply) };
|
|
1430
1811
|
}
|
|
@@ -1461,12 +1842,12 @@ async function runApplySelection(deps) {
|
|
|
1461
1842
|
provider: effectiveProvider,
|
|
1462
1843
|
model: effectiveModel
|
|
1463
1844
|
})).json();
|
|
1464
|
-
if (i(selJson, "ok") === true) {
|
|
1845
|
+
if (i$1(selJson, "ok") === true) {
|
|
1465
1846
|
let testText;
|
|
1466
|
-
const llmTestRaw = i(selJson, "llmTest");
|
|
1467
|
-
if (e$
|
|
1468
|
-
const testOutput = strOf(i(llmTestRaw, "output")).slice(0, 400);
|
|
1469
|
-
testText = i(llmTestRaw, "ok") === true ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
|
|
1847
|
+
const llmTestRaw = i$1(selJson, "llmTest");
|
|
1848
|
+
if (e$3(llmTestRaw)) {
|
|
1849
|
+
const testOutput = strOf(i$1(llmTestRaw, "output")).slice(0, 400);
|
|
1850
|
+
testText = i$1(llmTestRaw, "ok") === true ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
|
|
1470
1851
|
} else testText = t("notVerified");
|
|
1471
1852
|
const appliedText = t("applied", {
|
|
1472
1853
|
provider: effectiveProvider,
|
|
@@ -1479,7 +1860,7 @@ async function runApplySelection(deps) {
|
|
|
1479
1860
|
});
|
|
1480
1861
|
deps.refresh();
|
|
1481
1862
|
} else {
|
|
1482
|
-
const failure = strOf(i(selJson, "error"));
|
|
1863
|
+
const failure = strOf(i$1(selJson, "error"));
|
|
1483
1864
|
deps.setActionMsg({
|
|
1484
1865
|
ok: false,
|
|
1485
1866
|
text: failure === "" ? t("applyFailed") : failure
|
|
@@ -1502,11 +1883,11 @@ async function runMigrateKeys(deps) {
|
|
|
1502
1883
|
deps.setActionMsg(null);
|
|
1503
1884
|
try {
|
|
1504
1885
|
const json = await (await deps.post("migrate")).json();
|
|
1505
|
-
const migratedVal = i(json, "migrated");
|
|
1506
|
-
const skippedVal = i(json, "skippedUnknown");
|
|
1886
|
+
const migratedVal = i$1(json, "migrated");
|
|
1887
|
+
const skippedVal = i$1(json, "skippedUnknown");
|
|
1507
1888
|
const migrated = Array.isArray(migratedVal) ? migratedVal.filter((item) => typeof item === "string") : [];
|
|
1508
1889
|
const skipped = Array.isArray(skippedVal) ? skippedVal.filter((item) => typeof item === "string") : [];
|
|
1509
|
-
const ok = i(json, "ok") === true;
|
|
1890
|
+
const ok = i$1(json, "ok") === true;
|
|
1510
1891
|
const head = ok ? (migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey")) + (skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "") : t("migrateFailed");
|
|
1511
1892
|
deps.setActionMsg({
|
|
1512
1893
|
ok,
|
|
@@ -1530,8 +1911,8 @@ async function runTestConnection(deps) {
|
|
|
1530
1911
|
deps.setActionMsg(null);
|
|
1531
1912
|
try {
|
|
1532
1913
|
const json = await (await deps.post("test")).json();
|
|
1533
|
-
const ok = i(json, "ok") === true;
|
|
1534
|
-
const testOutput = strOf(i(json, "output"));
|
|
1914
|
+
const ok = i$1(json, "ok") === true;
|
|
1915
|
+
const testOutput = strOf(i$1(json, "output"));
|
|
1535
1916
|
deps.setActionMsg({
|
|
1536
1917
|
ok,
|
|
1537
1918
|
text: (ok ? t("llmTestPassed") : t("llmTestFailed")) + testOutput
|
|
@@ -1558,12 +1939,12 @@ async function runProbe(deps) {
|
|
|
1558
1939
|
deps.setProbeMsg(null);
|
|
1559
1940
|
try {
|
|
1560
1941
|
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"));
|
|
1942
|
+
const ok = i$1(json, "ok") === true;
|
|
1943
|
+
const head = ok ? t("probeVersion", { version: strOf(i$1(json, "version")) }) : t("probeReason", { reason: strOf(i$1(json, "reason")) });
|
|
1944
|
+
const output = strOf(i$1(json, "output"));
|
|
1564
1945
|
const lines = [
|
|
1565
1946
|
head,
|
|
1566
|
-
strOf(i(json, "changeText")),
|
|
1947
|
+
strOf(i$1(json, "changeText")),
|
|
1567
1948
|
output
|
|
1568
1949
|
].filter((line) => line !== "");
|
|
1569
1950
|
deps.setProbeMsg({
|
|
@@ -1585,8 +1966,8 @@ async function runProbe(deps) {
|
|
|
1585
1966
|
* 这时不能清 touched、也不能报成功——否则用户看到「已保存」而配置原封未动。
|
|
1586
1967
|
* 传输失败仍以 rejection 表达。 */
|
|
1587
1968
|
async function saveSettingsDraft(deps) {
|
|
1588
|
-
const { t
|
|
1589
|
-
const keys = t(touched, value);
|
|
1969
|
+
const { t, value, touched } = deps;
|
|
1970
|
+
const keys = t$1(touched, value);
|
|
1590
1971
|
if (keys.length === 0) return;
|
|
1591
1972
|
deps.setSettingsBusy(true);
|
|
1592
1973
|
deps.setSaveError(null);
|
|
@@ -1612,7 +1993,7 @@ async function saveSettingsDraft(deps) {
|
|
|
1612
1993
|
for (const field of refused) kept[field] = touched[field];
|
|
1613
1994
|
deps.setTouched(kept);
|
|
1614
1995
|
const reason = failures.length > 0 ? `(${errorSummary(failures[0])})` : "";
|
|
1615
|
-
deps.setSaveError(`${t
|
|
1996
|
+
deps.setSaveError(`${t("saveRefused", { fields: refused.join(", ") })}${reason}`);
|
|
1616
1997
|
return;
|
|
1617
1998
|
}
|
|
1618
1999
|
deps.setTouched({});
|
|
@@ -1620,11 +2001,11 @@ async function saveSettingsDraft(deps) {
|
|
|
1620
2001
|
function draftFieldsOf(touched, value) {
|
|
1621
2002
|
const eff = (field, def) => field in touched ? touched[field] : value[field] ?? def;
|
|
1622
2003
|
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),
|
|
2004
|
+
effortValue: textOf(eff("effort", "auto"), EFFORT_SCHEMA, "auto"),
|
|
2005
|
+
languageValue: textOf(eff("language", "中文"), LANGUAGE_SCHEMA, "中文"),
|
|
2006
|
+
autoVerifyValue: booleanOf(eff("autoVerify", true), true),
|
|
2007
|
+
maxCommentsValue: numOf(eff("maxComments", 12), 12, COUNT_SCHEMA),
|
|
2008
|
+
timeoutMinutesValue: numOf(eff("timeoutMinutes", 0), 0, COUNT_SCHEMA),
|
|
1628
2009
|
ocrConfigPathValue: strOf(eff("ocrConfigPath", ""))
|
|
1629
2010
|
};
|
|
1630
2011
|
}
|
|
@@ -1643,9 +2024,7 @@ function cardHints(t, ready, status, data) {
|
|
|
1643
2024
|
/** Provider 区 = provider/model 表单 + 最近一次动作的回执条。 */
|
|
1644
2025
|
function buildProviderPanel(deps) {
|
|
1645
2026
|
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);
|
|
2027
|
+
return [buildProviderSection(deps), actionMsg ? (0, react.createElement)("p", { className: statusClass(actionMsg.ok) }, actionMsg.text) : null];
|
|
1649
2028
|
}
|
|
1650
2029
|
/** 管理区的状态与动作(MCP 配置 + ChatGPT 登录态/切换);OcrCard 只负责摆位置。 */
|
|
1651
2030
|
function useAdminPanel(reader, t) {
|
|
@@ -1750,25 +2129,25 @@ function useAdminPanel(reader, t) {
|
|
|
1750
2129
|
};
|
|
1751
2130
|
}
|
|
1752
2131
|
function OcrCard(props) {
|
|
1753
|
-
const { t
|
|
2132
|
+
const { t } = props;
|
|
1754
2133
|
const snap = props.useCard((state) => state);
|
|
1755
2134
|
const { value } = snap;
|
|
1756
2135
|
const ready = snap.status === "ready";
|
|
1757
2136
|
const writable = snap.writable && ready;
|
|
1758
|
-
const adminState = useAdminPanel(props.reader, t
|
|
2137
|
+
const adminState = useAdminPanel(props.reader, t);
|
|
1759
2138
|
const [data, setData] = (0, react.useState)(null);
|
|
1760
2139
|
const [fetchError, setFetchError] = (0, react.useState)(null);
|
|
1761
2140
|
const [busy, setBusy] = (0, react.useState)(null);
|
|
1762
2141
|
const [actionMsg, setActionMsg] = (0, react.useState)(null);
|
|
1763
2142
|
const [probeMsg, setProbeMsg] = (0, react.useState)(null);
|
|
1764
|
-
const refresh = (0, react.useCallback)(async () => refreshProviders(props.reader, t
|
|
2143
|
+
const refresh = (0, react.useCallback)(async () => refreshProviders(props.reader, t, {
|
|
1765
2144
|
setData,
|
|
1766
2145
|
setFetchError
|
|
1767
|
-
}), [props.reader, t
|
|
2146
|
+
}), [props.reader, t]);
|
|
1768
2147
|
(0, react.useEffect)(() => {
|
|
1769
2148
|
refresh();
|
|
1770
2149
|
}, [refresh]);
|
|
1771
|
-
const post = readerPost(props.reader, t
|
|
2150
|
+
const post = readerPost(props.reader, t);
|
|
1772
2151
|
const [selProvider, setSelProvider] = (0, react.useState)("");
|
|
1773
2152
|
const [selModel, setSelModel] = (0, react.useState)("");
|
|
1774
2153
|
const { effectiveProvider, effectiveModel, chosenProvider, modelOptions } = resolveSelection(data?.providers ?? [], data?.current, selProvider, selModel);
|
|
@@ -1779,7 +2158,7 @@ function OcrCard(props) {
|
|
|
1779
2158
|
setSelModel(first);
|
|
1780
2159
|
}
|
|
1781
2160
|
const actions = {
|
|
1782
|
-
t
|
|
2161
|
+
t,
|
|
1783
2162
|
post,
|
|
1784
2163
|
refresh,
|
|
1785
2164
|
setBusy,
|
|
@@ -1790,7 +2169,7 @@ function OcrCard(props) {
|
|
|
1790
2169
|
const [touched, setTouched] = (0, react.useState)({});
|
|
1791
2170
|
const [settingsBusy, setSettingsBusy] = (0, react.useState)(false);
|
|
1792
2171
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
1793
|
-
const dirty = t(touched, value).length > 0;
|
|
2172
|
+
const dirty = t$1(touched, value).length > 0;
|
|
1794
2173
|
const setField = (field, draft) => {
|
|
1795
2174
|
setTouched((prev) => ({
|
|
1796
2175
|
...prev,
|
|
@@ -1802,7 +2181,7 @@ function OcrCard(props) {
|
|
|
1802
2181
|
setSaveError(null);
|
|
1803
2182
|
};
|
|
1804
2183
|
const deps = {
|
|
1805
|
-
t
|
|
2184
|
+
t,
|
|
1806
2185
|
data,
|
|
1807
2186
|
fetchError,
|
|
1808
2187
|
busy,
|
|
@@ -1813,7 +2192,7 @@ function OcrCard(props) {
|
|
|
1813
2192
|
effectiveModel,
|
|
1814
2193
|
chosenProvider,
|
|
1815
2194
|
modelOptions,
|
|
1816
|
-
...cardHints(t
|
|
2195
|
+
...cardHints(t, ready, snap.status, data),
|
|
1817
2196
|
binary: data?.ocrBinary ?? null,
|
|
1818
2197
|
probeMsg,
|
|
1819
2198
|
...draftFieldsOf(touched, value),
|
|
@@ -1833,7 +2212,7 @@ function OcrCard(props) {
|
|
|
1833
2212
|
},
|
|
1834
2213
|
onProbe: () => {
|
|
1835
2214
|
runProbe({
|
|
1836
|
-
t
|
|
2215
|
+
t,
|
|
1837
2216
|
post,
|
|
1838
2217
|
refresh,
|
|
1839
2218
|
setBusy,
|
|
@@ -1843,7 +2222,7 @@ function OcrCard(props) {
|
|
|
1843
2222
|
onField: setField,
|
|
1844
2223
|
onSave: () => {
|
|
1845
2224
|
saveSettingsDraft({
|
|
1846
|
-
t
|
|
2225
|
+
t,
|
|
1847
2226
|
value,
|
|
1848
2227
|
touched,
|
|
1849
2228
|
setTouched,
|
|
@@ -1859,15 +2238,10 @@ function OcrCard(props) {
|
|
|
1859
2238
|
const settingsSection = buildSettingsSection(deps, writable, setField);
|
|
1860
2239
|
const adminSection = buildAdminSection({
|
|
1861
2240
|
...adminState,
|
|
1862
|
-
t
|
|
2241
|
+
t,
|
|
1863
2242
|
writable
|
|
1864
2243
|
});
|
|
1865
|
-
|
|
1866
|
-
const headDesc = t$4("cardDescription");
|
|
1867
|
-
return (0, react.createElement)(OcrPluginCard, {
|
|
1868
|
-
title: headTitle,
|
|
1869
|
-
description: headDesc
|
|
1870
|
-
}, cliStatus, providerPanel, settingsSection, adminSection);
|
|
2244
|
+
return (0, react.createElement)(u, null, cliStatus, providerPanel, settingsSection, adminSection);
|
|
1871
2245
|
}
|
|
1872
2246
|
const inject = [
|
|
1873
2247
|
"slots",
|
|
@@ -1875,11 +2249,11 @@ const inject = [
|
|
|
1875
2249
|
"locale"
|
|
1876
2250
|
];
|
|
1877
2251
|
function apply(ctx) {
|
|
1878
|
-
if (!n(ctx, "ocrReviewCardApplied", "ocr-review-card: apply claim")) return;
|
|
2252
|
+
if (!n$1(ctx, "ocrReviewCardApplied", "ocr-review-card: apply claim")) return;
|
|
1879
2253
|
ctx.effect(() => {
|
|
1880
2254
|
const tag = document.createElement("style");
|
|
1881
2255
|
tag.id = "ocr-review-card-css";
|
|
1882
|
-
tag.textContent = CARD_CSS
|
|
2256
|
+
tag.textContent = `${CARD_CSS}\n${c}`;
|
|
1883
2257
|
document.head.append(tag);
|
|
1884
2258
|
return () => {
|
|
1885
2259
|
tag.remove();
|
|
@@ -1930,7 +2304,6 @@ exports.OcrCard = OcrCard;
|
|
|
1930
2304
|
exports.OcrCliActivation = OcrCliActivation;
|
|
1931
2305
|
exports.OcrCliBadge = OcrCliBadge;
|
|
1932
2306
|
exports.OcrCliSection = OcrCliSection;
|
|
1933
|
-
exports.SaveBar = SaveBar;
|
|
1934
2307
|
exports.apply = apply;
|
|
1935
2308
|
exports.binaryOf = binaryOf;
|
|
1936
2309
|
exports.binaryStatusText = binaryStatusText;
|