@jayyuen66/dsh-quality-gate 0.1.3 → 0.1.4
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 +12 -0
- package/README.md +7 -6
- package/README.zh-CN.md +6 -5
- package/THIRD_PARTY_NOTICES.md +4 -1
- package/client.js +501 -254
- package/docs/DETAIL.md +24 -0
- package/docs/DETAIL.zh.md +24 -0
- package/host.js +171 -20
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +40 -32
package/client.js
CHANGED
|
@@ -7,21 +7,353 @@ 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
|
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
|
|
11
|
-
function e(t, n, r) {
|
|
12
|
+
function e$1(t, n, r) {
|
|
12
13
|
if (t == null || n == null) return (t ?? null) === (n ?? null);
|
|
13
14
|
if (Object.is(t, n)) return !0;
|
|
14
15
|
if (typeof t != `object` || typeof n != `object`) return !1;
|
|
15
16
|
if (r.get(t) === n) return !0;
|
|
16
|
-
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));
|
|
17
|
+
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));
|
|
17
18
|
let i = Object.keys(t), a = Object.keys(n);
|
|
18
|
-
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
19
|
+
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e$1(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
19
20
|
}
|
|
20
|
-
function t(t, n) {
|
|
21
|
+
function t$1(t, n) {
|
|
21
22
|
let r = [];
|
|
22
|
-
for (let i of Object.keys(t)) e(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
23
|
+
for (let i of Object.keys(t)) e$1(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
23
24
|
return r;
|
|
24
25
|
}
|
|
26
|
+
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-ui.js
|
|
27
|
+
const c = `
|
|
28
|
+
.dsu-form{display:flex;flex-direction:column}
|
|
29
|
+
.dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
|
|
30
|
+
.dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
|
|
31
|
+
.dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
|
|
32
|
+
.dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
33
|
+
.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)}
|
|
34
|
+
.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)}
|
|
35
|
+
.dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
36
|
+
.dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
|
|
37
|
+
.dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
38
|
+
.dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
39
|
+
.dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
40
|
+
.dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
|
|
41
|
+
.dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
42
|
+
.dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
|
|
43
|
+
.dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
|
|
44
|
+
.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}
|
|
45
|
+
.dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
|
|
46
|
+
.dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
47
|
+
.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)}
|
|
48
|
+
.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}
|
|
49
|
+
.dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
50
|
+
.dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
|
|
51
|
+
.dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
52
|
+
.dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
53
|
+
.dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
|
|
54
|
+
.dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
55
|
+
.dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
|
|
56
|
+
.dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
|
|
57
|
+
.dsu-number .dsu-input{min-width:0}
|
|
58
|
+
.dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
59
|
+
.dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
|
|
60
|
+
.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}
|
|
61
|
+
.dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
|
|
62
|
+
.dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
|
|
63
|
+
.dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
|
|
64
|
+
.dsu-switch:disabled{opacity:.4;cursor:not-allowed}
|
|
65
|
+
.dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
66
|
+
.dsu-selectWrap{position:relative;width:100%;min-width:0}
|
|
67
|
+
.dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
|
|
68
|
+
.dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
|
|
69
|
+
.dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
70
|
+
.dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
|
|
71
|
+
.dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
|
|
72
|
+
.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}
|
|
73
|
+
.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}
|
|
74
|
+
.dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
|
|
75
|
+
.dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
|
|
76
|
+
.dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
|
|
77
|
+
.dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
|
|
78
|
+
.dsu-check{opacity:0;flex:none}
|
|
79
|
+
.dsu-option[aria-selected="true"] .dsu-check{opacity:1}
|
|
80
|
+
.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)}
|
|
81
|
+
.dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
|
|
82
|
+
.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}
|
|
83
|
+
.dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
|
|
84
|
+
.dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
|
|
85
|
+
.dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
86
|
+
.dsu-btn:disabled{opacity:.4;cursor:not-allowed}
|
|
87
|
+
.dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
88
|
+
.dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
|
|
89
|
+
.dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
|
|
90
|
+
.dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
|
|
91
|
+
.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)}
|
|
92
|
+
.dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
93
|
+
.dsu-statusError{color:var(--dsw-alias-state-error-primary)}
|
|
94
|
+
.dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
|
|
95
|
+
.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)}
|
|
96
|
+
.dsu-grid>.dsu-field .dsu-control{width:100%}
|
|
97
|
+
@media (max-width:640px){
|
|
98
|
+
.dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
99
|
+
.dsu-control{width:100%;max-width:720px}
|
|
100
|
+
.dsu-section{margin-top:20px;padding-top:20px}
|
|
101
|
+
}
|
|
102
|
+
@media (prefers-reduced-motion:reduce){
|
|
103
|
+
.dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
function l(e, t = 30) {
|
|
107
|
+
return e.length <= t ? { hint: e } : { detail: e };
|
|
108
|
+
}
|
|
109
|
+
function u(t) {
|
|
110
|
+
return (0, react.createElement)(`div`, { className: `dsu-form` }, t.children);
|
|
111
|
+
}
|
|
112
|
+
function d(t) {
|
|
113
|
+
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);
|
|
114
|
+
}
|
|
115
|
+
function m(a) {
|
|
116
|
+
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)(() => {
|
|
117
|
+
v.current !== null && (clearTimeout(v.current), v.current = null);
|
|
118
|
+
}, []), b = (0, react.useCallback)(() => {
|
|
119
|
+
let e = g.current, t = _.current;
|
|
120
|
+
/* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
|
|
121
|
+
if (e === null || t === null) return;
|
|
122
|
+
let n = e.getBoundingClientRect(), r = t.offsetWidth, i = t.offsetHeight, { left: a } = n, o = n.bottom + 8;
|
|
123
|
+
o + i > window.innerHeight - 8 && (o = n.top - i - 8), a + r > window.innerWidth - 8 && (a = window.innerWidth - r - 8);
|
|
124
|
+
let s = Math.max(8, window.innerWidth - r - 8), c = Math.max(8, window.innerHeight - i - 8);
|
|
125
|
+
t.style.left = `${Math.min(Math.max(8, a), s)}px`, t.style.top = `${Math.min(Math.max(8, o), c)}px`;
|
|
126
|
+
}, []), x = (0, react.useCallback)(() => {
|
|
127
|
+
y(), h.current = !1, f(!1), u(!1);
|
|
128
|
+
}, [y]), S = (0, react.useCallback)(() => {
|
|
129
|
+
y(), u(!0);
|
|
130
|
+
}, [y]), C = (0, react.useCallback)(() => {
|
|
131
|
+
y(), !(p.current || m.current || h.current) && (v.current = setTimeout(() => {
|
|
132
|
+
/* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
|
|
133
|
+
v.current = null, p.current || m.current || h.current || u(!1);
|
|
134
|
+
}, 120));
|
|
135
|
+
}, [y]), w = (0, react.useCallback)((e) => {
|
|
136
|
+
h.current = e, f(e);
|
|
137
|
+
}, []);
|
|
138
|
+
(0, react.useLayoutEffect)(() => {
|
|
139
|
+
if (!l) return () => {};
|
|
140
|
+
b();
|
|
141
|
+
let e = (e) => {
|
|
142
|
+
let { target: t } = e;
|
|
143
|
+
t instanceof Node && _.current?.contains(t) === !0 || (h.current ? b() : x());
|
|
144
|
+
}, t = () => {
|
|
145
|
+
b();
|
|
146
|
+
};
|
|
147
|
+
return window.addEventListener(`scroll`, e, !0), window.addEventListener(`resize`, t), () => {
|
|
148
|
+
window.removeEventListener(`scroll`, e, !0), window.removeEventListener(`resize`, t);
|
|
149
|
+
};
|
|
150
|
+
}, [
|
|
151
|
+
l,
|
|
152
|
+
a.detail,
|
|
153
|
+
b,
|
|
154
|
+
x
|
|
155
|
+
]), (0, react.useEffect)(() => {
|
|
156
|
+
if (!l) return () => {};
|
|
157
|
+
let e = (e) => {
|
|
158
|
+
e.key === `Escape` && x();
|
|
159
|
+
};
|
|
160
|
+
return document.addEventListener(`keydown`, e), () => {
|
|
161
|
+
document.removeEventListener(`keydown`, e);
|
|
162
|
+
};
|
|
163
|
+
}, [l, x]), (0, react.useEffect)(() => y, [y]);
|
|
164
|
+
let T = (0, react.createElement)(`button`, {
|
|
165
|
+
ref: g,
|
|
166
|
+
type: `button`,
|
|
167
|
+
className: `dsu-helpBtn`,
|
|
168
|
+
"aria-label": `${a.label} 说明`,
|
|
169
|
+
"aria-describedby": c,
|
|
170
|
+
"aria-pressed": d,
|
|
171
|
+
onClick: () => {
|
|
172
|
+
h.current ? x() : (w(!0), S());
|
|
173
|
+
}
|
|
174
|
+
}, (0, react.createElement)(`svg`, {
|
|
175
|
+
width: 12,
|
|
176
|
+
height: 12,
|
|
177
|
+
viewBox: `0 0 14 14`,
|
|
178
|
+
"aria-hidden": !0
|
|
179
|
+
}, (0, react.createElement)(`circle`, {
|
|
180
|
+
cx: 7,
|
|
181
|
+
cy: 7,
|
|
182
|
+
r: 6,
|
|
183
|
+
fill: `none`,
|
|
184
|
+
stroke: `currentColor`,
|
|
185
|
+
strokeWidth: 1.2
|
|
186
|
+
}), (0, react.createElement)(`path`, {
|
|
187
|
+
d: `M7 6.2v4`,
|
|
188
|
+
stroke: `currentColor`,
|
|
189
|
+
strokeWidth: 1.3,
|
|
190
|
+
strokeLinecap: `round`
|
|
191
|
+
}), (0, react.createElement)(`circle`, {
|
|
192
|
+
cx: 7,
|
|
193
|
+
cy: 4.3,
|
|
194
|
+
r: .8,
|
|
195
|
+
fill: `currentColor`
|
|
196
|
+
}))), E = (0, react.createElement)(`div`, {
|
|
197
|
+
ref: _,
|
|
198
|
+
id: c,
|
|
199
|
+
role: `tooltip`,
|
|
200
|
+
className: `dsu-tip`,
|
|
201
|
+
hidden: !l
|
|
202
|
+
}, a.detail), D = a.showLabel === !1 ? null : (0, react.createElement)(`span`, { className: `dsu-label${a.compact === !0 ? ` dsu-labelMono` : ``}` }, a.label);
|
|
203
|
+
return (0, react.createElement)(`span`, {
|
|
204
|
+
className: `dsu-help`,
|
|
205
|
+
onMouseEnter: () => {
|
|
206
|
+
p.current = !0, S();
|
|
207
|
+
},
|
|
208
|
+
onMouseLeave: () => {
|
|
209
|
+
p.current = !1, C();
|
|
210
|
+
},
|
|
211
|
+
onFocus: () => {
|
|
212
|
+
m.current = !0, S();
|
|
213
|
+
},
|
|
214
|
+
onBlur: (e) => {
|
|
215
|
+
let t = e.relatedTarget;
|
|
216
|
+
t instanceof Node && e.currentTarget.contains(t) || (m.current = !1, C());
|
|
217
|
+
}
|
|
218
|
+
}, D, T, E);
|
|
219
|
+
}
|
|
220
|
+
function h(t) {
|
|
221
|
+
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`, {
|
|
222
|
+
className: n,
|
|
223
|
+
htmlFor: t.controlId
|
|
224
|
+
}, t.label);
|
|
225
|
+
return (0, react.createElement)(`div`, { className: `dsu-labelRow` }, r, t.detail === void 0 ? null : (0, react.createElement)(m, {
|
|
226
|
+
label: t.label,
|
|
227
|
+
detail: t.detail,
|
|
228
|
+
showLabel: !1
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
function g(t) {
|
|
232
|
+
let n = t.controlId === void 0 ? void 0 : `${t.controlId}-hint`;
|
|
233
|
+
return (0, react.createElement)(`div`, { className: `dsu-field${t.wide === !0 ? ` dsu-fieldWide` : ``}` }, (0, react.createElement)(`div`, { className: `dsu-fieldMeta` }, (0, react.createElement)(h, {
|
|
234
|
+
label: t.label,
|
|
235
|
+
detail: t.detail,
|
|
236
|
+
compact: t.compact,
|
|
237
|
+
controlId: t.controlId
|
|
238
|
+
}), t.hint === void 0 ? null : (0, react.createElement)(`p`, {
|
|
239
|
+
className: `dsu-hint`,
|
|
240
|
+
id: n
|
|
241
|
+
}, t.hint)), (0, react.createElement)(`div`, { className: `dsu-control` }, t.children));
|
|
242
|
+
}
|
|
243
|
+
function _(t) {
|
|
244
|
+
let n = (0, react.useId)(), i = t.hint === void 0 ? void 0 : `${n}-hint`;
|
|
245
|
+
return (0, react.createElement)(g, {
|
|
246
|
+
...t,
|
|
247
|
+
controlId: n
|
|
248
|
+
}, (0, react.createElement)(`button`, {
|
|
249
|
+
type: `button`,
|
|
250
|
+
role: `switch`,
|
|
251
|
+
id: n,
|
|
252
|
+
className: `dsu-switch`,
|
|
253
|
+
"aria-checked": t.checked,
|
|
254
|
+
"aria-describedby": i,
|
|
255
|
+
"data-field": t.field,
|
|
256
|
+
disabled: t.disabled === !0,
|
|
257
|
+
onClick: () => {
|
|
258
|
+
t.onChange(!t.checked);
|
|
259
|
+
}
|
|
260
|
+
}, (0, react.createElement)(`span`, { className: `dsu-knob` })));
|
|
261
|
+
}
|
|
262
|
+
function v(t) {
|
|
263
|
+
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(` `);
|
|
264
|
+
(0, react.useEffect)(() => {
|
|
265
|
+
t.value !== d.current && (d.current = t.value, c(i), u(!1));
|
|
266
|
+
}, [t.value, i]);
|
|
267
|
+
let _ = (e) => {
|
|
268
|
+
let n = t.min ?? -1 / 0, r = t.max ?? 1 / 0;
|
|
269
|
+
if (!Number.isFinite(e) || e < n || e > r) return !1;
|
|
270
|
+
let { step: i } = t;
|
|
271
|
+
if (i === void 0 || i <= 0) return !0;
|
|
272
|
+
let a = (e - (t.min ?? 0)) / i;
|
|
273
|
+
return Math.abs(a - Math.round(a)) < 1e-9;
|
|
274
|
+
}, v = (e) => {
|
|
275
|
+
if (c(e), e === ``) {
|
|
276
|
+
u(!1), d.current = void 0, t.onChange(void 0);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
let n = Number(e);
|
|
280
|
+
_(n) ? (u(!1), d.current = n, t.onChange(n)) : u(!0);
|
|
281
|
+
}, y = () => {
|
|
282
|
+
a === `` || l || c(String(Number(a)));
|
|
283
|
+
};
|
|
284
|
+
return (0, react.createElement)(g, {
|
|
285
|
+
...t,
|
|
286
|
+
controlId: f
|
|
287
|
+
}, (0, react.createElement)(`div`, { className: `dsu-numberWrap` }, (0, react.createElement)(`div`, { className: `dsu-number` }, (0, react.createElement)(`input`, {
|
|
288
|
+
className: `dsu-input`,
|
|
289
|
+
type: `number`,
|
|
290
|
+
inputMode: `numeric`,
|
|
291
|
+
id: f,
|
|
292
|
+
value: a,
|
|
293
|
+
min: t.min,
|
|
294
|
+
max: t.max,
|
|
295
|
+
step: t.step,
|
|
296
|
+
placeholder: t.placeholder,
|
|
297
|
+
"aria-invalid": l || void 0,
|
|
298
|
+
"aria-describedby": h,
|
|
299
|
+
"data-field": t.field,
|
|
300
|
+
disabled: t.disabled === !0,
|
|
301
|
+
onChange: (e) => {
|
|
302
|
+
v(e.target.value);
|
|
303
|
+
},
|
|
304
|
+
onBlur: y
|
|
305
|
+
}), t.unit === void 0 ? null : (0, react.createElement)(`span`, { className: `dsu-unit` }, t.unit)), l ? (0, react.createElement)(`p`, {
|
|
306
|
+
className: `dsu-error`,
|
|
307
|
+
id: p
|
|
308
|
+
}, t.invalidHint) : null));
|
|
309
|
+
}
|
|
310
|
+
function y(t) {
|
|
311
|
+
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`;
|
|
312
|
+
(0, react.useEffect)(() => {
|
|
313
|
+
o || a(t.value);
|
|
314
|
+
}, [t.value, o]);
|
|
315
|
+
let d = {
|
|
316
|
+
id: l,
|
|
317
|
+
className: `dsu-input${t.multiline === !0 ? ` dsu-textarea` : ``}${t.monospace === !0 ? ` dsu-mono` : ``}`,
|
|
318
|
+
value: i,
|
|
319
|
+
placeholder: t.placeholder,
|
|
320
|
+
"aria-describedby": u,
|
|
321
|
+
"data-field": t.field,
|
|
322
|
+
disabled: t.disabled === !0,
|
|
323
|
+
onFocus: () => {
|
|
324
|
+
c(!0);
|
|
325
|
+
},
|
|
326
|
+
onBlur: () => {
|
|
327
|
+
c(!1);
|
|
328
|
+
},
|
|
329
|
+
onChange: (e) => {
|
|
330
|
+
a(e.target.value), t.onChange(e.target.value);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
return (0, react.createElement)(g, {
|
|
334
|
+
...t,
|
|
335
|
+
controlId: l
|
|
336
|
+
}, t.multiline === !0 ? (0, react.createElement)(`textarea`, d) : (0, react.createElement)(`input`, d));
|
|
337
|
+
}
|
|
338
|
+
function S(t) {
|
|
339
|
+
let n = !t.writable || t.busy || !t.dirty, r;
|
|
340
|
+
return r = t.error === null ? t.writable ? t.dirty ? t.dirtyLabel : t.cleanLabel : t.readOnlyLabel : t.error, (0, react.createElement)(`div`, {
|
|
341
|
+
className: `dsu-savebar`,
|
|
342
|
+
"data-dirty": t.dirty
|
|
343
|
+
}, (0, react.createElement)(`button`, {
|
|
344
|
+
type: `button`,
|
|
345
|
+
className: `dsu-btn dsu-btnPrimary`,
|
|
346
|
+
"data-field": `save`,
|
|
347
|
+
disabled: n,
|
|
348
|
+
onClick: t.onSave
|
|
349
|
+
}, t.busy ? t.savingLabel : t.saveLabel), (0, react.createElement)(`button`, {
|
|
350
|
+
type: `button`,
|
|
351
|
+
className: `dsu-btn`,
|
|
352
|
+
"data-field": `discard`,
|
|
353
|
+
disabled: n,
|
|
354
|
+
onClick: t.onDiscard
|
|
355
|
+
}, t.revertLabel), (0, react.createElement)(`span`, { className: t.error === null ? `dsu-status` : `dsu-status dsu-statusError` }, r));
|
|
356
|
+
}
|
|
25
357
|
// src/ui-messages.ts
|
|
26
358
|
const UI_MESSAGES = {
|
|
27
359
|
zh: {
|
|
@@ -36,11 +368,16 @@ const UI_MESSAGES = {
|
|
|
36
368
|
saveFailed: "保存失败:{message}",
|
|
37
369
|
saveRejected: "保存被宿主拒绝(配置校验未通过或写入被跳过),修改未生效",
|
|
38
370
|
outOfRange: "超出允许范围,未保存",
|
|
371
|
+
gateSection: "门禁",
|
|
372
|
+
memorySection: "记忆反馈",
|
|
373
|
+
unitTimes: "次",
|
|
374
|
+
unitSeconds: "秒",
|
|
375
|
+
dirtyCount: "已修改 {count} 项",
|
|
39
376
|
enabledLabel: "启用质量门禁",
|
|
40
377
|
enabledHint: "关闭后回合收口不再跑门禁(模型可带病收口)",
|
|
41
378
|
injectsLabel: "每回合注入配额",
|
|
42
379
|
injectsHint: "门禁失败每回合最多注入几次修复指令(1-5,默认 2,防跑飞)",
|
|
43
|
-
budgetLabel: "
|
|
380
|
+
budgetLabel: "门禁预算",
|
|
44
381
|
budgetHint: "单次门禁最大耗时(10s-600s,默认 300s;超时按失败注入不静默放行)",
|
|
45
382
|
memoryLabel: "门禁失败沉淀记忆",
|
|
46
383
|
memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。真正决定能否投递的是网关地址能否解析出值(见下面两项)",
|
|
@@ -61,11 +398,16 @@ const UI_MESSAGES = {
|
|
|
61
398
|
saveFailed: "Save failed: {message}",
|
|
62
399
|
saveRejected: "Save rejected by the host (validation failed or write skipped) — changes not applied",
|
|
63
400
|
outOfRange: "Out of the allowed range — not saved",
|
|
401
|
+
gateSection: "Gate",
|
|
402
|
+
memorySection: "Memory feedback",
|
|
403
|
+
unitTimes: "times",
|
|
404
|
+
unitSeconds: "seconds",
|
|
405
|
+
dirtyCount: "{count} changed",
|
|
64
406
|
enabledLabel: "Enable the quality gate",
|
|
65
407
|
enabledHint: "When off: no gate runs at turn end (the model may close a turn with broken code)",
|
|
66
408
|
injectsLabel: "Repair injections per turn",
|
|
67
409
|
injectsHint: "How many repair instructions a failing gate may inject per turn (1-5, default 2 — runaway protection)",
|
|
68
|
-
budgetLabel: "Gate budget
|
|
410
|
+
budgetLabel: "Gate budget",
|
|
69
411
|
budgetHint: "Max duration of one gate run (10s-600s, default 300s; a timeout injects a failure instead of passing silently)",
|
|
70
412
|
memoryLabel: "Persist gate failures to memory",
|
|
71
413
|
memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted. Whether it can actually deliver is decided by the gateway URL resolving to a value (see the two fields below)",
|
|
@@ -78,133 +420,69 @@ const UI_MESSAGES = {
|
|
|
78
420
|
// src/client-entry.ts
|
|
79
421
|
const NS = "quality-gate";
|
|
80
422
|
const BUNDLE_PKG = "@jayyuen66/dsh-quality-gate";
|
|
81
|
-
const CARD_CSS = [
|
|
82
|
-
".qgc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}",
|
|
83
|
-
".qgc-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
84
|
-
".qgc-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
|
|
85
|
-
".qgc-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
|
|
86
|
-
".qgc-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
|
|
87
|
-
".qgc-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
|
|
88
|
-
".qgc-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
|
|
89
|
-
".qgc-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}",
|
|
90
|
-
".qgc-chevron-open{transform:rotate(180deg)}",
|
|
91
|
-
".qgc-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:8px 0 12px}",
|
|
92
|
-
".qgc-row{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;padding:9px 0}",
|
|
93
|
-
".qgc-label{font-size:13px;color:var(--dsw-alias-label-primary,inherit)}",
|
|
94
|
-
".qgc-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);line-height:1.5;margin-top:2px}",
|
|
95
|
-
".qgc-switch{appearance:none;position:relative;width:34px;height:20px;border-radius:10px;background:var(--dsw-alias-fill-primary,#d8dbe2);transition:background .16s;cursor:pointer;border:0;flex:none}",
|
|
96
|
-
".qgc-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);transition:left .16s}",
|
|
97
|
-
".qgc-switch-on{background:var(--dsw-alias-brand-primary,#e07856)}",
|
|
98
|
-
".qgc-switch-on::after{left:16px}",
|
|
99
|
-
".qgc-switch:disabled{cursor:not-allowed;opacity:.6}",
|
|
100
|
-
".qgc-input{width:100%;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,transparent);border-radius:8px;padding:6px 8px;margin-top:4px}",
|
|
101
|
-
".qgc-savebar{display:flex;gap:8px;align-items:center;padding:10px 0 2px;border-top:1px dashed var(--dsw-alias-border-l2);margin-top:6px;flex-wrap:wrap}",
|
|
102
|
-
".qgc-btn{appearance:none;font:inherit;font-size:12px;cursor:pointer;border-radius:6px;padding:4px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,inherit)}",
|
|
103
|
-
".qgc-btn:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}",
|
|
104
|
-
".qgc-btn-primary{background:var(--dsw-alias-brand-primary,#e07856);border-color:var(--dsw-alias-brand-primary,#e07856);color:var(--dsw-alias-label-primary-foreground,#fff)}",
|
|
105
|
-
".qgc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#e07856);outline-offset:1px}",
|
|
106
|
-
".qgc-btn:disabled{opacity:.5;cursor:not-allowed}",
|
|
107
|
-
".qgc-dirty{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99)}",
|
|
108
|
-
".qgc-saveerr{font-size:12px;color:#c4483f}"
|
|
109
|
-
].join("\n");
|
|
110
|
-
function ToggleRow(props) {
|
|
111
|
-
return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint)), (0, react.createElement)("button", {
|
|
112
|
-
type: "button",
|
|
113
|
-
className: `qgc-switch${props.checked ? " qgc-switch-on" : ""}`,
|
|
114
|
-
role: "switch",
|
|
115
|
-
"aria-checked": props.checked,
|
|
116
|
-
"data-field": props.field,
|
|
117
|
-
disabled: props.disabled === true,
|
|
118
|
-
onClick: props.onToggle
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
/** 数字输入行:settings 数字字段 ↔ UI 字符串(v7 配置面补齐)。 */
|
|
122
|
-
function NumberInputRow(props) {
|
|
123
|
-
const num = typeof props.value === "number" && Number.isFinite(props.value) ? String(props.value) : "";
|
|
124
|
-
const [text, setText] = (0, react.useState)(num);
|
|
125
|
-
const [invalid, setInvalid] = (0, react.useState)(false);
|
|
126
|
-
(0, react.useEffect)(() => {
|
|
127
|
-
setText(num);
|
|
128
|
-
setInvalid(false);
|
|
129
|
-
}, [num]);
|
|
130
|
-
const invalidHint = props.t("outOfRange");
|
|
131
|
-
return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint), (0, react.createElement)("input", {
|
|
132
|
-
type: "number",
|
|
133
|
-
className: "qgc-input",
|
|
134
|
-
value: text,
|
|
135
|
-
disabled: props.disabled === true,
|
|
136
|
-
"aria-invalid": invalid || void 0,
|
|
137
|
-
onChange: (event) => {
|
|
138
|
-
const next = event.target.value;
|
|
139
|
-
setText(next);
|
|
140
|
-
if (next === "") {
|
|
141
|
-
setInvalid(false);
|
|
142
|
-
props.onClear(props.field);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const numeric = Number(next);
|
|
146
|
-
if (!Number.isFinite(numeric)) {
|
|
147
|
-
setInvalid(true);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const min = props.min ?? -Infinity;
|
|
151
|
-
const max = props.max ?? Infinity;
|
|
152
|
-
if (numeric >= min && numeric <= max) {
|
|
153
|
-
setInvalid(false);
|
|
154
|
-
props.onChange(props.field, numeric);
|
|
155
|
-
} else setInvalid(true);
|
|
156
|
-
}
|
|
157
|
-
}), invalid ? (0, react.createElement)("div", {
|
|
158
|
-
className: "qgc-hint",
|
|
159
|
-
style: { color: "var(--dsw-alias-danger, #e5484d)" }
|
|
160
|
-
}, invalidHint) : null));
|
|
161
|
-
}
|
|
162
423
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
424
|
+
* 两个数值字段的闭区间只写在 zod schema 上:host schema(`Schema.natural().min/max`)
|
|
425
|
+
* 与卡片输入框都从这里取。zod 4.6 的 `.minValue`/`.maxValue` 直接读出折叠后的边界,
|
|
426
|
+
* `NumberField` 的 `min/max` 因此不再另抄一份数字。
|
|
165
427
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
className: "qgc-input",
|
|
175
|
-
value: draft,
|
|
176
|
-
placeholder: props.placeholder ?? "",
|
|
177
|
-
disabled: props.disabled === true,
|
|
178
|
-
onChange: (event) => {
|
|
179
|
-
const next = event.target.value;
|
|
180
|
-
setDraft(next);
|
|
181
|
-
if (next.trim() === "") {
|
|
182
|
-
props.onClear(props.field);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
props.onChange(props.field, next.trim());
|
|
186
|
-
}
|
|
187
|
-
})));
|
|
428
|
+
const MAX_INJECTS_VALUE = zod.z.int().min(1).max(5);
|
|
429
|
+
const GATE_BUDGET_MS_VALUE = zod.z.int().min(1e4).max(6e5);
|
|
430
|
+
/** 把 zod 折叠后的显式边界摊成可选 props(null = 该方向没声明,不伪造一个上限)。 */
|
|
431
|
+
function boundsOf(schema) {
|
|
432
|
+
const bounds = {};
|
|
433
|
+
if (schema.minValue !== null) bounds.min = schema.minValue;
|
|
434
|
+
if (schema.maxValue !== null) bounds.max = schema.maxValue;
|
|
435
|
+
return bounds;
|
|
188
436
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
437
|
+
const MAX_INJECTS_BOUNDS = boundsOf(MAX_INJECTS_VALUE);
|
|
438
|
+
const GATE_BUDGET_MS_BOUNDS = boundsOf(GATE_BUDGET_MS_VALUE);
|
|
439
|
+
/** 两个数值 schema 的对外投影:测试用 `.minValue`/`.maxValue` 与 `z.toJSONSchema` 钉边界。 */
|
|
440
|
+
const CARD_NUMBER_SCHEMAS = {
|
|
441
|
+
maxInjectsPerTurn: MAX_INJECTS_VALUE,
|
|
442
|
+
gateBudgetMs: GATE_BUDGET_MS_VALUE
|
|
443
|
+
};
|
|
444
|
+
/** 可选整数:越界/小数/非有限值折成 undefined,只丢那一格而不是整份快照。
|
|
445
|
+
* `.validate()` 只回答合法性、不建 ZodError,失败路径比 `.safeParse().success` 短。
|
|
446
|
+
* 不引 `z.compile`:编译器给客户端 bundle 增约 28 KB,且 CSP 环境会拒绝 `new Function`;
|
|
447
|
+
* 卡片每帧只解析一枚快照对象,收益抵不上这两条代价。 */
|
|
448
|
+
const MAX_INJECTS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && MAX_INJECTS_VALUE.validate(value) ? value : void 0, MAX_INJECTS_VALUE.optional());
|
|
449
|
+
const GATE_BUDGET_MS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && GATE_BUDGET_MS_VALUE.validate(value) ? value : void 0, GATE_BUDGET_MS_VALUE.optional());
|
|
450
|
+
/** 卡片可编辑字段的值面。逐字段先把错误类型折成 undefined:宿主给了坏形状时只丢掉
|
|
451
|
+
* 那一格,其余字段照常渲染;整份快照不是对象时才整体回落到空表。 */
|
|
452
|
+
const optionalBoolean = zod.z.preprocess((value) => typeof value === "boolean" ? value : void 0, zod.z.boolean().optional());
|
|
453
|
+
const optionalString = zod.z.preprocess((value) => typeof value === "string" ? value : void 0, zod.z.string().optional());
|
|
454
|
+
/** 网关地址是 HTTP(S) URL;空串与坏文本折成未设置。本地回环/端口由 `z.url` 放行。 */
|
|
455
|
+
const HTTP_URL_SCHEMA = zod.z.url({ protocol: /^https?$/u });
|
|
456
|
+
const optionalHttpUrl = zod.z.preprocess((value) => {
|
|
457
|
+
let text;
|
|
458
|
+
if (typeof value === "string") {
|
|
459
|
+
const trimmed = value.trim();
|
|
460
|
+
if (trimmed !== "") text = trimmed;
|
|
461
|
+
}
|
|
462
|
+
return text;
|
|
463
|
+
}, HTTP_URL_SCHEMA.optional());
|
|
464
|
+
const CARD_VALUES = zod.z.object({
|
|
465
|
+
enabled: optionalBoolean,
|
|
466
|
+
maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
|
|
467
|
+
gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
|
|
468
|
+
memoryFeedback: optionalBoolean,
|
|
469
|
+
memoryGatewayUrl: optionalHttpUrl,
|
|
470
|
+
memoryGatewayKeyRef: optionalString
|
|
471
|
+
});
|
|
472
|
+
/** 草稿面仍按宽 string 读:`TextField` 没有 invalid 态,URL 收口只放在快照投影上,
|
|
473
|
+
* 用户输入中的半截地址不会被当场折成空串。 */
|
|
474
|
+
const CARD_FIELD_SCHEMAS = {
|
|
475
|
+
enabled: optionalBoolean,
|
|
476
|
+
maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
|
|
477
|
+
gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
|
|
478
|
+
memoryFeedback: optionalBoolean,
|
|
479
|
+
memoryGatewayUrl: optionalString,
|
|
480
|
+
memoryGatewayKeyRef: optionalString
|
|
481
|
+
};
|
|
482
|
+
/** 快照值投影:整份一次解析;每个字段的坏形状与越界值各自折成 undefined,其余字段照常渲染。 */
|
|
483
|
+
function projectCardValues(raw) {
|
|
484
|
+
const parsed = CARD_VALUES.safeParse(raw);
|
|
485
|
+
return parsed.success ? parsed.data : {};
|
|
208
486
|
}
|
|
209
487
|
/** 值语义相等(与 shared 的 diffTouched 同口径):表单值是标量与数组,引用相等会把等值数组判成改动。 */
|
|
210
488
|
function sameValue(left, right) {
|
|
@@ -243,162 +521,125 @@ async function writeTouched(channels) {
|
|
|
243
521
|
setSaveError(t("saveFailed", { message: String(error instanceof Error ? error.message : error) }));
|
|
244
522
|
}
|
|
245
523
|
}
|
|
246
|
-
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
["d"]: chevronPathData,
|
|
256
|
-
fill: "none",
|
|
257
|
-
stroke: "currentColor",
|
|
258
|
-
strokeWidth: 1.5,
|
|
259
|
-
strokeLinecap: "round",
|
|
260
|
-
strokeLinejoin: "round"
|
|
261
|
-
});
|
|
262
|
-
const chevron = (0, react.createElement)("svg", {
|
|
263
|
-
width: 14,
|
|
264
|
-
height: 14,
|
|
265
|
-
viewBox: "0 0 14 14",
|
|
266
|
-
"aria-hidden": true,
|
|
267
|
-
className: `qgc-chevron${open ? " qgc-chevron-open" : ""}`
|
|
268
|
-
}, chevronPath);
|
|
269
|
-
return (0, react.createElement)("button", {
|
|
270
|
-
type: "button",
|
|
271
|
-
className: "qgc-header",
|
|
272
|
-
"aria-expanded": open,
|
|
273
|
-
onClick: onToggle
|
|
274
|
-
}, headDiv, chevron);
|
|
275
|
-
}
|
|
276
|
-
/** 展开态的 `ul` 本体:行元素仍按从前的顺序逐个传给同一个 createElement(不折成数组
|
|
277
|
-
* 子节点,保持宿主侧 children 形状一致)。 */
|
|
278
|
-
function buildCardBody(props) {
|
|
279
|
-
const { t, writable, eff, setField, clearField, dirty, busy, saveError, onSave, onDiscard } = props;
|
|
280
|
-
const rowEnabled = (0, react.createElement)(ToggleRow, {
|
|
524
|
+
/** 门禁小节:总开关 + 注入配额 + 预算(毫秒按秒显示)。 */
|
|
525
|
+
function buildGateSection(props) {
|
|
526
|
+
const { t, writable, eff, setField } = props;
|
|
527
|
+
const maxInjects = eff("maxInjectsPerTurn");
|
|
528
|
+
const budgetMs = eff("gateBudgetMs");
|
|
529
|
+
const budgetSeconds = budgetMs === void 0 ? void 0 : budgetMs / 1e3;
|
|
530
|
+
const budgetMinSeconds = (GATE_BUDGET_MS_BOUNDS.min ?? 0) / 1e3;
|
|
531
|
+
const budgetMaxSeconds = (GATE_BUDGET_MS_BOUNDS.max ?? 0) / 1e3;
|
|
532
|
+
const enabled = (0, react.createElement)(_, {
|
|
281
533
|
label: t("enabledLabel"),
|
|
282
|
-
|
|
534
|
+
...l(t("enabledHint")),
|
|
283
535
|
field: "enabled",
|
|
284
|
-
checked: eff("enabled")
|
|
536
|
+
checked: eff("enabled") ?? true,
|
|
285
537
|
disabled: !writable,
|
|
286
|
-
|
|
287
|
-
setField("enabled",
|
|
538
|
+
onChange: (next) => {
|
|
539
|
+
setField("enabled", next);
|
|
288
540
|
}
|
|
289
541
|
});
|
|
290
|
-
const
|
|
291
|
-
t,
|
|
542
|
+
const injects = (0, react.createElement)(v, {
|
|
292
543
|
label: t("injectsLabel"),
|
|
293
|
-
|
|
544
|
+
...l(t("injectsHint")),
|
|
294
545
|
field: "maxInjectsPerTurn",
|
|
295
|
-
value:
|
|
546
|
+
value: maxInjects,
|
|
547
|
+
...MAX_INJECTS_BOUNDS,
|
|
548
|
+
step: 1,
|
|
549
|
+
unit: t("unitTimes"),
|
|
550
|
+
invalidHint: t("outOfRange"),
|
|
296
551
|
disabled: !writable,
|
|
297
|
-
onChange: (
|
|
298
|
-
setField(
|
|
299
|
-
}
|
|
300
|
-
onClear: (field) => {
|
|
301
|
-
clearField(field);
|
|
302
|
-
},
|
|
303
|
-
min: 1,
|
|
304
|
-
max: 5
|
|
552
|
+
onChange: (next) => {
|
|
553
|
+
setField("maxInjectsPerTurn", next);
|
|
554
|
+
}
|
|
305
555
|
});
|
|
306
|
-
const
|
|
307
|
-
t,
|
|
556
|
+
const budget = (0, react.createElement)(v, {
|
|
308
557
|
label: t("budgetLabel"),
|
|
309
|
-
|
|
558
|
+
...l(t("budgetHint")),
|
|
310
559
|
field: "gateBudgetMs",
|
|
311
|
-
value:
|
|
560
|
+
value: budgetSeconds,
|
|
561
|
+
min: budgetMinSeconds,
|
|
562
|
+
max: budgetMaxSeconds,
|
|
563
|
+
step: 1,
|
|
564
|
+
unit: t("unitSeconds"),
|
|
565
|
+
invalidHint: t("outOfRange"),
|
|
312
566
|
disabled: !writable,
|
|
313
|
-
onChange: (
|
|
314
|
-
setField(
|
|
315
|
-
}
|
|
316
|
-
onClear: (field) => {
|
|
317
|
-
clearField(field);
|
|
318
|
-
},
|
|
319
|
-
min: 1e4,
|
|
320
|
-
max: 6e5
|
|
567
|
+
onChange: (next) => {
|
|
568
|
+
setField("gateBudgetMs", next === void 0 ? void 0 : next * 1e3);
|
|
569
|
+
}
|
|
321
570
|
});
|
|
322
|
-
|
|
571
|
+
return (0, react.createElement)(d, { title: t("gateSection") }, enabled, injects, budget);
|
|
572
|
+
}
|
|
573
|
+
/** 记忆反馈小节:开关 + 网关地址 + 密钥引用。 */
|
|
574
|
+
function buildMemorySection(props) {
|
|
575
|
+
const { t, writable, eff, setField } = props;
|
|
576
|
+
const gatewayUrl = eff("memoryGatewayUrl") ?? "";
|
|
577
|
+
const keyRef = eff("memoryGatewayKeyRef") ?? "";
|
|
578
|
+
const memory = (0, react.createElement)(_, {
|
|
323
579
|
label: t("memoryLabel"),
|
|
324
|
-
|
|
580
|
+
...l(t("memoryHint")),
|
|
325
581
|
field: "memoryFeedback",
|
|
326
582
|
checked: eff("memoryFeedback") === true,
|
|
327
583
|
disabled: !writable,
|
|
328
|
-
|
|
329
|
-
setField("memoryFeedback",
|
|
584
|
+
onChange: (next) => {
|
|
585
|
+
setField("memoryFeedback", next);
|
|
330
586
|
}
|
|
331
587
|
});
|
|
332
|
-
const
|
|
588
|
+
const url = (0, react.createElement)(y, {
|
|
333
589
|
label: t("gatewayUrlLabel"),
|
|
334
|
-
|
|
590
|
+
...l(t("gatewayUrlHint")),
|
|
335
591
|
field: "memoryGatewayUrl",
|
|
336
|
-
value:
|
|
592
|
+
value: gatewayUrl,
|
|
337
593
|
placeholder: "http://127.0.0.1:8420",
|
|
594
|
+
monospace: true,
|
|
338
595
|
disabled: !writable,
|
|
339
|
-
onChange: (
|
|
340
|
-
setField(
|
|
341
|
-
},
|
|
342
|
-
onClear: (field) => {
|
|
343
|
-
clearField(field);
|
|
596
|
+
onChange: (next) => {
|
|
597
|
+
setField("memoryGatewayUrl", next.trim() === "" ? void 0 : next.trim());
|
|
344
598
|
}
|
|
345
599
|
});
|
|
346
|
-
const
|
|
600
|
+
const keyRefField = (0, react.createElement)(y, {
|
|
347
601
|
label: t("keyRefLabel"),
|
|
348
|
-
|
|
602
|
+
...l(t("keyRefHint")),
|
|
349
603
|
field: "memoryGatewayKeyRef",
|
|
350
|
-
value:
|
|
604
|
+
value: keyRef,
|
|
351
605
|
placeholder: "TDAI_GATEWAY_KEY",
|
|
606
|
+
monospace: true,
|
|
352
607
|
disabled: !writable,
|
|
353
|
-
onChange: (
|
|
354
|
-
setField(
|
|
355
|
-
},
|
|
356
|
-
onClear: (field) => {
|
|
357
|
-
clearField(field);
|
|
608
|
+
onChange: (next) => {
|
|
609
|
+
setField("memoryGatewayKeyRef", next.trim() === "" ? void 0 : next.trim());
|
|
358
610
|
}
|
|
359
611
|
});
|
|
360
|
-
|
|
361
|
-
t,
|
|
362
|
-
dirty,
|
|
363
|
-
writable,
|
|
364
|
-
busy,
|
|
365
|
-
error: saveError,
|
|
366
|
-
onSave,
|
|
367
|
-
onDiscard
|
|
368
|
-
});
|
|
369
|
-
return (0, react.createElement)("ul", { className: "qgc-body" }, rowEnabled, rowInjects, rowBudget, rowMemory, rowGatewayUrl, rowGatewayKeyRef, rowSave);
|
|
612
|
+
return (0, react.createElement)(d, { title: t("memorySection") }, memory, url, keyRefField);
|
|
370
613
|
}
|
|
371
614
|
function DgCard(props) {
|
|
372
|
-
const { t
|
|
373
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
615
|
+
const { t } = props;
|
|
374
616
|
const snap = props.useCard((cardSnap) => cardSnap);
|
|
375
|
-
const
|
|
617
|
+
const value = projectCardValues(snap.value);
|
|
376
618
|
const writable = snap.status === "ready" && snap.writable;
|
|
377
619
|
const [touched, setTouched] = (0, react.useState)({});
|
|
378
620
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
379
621
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
380
|
-
/** 渲染取值:touched
|
|
381
|
-
const eff = (field) =>
|
|
382
|
-
|
|
622
|
+
/** 渲染取值:touched 优先(经 zod 收窄),快照兜底(未触碰字段实时跟随外部快照)。 */
|
|
623
|
+
const eff = (field) => {
|
|
624
|
+
if (!(field in touched)) return value[field];
|
|
625
|
+
const parsedTouched = CARD_FIELD_SCHEMAS[field].safeParse(touched[field]);
|
|
626
|
+
return parsedTouched.success ? parsedTouched.data : void 0;
|
|
627
|
+
};
|
|
628
|
+
const dirtyKeys = t$1(touched, value);
|
|
629
|
+
const dirty = dirtyKeys.length > 0;
|
|
383
630
|
const setField = (field, val) => {
|
|
384
631
|
setTouched((prev) => ({
|
|
385
632
|
...prev,
|
|
386
633
|
[field]: val
|
|
387
634
|
}));
|
|
388
635
|
};
|
|
389
|
-
const clearField = (field) => {
|
|
390
|
-
setTouched((prev) => ({
|
|
391
|
-
...prev,
|
|
392
|
-
[field]: void 0
|
|
393
|
-
}));
|
|
394
|
-
};
|
|
395
636
|
const save = () => {
|
|
396
|
-
const keys = t(touched, value);
|
|
637
|
+
const keys = t$1(touched, value);
|
|
397
638
|
if (keys.length === 0) return;
|
|
398
639
|
setBusy(true);
|
|
399
640
|
setSaveError(null);
|
|
400
641
|
writeTouched({
|
|
401
|
-
t
|
|
642
|
+
t,
|
|
402
643
|
set: props.set,
|
|
403
644
|
unset: props.unset,
|
|
404
645
|
keys,
|
|
@@ -412,26 +653,29 @@ function DgCard(props) {
|
|
|
412
653
|
setTouched({});
|
|
413
654
|
setSaveError(null);
|
|
414
655
|
};
|
|
415
|
-
const
|
|
416
|
-
t
|
|
417
|
-
open,
|
|
418
|
-
onToggle: () => {
|
|
419
|
-
setOpen(!open);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
const body = open ? buildCardBody({
|
|
423
|
-
t: t$1,
|
|
656
|
+
const inputs = {
|
|
657
|
+
t,
|
|
424
658
|
writable,
|
|
425
659
|
eff,
|
|
426
|
-
setField
|
|
427
|
-
|
|
660
|
+
setField
|
|
661
|
+
};
|
|
662
|
+
const gate = buildGateSection(inputs);
|
|
663
|
+
const memory = buildMemorySection(inputs);
|
|
664
|
+
const saveBar = (0, react.createElement)(S, {
|
|
428
665
|
dirty,
|
|
429
666
|
busy,
|
|
430
|
-
|
|
667
|
+
writable,
|
|
668
|
+
error: saveError,
|
|
669
|
+
saveLabel: t("save"),
|
|
670
|
+
savingLabel: t("saving"),
|
|
671
|
+
revertLabel: t("revert"),
|
|
672
|
+
dirtyLabel: t("dirtyCount", { count: dirtyKeys.length }),
|
|
673
|
+
cleanLabel: t("statusClean"),
|
|
674
|
+
readOnlyLabel: t("statusReadOnly"),
|
|
431
675
|
onSave: save,
|
|
432
676
|
onDiscard: discard
|
|
433
|
-
})
|
|
434
|
-
return (0, react.createElement)(
|
|
677
|
+
});
|
|
678
|
+
return (0, react.createElement)(u, null, gate, memory, saveBar);
|
|
435
679
|
}
|
|
436
680
|
function cardStore(scope) {
|
|
437
681
|
let cachedSnap = null;
|
|
@@ -467,8 +711,8 @@ const inject = [
|
|
|
467
711
|
function apply(ctx) {
|
|
468
712
|
ctx.effect(() => {
|
|
469
713
|
const tag = document.createElement("style");
|
|
470
|
-
tag.id = "
|
|
471
|
-
tag.textContent =
|
|
714
|
+
tag.id = "dsh-card-ui-css-quality-gate";
|
|
715
|
+
tag.textContent = c;
|
|
472
716
|
document.head.append(tag);
|
|
473
717
|
return () => {
|
|
474
718
|
tag.remove();
|
|
@@ -491,8 +735,11 @@ function apply(ctx) {
|
|
|
491
735
|
}, DgCard);
|
|
492
736
|
});
|
|
493
737
|
}
|
|
738
|
+
exports.CARD_NUMBER_SCHEMAS = CARD_NUMBER_SCHEMAS;
|
|
494
739
|
exports.apply = apply;
|
|
495
740
|
exports.inject = inject;
|
|
741
|
+
exports.projectCardValues = projectCardValues;
|
|
742
|
+
exports.writeTouched = writeTouched;
|
|
496
743
|
return module.exports;
|
|
497
744
|
},
|
|
498
745
|
});
|