dsh-llm-verifier 0.2.0 → 0.2.1

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/lib/client.js CHANGED
@@ -111,6 +111,7 @@ window.__ModuleLoader__.load({
111
111
  "settings.catalogFailures": "部分模型目录读取失败",
112
112
  "settings.saved": "设置已保存。",
113
113
  "settings.reload": "重新载入",
114
+ "settings.unsaved": "当前页面有未保存的修改:点击「保存」后才会写入设置;「重新载入」会丢弃这些修改。",
114
115
  "settings.save": "保存设置",
115
116
  "settings.saving": "保存中…",
116
117
  "stats.pageTitle": "Verifier 工具统计",
@@ -291,6 +292,7 @@ window.__ModuleLoader__.load({
291
292
  "settings.catalogFailures": "Some model catalogs failed to load",
292
293
  "settings.saved": "Settings saved.",
293
294
  "settings.reload": "Reload",
295
+ "settings.unsaved": "This page has unsaved changes: click Save to write them; Reload discards them.",
294
296
  "settings.save": "Save Settings",
295
297
  "settings.saving": "Saving…",
296
298
  "stats.pageTitle": "Verifier Tool Statistics",
@@ -447,6 +449,55 @@ window.__ModuleLoader__.load({
447
449
  if (typeof draft === "string" && draft.trim()) return draft.trim();
448
450
  if (typeof previous === "string" && previous.trim()) return previous.trim();
449
451
  }
452
+ /**
453
+ * Deep equality for JSON-shaped settings values, insensitive to object key
454
+ * order and treating a missing key as `undefined`. The settings page compares a
455
+ * draft against a resolved view that was built from a different object shape,
456
+ * so identity comparison is never enough.
457
+ */
458
+ function sameSettingValue(left, right) {
459
+ if (left === right) return true;
460
+ if (Array.isArray(left) && Array.isArray(right)) {
461
+ if (left.length !== right.length) return false;
462
+ for (let index = 0; index < left.length; index += 1) if (!sameSettingValue(left[index], right[index])) return false;
463
+ return true;
464
+ }
465
+ if (Array.isArray(left) || Array.isArray(right)) return false;
466
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
467
+ const a = left;
468
+ const b = right;
469
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) if (!sameSettingValue(a[key], b[key])) return false;
470
+ return true;
471
+ }
472
+ /**
473
+ * Build the user layer for one settings save.
474
+ *
475
+ * Keys this client does not own survive untouched and every draft field is
476
+ * written, but a field that only repeats the base composition is *dropped*
477
+ * rather than pinned. Pinning is what makes a shipped default change
478
+ * unreachable: the budget fields once defaulted to 48/160, and a section that
479
+ * stored those numbers kept overriding 64/240 and then 96/240 forever. `base`
480
+ * is the descriptor's composition base (the resolved plugin config), so the
481
+ * stored section keeps exactly what the user really overrode.
482
+ *
483
+ * An explicit `undefined` draft value means "clear this override": it deletes
484
+ * the stored key instead of pinning a valueless one, which is how the form
485
+ * removes a judge label, a reasoning effort or a cache directory.
486
+ *
487
+ * A host that reports no `base` cannot be pruned against and keeps the old
488
+ * write-everything behavior.
489
+ */
490
+ function sectionForSave(user, draft, base) {
491
+ const section = { ...user };
492
+ for (const [key, value] of Object.entries(draft)) {
493
+ if (value === void 0 || base !== void 0 && sameSettingValue(value, base[key])) {
494
+ delete section[key];
495
+ continue;
496
+ }
497
+ section[key] = value;
498
+ }
499
+ return section;
500
+ }
450
501
  /** An eight-candidate select: ring + pivot rounds (18 pairs) x three criteria, one repeat. */
451
502
  const WORST_CASE_ROUTE_CALLS_PER_JUDGE = 54;
452
503
  /** Final acceptance: three criteria x the default two repeats (one per A/B position). */
@@ -836,6 +887,50 @@ window.__ModuleLoader__.load({
836
887
  (0, react.useEffect)(() => {
837
888
  load();
838
889
  }, []);
890
+ const dirty = (0, react.useMemo)(() => loaded !== null && draft !== null && !sameSettingValue(draft, values(loaded.settings)), [loaded, draft]);
891
+ const latest = (0, react.useRef)({
892
+ loaded,
893
+ draft
894
+ });
895
+ latest.current = {
896
+ loaded,
897
+ draft
898
+ };
899
+ (0, react.useEffect)(() => {
900
+ const sync = () => {
901
+ if (document.visibilityState === "hidden") return;
902
+ const { loaded: at, draft: local } = latest.current;
903
+ if (!at || !local) return;
904
+ if (!sameSettingValue(local, values(at.settings))) return;
905
+ (async () => {
906
+ try {
907
+ const s = await remote.settings.describe();
908
+ if (!s.ok) return;
909
+ const view = s.value.namespaces.find((x) => x.ns === NS);
910
+ if (!view || view.revision === at.settings.revision) return;
911
+ const next = {
912
+ ...at,
913
+ settings: view,
914
+ writable: s.value.writable
915
+ };
916
+ latest.current = {
917
+ loaded: next,
918
+ draft: values(view)
919
+ };
920
+ setLoaded(next);
921
+ setDraft(values(view));
922
+ setEditing({});
923
+ setSaved(false);
924
+ } catch {}
925
+ })();
926
+ };
927
+ window.addEventListener("focus", sync);
928
+ document.addEventListener("visibilitychange", sync);
929
+ return () => {
930
+ window.removeEventListener("focus", sync);
931
+ document.removeEventListener("visibilitychange", sync);
932
+ };
933
+ }, [remote]);
839
934
  const models = (0, react.useMemo)(() => loaded?.groups.find((g) => g.id === draft?.provider)?.models ?? [], [loaded, draft?.provider]);
840
935
  const efforts = models.find((m) => m.id === draft?.model)?.reasoning?.efforts ?? [];
841
936
  const patch = (key, value) => {
@@ -894,17 +989,15 @@ window.__ModuleLoader__.load({
894
989
  setSaved(false);
895
990
  setError(null);
896
991
  try {
897
- const section = {
898
- ...record(loaded.settings.user),
992
+ const editable = {
899
993
  ...draft,
900
- extraJudges: serializeExtraJudges(draft.extraJudges)
994
+ extraJudges: serializeExtraJudges(draft.extraJudges),
995
+ reasoningEffort: draft.reasoningEffort || void 0,
996
+ label: draft.label?.trim() || void 0,
997
+ cacheDir: resolveCacheDirOnSave(draft.cacheDir, values(loaded.settings).cacheDir)
901
998
  };
902
- if (!draft.reasoningEffort) delete section.reasoningEffort;
903
- if (!draft.label || !draft.label.trim()) delete section.label;
904
- else section.label = draft.label.trim();
905
- const resolvedCacheDir = resolveCacheDirOnSave(draft.cacheDir, values(loaded.settings).cacheDir);
906
- if (!resolvedCacheDir) delete section.cacheDir;
907
- else section.cacheDir = resolvedCacheDir;
999
+ const base = loaded.settings.base === void 0 ? void 0 : record(loaded.settings.base);
1000
+ const section = sectionForSave(record(loaded.settings.user), editable, base);
908
1001
  const res = await remote.settings.update(NS, section, loaded.settings.revision);
909
1002
  if (!res.ok) throw new Error(res.error.message);
910
1003
  setLoaded((v) => v ? {
@@ -1629,7 +1722,7 @@ window.__ModuleLoader__.load({
1629
1722
  },
1630
1723
  children: error
1631
1724
  }),
1632
- saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1725
+ saved && !dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1633
1726
  style: {
1634
1727
  margin: 0,
1635
1728
  fontSize: 12,
@@ -1638,6 +1731,15 @@ window.__ModuleLoader__.load({
1638
1731
  },
1639
1732
  children: t["settings.saved"]
1640
1733
  }),
1734
+ dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1735
+ style: {
1736
+ margin: 0,
1737
+ fontSize: 12,
1738
+ lineHeight: "18px",
1739
+ color: "var(--dsw-alias-state-warn-label)"
1740
+ },
1741
+ children: t["settings.unsaved"]
1742
+ }),
1641
1743
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1642
1744
  style: {
1643
1745
  display: "flex",
@@ -2633,6 +2735,8 @@ window.__ModuleLoader__.load({
2633
2735
  exports.isVerdictFailed = isVerdictFailed;
2634
2736
  exports.money = money;
2635
2737
  exports.resolveCacheDirOnSave = resolveCacheDirOnSave;
2738
+ exports.sameSettingValue = sameSettingValue;
2739
+ exports.sectionForSave = sectionForSave;
2636
2740
  exports.tFormat = tFormat;
2637
2741
  exports.toolLabels = toolLabels;
2638
2742
  exports.useLanguage = useLanguage;