dsh-tool-vision 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -23
- package/README.md +26 -0
- package/README.zh.md +192 -172
- package/client.js +145 -36
- package/index.js +744 -673
- package/lib/session-header.js +57 -57
- package/lib/vision-tools.js +2485 -2485
- package/package.json +2 -2
package/client.js
CHANGED
|
@@ -40,6 +40,10 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
".__tv_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}" +
|
|
41
41
|
".__tv_btn:disabled{opacity:.5;cursor:default}" +
|
|
42
42
|
".__tv_btnPrimary{border-color:var(--dsw-alias-state-business-primary, #3964fe);background:var(--dsw-alias-state-business-primary, #3964fe);color:#fff}" +
|
|
43
|
+
".__tv_btnDanger{border-color:var(--dsw-alias-state-error-primary, #e5484d);color:var(--dsw-alias-state-error-primary, #e5484d)}" +
|
|
44
|
+
".__tv_btnDanger:hover:not(:disabled){background:var(--dsw-alias-state-error-primary, #e5484d);color:#fff}" +
|
|
45
|
+
".__tv_master{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;margin:2px 0 6px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}" +
|
|
46
|
+
".__tv_masterLabel{font-size:13px;color:var(--dsw-alias-label-primary)}" +
|
|
43
47
|
".__tv_status{font-size:12px;color:var(--dsw-alias-label-tertiary)}" +
|
|
44
48
|
".__tv_error{font-size:12px;color:var(--dsw-alias-state-error-primary)}" +
|
|
45
49
|
".__tv_unavailable{font-size:13px;color:var(--dsw-alias-label-tertiary)}";
|
|
@@ -97,7 +101,14 @@ window.__ModuleLoader__.load({
|
|
|
97
101
|
error: "保存失败",
|
|
98
102
|
unavailable: "设置命名空间不可用(服务端未注册 tool-vision 命名空间?)",
|
|
99
103
|
overridden: "已覆盖",
|
|
100
|
-
loading: "加载中…"
|
|
104
|
+
loading: "加载中…",
|
|
105
|
+
fieldEnabled: "启用插件(关闭后所有视觉工具下线)",
|
|
106
|
+
masterSwitch: "总开关",
|
|
107
|
+
turnOff: "一键关闭",
|
|
108
|
+
turnOn: "重新启用",
|
|
109
|
+
enabledNotice: "已启用:视觉工具已上线",
|
|
110
|
+
disabledNotice: "已关闭:所有视觉工具已下线",
|
|
111
|
+
notApplied: "写入未生效"
|
|
101
112
|
};
|
|
102
113
|
var en = {
|
|
103
114
|
nav: "Vision Model",
|
|
@@ -141,11 +152,19 @@ window.__ModuleLoader__.load({
|
|
|
141
152
|
error: "Save failed",
|
|
142
153
|
unavailable: "Settings namespace unavailable (tool-vision namespace not registered server-side?)",
|
|
143
154
|
overridden: "overridden",
|
|
144
|
-
loading: "Loading…"
|
|
155
|
+
loading: "Loading…",
|
|
156
|
+
fieldEnabled: "Enable plugin (off takes every vision tool offline)",
|
|
157
|
+
masterSwitch: "Master switch",
|
|
158
|
+
turnOff: "Disable all",
|
|
159
|
+
turnOn: "Re-enable",
|
|
160
|
+
enabledNotice: "Enabled: vision tools are live",
|
|
161
|
+
disabledNotice: "Disabled: every vision tool is offline",
|
|
162
|
+
notApplied: "Write did not take effect"
|
|
145
163
|
};
|
|
146
164
|
|
|
147
165
|
// ── field spec ────────────────────────────────────────────────────────
|
|
148
166
|
var FIELDS = [
|
|
167
|
+
{ key: "enabled", label: "fieldEnabled", type: "checkbox" },
|
|
149
168
|
{ key: "baseURL", label: "fieldBaseUrl", type: "text", placeholder: "https://api.openai.com/v1" },
|
|
150
169
|
{ key: "apiKey", label: "fieldApiKey", type: "password", secret: true },
|
|
151
170
|
{ key: "apiKeyEnv", label: "fieldApiKeyEnv", type: "text" },
|
|
@@ -196,7 +215,10 @@ window.__ModuleLoader__.load({
|
|
|
196
215
|
var [error, setError] = react.useState(null);
|
|
197
216
|
|
|
198
217
|
react.useEffect(function () {
|
|
199
|
-
|
|
218
|
+
// No refresh call: the scope's public seam has no load() — reads ride the
|
|
219
|
+
// shared describe mirror, which re-reads on every Host
|
|
220
|
+
// `settings/document-updated`. The guarded scope.load() that used to sit
|
|
221
|
+
// here was dead code that read like a refresh that never happened.
|
|
200
222
|
var alive = true;
|
|
201
223
|
var sync = function () { if (alive) setSnapshot(scope.getSnapshot()); };
|
|
202
224
|
var un = typeof scope.subscribe === "function" ? scope.subscribe(sync) : null;
|
|
@@ -239,66 +261,154 @@ window.__ModuleLoader__.load({
|
|
|
239
261
|
setError(null);
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
264
|
+
// One atomic mutation carrying ONE revision fence.
|
|
265
|
+
//
|
|
266
|
+
// The fields used to be issued as parallel scope.set()/unset() calls. Every
|
|
267
|
+
// write is queued and re-reads the namespace revision, but a write whose
|
|
268
|
+
// fence is behind the Host document is REFUSED with settings/conflict — and
|
|
269
|
+
// a refused write still resolves (the scope's contract is "settle after any
|
|
270
|
+
// recovery read", not "throw on refusal"). The section therefore reported
|
|
271
|
+
// "Saved" while the edits silently reverted. Batching every change into a
|
|
272
|
+
// single mutate() gives it one fence and one persistence decision.
|
|
273
|
+
function buildOps() {
|
|
274
|
+
var ops = [];
|
|
275
|
+
FIELDS.forEach(function (f) {
|
|
245
276
|
var d = fieldDraft(f);
|
|
246
277
|
if (f.type === "csv") {
|
|
247
278
|
var arr = String(d).split(",").map(function (s) { return s.trim(); }).filter(Boolean);
|
|
248
279
|
var cur = value[f.key] || [];
|
|
249
|
-
if (arr.length === cur.length && arr.every(function (x, i) { return x === cur[i]; })) return
|
|
250
|
-
|
|
280
|
+
if (arr.length === cur.length && arr.every(function (x, i) { return x === cur[i]; })) return;
|
|
281
|
+
ops.push({ op: "set", path: [f.key], value: arr });
|
|
282
|
+
return;
|
|
251
283
|
}
|
|
252
284
|
if (f.type === "checkbox") {
|
|
253
|
-
if (Boolean(d) === Boolean(value[f.key])) return
|
|
254
|
-
|
|
285
|
+
if (Boolean(d) === Boolean(value[f.key])) return;
|
|
286
|
+
ops.push(Boolean(d) ? { op: "set", path: [f.key], value: true }
|
|
287
|
+
: { op: "unset", path: [f.key] });
|
|
288
|
+
return;
|
|
255
289
|
}
|
|
256
290
|
if (f.type === "password") {
|
|
257
|
-
if (!d) return
|
|
258
|
-
if (d === String(value[f.key] ?? "")) return
|
|
259
|
-
|
|
291
|
+
if (!d) return; // blank keeps the current key
|
|
292
|
+
if (d === String(value[f.key] ?? "")) return;
|
|
293
|
+
ops.push({ op: "set", path: [f.key], value: d });
|
|
294
|
+
return;
|
|
260
295
|
}
|
|
261
|
-
if (String(d) === String(value[f.key] ?? "")) return
|
|
262
|
-
if (String(d).trim() === "" && !(f.key in user)) return
|
|
263
|
-
|
|
296
|
+
if (String(d) === String(value[f.key] ?? "")) return;
|
|
297
|
+
if (String(d).trim() === "" && !(f.key in user)) return;
|
|
298
|
+
ops.push(String(d).trim() === ""
|
|
299
|
+
? { op: "unset", path: [f.key] }
|
|
300
|
+
: { op: "set", path: [f.key], value: f.type === "number" ? Number(d) : d });
|
|
301
|
+
});
|
|
302
|
+
return ops;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Whether the namespace section now reflects every queued operation. */
|
|
306
|
+
function opsApplied(ops, snap) {
|
|
307
|
+
if (snap.status !== "ready" || snap.value === void 0) return false;
|
|
308
|
+
return ops.every(function (op) {
|
|
309
|
+
var key = op.path[0];
|
|
310
|
+
if (op.op === "unset") return !(snap.user && key in snap.user);
|
|
311
|
+
return JSON.stringify(snap.value[key]) === JSON.stringify(op.value);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Settle one namespace change and report what actually happened.
|
|
317
|
+
*
|
|
318
|
+
* Reads settle after the write and any recovery read, so the section is
|
|
319
|
+
* inspected afterwards instead of trusting the promise: that check is the
|
|
320
|
+
* only way to tell a committed change from a refused one. Hosts without
|
|
321
|
+
* mutate() apply the same ops in order — each waits for its predecessor, so
|
|
322
|
+
* the revision chain still holds.
|
|
323
|
+
*/
|
|
324
|
+
function commit(ops) {
|
|
325
|
+
var run;
|
|
326
|
+
if (typeof scope.mutate === "function") {
|
|
327
|
+
run = scope.mutate(ops, scope.getSnapshot().revision);
|
|
328
|
+
} else {
|
|
329
|
+
run = ops.reduce(function (chain, op) {
|
|
330
|
+
return chain.then(function () {
|
|
331
|
+
return op.op === "unset" ? scope.unset(op.path[0]) : scope.set(op.path[0], op.value);
|
|
332
|
+
});
|
|
333
|
+
}, Promise.resolve());
|
|
334
|
+
}
|
|
335
|
+
return Promise.resolve(run).then(function () {
|
|
336
|
+
return opsApplied(ops, scope.getSnapshot());
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function reportOutcome(ok, okMessage) {
|
|
341
|
+
setBusy(false);
|
|
342
|
+
if (ok) {
|
|
343
|
+
setNotice(okMessage || t("saved"));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
setError(t("error") + ":" + t("notApplied"));
|
|
347
|
+
reseedDraft();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function onSave() {
|
|
351
|
+
setBusy(true); setNotice(null); setError(null);
|
|
352
|
+
var ops = buildOps();
|
|
353
|
+
if (ops.length === 0) { setBusy(false); setNotice(t("saved")); return; }
|
|
354
|
+
commit(ops).then(function (ok) {
|
|
355
|
+
reportOutcome(ok);
|
|
356
|
+
}).catch(function (e) {
|
|
357
|
+
setBusy(false); setError(t("error") + ":" + String(e && e.message || e));
|
|
264
358
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// One-click master switch: the same field the checkbox edits, written on its
|
|
362
|
+
// own so taking the plugin offline never depends on the rest of the form.
|
|
363
|
+
function onToggleEnabled() {
|
|
364
|
+
setBusy(true); setNotice(null); setError(null);
|
|
365
|
+
var next = value.enabled === false;
|
|
366
|
+
commit([{ op: "set", path: ["enabled"], value: next }]).then(function (ok) {
|
|
367
|
+
reportOutcome(ok, next ? t("enabledNotice") : t("disabledNotice"));
|
|
268
368
|
}).catch(function (e) {
|
|
269
|
-
setBusy(false); setError(t("error") + "
|
|
369
|
+
setBusy(false); setError(t("error") + ":" + String(e && e.message || e));
|
|
270
370
|
});
|
|
271
371
|
}
|
|
272
372
|
|
|
273
373
|
function reseedDraft() {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
374
|
+
// The mirror folds a write's answer in before the promise settles, so the
|
|
375
|
+
// snapshot is already current; the timeout only covers a host that
|
|
376
|
+
// answers late.
|
|
377
|
+
var fresh = scope.getSnapshot();
|
|
378
|
+
if (fresh.status === "ready" && fresh.value !== void 0) {
|
|
379
|
+
setDraft(Object.assign({}, valueToDraft(fresh.value)));
|
|
380
|
+
return;
|
|
283
381
|
}
|
|
284
382
|
setTimeout(function () {
|
|
285
|
-
var
|
|
286
|
-
if (
|
|
383
|
+
var later = scope.getSnapshot();
|
|
384
|
+
if (later.status === "ready" && later.value !== void 0) setDraft(Object.assign({}, valueToDraft(later.value)));
|
|
287
385
|
}, 120);
|
|
288
386
|
}
|
|
289
387
|
|
|
290
388
|
function onReset() {
|
|
291
389
|
setBusy(true); setNotice(null); setError(null);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
390
|
+
var ops = FIELDS
|
|
391
|
+
.filter(function (f) { return f.key in user; })
|
|
392
|
+
.map(function (f) { return { op: "unset", path: [f.key] }; });
|
|
393
|
+
if (ops.length === 0) { setBusy(false); setNotice(t("saved")); return; }
|
|
394
|
+
commit(ops).then(function (ok) {
|
|
395
|
+
reportOutcome(ok);
|
|
295
396
|
}).catch(function (e) {
|
|
296
|
-
setBusy(false); setError(t("error") + "
|
|
397
|
+
setBusy(false); setError(t("error") + ":" + String(e && e.message || e));
|
|
297
398
|
});
|
|
298
399
|
}
|
|
299
400
|
|
|
300
401
|
return h("div", { className: "__tv_root" },
|
|
301
402
|
h("p", { className: "__tv_hint", style: { margin: "0 0 4px" } }, t("intro")),
|
|
403
|
+
h("div", { className: "__tv_master" },
|
|
404
|
+
h("span", { className: "__tv_masterLabel" },
|
|
405
|
+
t("masterSwitch") + (value.enabled === false ? ":" + t("disabledNotice") : "")),
|
|
406
|
+
h("button", {
|
|
407
|
+
type: "button",
|
|
408
|
+
className: "__tv_btn " + (value.enabled === false ? "__tv_btnPrimary" : "__tv_btnDanger"),
|
|
409
|
+
onClick: onToggleEnabled,
|
|
410
|
+
disabled: busy || !snapshot.writable
|
|
411
|
+
}, value.enabled === false ? t("turnOn") : t("turnOff"))),
|
|
302
412
|
FIELDS.map(function (f) {
|
|
303
413
|
var overridden = f.key in user;
|
|
304
414
|
if (f.type === "checkbox") {
|
|
@@ -500,7 +610,6 @@ window.__ModuleLoader__.load({
|
|
|
500
610
|
var t = ctx.locale.bind(NS);
|
|
501
611
|
ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-tool-vision: dictionaries");
|
|
502
612
|
var scope = ctx.settingsScope.bind({ namespace: "tool-vision" });
|
|
503
|
-
if (typeof scope.load === "function") scope.load();
|
|
504
613
|
attachBridgePreview(ctx, scope);
|
|
505
614
|
ctx.slots.inject("settings.section", function () {
|
|
506
615
|
return ctx.slots.register({
|