dsh-qoder-connect 0.1.2 → 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/README.md +148 -148
- package/lib/bin.js +1 -1
- package/lib/client.js +1154 -1215
- package/lib/index.js +45 -4
- package/lib/{variants-C0iZD8AD.js → variants-CSeJqJpR.js} +75 -6
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -61,242 +61,691 @@ window.__ModuleLoader__.load({
|
|
|
61
61
|
return status === "error" && typeof wrapped["message"] === "string";
|
|
62
62
|
}
|
|
63
63
|
//#endregion
|
|
64
|
-
//#region src/client/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
titleKey: "title",
|
|
70
|
-
introKey: "intro",
|
|
71
|
-
signedOutKey: "signedOutHint",
|
|
72
|
-
patGuideKey: "patGuide",
|
|
73
|
-
patPlaceholderKey: "patPlaceholder",
|
|
74
|
-
statusPath: QODER_STATUS_PATH,
|
|
75
|
-
probePath: QODER_PROBE_PATH,
|
|
76
|
-
authPath: QODER_AUTH_PATH
|
|
64
|
+
//#region src/client/quota-settings-store.ts
|
|
65
|
+
let pollIntervalMs = 3e5;
|
|
66
|
+
const toggles = {
|
|
67
|
+
cn: false,
|
|
68
|
+
global: false
|
|
77
69
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
titleKey: "titleAI",
|
|
82
|
-
introKey: "introAI",
|
|
83
|
-
signedOutKey: "signedOutHintAI",
|
|
84
|
-
patGuideKey: "patGuideAI",
|
|
85
|
-
patPlaceholderKey: "patPlaceholderAI",
|
|
86
|
-
statusPath: QODER_GLOBAL_STATUS_PATH,
|
|
87
|
-
probePath: QODER_GLOBAL_PROBE_PATH,
|
|
88
|
-
authPath: QODER_GLOBAL_AUTH_PATH
|
|
89
|
-
}];
|
|
90
|
-
const POLL_INTERVAL_MS = 6e4;
|
|
91
|
-
const cardStyle$1 = {
|
|
92
|
-
listStyle: "none",
|
|
93
|
-
borderWidth: "0.5px",
|
|
94
|
-
borderStyle: "solid",
|
|
95
|
-
borderColor: "var(--dsw-alias-border-l4)",
|
|
96
|
-
borderRadius: 16,
|
|
97
|
-
background: "var(--dsw-alias-bg-layer-3)",
|
|
98
|
-
transition: "border-color .16s, background .16s"
|
|
70
|
+
let togglesSnapshot = {
|
|
71
|
+
cn: false,
|
|
72
|
+
global: false
|
|
99
73
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const cardOpenStyle$1 = {
|
|
104
|
-
background: "var(--dsw-alias-bg-layer-2)",
|
|
105
|
-
borderColor: "var(--dsw-alias-label-dimmed)"
|
|
74
|
+
const signIn = {
|
|
75
|
+
cn: false,
|
|
76
|
+
global: false
|
|
106
77
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
display: "flex",
|
|
111
|
-
alignItems: "center",
|
|
112
|
-
gap: 12,
|
|
113
|
-
border: 0,
|
|
114
|
-
borderRadius: 12,
|
|
115
|
-
padding: "14px 16px",
|
|
116
|
-
background: "transparent",
|
|
117
|
-
color: "inherit",
|
|
118
|
-
font: "inherit",
|
|
119
|
-
textAlign: "left",
|
|
120
|
-
cursor: "pointer",
|
|
121
|
-
appearance: "none"
|
|
78
|
+
let signInSnapshot = {
|
|
79
|
+
cn: false,
|
|
80
|
+
global: false
|
|
122
81
|
};
|
|
82
|
+
let revision = 0;
|
|
83
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
84
|
+
function bump() {
|
|
85
|
+
revision += 1;
|
|
86
|
+
for (const listener of listeners) listener();
|
|
87
|
+
}
|
|
88
|
+
/** Update the shared poll interval (from the settings document). */
|
|
89
|
+
function setQuotaPollMs(ms) {
|
|
90
|
+
if (Number.isFinite(ms) && ms >= 6e4 && pollIntervalMs !== ms) {
|
|
91
|
+
pollIntervalMs = ms;
|
|
92
|
+
bump();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Read the configured poll interval. */
|
|
96
|
+
function quotaPollMs() {
|
|
97
|
+
return pollIntervalMs;
|
|
98
|
+
}
|
|
99
|
+
/** Update both sidebar toggles (from the settings document). */
|
|
100
|
+
function setQuotaToggles(cn, global) {
|
|
101
|
+
if (toggles.cn !== cn || toggles.global !== global) {
|
|
102
|
+
toggles.cn = cn;
|
|
103
|
+
toggles.global = global;
|
|
104
|
+
togglesSnapshot = { ...toggles };
|
|
105
|
+
bump();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Read the current toggles. */
|
|
109
|
+
function quotaToggles() {
|
|
110
|
+
return togglesSnapshot;
|
|
111
|
+
}
|
|
112
|
+
/** Record a variant's sign-in state from any successful status poll. */
|
|
113
|
+
function noteQuotaSignIn(variantId, signedIn) {
|
|
114
|
+
if (variantId === "qoder" && signIn.cn !== signedIn) {
|
|
115
|
+
signIn.cn = signedIn;
|
|
116
|
+
signInSnapshot = { ...signIn };
|
|
117
|
+
bump();
|
|
118
|
+
} else if (variantId === "qoder-global" && signIn.global !== signedIn) {
|
|
119
|
+
signIn.global = signedIn;
|
|
120
|
+
signInSnapshot = { ...signIn };
|
|
121
|
+
bump();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Read the cached sign-in state. */
|
|
125
|
+
function quotaSignInState() {
|
|
126
|
+
return signInSnapshot;
|
|
127
|
+
}
|
|
128
|
+
/** Subscribe to any flag change; returns the disposer. */
|
|
129
|
+
function onQuotaSettingsChange(listener) {
|
|
130
|
+
listeners.add(listener);
|
|
131
|
+
return () => {
|
|
132
|
+
listeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** The current revision — the useSyncExternalStore snapshot value. */
|
|
136
|
+
function quotaSettingsRevision() {
|
|
137
|
+
return revision;
|
|
138
|
+
}
|
|
139
|
+
/** Which variant a status route belongs to, from the route path. */
|
|
140
|
+
function variantOfStatusPath(statusPath) {
|
|
141
|
+
return statusPath.includes("/global/") ? "qoder-global" : "qoder";
|
|
142
|
+
}
|
|
123
143
|
/**
|
|
124
|
-
* The
|
|
144
|
+
* The last status document per variant, shared by EVERY quota surface.
|
|
125
145
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
146
|
+
* The sidebar cards and the dashboard each used to fetch independently, so a
|
|
147
|
+
* dashboard refresh updated the panel while the sidebar card kept showing its
|
|
148
|
+
* previous read until its own next tick — two different numbers for one
|
|
149
|
+
* account on one screen. One store, one write path
|
|
150
|
+
* ({@link noteQuotaStatus}), and every subscriber re-renders through the
|
|
151
|
+
* same revision: whatever surface refreshed last, all of them show it.
|
|
152
|
+
*
|
|
153
|
+
* Documents are kept by identity (never mutated), so reference comparisons
|
|
154
|
+
* in useSyncExternalStore selectors stay cheap and stable.
|
|
131
155
|
*/
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
136
|
-
const headTextStyle$1 = {
|
|
137
|
-
display: "flex",
|
|
138
|
-
flex: 1,
|
|
139
|
-
minWidth: 0,
|
|
140
|
-
flexDirection: "column",
|
|
141
|
-
gap: 4
|
|
142
|
-
};
|
|
143
|
-
const nameStyle = {
|
|
144
|
-
fontSize: 15,
|
|
145
|
-
lineHeight: 1.4,
|
|
146
|
-
fontWeight: 600,
|
|
147
|
-
color: "var(--dsw-alias-label-primary)"
|
|
156
|
+
const statusDocuments = {
|
|
157
|
+
cn: void 0,
|
|
158
|
+
global: void 0
|
|
148
159
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Publish one variant's freshly fetched status document. Downstream
|
|
162
|
+
* subscribers (both sidebar cards and the dashboard, through whichever
|
|
163
|
+
* observable wraps this store) re-render on the revision bump.
|
|
164
|
+
*/
|
|
165
|
+
function noteQuotaStatus(variantId, status) {
|
|
166
|
+
if (variantId === "qoder" && statusDocuments.cn !== status) {
|
|
167
|
+
statusDocuments.cn = status;
|
|
168
|
+
statusFetchedAt.cn = Date.now();
|
|
169
|
+
bump();
|
|
170
|
+
} else if (variantId === "qoder-global" && statusDocuments.global !== status) {
|
|
171
|
+
statusDocuments.global = status;
|
|
172
|
+
statusFetchedAt.global = Date.now();
|
|
173
|
+
bump();
|
|
174
|
+
}
|
|
175
|
+
noteQuotaSignIn(variantId, status.status === "signed-in");
|
|
176
|
+
}
|
|
177
|
+
/** Read one variant's latest status document. */
|
|
178
|
+
function quotaStatus(variantId) {
|
|
179
|
+
return variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
|
|
180
|
+
}
|
|
181
|
+
/** When each variant's document was last fetched (per publish, not per read). */
|
|
182
|
+
const statusFetchedAt = {
|
|
183
|
+
cn: void 0,
|
|
184
|
+
global: void 0
|
|
153
185
|
};
|
|
186
|
+
/** Read the time a variant's current document was fetched, if any. */
|
|
187
|
+
function quotaStatusFetchedAt(variantId) {
|
|
188
|
+
return variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
|
|
189
|
+
}
|
|
154
190
|
/**
|
|
155
|
-
*
|
|
191
|
+
* Whether ONE variant's shared document is fresh enough to skip a fetch:
|
|
192
|
+
* the user's rule — a click/mount/interval tick within the configured
|
|
193
|
+
* interval of the last successful read reuses the cached document, and only
|
|
194
|
+
* a variant with NO result yet (or a failed read that never landed one)
|
|
195
|
+
* forces the upstream call. The interval is a cache lifetime, not a metronome.
|
|
156
196
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
197
|
+
* A `maxAgeMs` of the poll interval comes from the settings document; a
|
|
198
|
+
* failed last read is NOT tracked here (callers gate failures themselves),
|
|
199
|
+
* because "the last read failed" still means "no usable result".
|
|
200
|
+
*
|
|
201
|
+
* @param variantId - which variant's freshness to test.
|
|
202
|
+
* @param maxAgeMs - the configured poll interval (cache lifetime).
|
|
162
203
|
*/
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
fill: "none",
|
|
169
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
170
|
-
"aria-hidden": "true",
|
|
171
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
172
|
-
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
173
|
-
fill: "currentColor"
|
|
174
|
-
})
|
|
175
|
-
});
|
|
204
|
+
function quotaStatusIsFresh(variantId, maxAgeMs) {
|
|
205
|
+
const fetchedAt = variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
|
|
206
|
+
const document = variantId === "qoder" ? statusDocuments.cn : statusDocuments.global;
|
|
207
|
+
if (fetchedAt === void 0 || document === void 0) return false;
|
|
208
|
+
return Date.now() - fetchedAt < maxAgeMs;
|
|
176
209
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/client/QuotaSettingsCard.tsx
|
|
212
|
+
/**
|
|
213
|
+
* The shared quota-settings card: one card above the two variant cards that
|
|
214
|
+
* configures both sidebar quota widgets.
|
|
215
|
+
*
|
|
216
|
+
* Like the built-in plugin cards, it registers into `settings.plugin.item`
|
|
217
|
+
* keyed by its own settings namespace (`qoder-quota`), binds that
|
|
218
|
+
* namespace through the client settings scope, and writes through the scope's
|
|
219
|
+
* revision-fenced `set` — the same durable-write path every preference row
|
|
220
|
+
* uses. A toggle commits on click: each click is one explicit user choice,
|
|
221
|
+
* and the scope's ordering makes the last one win, so no staged-draft form is
|
|
222
|
+
* needed for two booleans and a number.
|
|
223
|
+
*
|
|
224
|
+
* The two toggles gate the CN and international sidebar cards respectively;
|
|
225
|
+
* the interval is one shared poll period. Toggles are disabled while their
|
|
226
|
+
* variant is signed out: a quota card for an account nobody is signed into
|
|
227
|
+
* would render an error forever, so the setting waits for a session.
|
|
228
|
+
*/
|
|
229
|
+
/** The default poll interval shown before a value is stored. */
|
|
230
|
+
const POLL_DEFAULT_MS = 3e5;
|
|
231
|
+
/** Floor the schema also enforces; mirrored here for immediate UI feedback. */
|
|
232
|
+
const POLL_MIN_MS = 6e4;
|
|
233
|
+
/** Read the section values out of a scope snapshot (defaults when absent). */
|
|
234
|
+
function project(scope) {
|
|
235
|
+
if (scope === void 0) return {
|
|
236
|
+
status: "unavailable",
|
|
237
|
+
writable: false,
|
|
238
|
+
values: {
|
|
239
|
+
sidebarQuotaCN: false,
|
|
240
|
+
sidebarQuotaGlobal: false,
|
|
241
|
+
quotaPollMs: POLL_DEFAULT_MS
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const snapshot = scope.getSnapshot();
|
|
245
|
+
const value = snapshot.value ?? {};
|
|
246
|
+
return {
|
|
247
|
+
status: snapshot.status,
|
|
248
|
+
writable: snapshot.writable,
|
|
249
|
+
values: {
|
|
250
|
+
sidebarQuotaCN: value.sidebarQuotaCN === true,
|
|
251
|
+
sidebarQuotaGlobal: value.sidebarQuotaGlobal === true,
|
|
252
|
+
quotaPollMs: typeof value.quotaPollMs === "number" ? value.quotaPollMs : POLL_DEFAULT_MS
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Stable-reference projection cache.
|
|
258
|
+
*
|
|
259
|
+
* React's useSyncExternalStore requires getSnapshot() to return THE SAME
|
|
260
|
+
* reference between renders unless the store actually changed. project()
|
|
261
|
+
* builds a fresh object every call, which re-renders forever and crashes the
|
|
262
|
+
* card with React error #185 ("maximum update depth exceeded") — exactly the
|
|
263
|
+
* crash the slot ledger reported. The cache below returns the last built
|
|
264
|
+
* projection until the underlying scope snapshot (or scope identity) changes,
|
|
265
|
+
* which is the only thing the projection actually derives from.
|
|
266
|
+
*/
|
|
267
|
+
let cachedScope;
|
|
268
|
+
let cachedProjection;
|
|
269
|
+
const UNAVAILABLE = {
|
|
270
|
+
status: "unavailable",
|
|
271
|
+
writable: false,
|
|
272
|
+
values: {
|
|
273
|
+
sidebarQuotaCN: false,
|
|
274
|
+
sidebarQuotaGlobal: false,
|
|
275
|
+
quotaPollMs: POLL_DEFAULT_MS
|
|
276
|
+
}
|
|
227
277
|
};
|
|
228
|
-
|
|
278
|
+
function stableProject(scope) {
|
|
279
|
+
if (scope === void 0) return UNAVAILABLE;
|
|
280
|
+
const next = project(scope);
|
|
281
|
+
if (cachedProjection === void 0 || cachedScope !== scope || cachedProjection.status !== next.status || cachedProjection.writable !== next.writable || cachedProjection.values.sidebarQuotaCN !== next.values.sidebarQuotaCN || cachedProjection.values.sidebarQuotaGlobal !== next.values.sidebarQuotaGlobal || cachedProjection.values.quotaPollMs !== next.values.quotaPollMs) {
|
|
282
|
+
cachedScope = scope;
|
|
283
|
+
cachedProjection = next;
|
|
284
|
+
}
|
|
285
|
+
return cachedProjection;
|
|
286
|
+
}
|
|
287
|
+
/** One toggle row: label, hint, and a switch drawn to the shell's proportions. */
|
|
288
|
+
function ToggleRow({ label, hint, checked, disabled, disabledHint, onToggle }) {
|
|
289
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
290
|
+
style: rowStyle$1,
|
|
291
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
292
|
+
style: rowTextStyle,
|
|
293
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
294
|
+
style: labelStyle$1,
|
|
295
|
+
children: label
|
|
296
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
297
|
+
style: hintStyle,
|
|
298
|
+
children: disabled === true && disabledHint !== void 0 ? disabledHint : hint
|
|
299
|
+
})]
|
|
300
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
301
|
+
type: "button",
|
|
302
|
+
role: "switch",
|
|
303
|
+
"aria-checked": checked,
|
|
304
|
+
disabled,
|
|
305
|
+
"aria-label": label,
|
|
306
|
+
onClick: () => {
|
|
307
|
+
if (disabled) return;
|
|
308
|
+
onToggle(!checked);
|
|
309
|
+
},
|
|
310
|
+
style: {
|
|
311
|
+
...switchStyle,
|
|
312
|
+
background: checked ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.2))",
|
|
313
|
+
justifyContent: checked ? "flex-end" : "flex-start",
|
|
314
|
+
opacity: disabled === true ? .45 : 1,
|
|
315
|
+
cursor: disabled === true ? "not-allowed" : "pointer"
|
|
316
|
+
},
|
|
317
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: knobStyle })
|
|
318
|
+
})]
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** The inner controls for sidebar quota settings. */
|
|
322
|
+
function QuotaSettingsContent({ t = (key) => key, scope, signedIn }) {
|
|
323
|
+
const subscribe = (0, react.useCallback)((onStoreChange) => {
|
|
324
|
+
return scope?.subscribe(onStoreChange) ?? (() => {});
|
|
325
|
+
}, [scope]);
|
|
326
|
+
const projection = (0, react.useSyncExternalStore)(subscribe, () => stableProject(scope));
|
|
327
|
+
const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
|
|
328
|
+
const [probe, setProbe] = (0, react.useState)();
|
|
329
|
+
(0, react.useEffect)(() => {
|
|
330
|
+
let disposed = false;
|
|
331
|
+
const probeOne = async (path) => {
|
|
332
|
+
try {
|
|
333
|
+
const body = await (await fetch(path, { headers: { accept: "application/json" } })).json();
|
|
334
|
+
if (disposed || !isQoderWebStatus(body)) return void 0;
|
|
335
|
+
noteQuotaSignIn(variantOfStatusPath(path), body.status === "signed-in");
|
|
336
|
+
return body.status === "signed-in";
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
(async () => {
|
|
342
|
+
const [cn, global] = await Promise.all([probeOne(QODER_STATUS_PATH), probeOne(QODER_GLOBAL_STATUS_PATH)]);
|
|
343
|
+
if (!disposed) setProbe({
|
|
344
|
+
cn: cn === true,
|
|
345
|
+
global: global === true
|
|
346
|
+
});
|
|
347
|
+
})();
|
|
348
|
+
return () => {
|
|
349
|
+
disposed = true;
|
|
350
|
+
};
|
|
351
|
+
}, []);
|
|
352
|
+
if (projection.status === "unavailable") return null;
|
|
353
|
+
const reported = signedIn?.();
|
|
354
|
+
const deriveSigned = (variant, variantId) => {
|
|
355
|
+
if (reported !== void 0) return Boolean(reported[variant]);
|
|
356
|
+
const currentStatus = quotaStatus(variantId);
|
|
357
|
+
if (currentStatus?.status === "signed-out") return false;
|
|
358
|
+
const live = liveSignIn[variant];
|
|
359
|
+
if (probe !== void 0) {
|
|
360
|
+
if (!probe[variant]) return Boolean(live && currentStatus?.status === "signed-in");
|
|
361
|
+
return Boolean(live);
|
|
362
|
+
}
|
|
363
|
+
return Boolean(live && currentStatus?.status === "signed-in");
|
|
364
|
+
};
|
|
365
|
+
const signed = {
|
|
366
|
+
cn: deriveSigned("cn", "qoder"),
|
|
367
|
+
global: deriveSigned("global", "qoder-global")
|
|
368
|
+
};
|
|
369
|
+
const write = (field, value) => {
|
|
370
|
+
if (field === "sidebarQuotaCN" && value === true && !signed.cn) return;
|
|
371
|
+
if (field === "sidebarQuotaGlobal" && value === true && !signed.global) return;
|
|
372
|
+
scope?.set(field, value);
|
|
373
|
+
};
|
|
374
|
+
const minutes = Math.max(POLL_MIN_MS / 6e4, Math.round(projection.values.quotaPollMs / 6e4));
|
|
375
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
376
|
+
style: {
|
|
377
|
+
display: "flex",
|
|
378
|
+
flexDirection: "column",
|
|
379
|
+
gap: 4
|
|
380
|
+
},
|
|
381
|
+
children: [
|
|
382
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
383
|
+
label: t("quotaToggleCN"),
|
|
384
|
+
hint: t("quotaToggleHint"),
|
|
385
|
+
checked: projection.values.sidebarQuotaCN,
|
|
386
|
+
disabled: !signed.cn,
|
|
387
|
+
disabledHint: t("quotaSignInRequired"),
|
|
388
|
+
onToggle: (next) => write("sidebarQuotaCN", next)
|
|
389
|
+
}),
|
|
390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
391
|
+
label: t("quotaToggleGlobal"),
|
|
392
|
+
hint: t("quotaToggleHint"),
|
|
393
|
+
checked: projection.values.sidebarQuotaGlobal,
|
|
394
|
+
disabled: !signed.global,
|
|
395
|
+
disabledHint: t("quotaSignInRequired"),
|
|
396
|
+
onToggle: (next) => write("sidebarQuotaGlobal", next)
|
|
397
|
+
}),
|
|
398
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
399
|
+
style: {
|
|
400
|
+
...rowStyle$1,
|
|
401
|
+
borderBottom: "none",
|
|
402
|
+
paddingBottom: 0
|
|
403
|
+
},
|
|
404
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
405
|
+
style: rowTextStyle,
|
|
406
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
407
|
+
style: labelStyle$1,
|
|
408
|
+
children: t("quotaPollLabel")
|
|
409
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
410
|
+
style: hintStyle,
|
|
411
|
+
children: t("quotaPollHint")
|
|
412
|
+
})]
|
|
413
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
414
|
+
style: pollFieldStyle,
|
|
415
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
416
|
+
type: "number",
|
|
417
|
+
min: POLL_MIN_MS / 6e4,
|
|
418
|
+
step: 1,
|
|
419
|
+
value: minutes,
|
|
420
|
+
"aria-label": t("quotaPollLabel"),
|
|
421
|
+
onChange: (event) => {
|
|
422
|
+
const mins = Number.parseInt(event.target.value, 10);
|
|
423
|
+
if (Number.isFinite(mins) && mins > 0) write("quotaPollMs", Math.max(POLL_MIN_MS, mins * 6e4));
|
|
424
|
+
},
|
|
425
|
+
style: inputStyle
|
|
426
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
427
|
+
style: hintStyle,
|
|
428
|
+
children: t("quotaPollUnit")
|
|
429
|
+
})]
|
|
430
|
+
})]
|
|
431
|
+
}),
|
|
432
|
+
projection.writable === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
433
|
+
style: hintStyle,
|
|
434
|
+
children: t("quotaSettingsSaveFailed")
|
|
435
|
+
}) : null
|
|
436
|
+
]
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* One settings row: NO box of its own (the bordered rows read as nested
|
|
441
|
+
* cards, which the user ruled against) — rows are separated by a hairline
|
|
442
|
+
* bottom rule like the settings shell's own preference lists.
|
|
443
|
+
*/
|
|
444
|
+
const rowStyle$1 = {
|
|
229
445
|
display: "flex",
|
|
230
|
-
|
|
231
|
-
gap:
|
|
232
|
-
|
|
446
|
+
alignItems: "center",
|
|
447
|
+
gap: 12,
|
|
448
|
+
borderBottom: ".5px solid var(--dsw-alias-border-l2)",
|
|
449
|
+
paddingBottom: 10
|
|
233
450
|
};
|
|
234
|
-
const
|
|
451
|
+
const rowTextStyle = {
|
|
235
452
|
display: "flex",
|
|
453
|
+
flex: 1,
|
|
454
|
+
minWidth: 0,
|
|
236
455
|
flexDirection: "column",
|
|
237
|
-
gap:
|
|
456
|
+
gap: 2
|
|
238
457
|
};
|
|
239
|
-
const
|
|
240
|
-
margin: 0,
|
|
458
|
+
const labelStyle$1 = {
|
|
241
459
|
fontSize: 13,
|
|
460
|
+
fontWeight: 500,
|
|
242
461
|
lineHeight: 1.5,
|
|
243
|
-
fontWeight: 600,
|
|
244
462
|
color: "var(--dsw-alias-label-primary)"
|
|
245
463
|
};
|
|
246
|
-
const
|
|
247
|
-
display: "flex",
|
|
248
|
-
justifyContent: "space-between",
|
|
249
|
-
gap: 12,
|
|
250
|
-
fontSize: 13,
|
|
251
|
-
lineHeight: 1.5,
|
|
252
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
253
|
-
};
|
|
254
|
-
const modelRateStyle = {
|
|
464
|
+
const hintStyle = {
|
|
255
465
|
fontSize: 12,
|
|
256
466
|
lineHeight: 1.5,
|
|
257
467
|
color: "var(--dsw-alias-label-tertiary)"
|
|
258
468
|
};
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
alignItems: "flex-start",
|
|
262
|
-
gap: 9,
|
|
263
|
-
padding: "10px 12px",
|
|
264
|
-
border: ".5px solid var(--dsw-alias-border-l4)",
|
|
265
|
-
borderRadius: 8,
|
|
266
|
-
background: "var(--dsw-alias-bg-layer-3)",
|
|
267
|
-
color: "var(--dsw-alias-label-primary)",
|
|
268
|
-
fontSize: 13,
|
|
269
|
-
lineHeight: 1.5
|
|
270
|
-
};
|
|
271
|
-
const contextPreferenceCopyStyle = {
|
|
272
|
-
display: "flex",
|
|
273
|
-
flexDirection: "column",
|
|
274
|
-
gap: 2
|
|
275
|
-
};
|
|
276
|
-
/** The right-hand cell of one model row: value and its note on one line. */
|
|
277
|
-
const contextPickerRowStyle = {
|
|
469
|
+
const switchStyle = {
|
|
470
|
+
flex: "none",
|
|
278
471
|
display: "flex",
|
|
472
|
+
width: 36,
|
|
473
|
+
height: 20,
|
|
474
|
+
borderRadius: 10,
|
|
475
|
+
borderWidth: "1px",
|
|
476
|
+
borderStyle: "solid",
|
|
477
|
+
borderColor: "var(--dsw-alias-border-l2)",
|
|
478
|
+
padding: 1,
|
|
479
|
+
cursor: "pointer",
|
|
279
480
|
alignItems: "center",
|
|
280
|
-
|
|
281
|
-
gap: 8,
|
|
282
|
-
flexWrap: "wrap"
|
|
481
|
+
transition: "background .16s"
|
|
283
482
|
};
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
483
|
+
const knobStyle = {
|
|
484
|
+
display: "block",
|
|
485
|
+
width: 16,
|
|
486
|
+
height: 16,
|
|
487
|
+
borderRadius: "50%",
|
|
488
|
+
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
489
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.2)"
|
|
289
490
|
};
|
|
290
|
-
|
|
291
|
-
* The PAT entry row: the password field takes the row's flexible width, the
|
|
292
|
-
* Save button keeps its own, and the whole block sits inside the card body
|
|
293
|
-
* without a nested box (the same rule the settings rows follow).
|
|
294
|
-
*/
|
|
295
|
-
const patRowStyle = {
|
|
491
|
+
const pollFieldStyle = {
|
|
296
492
|
display: "flex",
|
|
297
493
|
alignItems: "center",
|
|
298
|
-
gap:
|
|
299
|
-
|
|
494
|
+
gap: 6,
|
|
495
|
+
flex: "none"
|
|
496
|
+
};
|
|
497
|
+
const inputStyle = {
|
|
498
|
+
boxSizing: "border-box",
|
|
499
|
+
width: 55,
|
|
500
|
+
padding: "5px 8px",
|
|
501
|
+
borderWidth: "1px",
|
|
502
|
+
borderStyle: "solid",
|
|
503
|
+
borderColor: "var(--dsw-alias-border-l2)",
|
|
504
|
+
borderRadius: 8,
|
|
505
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
506
|
+
color: "var(--dsw-alias-label-primary)",
|
|
507
|
+
font: "inherit",
|
|
508
|
+
fontSize: 13,
|
|
509
|
+
textAlign: "right"
|
|
510
|
+
};
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/client/QoderPluginCard.tsx
|
|
513
|
+
/** Qoder status card contributed to Harness Plugin configuration. */
|
|
514
|
+
/** China Qoder; the plugin's primary card and default. */
|
|
515
|
+
const QODER_CN_CARD = {
|
|
516
|
+
id: "qoder",
|
|
517
|
+
titleKey: "title",
|
|
518
|
+
introKey: "intro",
|
|
519
|
+
signedOutKey: "signedOutHint",
|
|
520
|
+
patGuideKey: "patGuide",
|
|
521
|
+
patPlaceholderKey: "patPlaceholder",
|
|
522
|
+
statusPath: QODER_STATUS_PATH,
|
|
523
|
+
probePath: QODER_PROBE_PATH,
|
|
524
|
+
authPath: QODER_AUTH_PATH
|
|
525
|
+
};
|
|
526
|
+
/** International Qoder Global. */
|
|
527
|
+
const QODER_GLOBAL_CARD = {
|
|
528
|
+
id: "qoder-global",
|
|
529
|
+
titleKey: "titleAI",
|
|
530
|
+
introKey: "introAI",
|
|
531
|
+
signedOutKey: "signedOutHintAI",
|
|
532
|
+
patGuideKey: "patGuideAI",
|
|
533
|
+
patPlaceholderKey: "patPlaceholderAI",
|
|
534
|
+
statusPath: QODER_GLOBAL_STATUS_PATH,
|
|
535
|
+
probePath: QODER_GLOBAL_PROBE_PATH,
|
|
536
|
+
authPath: QODER_GLOBAL_AUTH_PATH
|
|
537
|
+
};
|
|
538
|
+
/** Both cards, in display order (China first). */
|
|
539
|
+
const QODER_CARD_VARIANTS = [QODER_CN_CARD, QODER_GLOBAL_CARD];
|
|
540
|
+
const POLL_INTERVAL_MS = 6e4;
|
|
541
|
+
const cardStyle = {
|
|
542
|
+
listStyle: "none",
|
|
543
|
+
borderWidth: "0.5px",
|
|
544
|
+
borderStyle: "solid",
|
|
545
|
+
borderColor: "var(--dsw-alias-border-l4)",
|
|
546
|
+
borderRadius: 16,
|
|
547
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
548
|
+
transition: "border-color .16s, background .16s"
|
|
549
|
+
};
|
|
550
|
+
/** Hover, matching the built-in card's `:hover`. Inline styles cannot express a pseudo-class. */
|
|
551
|
+
const cardHoverStyle = { borderColor: "var(--dsw-alias-label-dimmed)" };
|
|
552
|
+
/** Expanded, matching the built-in card's open state. */
|
|
553
|
+
const cardOpenStyle = {
|
|
554
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
555
|
+
borderColor: "var(--dsw-alias-label-dimmed)"
|
|
556
|
+
};
|
|
557
|
+
const headerStyle = {
|
|
558
|
+
boxSizing: "border-box",
|
|
559
|
+
width: "100%",
|
|
560
|
+
display: "flex",
|
|
561
|
+
alignItems: "center",
|
|
562
|
+
gap: 12,
|
|
563
|
+
border: 0,
|
|
564
|
+
borderRadius: 12,
|
|
565
|
+
padding: "14px 16px",
|
|
566
|
+
background: "transparent",
|
|
567
|
+
color: "inherit",
|
|
568
|
+
font: "inherit",
|
|
569
|
+
textAlign: "left",
|
|
570
|
+
cursor: "pointer",
|
|
571
|
+
appearance: "none"
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* The built-in header's keyboard focus ring.
|
|
575
|
+
*
|
|
576
|
+
* `:focus-visible` is what makes the ring appear for keyboard navigation but not
|
|
577
|
+
* for a mouse click, and an inline style cannot express a pseudo-class — so the
|
|
578
|
+
* component tracks it and applies this instead. Without it the header falls back
|
|
579
|
+
* to the browser's own outline, which is the black box that used to appear on
|
|
580
|
+
* focus where the built-in card shows a brand-coloured ring.
|
|
581
|
+
*/
|
|
582
|
+
const headerFocusStyle = {
|
|
583
|
+
outline: "2px solid var(--dsw-alias-brand-primary)",
|
|
584
|
+
outlineOffset: -2
|
|
585
|
+
};
|
|
586
|
+
const headTextStyle = {
|
|
587
|
+
display: "flex",
|
|
588
|
+
flex: 1,
|
|
589
|
+
minWidth: 0,
|
|
590
|
+
flexDirection: "column",
|
|
591
|
+
gap: 4
|
|
592
|
+
};
|
|
593
|
+
const nameStyle = {
|
|
594
|
+
fontSize: 15,
|
|
595
|
+
lineHeight: 1.4,
|
|
596
|
+
fontWeight: 600,
|
|
597
|
+
color: "var(--dsw-alias-label-primary)"
|
|
598
|
+
};
|
|
599
|
+
const descriptionStyle = {
|
|
600
|
+
fontSize: 13,
|
|
601
|
+
lineHeight: 1.5,
|
|
602
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* The disclosure chevron, drawn to match the Settings panel's own card.
|
|
606
|
+
*
|
|
607
|
+
* The built-in card renders `IconChevronDownOutline14` from the client's shared
|
|
608
|
+
* icon catalog, which the shell seeds into the module table. This plugin does
|
|
609
|
+
* not request that catalog, so the same outline is drawn here from the same path
|
|
610
|
+
* data: the text `⌄` glyph this replaces had a different shape, weight, and
|
|
611
|
+
* baseline from the icon the cards beside it use.
|
|
612
|
+
*/
|
|
613
|
+
function ChevronDownIcon() {
|
|
614
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
615
|
+
width: 14,
|
|
616
|
+
height: 14,
|
|
617
|
+
viewBox: "0 0 14 14",
|
|
618
|
+
fill: "none",
|
|
619
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
620
|
+
"aria-hidden": "true",
|
|
621
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
622
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
623
|
+
fill: "currentColor"
|
|
624
|
+
})
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/** The built-in card's chevron rule: tertiary color, and only the rotation animates. */
|
|
628
|
+
const chevronStyle = {
|
|
629
|
+
flex: "none",
|
|
630
|
+
display: "flex",
|
|
631
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
632
|
+
transition: "transform .16s"
|
|
633
|
+
};
|
|
634
|
+
const cardBodyStyle = {
|
|
635
|
+
borderTop: ".5px solid var(--dsw-alias-border-l2)",
|
|
636
|
+
margin: "0 16px",
|
|
637
|
+
padding: "12px 0 8px"
|
|
638
|
+
};
|
|
639
|
+
const bodyStyle = {
|
|
640
|
+
margin: 0,
|
|
641
|
+
fontSize: 13,
|
|
642
|
+
lineHeight: 1.5,
|
|
643
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
644
|
+
};
|
|
645
|
+
const rowStyle = {
|
|
646
|
+
display: "flex",
|
|
647
|
+
alignItems: "center",
|
|
648
|
+
justifyContent: "space-between",
|
|
649
|
+
flexWrap: "wrap",
|
|
650
|
+
gap: 12
|
|
651
|
+
};
|
|
652
|
+
const statusStyle = {
|
|
653
|
+
display: "flex",
|
|
654
|
+
alignItems: "center",
|
|
655
|
+
gap: 8,
|
|
656
|
+
fontSize: 13,
|
|
657
|
+
fontWeight: 500,
|
|
658
|
+
lineHeight: 1.5,
|
|
659
|
+
color: "var(--dsw-alias-label-primary)"
|
|
660
|
+
};
|
|
661
|
+
/** The built-in secondary button: transparent, hairline border, 8px radius. */
|
|
662
|
+
const buttonStyle$1 = {
|
|
663
|
+
boxSizing: "border-box",
|
|
664
|
+
padding: "5px 14px",
|
|
665
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
666
|
+
borderRadius: 8,
|
|
667
|
+
background: "transparent",
|
|
668
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
669
|
+
font: "inherit",
|
|
670
|
+
fontSize: 13,
|
|
671
|
+
lineHeight: 1.5,
|
|
672
|
+
cursor: "pointer"
|
|
673
|
+
};
|
|
674
|
+
const errorStyle = {
|
|
675
|
+
...bodyStyle,
|
|
676
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
677
|
+
};
|
|
678
|
+
const quotaListStyle = {
|
|
679
|
+
display: "flex",
|
|
680
|
+
flexDirection: "column",
|
|
681
|
+
gap: 18,
|
|
682
|
+
paddingTop: 2
|
|
683
|
+
};
|
|
684
|
+
const quotaGroupStyle = {
|
|
685
|
+
display: "flex",
|
|
686
|
+
flexDirection: "column",
|
|
687
|
+
gap: 10
|
|
688
|
+
};
|
|
689
|
+
const quotaTitleStyle = {
|
|
690
|
+
margin: 0,
|
|
691
|
+
fontSize: 13,
|
|
692
|
+
lineHeight: 1.5,
|
|
693
|
+
fontWeight: 600,
|
|
694
|
+
color: "var(--dsw-alias-label-primary)"
|
|
695
|
+
};
|
|
696
|
+
const quotaLabelStyle = {
|
|
697
|
+
display: "flex",
|
|
698
|
+
justifyContent: "space-between",
|
|
699
|
+
gap: 12,
|
|
700
|
+
fontSize: 13,
|
|
701
|
+
lineHeight: 1.5,
|
|
702
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
703
|
+
};
|
|
704
|
+
const modelRateStyle = {
|
|
705
|
+
fontSize: 12,
|
|
706
|
+
lineHeight: 1.5,
|
|
707
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
708
|
+
};
|
|
709
|
+
const contextPreferenceStyle = {
|
|
710
|
+
display: "flex",
|
|
711
|
+
alignItems: "flex-start",
|
|
712
|
+
gap: 9,
|
|
713
|
+
padding: "10px 12px",
|
|
714
|
+
border: ".5px solid var(--dsw-alias-border-l4)",
|
|
715
|
+
borderRadius: 8,
|
|
716
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
717
|
+
color: "var(--dsw-alias-label-primary)",
|
|
718
|
+
fontSize: 13,
|
|
719
|
+
lineHeight: 1.5
|
|
720
|
+
};
|
|
721
|
+
const contextPreferenceCopyStyle = {
|
|
722
|
+
display: "flex",
|
|
723
|
+
flexDirection: "column",
|
|
724
|
+
gap: 2
|
|
725
|
+
};
|
|
726
|
+
const contextPickerRowStyle = {
|
|
727
|
+
display: "flex",
|
|
728
|
+
alignItems: "center",
|
|
729
|
+
justifyContent: "flex-end",
|
|
730
|
+
gap: 8,
|
|
731
|
+
flexWrap: "wrap"
|
|
732
|
+
};
|
|
733
|
+
const progressTrackStyle = {
|
|
734
|
+
height: 8,
|
|
735
|
+
overflow: "hidden",
|
|
736
|
+
borderRadius: 999,
|
|
737
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.08))"
|
|
738
|
+
};
|
|
739
|
+
/**
|
|
740
|
+
* The PAT entry row: the password field takes the row's flexible width, the
|
|
741
|
+
* Save button keeps its own, and the whole block sits inside the card body
|
|
742
|
+
* without a nested box (the same rule the settings rows follow).
|
|
743
|
+
*/
|
|
744
|
+
const patRowStyle = {
|
|
745
|
+
display: "flex",
|
|
746
|
+
alignItems: "center",
|
|
747
|
+
gap: 8,
|
|
748
|
+
flexWrap: "wrap"
|
|
300
749
|
};
|
|
301
750
|
const patInputStyle = {
|
|
302
751
|
boxSizing: "border-box",
|
|
@@ -353,11 +802,49 @@ window.__ModuleLoader__.load({
|
|
|
353
802
|
*/
|
|
354
803
|
const primaryButtonStyle$1 = {
|
|
355
804
|
...buttonStyle$1,
|
|
356
|
-
|
|
805
|
+
borderWidth: "1px",
|
|
806
|
+
borderStyle: "solid",
|
|
807
|
+
borderColor: "var(--dsw-alias-button-primary-fill)",
|
|
357
808
|
background: "var(--dsw-alias-button-primary-fill)",
|
|
358
809
|
color: "var(--dsw-alias-label-primary-foreground)"
|
|
359
810
|
};
|
|
360
|
-
|
|
811
|
+
const segmentedContainerStyle = {
|
|
812
|
+
display: "flex",
|
|
813
|
+
alignItems: "center",
|
|
814
|
+
background: "var(--dsw-alias-bg-layer-1, rgba(20, 20, 20, 0.6))",
|
|
815
|
+
borderWidth: "1px",
|
|
816
|
+
borderStyle: "solid",
|
|
817
|
+
borderColor: "var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.08))",
|
|
818
|
+
borderRadius: 8,
|
|
819
|
+
padding: 3,
|
|
820
|
+
gap: 4,
|
|
821
|
+
marginTop: 14,
|
|
822
|
+
marginBottom: 16
|
|
823
|
+
};
|
|
824
|
+
function segmentedTabItemStyle(active) {
|
|
825
|
+
return {
|
|
826
|
+
flex: 1,
|
|
827
|
+
display: "flex",
|
|
828
|
+
alignItems: "center",
|
|
829
|
+
justifyContent: "center",
|
|
830
|
+
gap: 8,
|
|
831
|
+
padding: "6px 12px",
|
|
832
|
+
borderRadius: 6,
|
|
833
|
+
borderWidth: "1px",
|
|
834
|
+
borderStyle: "solid",
|
|
835
|
+
borderColor: active ? "var(--dsw-alias-border-l4, rgba(255, 255, 255, 0.18))" : "transparent",
|
|
836
|
+
background: active ? "var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.08))" : "transparent",
|
|
837
|
+
color: active ? "var(--dsw-alias-label-primary, #fff)" : "var(--dsw-alias-label-tertiary, #8c8c8c)",
|
|
838
|
+
fontWeight: active ? 500 : 400,
|
|
839
|
+
fontSize: 13,
|
|
840
|
+
lineHeight: "18px",
|
|
841
|
+
cursor: "pointer",
|
|
842
|
+
appearance: "none",
|
|
843
|
+
outline: "none",
|
|
844
|
+
transition: "all .16s ease"
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function progressFillStyle(percent) {
|
|
361
848
|
return {
|
|
362
849
|
width: `${Math.max(0, Math.min(100, percent))}%`,
|
|
363
850
|
height: "100%",
|
|
@@ -373,11 +860,11 @@ window.__ModuleLoader__.load({
|
|
|
373
860
|
*/
|
|
374
861
|
function dotStyle(status) {
|
|
375
862
|
return {
|
|
376
|
-
width:
|
|
377
|
-
height:
|
|
863
|
+
width: 8,
|
|
864
|
+
height: 8,
|
|
378
865
|
borderRadius: "50%",
|
|
379
866
|
flex: "0 0 auto",
|
|
380
|
-
background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #
|
|
867
|
+
background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #8c8c8c)"
|
|
381
868
|
};
|
|
382
869
|
}
|
|
383
870
|
function formatNumber(value) {
|
|
@@ -397,7 +884,6 @@ window.__ModuleLoader__.load({
|
|
|
397
884
|
if (!Number.isNaN(parsed)) return formatTime(parsed);
|
|
398
885
|
return time;
|
|
399
886
|
}
|
|
400
|
-
/** Localize where the credential in effect came from. */
|
|
401
887
|
function patSourceText(source, t) {
|
|
402
888
|
if (source === "env") return t("patSourceEnv");
|
|
403
889
|
if (source === "cli") return t("patSourceCli");
|
|
@@ -549,72 +1035,35 @@ window.__ModuleLoader__.load({
|
|
|
549
1035
|
]
|
|
550
1036
|
});
|
|
551
1037
|
}
|
|
552
|
-
/**
|
|
553
|
-
* Compact token count for display: the catalog's own round numbers (`200000`,
|
|
554
|
-
* `1000000`) read better as `200K` / `1M`, and no precision is lost because
|
|
555
|
-
* these values are always whole thousands.
|
|
556
|
-
*/
|
|
557
1038
|
function formatTokens(tokens) {
|
|
558
1039
|
if (tokens >= 1e6 && tokens % 1e6 === 0) return `${tokens / 1e6}M`;
|
|
559
1040
|
if (tokens >= 1e3 && tokens % 1e3 === 0) return `${tokens / 1e3}K`;
|
|
560
1041
|
return String(tokens);
|
|
561
1042
|
}
|
|
562
1043
|
/** Render Qoder PAT state, quota, catalog, and context capacities as one expandable card. */
|
|
563
|
-
function QoderPluginCard(
|
|
1044
|
+
function QoderPluginCard(props) {
|
|
1045
|
+
const { t, scope, signedIn, variant, unified } = props;
|
|
564
1046
|
if (t === void 0) throw new Error("Qoder plugin card requires its translation function");
|
|
1047
|
+
const isUnified = unified === true;
|
|
1048
|
+
const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
|
|
1049
|
+
const [activeVariantId, setActiveVariantId] = (0, react.useState)("qoder");
|
|
1050
|
+
const currentVariant = isUnified ? activeVariantId === "qoder" ? QODER_CN_CARD : QODER_GLOBAL_CARD : variant ?? QODER_CN_CARD;
|
|
565
1051
|
const [open, setOpen] = (0, react.useState)(false);
|
|
566
|
-
/** Whether the pointer is over the card; drives the same border tint the built-in card gets on hover. */
|
|
567
1052
|
const [hovered, setHovered] = (0, react.useState)(false);
|
|
568
|
-
/** Keyboard focus on the header, reproducing the built-in's `:focus-visible` ring. */
|
|
569
1053
|
const [headerFocused, setHeaderFocused] = (0, react.useState)(false);
|
|
570
|
-
/**
|
|
571
|
-
* The document to render. `undefined` means *not read yet*, which is a
|
|
572
|
-
* distinct state from "signed out": seeding this with a signed-out document
|
|
573
|
-
* told an already-signed-in user they were signed out for the whole first
|
|
574
|
-
* round trip (and forever, if the read never settled).
|
|
575
|
-
*/
|
|
576
1054
|
const [status, setStatus] = (0, react.useState)();
|
|
577
|
-
|
|
578
|
-
* Whether the last **successful** read found a usable credential.
|
|
579
|
-
*
|
|
580
|
-
* Kept apart from `status` because the poll's liveness must depend on what the
|
|
581
|
-
* account actually is, not on what the card last displayed: a failed read
|
|
582
|
-
* leaves this untouched, so a transient failure cannot disarm the interval,
|
|
583
|
-
* while a genuine signed-out answer still stops it.
|
|
584
|
-
*
|
|
585
|
-
* `undefined` therefore means "no successful read yet", which is also the
|
|
586
|
-
* condition that decides whether a failed read has anything to preserve.
|
|
587
|
-
*/
|
|
588
|
-
const [signedIn, setSignedIn] = (0, react.useState)();
|
|
589
|
-
/**
|
|
590
|
-
* Why the most recent read failed, when it did. Rendered as a notice beside
|
|
591
|
-
* whatever document is still on screen, rather than replacing it.
|
|
592
|
-
*/
|
|
1055
|
+
const [signedInState, setSignedInState] = (0, react.useState)();
|
|
593
1056
|
const [readFailure, setReadFailure] = (0, react.useState)();
|
|
594
1057
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
595
|
-
/** The PAT draft in the input box. Never persisted; lives only in component state. */
|
|
596
1058
|
const [patDraft, setPatDraft] = (0, react.useState)("");
|
|
597
|
-
/** Whether the signed-in card is showing the replace-PAT entry. */
|
|
598
1059
|
const [replacing, setReplacing] = (0, react.useState)(false);
|
|
599
|
-
/** Whether a PAT save or clear is in flight. Own flag, so the 60s busy label stays honest. */
|
|
600
1060
|
const [patBusy, setPatBusy] = (0, react.useState)(false);
|
|
601
|
-
/** Why the most recent PAT save/clear failed, when it did. */
|
|
602
1061
|
const [patError, setPatError] = (0, react.useState)();
|
|
603
|
-
/** The most recent PAT outcome the card should report beside the entry. */
|
|
604
1062
|
const [patNotice, setPatNotice] = (0, react.useState)();
|
|
605
|
-
/** The password input, so 「更换 PAT」 can focus and pre-clear it. */
|
|
606
1063
|
const patInput = (0, react.useRef)(null);
|
|
607
1064
|
const [tab, setTab] = (0, react.useState)("status");
|
|
608
1065
|
const mounted = (0, react.useRef)(true);
|
|
609
|
-
/**
|
|
610
|
-
* Identity of the newest read that may write. Assigned when a read *starts*,
|
|
611
|
-
* so a response is superseded by anything begun after it — "the response whose
|
|
612
|
-
* request started last wins". Without this, a slow poll begun before a manual
|
|
613
|
-
* action could settle after the action's own refresh and restore the older
|
|
614
|
-
* document.
|
|
615
|
-
*/
|
|
616
1066
|
const readSeq = (0, react.useRef)(0);
|
|
617
|
-
/** Manual requests in flight, so unmount can abort them like the poll's. */
|
|
618
1067
|
const manualControllers = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
619
1068
|
(0, react.useEffect)(() => {
|
|
620
1069
|
mounted.current = true;
|
|
@@ -624,39 +1073,17 @@ window.__ModuleLoader__.load({
|
|
|
624
1073
|
manualControllers.current.clear();
|
|
625
1074
|
};
|
|
626
1075
|
}, []);
|
|
627
|
-
/** Register a manual request's controller so unmount aborts it. */
|
|
628
1076
|
const trackController = (0, react.useCallback)(() => {
|
|
629
1077
|
const controller = new AbortController();
|
|
630
1078
|
manualControllers.current.add(controller);
|
|
631
1079
|
return controller;
|
|
632
1080
|
}, []);
|
|
633
|
-
/**
|
|
634
|
-
* The in-process key authorizing this card's PAT writes, or undefined until a
|
|
635
|
-
* document carrying one has been read.
|
|
636
|
-
*
|
|
637
|
-
* Derived once rather than read off each use site: the `error` arm carries no
|
|
638
|
-
* key, and reaching for `status.authKey` in three places is three chances to
|
|
639
|
-
* dereference a state that has none. The constant-time comparison happens on
|
|
640
|
-
* the host; the card only passes the key through.
|
|
641
|
-
*/
|
|
642
1081
|
const authKey = status === void 0 || status.status === "error" ? void 0 : status.authKey;
|
|
643
|
-
/**
|
|
644
|
-
* Read the status document and apply it under the two policies the card's
|
|
645
|
-
* correctness rests on:
|
|
646
|
-
*
|
|
647
|
-
* - a non-document body (empty, `null`, a non-JSON page) is a failed read, not
|
|
648
|
-
* something to store and then dereference in the render;
|
|
649
|
-
* - a failed read never discards a document already on screen. It is recorded
|
|
650
|
-
* and shown as a notice beside that document; only when nothing has been
|
|
651
|
-
* read yet does the failure itself become the rendered state.
|
|
652
|
-
*
|
|
653
|
-
* Returns whether this read produced the current document.
|
|
654
|
-
*/
|
|
655
1082
|
const refresh = (0, react.useCallback)(async (signal) => {
|
|
656
1083
|
const seq = ++readSeq.current;
|
|
657
1084
|
const current = () => mounted.current && signal?.aborted !== true && seq === readSeq.current;
|
|
658
1085
|
try {
|
|
659
|
-
const response = await fetch(
|
|
1086
|
+
const response = await fetch(currentVariant.statusPath, {
|
|
660
1087
|
headers: { accept: "application/json" },
|
|
661
1088
|
credentials: "same-origin",
|
|
662
1089
|
...signal === void 0 ? {} : { signal }
|
|
@@ -666,8 +1093,13 @@ window.__ModuleLoader__.load({
|
|
|
666
1093
|
if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
|
|
667
1094
|
if (!current()) return false;
|
|
668
1095
|
setStatus(value);
|
|
669
|
-
if (value.status === "signed-in")
|
|
670
|
-
|
|
1096
|
+
if (value.status === "signed-in") {
|
|
1097
|
+
setSignedInState(true);
|
|
1098
|
+
noteQuotaStatus(currentVariant.id, value);
|
|
1099
|
+
} else if (value.status === "signed-out") {
|
|
1100
|
+
setSignedInState(false);
|
|
1101
|
+
noteQuotaStatus(currentVariant.id, value);
|
|
1102
|
+
}
|
|
671
1103
|
setReadFailure(void 0);
|
|
672
1104
|
return true;
|
|
673
1105
|
} catch (error) {
|
|
@@ -681,17 +1113,28 @@ window.__ModuleLoader__.load({
|
|
|
681
1113
|
}
|
|
682
1114
|
return false;
|
|
683
1115
|
}
|
|
684
|
-
}, [
|
|
1116
|
+
}, [currentVariant.statusPath, t]);
|
|
685
1117
|
(0, react.useEffect)(() => {
|
|
686
1118
|
if (!open) return;
|
|
1119
|
+
setStatus(void 0);
|
|
1120
|
+
setSignedInState(void 0);
|
|
1121
|
+
setReadFailure(void 0);
|
|
1122
|
+
setPatDraft("");
|
|
1123
|
+
setReplacing(false);
|
|
1124
|
+
setPatError(void 0);
|
|
1125
|
+
setPatNotice(void 0);
|
|
687
1126
|
const controller = new AbortController();
|
|
688
1127
|
refresh(controller.signal);
|
|
689
1128
|
return () => {
|
|
690
1129
|
controller.abort();
|
|
691
1130
|
};
|
|
692
|
-
}, [
|
|
1131
|
+
}, [
|
|
1132
|
+
open,
|
|
1133
|
+
currentVariant.statusPath,
|
|
1134
|
+
refresh
|
|
1135
|
+
]);
|
|
693
1136
|
(0, react.useEffect)(() => {
|
|
694
|
-
if (!open ||
|
|
1137
|
+
if (!open || signedInState === false) return;
|
|
695
1138
|
const controller = new AbortController();
|
|
696
1139
|
const timer = window.setInterval(() => {
|
|
697
1140
|
refresh(controller.signal);
|
|
@@ -703,7 +1146,7 @@ window.__ModuleLoader__.load({
|
|
|
703
1146
|
}, [
|
|
704
1147
|
open,
|
|
705
1148
|
refresh,
|
|
706
|
-
|
|
1149
|
+
signedInState
|
|
707
1150
|
]);
|
|
708
1151
|
const manualRefresh = async () => {
|
|
709
1152
|
setBusy(true);
|
|
@@ -729,7 +1172,7 @@ window.__ModuleLoader__.load({
|
|
|
729
1172
|
setBusy(true);
|
|
730
1173
|
const controller = trackController();
|
|
731
1174
|
try {
|
|
732
|
-
const response = await fetch(
|
|
1175
|
+
const response = await fetch(currentVariant.probePath, {
|
|
733
1176
|
method: "POST",
|
|
734
1177
|
headers: {
|
|
735
1178
|
"Content-Type": "application/json",
|
|
@@ -753,11 +1196,11 @@ window.__ModuleLoader__.load({
|
|
|
753
1196
|
manualControllers.current.delete(controller);
|
|
754
1197
|
}
|
|
755
1198
|
}, [
|
|
1199
|
+
currentVariant.probePath,
|
|
756
1200
|
refresh,
|
|
757
1201
|
status,
|
|
758
1202
|
t,
|
|
759
|
-
trackController
|
|
760
|
-
variant.probePath
|
|
1203
|
+
trackController
|
|
761
1204
|
]);
|
|
762
1205
|
/**
|
|
763
1206
|
* Run one probe-route control action and refresh the card's state afterwards.
|
|
@@ -772,7 +1215,7 @@ window.__ModuleLoader__.load({
|
|
|
772
1215
|
setBusy(true);
|
|
773
1216
|
const controller = trackController();
|
|
774
1217
|
try {
|
|
775
|
-
const response = await fetch(
|
|
1218
|
+
const response = await fetch(currentVariant.probePath, {
|
|
776
1219
|
method: "POST",
|
|
777
1220
|
headers: {
|
|
778
1221
|
"Content-Type": "application/json",
|
|
@@ -799,11 +1242,11 @@ window.__ModuleLoader__.load({
|
|
|
799
1242
|
if (mounted.current) setBusy(false);
|
|
800
1243
|
}
|
|
801
1244
|
}, [
|
|
1245
|
+
currentVariant.probePath,
|
|
802
1246
|
refresh,
|
|
803
1247
|
status,
|
|
804
1248
|
t,
|
|
805
|
-
trackController
|
|
806
|
-
variant.probePath
|
|
1249
|
+
trackController
|
|
807
1250
|
]);
|
|
808
1251
|
/**
|
|
809
1252
|
* Validate and store the pasted PAT (or overwrite the stored one).
|
|
@@ -823,7 +1266,7 @@ window.__ModuleLoader__.load({
|
|
|
823
1266
|
setPatNotice(void 0);
|
|
824
1267
|
const controller = trackController();
|
|
825
1268
|
try {
|
|
826
|
-
const response = await fetch(
|
|
1269
|
+
const response = await fetch(currentVariant.authPath, {
|
|
827
1270
|
method: "POST",
|
|
828
1271
|
headers: {
|
|
829
1272
|
"Content-Type": "application/json",
|
|
@@ -850,6 +1293,7 @@ window.__ModuleLoader__.load({
|
|
|
850
1293
|
setPatDraft("");
|
|
851
1294
|
setReplacing(false);
|
|
852
1295
|
setPatNotice(t("patSaved"));
|
|
1296
|
+
noteQuotaSignIn(currentVariant.id, true);
|
|
853
1297
|
await refresh(controller.signal);
|
|
854
1298
|
} catch (error) {
|
|
855
1299
|
if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
|
|
@@ -859,12 +1303,12 @@ window.__ModuleLoader__.load({
|
|
|
859
1303
|
}
|
|
860
1304
|
}, [
|
|
861
1305
|
authKey,
|
|
1306
|
+
currentVariant.authPath,
|
|
862
1307
|
patBusy,
|
|
863
1308
|
patDraft,
|
|
864
1309
|
refresh,
|
|
865
1310
|
t,
|
|
866
|
-
trackController
|
|
867
|
-
variant.authPath
|
|
1311
|
+
trackController
|
|
868
1312
|
]);
|
|
869
1313
|
/** Remove the stored PAT; the host answer is `{ ok: true }`, then the card re-reads. */
|
|
870
1314
|
const clearPat = (0, react.useCallback)(async () => {
|
|
@@ -876,7 +1320,7 @@ window.__ModuleLoader__.load({
|
|
|
876
1320
|
setReplacing(false);
|
|
877
1321
|
const controller = trackController();
|
|
878
1322
|
try {
|
|
879
|
-
const response = await fetch(
|
|
1323
|
+
const response = await fetch(currentVariant.authPath, {
|
|
880
1324
|
method: "POST",
|
|
881
1325
|
headers: {
|
|
882
1326
|
"Content-Type": "application/json",
|
|
@@ -894,6 +1338,14 @@ window.__ModuleLoader__.load({
|
|
|
894
1338
|
return;
|
|
895
1339
|
}
|
|
896
1340
|
setPatDraft("");
|
|
1341
|
+
const signedOutDoc = {
|
|
1342
|
+
status: "signed-out",
|
|
1343
|
+
authKey: key
|
|
1344
|
+
};
|
|
1345
|
+
setStatus(signedOutDoc);
|
|
1346
|
+
setSignedInState(false);
|
|
1347
|
+
noteQuotaStatus(currentVariant.id, signedOutDoc);
|
|
1348
|
+
noteQuotaSignIn(currentVariant.id, false);
|
|
897
1349
|
await refresh(controller.signal);
|
|
898
1350
|
} catch (error) {
|
|
899
1351
|
if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
|
|
@@ -903,11 +1355,11 @@ window.__ModuleLoader__.load({
|
|
|
903
1355
|
}
|
|
904
1356
|
}, [
|
|
905
1357
|
authKey,
|
|
1358
|
+
currentVariant.authPath,
|
|
906
1359
|
patBusy,
|
|
907
1360
|
refresh,
|
|
908
1361
|
t,
|
|
909
|
-
trackController
|
|
910
|
-
variant.authPath
|
|
1362
|
+
trackController
|
|
911
1363
|
]);
|
|
912
1364
|
/**
|
|
913
1365
|
* 更换 PAT: open the entry over the signed-in document, pre-cleared and
|
|
@@ -925,7 +1377,6 @@ window.__ModuleLoader__.load({
|
|
|
925
1377
|
patInput.current?.focus();
|
|
926
1378
|
});
|
|
927
1379
|
}, []);
|
|
928
|
-
/** The PAT entry: guide line, password input, Save. Shared by both arms. */
|
|
929
1380
|
const patEntry = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
930
1381
|
style: {
|
|
931
1382
|
display: "flex",
|
|
@@ -935,7 +1386,7 @@ window.__ModuleLoader__.load({
|
|
|
935
1386
|
children: [
|
|
936
1387
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
937
1388
|
style: bodyStyle,
|
|
938
|
-
children: t(
|
|
1389
|
+
children: t(currentVariant.patGuideKey)
|
|
939
1390
|
}),
|
|
940
1391
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
941
1392
|
style: patRowStyle,
|
|
@@ -944,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
944
1395
|
ref: patInput,
|
|
945
1396
|
type: "password",
|
|
946
1397
|
value: patDraft,
|
|
947
|
-
placeholder: t(
|
|
1398
|
+
placeholder: t(currentVariant.patPlaceholderKey),
|
|
948
1399
|
"aria-label": t("patHeading"),
|
|
949
1400
|
disabled: patBusy,
|
|
950
1401
|
onChange: (event) => {
|
|
@@ -990,9 +1441,9 @@ window.__ModuleLoader__.load({
|
|
|
990
1441
|
})
|
|
991
1442
|
]
|
|
992
1443
|
});
|
|
993
|
-
const
|
|
1444
|
+
const cardTitle = isUnified ? t("unifiedTitle") : t(currentVariant.titleKey);
|
|
1445
|
+
const cardIntro = isUnified ? t("unifiedIntro") : t(currentVariant.introKey);
|
|
994
1446
|
const label = status === void 0 ? t("loading") : status.status === "signed-in" ? t("signedIn") : status.status === "error" ? t("requestFailed") : t("signedOut");
|
|
995
|
-
/** One line summarizing the PAT in effect: source, saved-at, and the redacted tail. */
|
|
996
1447
|
const patSummaryLine = (pat) => {
|
|
997
1448
|
const parts = [
|
|
998
1449
|
patSourceText(pat.source, t),
|
|
@@ -1004,11 +1455,16 @@ window.__ModuleLoader__.load({
|
|
|
1004
1455
|
children: parts.join(" · ")
|
|
1005
1456
|
});
|
|
1006
1457
|
};
|
|
1458
|
+
const reported = signedIn?.();
|
|
1459
|
+
const cnSignedIn = reported !== void 0 ? reported.cn : liveSignIn.cn;
|
|
1460
|
+
const globalSignedIn = reported !== void 0 ? reported.global : liveSignIn.global;
|
|
1461
|
+
const cnDotStatus = isUnified && activeVariantId === "qoder" ? status === void 0 ? "loading" : status.status : cnSignedIn ? "signed-in" : "signed-out";
|
|
1462
|
+
const globalDotStatus = isUnified && activeVariantId === "qoder-global" ? status === void 0 ? "loading" : status.status : globalSignedIn ? "signed-in" : "signed-out";
|
|
1007
1463
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1008
1464
|
style: {
|
|
1009
|
-
...cardStyle
|
|
1010
|
-
...hovered ? cardHoverStyle
|
|
1011
|
-
...open ? cardOpenStyle
|
|
1465
|
+
...cardStyle,
|
|
1466
|
+
...hovered ? cardHoverStyle : {},
|
|
1467
|
+
...open ? cardOpenStyle : {}
|
|
1012
1468
|
},
|
|
1013
1469
|
onMouseEnter: () => {
|
|
1014
1470
|
setHovered(true);
|
|
@@ -1020,10 +1476,10 @@ window.__ModuleLoader__.load({
|
|
|
1020
1476
|
type: "button",
|
|
1021
1477
|
style: {
|
|
1022
1478
|
...headerStyle,
|
|
1023
|
-
...headerFocused ? headerFocusStyle
|
|
1479
|
+
...headerFocused ? headerFocusStyle : {}
|
|
1024
1480
|
},
|
|
1025
1481
|
"aria-expanded": open,
|
|
1026
|
-
"aria-label": `${t(open ? "collapse" : "expand")}: ${
|
|
1482
|
+
"aria-label": `${t(open ? "collapse" : "expand")}: ${cardTitle}`,
|
|
1027
1483
|
onClick: () => {
|
|
1028
1484
|
setOpen(!open);
|
|
1029
1485
|
},
|
|
@@ -1040,30 +1496,60 @@ window.__ModuleLoader__.load({
|
|
|
1040
1496
|
setHeaderFocused(false);
|
|
1041
1497
|
},
|
|
1042
1498
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1043
|
-
style: headTextStyle
|
|
1499
|
+
style: headTextStyle,
|
|
1044
1500
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1045
1501
|
style: nameStyle,
|
|
1046
|
-
children:
|
|
1502
|
+
children: cardTitle
|
|
1047
1503
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1048
1504
|
style: descriptionStyle,
|
|
1049
|
-
children:
|
|
1505
|
+
children: cardIntro
|
|
1050
1506
|
})]
|
|
1051
1507
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1052
1508
|
style: {
|
|
1053
|
-
...chevronStyle
|
|
1509
|
+
...chevronStyle,
|
|
1054
1510
|
transform: open ? "rotate(180deg)" : "none"
|
|
1055
1511
|
},
|
|
1056
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon
|
|
1512
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, {})
|
|
1057
1513
|
})]
|
|
1058
1514
|
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1059
|
-
style: cardBodyStyle
|
|
1515
|
+
style: cardBodyStyle,
|
|
1060
1516
|
children: [
|
|
1517
|
+
isUnified ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaSettingsContent, {
|
|
1518
|
+
t,
|
|
1519
|
+
scope,
|
|
1520
|
+
signedIn
|
|
1521
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1522
|
+
style: segmentedContainerStyle,
|
|
1523
|
+
role: "tablist",
|
|
1524
|
+
"aria-label": "Qoder Version Selection",
|
|
1525
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1526
|
+
type: "button",
|
|
1527
|
+
role: "tab",
|
|
1528
|
+
"aria-selected": activeVariantId === "qoder",
|
|
1529
|
+
style: segmentedTabItemStyle(activeVariantId === "qoder"),
|
|
1530
|
+
onClick: () => setActiveVariantId("qoder"),
|
|
1531
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1532
|
+
style: dotStyle(cnDotStatus),
|
|
1533
|
+
"aria-hidden": "true"
|
|
1534
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabCN") })]
|
|
1535
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1536
|
+
type: "button",
|
|
1537
|
+
role: "tab",
|
|
1538
|
+
"aria-selected": activeVariantId === "qoder-global",
|
|
1539
|
+
style: segmentedTabItemStyle(activeVariantId === "qoder-global"),
|
|
1540
|
+
onClick: () => setActiveVariantId("qoder-global"),
|
|
1541
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1542
|
+
style: dotStyle(globalDotStatus),
|
|
1543
|
+
"aria-hidden": "true"
|
|
1544
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabGlobal") })]
|
|
1545
|
+
})]
|
|
1546
|
+
})] }) : null,
|
|
1061
1547
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1062
1548
|
style: quotaTitleStyle,
|
|
1063
1549
|
children: t("accountHeading")
|
|
1064
1550
|
}),
|
|
1065
1551
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1066
|
-
style: rowStyle
|
|
1552
|
+
style: rowStyle,
|
|
1067
1553
|
children: [
|
|
1068
1554
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1069
1555
|
style: statusStyle,
|
|
@@ -1100,7 +1586,7 @@ window.__ModuleLoader__.load({
|
|
|
1100
1586
|
})] })
|
|
1101
1587
|
]
|
|
1102
1588
|
}),
|
|
1103
|
-
readFailure === void 0 ||
|
|
1589
|
+
readFailure === void 0 || signedInState === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1104
1590
|
style: errorStyle,
|
|
1105
1591
|
children: t("statusRefreshFailed", { message: readFailure })
|
|
1106
1592
|
}),
|
|
@@ -1112,7 +1598,7 @@ window.__ModuleLoader__.load({
|
|
|
1112
1598
|
children: t("jobTokenRefreshed", { time: formatTime(status.jobTokenRefreshedAt) })
|
|
1113
1599
|
}),
|
|
1114
1600
|
status.catalog === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1115
|
-
style: rowStyle
|
|
1601
|
+
style: rowStyle,
|
|
1116
1602
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1117
1603
|
style: bodyStyle,
|
|
1118
1604
|
children: status.catalog.source === "live" && status.catalog.fetchedAt !== void 0 ? t("catalogLive", { time: formatTime(status.catalog.fetchedAt) }) : status.catalog.source === "saved" && status.catalog.fetchedAt !== void 0 ? t("catalogSaved", { time: formatTime(status.catalog.fetchedAt) }) : t("catalogFallback")
|
|
@@ -1156,7 +1642,7 @@ window.__ModuleLoader__.load({
|
|
|
1156
1642
|
children: [status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1157
1643
|
style: quotaListStyle,
|
|
1158
1644
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1159
|
-
style: rowStyle
|
|
1645
|
+
style: rowStyle,
|
|
1160
1646
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1161
1647
|
style: quotaTitleStyle,
|
|
1162
1648
|
children: t("creditsHeading")
|
|
@@ -1206,7 +1692,7 @@ window.__ModuleLoader__.load({
|
|
|
1206
1692
|
] }) : null,
|
|
1207
1693
|
status?.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1208
1694
|
style: status.reason === void 0 ? bodyStyle : errorStyle,
|
|
1209
|
-
children: status.reason ?? t(
|
|
1695
|
+
children: status.reason ?? t(currentVariant.signedOutKey)
|
|
1210
1696
|
}), authKey === void 0 ? null : patEntry()] }) : null,
|
|
1211
1697
|
status?.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1212
1698
|
style: errorStyle,
|
|
@@ -1278,7 +1764,7 @@ window.__ModuleLoader__.load({
|
|
|
1278
1764
|
* it names the feature, so it should read as an annotation attached to the icon
|
|
1279
1765
|
* rather than compete with the adjacent model selector.
|
|
1280
1766
|
*/
|
|
1281
|
-
const labelStyle
|
|
1767
|
+
const labelStyle = {
|
|
1282
1768
|
fontSize: 11,
|
|
1283
1769
|
lineHeight: "16px",
|
|
1284
1770
|
color: "var(--dsw-alias-label-tertiary)"
|
|
@@ -1287,919 +1773,365 @@ window.__ModuleLoader__.load({
|
|
|
1287
1773
|
const tooltipStyle = {
|
|
1288
1774
|
position: "absolute",
|
|
1289
1775
|
left: "50%",
|
|
1290
|
-
bottom: "calc(100% + 8px)",
|
|
1291
|
-
zIndex: 1e3,
|
|
1292
|
-
transform: "translateX(-50%)",
|
|
1293
|
-
padding: "4px 8px",
|
|
1294
|
-
borderRadius: 6,
|
|
1295
|
-
background: "var(--dsw-specific-tip, #1f2329)",
|
|
1296
|
-
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1297
|
-
color: "var(--dsw-alias-label-primary, #fff)",
|
|
1298
|
-
fontSize: 12,
|
|
1299
|
-
lineHeight: "18px",
|
|
1300
|
-
whiteSpace: "nowrap",
|
|
1301
|
-
pointerEvents: "none"
|
|
1302
|
-
};
|
|
1303
|
-
/** Confirmation bubble: same anchor, but interactive and allowed to wrap. */
|
|
1304
|
-
const confirmStyle = {
|
|
1305
|
-
position: "absolute",
|
|
1306
|
-
right: 0,
|
|
1307
|
-
bottom: "calc(100% + 8px)",
|
|
1308
|
-
zIndex: 1001,
|
|
1309
|
-
display: "flex",
|
|
1310
|
-
flexDirection: "column",
|
|
1311
|
-
gap: 8,
|
|
1312
|
-
width: 260,
|
|
1313
|
-
padding: "10px 12px",
|
|
1314
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1315
|
-
borderRadius: 8,
|
|
1316
|
-
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
1317
|
-
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1318
|
-
color: "var(--dsw-alias-label-primary)",
|
|
1319
|
-
fontSize: 12,
|
|
1320
|
-
lineHeight: "18px"
|
|
1321
|
-
};
|
|
1322
|
-
const confirmRowStyle = {
|
|
1323
|
-
display: "flex",
|
|
1324
|
-
justifyContent: "flex-end",
|
|
1325
|
-
gap: 8
|
|
1326
|
-
};
|
|
1327
|
-
const confirmButtonStyle = {
|
|
1328
|
-
padding: "3px 10px",
|
|
1329
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1330
|
-
borderRadius: 6,
|
|
1331
|
-
background: "transparent",
|
|
1332
|
-
color: "inherit",
|
|
1333
|
-
font: "inherit",
|
|
1334
|
-
fontSize: 12,
|
|
1335
|
-
cursor: "pointer"
|
|
1336
|
-
};
|
|
1337
|
-
/**
|
|
1338
|
-
* Primary action inside the confirmation bubble.
|
|
1339
|
-
*
|
|
1340
|
-
* The fill and its text colour must come as a pair: `brand-primary` resolves to
|
|
1341
|
-
* a light accent in this theme, so hardcoding `color: #fff` on top of it renders
|
|
1342
|
-
* white-on-white. `button-primary-fill` + `label-primary-foreground` is the
|
|
1343
|
-
* theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
|
|
1344
|
-
* the same job.
|
|
1345
|
-
*/
|
|
1346
|
-
const primaryButtonStyle = {
|
|
1347
|
-
...confirmButtonStyle,
|
|
1348
|
-
border: "1px solid var(--dsw-alias-button-primary-fill)",
|
|
1349
|
-
background: "var(--dsw-alias-button-primary-fill)",
|
|
1350
|
-
color: "var(--dsw-alias-label-primary-foreground)"
|
|
1351
|
-
};
|
|
1352
|
-
/**
|
|
1353
|
-
* Result note: a single line + a dismiss button, anchored to the control's
|
|
1354
|
-
* right side. Smaller than the confirmation bubble because it carries an
|
|
1355
|
-
* *outcome*, not a *decision* — the work is done, the user only has to read
|
|
1356
|
-
* and dismiss.
|
|
1357
|
-
*/
|
|
1358
|
-
const noteStyle = {
|
|
1359
|
-
position: "absolute",
|
|
1360
|
-
right: 0,
|
|
1361
|
-
bottom: "calc(100% + 8px)",
|
|
1362
|
-
zIndex: 1001,
|
|
1363
|
-
display: "flex",
|
|
1364
|
-
alignItems: "center",
|
|
1365
|
-
gap: 12,
|
|
1366
|
-
padding: "6px 10px",
|
|
1367
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1368
|
-
borderRadius: 8,
|
|
1369
|
-
background: "var(--dsw-alias-bg-layer-1)",
|
|
1370
|
-
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1371
|
-
color: "var(--dsw-alias-label-primary)",
|
|
1372
|
-
fontSize: 12,
|
|
1373
|
-
lineHeight: "18px",
|
|
1374
|
-
whiteSpace: "nowrap"
|
|
1375
|
-
};
|
|
1376
|
-
/**
|
|
1377
|
-
* The note's dismiss action. Outlined rather than bare text: inside an already
|
|
1378
|
-
* bordered bubble, an unbordered word does not read as something you can click.
|
|
1379
|
-
* Matches the outlined pill convention the plugin's other secondary actions use.
|
|
1380
|
-
*/
|
|
1381
|
-
const noteDismissStyle = {
|
|
1382
|
-
padding: "2px 8px",
|
|
1383
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1384
|
-
borderRadius: 6,
|
|
1385
|
-
background: "transparent",
|
|
1386
|
-
color: "var(--dsw-alias-label-secondary)",
|
|
1387
|
-
font: "inherit",
|
|
1388
|
-
fontSize: 12,
|
|
1389
|
-
lineHeight: "18px",
|
|
1390
|
-
cursor: "pointer"
|
|
1391
|
-
};
|
|
1392
|
-
/**
|
|
1393
|
-
* The feature's static inline label. Deliberately not a state readout — see the
|
|
1394
|
-
* module comment.
|
|
1395
|
-
*/
|
|
1396
|
-
function useLabel(t) {
|
|
1397
|
-
return t("probeLabel");
|
|
1398
|
-
}
|
|
1399
|
-
/** Pick the model's recorded observation out of the probe section. */
|
|
1400
|
-
function resultFor(status, model) {
|
|
1401
|
-
if (status.status !== "signed-in") return void 0;
|
|
1402
|
-
return status.probe?.results.find((result) => result.id === model);
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* The one-line tooltip: current state first, then what a click does — the same
|
|
1406
|
-
* two-part shape Fast Mode uses.
|
|
1407
|
-
*
|
|
1408
|
-
* A recorded result outranks a remembered failure. `failed` only means "the last
|
|
1409
|
-
* run from this control did not complete"; the host can record a result for the
|
|
1410
|
-
* same model at any time (a detection started from the settings card, another
|
|
1411
|
-
* conversation, or a finished sweep), and the levels the user paid for are the
|
|
1412
|
-
* more useful answer than the stale failure. Failure copy is what remains when
|
|
1413
|
-
* there is no result to report.
|
|
1414
|
-
*/
|
|
1415
|
-
function tooltipText(t, model, state) {
|
|
1416
|
-
if (state.busy) return t("probeRunning", { model });
|
|
1417
|
-
const result = state.result;
|
|
1418
|
-
if (result !== void 0) {
|
|
1419
|
-
if (result.validation === "validating" && result.efforts.length > 0) return t("probeTooltipVerified", { levels: result.efforts.join(" / ") });
|
|
1420
|
-
if (result.validation === "non-validating") return t("probeTooltipNotValidating");
|
|
1421
|
-
return t("probeTooltipRetry");
|
|
1422
|
-
}
|
|
1423
|
-
if (state.failed) return t("probeTooltipRetry");
|
|
1424
|
-
return t("probeTooltipIdle", { model });
|
|
1425
|
-
}
|
|
1426
|
-
/** Model-independent shell: resolves the selection, then delegates per model. */
|
|
1427
|
-
function QoderProbeControl({ directory, t }) {
|
|
1428
|
-
const subscribe = (0, react.useCallback)((listener) => directory.subscribe(listener), [directory]);
|
|
1429
|
-
const snapshot = (0, react.useCallback)(() => directory.getSnapshot(), [directory]);
|
|
1430
|
-
const selection = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot).current;
|
|
1431
|
-
const card = selection == null ? void 0 : cardVariantFor(selection.provider);
|
|
1432
|
-
const key = card === void 0 || selection == null ? void 0 : `${card.id}:${selection.model}`;
|
|
1433
|
-
return card === void 0 || selection == null || key === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelProbe, {
|
|
1434
|
-
model: selection.model,
|
|
1435
|
-
card,
|
|
1436
|
-
label: useLabel(t),
|
|
1437
|
-
t
|
|
1438
|
-
}, key);
|
|
1439
|
-
}
|
|
1440
|
-
function ModelProbe({ model, card, label, t }) {
|
|
1441
|
-
const [status, setStatus] = (0, react.useState)();
|
|
1442
|
-
const [busy, setBusy] = (0, react.useState)(false);
|
|
1443
|
-
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
1444
|
-
const [tooltipVisible, setTooltipVisible] = (0, react.useState)(false);
|
|
1445
|
-
const [failed, setFailed] = (0, react.useState)(false);
|
|
1446
|
-
const [note, setNote] = (0, react.useState)();
|
|
1447
|
-
const inFlight = (0, react.useRef)(false);
|
|
1448
|
-
const mounted = (0, react.useRef)(false);
|
|
1449
|
-
const readSeq = (0, react.useRef)(0);
|
|
1450
|
-
const tooltipId = (0, react.useId)();
|
|
1451
|
-
const refresh = (0, react.useCallback)(async (signal) => {
|
|
1452
|
-
const seq = ++readSeq.current;
|
|
1453
|
-
const response = await fetch(card.statusPath, {
|
|
1454
|
-
credentials: "same-origin",
|
|
1455
|
-
headers: { accept: "application/json" },
|
|
1456
|
-
...signal === void 0 ? {} : { signal }
|
|
1457
|
-
});
|
|
1458
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1459
|
-
const value = await response.json().catch(() => void 0);
|
|
1460
|
-
if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
|
|
1461
|
-
if (mounted.current && !signal?.aborted && seq === readSeq.current) setStatus(value);
|
|
1462
|
-
}, [card.statusPath, t]);
|
|
1463
|
-
(0, react.useEffect)(() => {
|
|
1464
|
-
mounted.current = true;
|
|
1465
|
-
const controller = new AbortController();
|
|
1466
|
-
const load = () => {
|
|
1467
|
-
refresh(controller.signal).catch(() => {});
|
|
1468
|
-
};
|
|
1469
|
-
load();
|
|
1470
|
-
const timer = window.setInterval(load, RECONCILE_MS);
|
|
1471
|
-
window.addEventListener("focus", load);
|
|
1472
|
-
return () => {
|
|
1473
|
-
mounted.current = false;
|
|
1474
|
-
controller.abort();
|
|
1475
|
-
window.clearInterval(timer);
|
|
1476
|
-
window.removeEventListener("focus", load);
|
|
1477
|
-
};
|
|
1478
|
-
}, [refresh]);
|
|
1479
|
-
const probe = status?.status === "signed-in" ? status.probe : void 0;
|
|
1480
|
-
const key = status?.status === "signed-in" ? status.probeKey : void 0;
|
|
1481
|
-
const result = status === void 0 ? void 0 : resultFor(status, model);
|
|
1482
|
-
const visible = probe?.candidates.includes(model) === true || result !== void 0;
|
|
1483
|
-
(0, react.useEffect)(() => {
|
|
1484
|
-
if (result !== void 0) setFailed(false);
|
|
1485
|
-
}, [result]);
|
|
1486
|
-
(0, react.useEffect)(() => {
|
|
1487
|
-
setConfirming(false);
|
|
1488
|
-
setNote(void 0);
|
|
1489
|
-
}, [model]);
|
|
1490
|
-
const detect = async () => {
|
|
1491
|
-
if (key === void 0 || inFlight.current || probe?.running === true) return;
|
|
1492
|
-
inFlight.current = true;
|
|
1493
|
-
setNote(void 0);
|
|
1494
|
-
setConfirming(false);
|
|
1495
|
-
setBusy(true);
|
|
1496
|
-
setFailed(false);
|
|
1497
|
-
try {
|
|
1498
|
-
const response = await fetch(card.probePath, {
|
|
1499
|
-
method: "POST",
|
|
1500
|
-
credentials: "same-origin",
|
|
1501
|
-
headers: {
|
|
1502
|
-
"Content-Type": "application/json",
|
|
1503
|
-
"X-Qoder-Probe-Key": key
|
|
1504
|
-
},
|
|
1505
|
-
body: JSON.stringify({
|
|
1506
|
-
action: "probe",
|
|
1507
|
-
model
|
|
1508
|
-
})
|
|
1509
|
-
});
|
|
1510
|
-
const body = await response.json();
|
|
1511
|
-
if (!response.ok || body.state !== "ok" || body.validation !== "validating" && body.validation !== "non-validating" || !Array.isArray(body.efforts) || !body.efforts.every((effort) => typeof effort === "string")) throw new Error("probe failed");
|
|
1512
|
-
if (mounted.current) {
|
|
1513
|
-
const completed = {
|
|
1514
|
-
id: model,
|
|
1515
|
-
name: model,
|
|
1516
|
-
validation: body.validation,
|
|
1517
|
-
efforts: body.efforts,
|
|
1518
|
-
probedAt: Date.now()
|
|
1519
|
-
};
|
|
1520
|
-
setNote(completed);
|
|
1521
|
-
}
|
|
1522
|
-
refresh().catch(() => {});
|
|
1523
|
-
} catch {
|
|
1524
|
-
if (mounted.current) setFailed(true);
|
|
1525
|
-
} finally {
|
|
1526
|
-
inFlight.current = false;
|
|
1527
|
-
if (mounted.current) setBusy(false);
|
|
1528
|
-
}
|
|
1529
|
-
};
|
|
1530
|
-
if (!visible) return null;
|
|
1531
|
-
const text = tooltipText(t, model, {
|
|
1532
|
-
busy,
|
|
1533
|
-
result,
|
|
1534
|
-
failed
|
|
1535
|
-
});
|
|
1536
|
-
const disabled = busy || probe?.running === true || key === void 0;
|
|
1537
|
-
const showTooltip = tooltipVisible && !confirming && note === void 0;
|
|
1538
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1539
|
-
style: wrapperStyle,
|
|
1540
|
-
onMouseEnter: () => {
|
|
1541
|
-
setTooltipVisible(true);
|
|
1542
|
-
},
|
|
1543
|
-
onMouseLeave: () => {
|
|
1544
|
-
setTooltipVisible(false);
|
|
1545
|
-
},
|
|
1546
|
-
children: [
|
|
1547
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1548
|
-
type: "button",
|
|
1549
|
-
"aria-label": text,
|
|
1550
|
-
"aria-describedby": showTooltip ? tooltipId : void 0,
|
|
1551
|
-
"aria-busy": busy,
|
|
1552
|
-
"aria-expanded": confirming,
|
|
1553
|
-
disabled,
|
|
1554
|
-
onClick: () => {
|
|
1555
|
-
setConfirming(true);
|
|
1556
|
-
},
|
|
1557
|
-
onFocus: () => {
|
|
1558
|
-
setTooltipVisible(true);
|
|
1559
|
-
},
|
|
1560
|
-
onBlur: () => {
|
|
1561
|
-
setTooltipVisible(false);
|
|
1562
|
-
},
|
|
1563
|
-
style: {
|
|
1564
|
-
...buttonStyle,
|
|
1565
|
-
opacity: disabled && !confirming ? .6 : 1,
|
|
1566
|
-
cursor: disabled ? "default" : "pointer"
|
|
1567
|
-
},
|
|
1568
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1569
|
-
width: "16",
|
|
1570
|
-
height: "16",
|
|
1571
|
-
viewBox: "0 0 24 24",
|
|
1572
|
-
fill: "none",
|
|
1573
|
-
stroke: "currentColor",
|
|
1574
|
-
strokeWidth: "1.6",
|
|
1575
|
-
"aria-hidden": "true",
|
|
1576
|
-
focusable: "false",
|
|
1577
|
-
children: [
|
|
1578
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1579
|
-
cx: "12",
|
|
1580
|
-
cy: "12",
|
|
1581
|
-
r: "9"
|
|
1582
|
-
}),
|
|
1583
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1584
|
-
cx: "12",
|
|
1585
|
-
cy: "12",
|
|
1586
|
-
r: "4"
|
|
1587
|
-
}),
|
|
1588
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 12 20 4" }),
|
|
1589
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1590
|
-
cx: "12",
|
|
1591
|
-
cy: "12",
|
|
1592
|
-
r: "1"
|
|
1593
|
-
})
|
|
1594
|
-
]
|
|
1595
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1596
|
-
style: labelStyle$1,
|
|
1597
|
-
children: label
|
|
1598
|
-
})]
|
|
1599
|
-
}),
|
|
1600
|
-
showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1601
|
-
id: tooltipId,
|
|
1602
|
-
role: "tooltip",
|
|
1603
|
-
style: tooltipStyle,
|
|
1604
|
-
children: text
|
|
1605
|
-
}),
|
|
1606
|
-
confirming && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1607
|
-
style: confirmStyle,
|
|
1608
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("probeBubbleBody") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1609
|
-
style: confirmRowStyle,
|
|
1610
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1611
|
-
type: "button",
|
|
1612
|
-
style: confirmButtonStyle,
|
|
1613
|
-
onClick: () => {
|
|
1614
|
-
setConfirming(false);
|
|
1615
|
-
},
|
|
1616
|
-
children: t("cancel")
|
|
1617
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1618
|
-
type: "button",
|
|
1619
|
-
style: primaryButtonStyle,
|
|
1620
|
-
onClick: () => {
|
|
1621
|
-
detect();
|
|
1622
|
-
},
|
|
1623
|
-
children: t("probeConfirmAction")
|
|
1624
|
-
})]
|
|
1625
|
-
})]
|
|
1626
|
-
}),
|
|
1627
|
-
note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1628
|
-
role: "status",
|
|
1629
|
-
"aria-live": "polite",
|
|
1630
|
-
style: noteStyle,
|
|
1631
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: noteText(t, note) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1632
|
-
type: "button",
|
|
1633
|
-
style: noteDismissStyle,
|
|
1634
|
-
onClick: () => {
|
|
1635
|
-
setNote(void 0);
|
|
1636
|
-
},
|
|
1637
|
-
children: t("probeNoteDismiss")
|
|
1638
|
-
})]
|
|
1639
|
-
})
|
|
1640
|
-
]
|
|
1641
|
-
});
|
|
1642
|
-
}
|
|
1643
|
-
/** Compose the one-line outcome string the note bubble shows. */
|
|
1644
|
-
function noteText(t, result) {
|
|
1645
|
-
if (result.validation === "validating" && result.efforts.length > 0) return t("probeNoteVerified", { levels: result.efforts.join(" / ") });
|
|
1646
|
-
if (result.validation === "non-validating") return t("probeNoteNotValidating");
|
|
1647
|
-
return t("probeNoteUnknown");
|
|
1648
|
-
}
|
|
1649
|
-
//#endregion
|
|
1650
|
-
//#region src/client/quota-settings-store.ts
|
|
1651
|
-
let pollIntervalMs = 3e5;
|
|
1652
|
-
const toggles = {
|
|
1653
|
-
cn: false,
|
|
1654
|
-
global: false
|
|
1776
|
+
bottom: "calc(100% + 8px)",
|
|
1777
|
+
zIndex: 1e3,
|
|
1778
|
+
transform: "translateX(-50%)",
|
|
1779
|
+
padding: "4px 8px",
|
|
1780
|
+
borderRadius: 6,
|
|
1781
|
+
background: "var(--dsw-specific-tip, #1f2329)",
|
|
1782
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1783
|
+
color: "var(--dsw-alias-label-primary, #fff)",
|
|
1784
|
+
fontSize: 12,
|
|
1785
|
+
lineHeight: "18px",
|
|
1786
|
+
whiteSpace: "nowrap",
|
|
1787
|
+
pointerEvents: "none"
|
|
1655
1788
|
};
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1789
|
+
/** Confirmation bubble: same anchor, but interactive and allowed to wrap. */
|
|
1790
|
+
const confirmStyle = {
|
|
1791
|
+
position: "absolute",
|
|
1792
|
+
right: 0,
|
|
1793
|
+
bottom: "calc(100% + 8px)",
|
|
1794
|
+
zIndex: 1001,
|
|
1795
|
+
display: "flex",
|
|
1796
|
+
flexDirection: "column",
|
|
1797
|
+
gap: 8,
|
|
1798
|
+
width: 260,
|
|
1799
|
+
padding: "10px 12px",
|
|
1800
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1801
|
+
borderRadius: 8,
|
|
1802
|
+
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
1803
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1804
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1805
|
+
fontSize: 12,
|
|
1806
|
+
lineHeight: "18px"
|
|
1807
|
+
};
|
|
1808
|
+
const confirmRowStyle = {
|
|
1809
|
+
display: "flex",
|
|
1810
|
+
justifyContent: "flex-end",
|
|
1811
|
+
gap: 8
|
|
1812
|
+
};
|
|
1813
|
+
const confirmButtonStyle = {
|
|
1814
|
+
padding: "3px 10px",
|
|
1815
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1816
|
+
borderRadius: 6,
|
|
1817
|
+
background: "transparent",
|
|
1818
|
+
color: "inherit",
|
|
1819
|
+
font: "inherit",
|
|
1820
|
+
fontSize: 12,
|
|
1821
|
+
cursor: "pointer"
|
|
1659
1822
|
};
|
|
1660
|
-
let revision = 0;
|
|
1661
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1662
|
-
function bump() {
|
|
1663
|
-
revision += 1;
|
|
1664
|
-
for (const listener of listeners) listener();
|
|
1665
|
-
}
|
|
1666
|
-
/** Update the shared poll interval (from the settings document). */
|
|
1667
|
-
function setQuotaPollMs(ms) {
|
|
1668
|
-
if (Number.isFinite(ms) && ms >= 6e4 && pollIntervalMs !== ms) {
|
|
1669
|
-
pollIntervalMs = ms;
|
|
1670
|
-
bump();
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
/** Read the configured poll interval. */
|
|
1674
|
-
function quotaPollMs() {
|
|
1675
|
-
return pollIntervalMs;
|
|
1676
|
-
}
|
|
1677
|
-
/** Update both sidebar toggles (from the settings document). */
|
|
1678
|
-
function setQuotaToggles(cn, global) {
|
|
1679
|
-
if (toggles.cn !== cn || toggles.global !== global) {
|
|
1680
|
-
toggles.cn = cn;
|
|
1681
|
-
toggles.global = global;
|
|
1682
|
-
bump();
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
/** Read the current toggles. */
|
|
1686
|
-
function quotaToggles() {
|
|
1687
|
-
return { ...toggles };
|
|
1688
|
-
}
|
|
1689
|
-
/** Record a variant's sign-in state from any successful status poll. */
|
|
1690
|
-
function noteQuotaSignIn(variantId, signedIn) {
|
|
1691
|
-
if (variantId === "qoder" && signIn.cn !== signedIn) {
|
|
1692
|
-
signIn.cn = signedIn;
|
|
1693
|
-
bump();
|
|
1694
|
-
} else if (variantId === "qoder-global" && signIn.global !== signedIn) {
|
|
1695
|
-
signIn.global = signedIn;
|
|
1696
|
-
bump();
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
/** Read the cached sign-in state. */
|
|
1700
|
-
function quotaSignInState() {
|
|
1701
|
-
return { ...signIn };
|
|
1702
|
-
}
|
|
1703
|
-
/** Subscribe to any flag change; returns the disposer. */
|
|
1704
|
-
function onQuotaSettingsChange(listener) {
|
|
1705
|
-
listeners.add(listener);
|
|
1706
|
-
return () => {
|
|
1707
|
-
listeners.delete(listener);
|
|
1708
|
-
};
|
|
1709
|
-
}
|
|
1710
|
-
/** The current revision — the useSyncExternalStore snapshot value. */
|
|
1711
|
-
function quotaSettingsRevision() {
|
|
1712
|
-
return revision;
|
|
1713
|
-
}
|
|
1714
|
-
/** Which variant a status route belongs to, from the route path. */
|
|
1715
|
-
function variantOfStatusPath(statusPath) {
|
|
1716
|
-
return statusPath.includes("/global/") ? "qoder-global" : "qoder";
|
|
1717
|
-
}
|
|
1718
1823
|
/**
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
* The sidebar cards and the dashboard each used to fetch independently, so a
|
|
1722
|
-
* dashboard refresh updated the panel while the sidebar card kept showing its
|
|
1723
|
-
* previous read until its own next tick — two different numbers for one
|
|
1724
|
-
* account on one screen. One store, one write path
|
|
1725
|
-
* ({@link noteQuotaStatus}), and every subscriber re-renders through the
|
|
1726
|
-
* same revision: whatever surface refreshed last, all of them show it.
|
|
1824
|
+
* Primary action inside the confirmation bubble.
|
|
1727
1825
|
*
|
|
1728
|
-
*
|
|
1729
|
-
* in
|
|
1826
|
+
* The fill and its text colour must come as a pair: `brand-primary` resolves to
|
|
1827
|
+
* a light accent in this theme, so hardcoding `color: #fff` on top of it renders
|
|
1828
|
+
* white-on-white. `button-primary-fill` + `label-primary-foreground` is the
|
|
1829
|
+
* theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
|
|
1830
|
+
* the same job.
|
|
1730
1831
|
*/
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
|
|
1832
|
+
const primaryButtonStyle = {
|
|
1833
|
+
...confirmButtonStyle,
|
|
1834
|
+
border: "1px solid var(--dsw-alias-button-primary-fill)",
|
|
1835
|
+
background: "var(--dsw-alias-button-primary-fill)",
|
|
1836
|
+
color: "var(--dsw-alias-label-primary-foreground)"
|
|
1734
1837
|
};
|
|
1735
1838
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1839
|
+
* Result note: a single line + a dismiss button, anchored to the control's
|
|
1840
|
+
* right side. Smaller than the confirmation bubble because it carries an
|
|
1841
|
+
* *outcome*, not a *decision* — the work is done, the user only has to read
|
|
1842
|
+
* and dismiss.
|
|
1739
1843
|
*/
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
const statusFetchedAt = {
|
|
1758
|
-
cn: void 0,
|
|
1759
|
-
global: void 0
|
|
1844
|
+
const noteStyle = {
|
|
1845
|
+
position: "absolute",
|
|
1846
|
+
right: 0,
|
|
1847
|
+
bottom: "calc(100% + 8px)",
|
|
1848
|
+
zIndex: 1001,
|
|
1849
|
+
display: "flex",
|
|
1850
|
+
alignItems: "center",
|
|
1851
|
+
gap: 12,
|
|
1852
|
+
padding: "6px 10px",
|
|
1853
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1854
|
+
borderRadius: 8,
|
|
1855
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1856
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
1857
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1858
|
+
fontSize: 12,
|
|
1859
|
+
lineHeight: "18px",
|
|
1860
|
+
whiteSpace: "nowrap"
|
|
1760
1861
|
};
|
|
1761
|
-
/** Read the time a variant's current document was fetched, if any. */
|
|
1762
|
-
function quotaStatusFetchedAt(variantId) {
|
|
1763
|
-
return variantId === "qoder" ? statusFetchedAt.cn : statusFetchedAt.global;
|
|
1764
|
-
}
|
|
1765
1862
|
/**
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1769
|
-
* a variant with NO result yet (or a failed read that never landed one)
|
|
1770
|
-
* forces the upstream call. The interval is a cache lifetime, not a metronome.
|
|
1771
|
-
*
|
|
1772
|
-
* A `maxAgeMs` of the poll interval comes from the settings document; a
|
|
1773
|
-
* failed last read is NOT tracked here (callers gate failures themselves),
|
|
1774
|
-
* because "the last read failed" still means "no usable result".
|
|
1775
|
-
*
|
|
1776
|
-
* @param variantId - which variant's freshness to test.
|
|
1777
|
-
* @param maxAgeMs - the configured poll interval (cache lifetime).
|
|
1863
|
+
* The note's dismiss action. Outlined rather than bare text: inside an already
|
|
1864
|
+
* bordered bubble, an unbordered word does not read as something you can click.
|
|
1865
|
+
* Matches the outlined pill convention the plugin's other secondary actions use.
|
|
1778
1866
|
*/
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1867
|
+
const noteDismissStyle = {
|
|
1868
|
+
padding: "2px 8px",
|
|
1869
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1870
|
+
borderRadius: 6,
|
|
1871
|
+
background: "transparent",
|
|
1872
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1873
|
+
font: "inherit",
|
|
1874
|
+
fontSize: 12,
|
|
1875
|
+
lineHeight: "18px",
|
|
1876
|
+
cursor: "pointer"
|
|
1877
|
+
};
|
|
1787
1878
|
/**
|
|
1788
|
-
* The
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1791
|
-
* Like the built-in plugin cards, it registers into `settings.plugin.item`
|
|
1792
|
-
* keyed by its own settings namespace (`qoder-quota`), binds that
|
|
1793
|
-
* namespace through the client settings scope, and writes through the scope's
|
|
1794
|
-
* revision-fenced `set` — the same durable-write path every preference row
|
|
1795
|
-
* uses. A toggle commits on click: each click is one explicit user choice,
|
|
1796
|
-
* and the scope's ordering makes the last one win, so no staged-draft form is
|
|
1797
|
-
* needed for two booleans and a number.
|
|
1798
|
-
*
|
|
1799
|
-
* The two toggles gate the CN and international sidebar cards respectively;
|
|
1800
|
-
* the interval is one shared poll period. Toggles are disabled while their
|
|
1801
|
-
* variant is signed out: a quota card for an account nobody is signed into
|
|
1802
|
-
* would render an error forever, so the setting waits for a session.
|
|
1879
|
+
* The feature's static inline label. Deliberately not a state readout — see the
|
|
1880
|
+
* module comment.
|
|
1803
1881
|
*/
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
status: "unavailable",
|
|
1812
|
-
writable: false,
|
|
1813
|
-
values: {
|
|
1814
|
-
sidebarQuotaCN: false,
|
|
1815
|
-
sidebarQuotaGlobal: false,
|
|
1816
|
-
quotaPollMs: POLL_DEFAULT_MS
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1819
|
-
const snapshot = scope.getSnapshot();
|
|
1820
|
-
const value = snapshot.value ?? {};
|
|
1821
|
-
return {
|
|
1822
|
-
status: snapshot.status,
|
|
1823
|
-
writable: snapshot.writable,
|
|
1824
|
-
values: {
|
|
1825
|
-
sidebarQuotaCN: value.sidebarQuotaCN === true,
|
|
1826
|
-
sidebarQuotaGlobal: value.sidebarQuotaGlobal === true,
|
|
1827
|
-
quotaPollMs: typeof value.quotaPollMs === "number" ? value.quotaPollMs : POLL_DEFAULT_MS
|
|
1828
|
-
}
|
|
1829
|
-
};
|
|
1882
|
+
function useLabel(t) {
|
|
1883
|
+
return t("probeLabel");
|
|
1884
|
+
}
|
|
1885
|
+
/** Pick the model's recorded observation out of the probe section. */
|
|
1886
|
+
function resultFor(status, model) {
|
|
1887
|
+
if (status.status !== "signed-in") return void 0;
|
|
1888
|
+
return status.probe?.results.find((result) => result.id === model);
|
|
1830
1889
|
}
|
|
1831
1890
|
/**
|
|
1832
|
-
*
|
|
1891
|
+
* The one-line tooltip: current state first, then what a click does — the same
|
|
1892
|
+
* two-part shape Fast Mode uses.
|
|
1833
1893
|
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1838
|
-
*
|
|
1839
|
-
*
|
|
1840
|
-
* which is the only thing the projection actually derives from.
|
|
1894
|
+
* A recorded result outranks a remembered failure. `failed` only means "the last
|
|
1895
|
+
* run from this control did not complete"; the host can record a result for the
|
|
1896
|
+
* same model at any time (a detection started from the settings card, another
|
|
1897
|
+
* conversation, or a finished sweep), and the levels the user paid for are the
|
|
1898
|
+
* more useful answer than the stale failure. Failure copy is what remains when
|
|
1899
|
+
* there is no result to report.
|
|
1841
1900
|
*/
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
sidebarQuotaCN: false,
|
|
1850
|
-
sidebarQuotaGlobal: false,
|
|
1851
|
-
quotaPollMs: POLL_DEFAULT_MS
|
|
1852
|
-
}
|
|
1853
|
-
};
|
|
1854
|
-
function stableProject(scope) {
|
|
1855
|
-
if (scope === void 0) return UNAVAILABLE;
|
|
1856
|
-
const source = scope.getSnapshot();
|
|
1857
|
-
if (scope !== cachedScope || source !== cachedSource || cachedProjection === void 0) {
|
|
1858
|
-
cachedScope = scope;
|
|
1859
|
-
cachedSource = source;
|
|
1860
|
-
cachedProjection = project(scope);
|
|
1901
|
+
function tooltipText(t, model, state) {
|
|
1902
|
+
if (state.busy) return t("probeRunning", { model });
|
|
1903
|
+
const result = state.result;
|
|
1904
|
+
if (result !== void 0) {
|
|
1905
|
+
if (result.validation === "validating" && result.efforts.length > 0) return t("probeTooltipVerified", { levels: result.efforts.join(" / ") });
|
|
1906
|
+
if (result.validation === "non-validating") return t("probeTooltipNotValidating");
|
|
1907
|
+
return t("probeTooltipRetry");
|
|
1861
1908
|
}
|
|
1862
|
-
return
|
|
1909
|
+
if (state.failed) return t("probeTooltipRetry");
|
|
1910
|
+
return t("probeTooltipIdle", { model });
|
|
1863
1911
|
}
|
|
1864
|
-
/**
|
|
1865
|
-
function
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1878
|
-
type: "button",
|
|
1879
|
-
role: "switch",
|
|
1880
|
-
"aria-checked": checked,
|
|
1881
|
-
disabled,
|
|
1882
|
-
"aria-label": label,
|
|
1883
|
-
onClick: () => onToggle(!checked),
|
|
1884
|
-
style: {
|
|
1885
|
-
...switchStyle,
|
|
1886
|
-
background: checked ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.2))",
|
|
1887
|
-
justifyContent: checked ? "flex-end" : "flex-start",
|
|
1888
|
-
opacity: disabled === true ? .45 : 1,
|
|
1889
|
-
cursor: disabled === true ? "not-allowed" : "pointer"
|
|
1890
|
-
},
|
|
1891
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: knobStyle })
|
|
1892
|
-
})]
|
|
1893
|
-
});
|
|
1912
|
+
/** Model-independent shell: resolves the selection, then delegates per model. */
|
|
1913
|
+
function QoderProbeControl({ directory, t }) {
|
|
1914
|
+
const subscribe = (0, react.useCallback)((listener) => directory.subscribe(listener), [directory]);
|
|
1915
|
+
const snapshot = (0, react.useCallback)(() => directory.getSnapshot(), [directory]);
|
|
1916
|
+
const selection = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot).current;
|
|
1917
|
+
const card = selection == null ? void 0 : cardVariantFor(selection.provider);
|
|
1918
|
+
const key = card === void 0 || selection == null ? void 0 : `${card.id}:${selection.model}`;
|
|
1919
|
+
return card === void 0 || selection == null || key === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelProbe, {
|
|
1920
|
+
model: selection.model,
|
|
1921
|
+
card,
|
|
1922
|
+
label: useLabel(t),
|
|
1923
|
+
t
|
|
1924
|
+
}, key);
|
|
1894
1925
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
const
|
|
1898
|
-
const
|
|
1899
|
-
const [
|
|
1900
|
-
const [
|
|
1901
|
-
const [
|
|
1902
|
-
const
|
|
1926
|
+
function ModelProbe({ model, card, label, t }) {
|
|
1927
|
+
const [status, setStatus] = (0, react.useState)();
|
|
1928
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1929
|
+
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
1930
|
+
const [tooltipVisible, setTooltipVisible] = (0, react.useState)(false);
|
|
1931
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
1932
|
+
const [note, setNote] = (0, react.useState)();
|
|
1933
|
+
const inFlight = (0, react.useRef)(false);
|
|
1934
|
+
const mounted = (0, react.useRef)(false);
|
|
1935
|
+
const readSeq = (0, react.useRef)(0);
|
|
1936
|
+
const tooltipId = (0, react.useId)();
|
|
1937
|
+
const refresh = (0, react.useCallback)(async (signal) => {
|
|
1938
|
+
const seq = ++readSeq.current;
|
|
1939
|
+
const response = await fetch(card.statusPath, {
|
|
1940
|
+
credentials: "same-origin",
|
|
1941
|
+
headers: { accept: "application/json" },
|
|
1942
|
+
...signal === void 0 ? {} : { signal }
|
|
1943
|
+
});
|
|
1944
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1945
|
+
const value = await response.json().catch(() => void 0);
|
|
1946
|
+
if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
|
|
1947
|
+
if (mounted.current && !signal?.aborted && seq === readSeq.current) setStatus(value);
|
|
1948
|
+
}, [card.statusPath, t]);
|
|
1903
1949
|
(0, react.useEffect)(() => {
|
|
1904
|
-
|
|
1905
|
-
const
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (disposed || !isQoderWebStatus(body)) return void 0;
|
|
1909
|
-
noteQuotaSignIn(variantOfStatusPath(path), body.status === "signed-in");
|
|
1910
|
-
return body.status === "signed-in";
|
|
1911
|
-
} catch {
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1950
|
+
mounted.current = true;
|
|
1951
|
+
const controller = new AbortController();
|
|
1952
|
+
const load = () => {
|
|
1953
|
+
refresh(controller.signal).catch(() => {});
|
|
1914
1954
|
};
|
|
1915
|
-
(
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
cn: cn === true,
|
|
1919
|
-
global: global === true
|
|
1920
|
-
});
|
|
1921
|
-
})();
|
|
1955
|
+
load();
|
|
1956
|
+
const timer = window.setInterval(load, RECONCILE_MS);
|
|
1957
|
+
window.addEventListener("focus", load);
|
|
1922
1958
|
return () => {
|
|
1923
|
-
|
|
1959
|
+
mounted.current = false;
|
|
1960
|
+
controller.abort();
|
|
1961
|
+
window.clearInterval(timer);
|
|
1962
|
+
window.removeEventListener("focus", load);
|
|
1924
1963
|
};
|
|
1925
|
-
}, [
|
|
1926
|
-
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1964
|
+
}, [refresh]);
|
|
1965
|
+
const probe = status?.status === "signed-in" ? status.probe : void 0;
|
|
1966
|
+
const key = status?.status === "signed-in" ? status.probeKey : void 0;
|
|
1967
|
+
const result = status === void 0 ? void 0 : resultFor(status, model);
|
|
1968
|
+
const visible = probe?.candidates.includes(model) === true || result !== void 0;
|
|
1969
|
+
(0, react.useEffect)(() => {
|
|
1970
|
+
if (result !== void 0) setFailed(false);
|
|
1971
|
+
}, [result]);
|
|
1972
|
+
(0, react.useEffect)(() => {
|
|
1973
|
+
setConfirming(false);
|
|
1974
|
+
setNote(void 0);
|
|
1975
|
+
}, [model]);
|
|
1976
|
+
const detect = async () => {
|
|
1977
|
+
if (key === void 0 || inFlight.current || probe?.running === true) return;
|
|
1978
|
+
inFlight.current = true;
|
|
1979
|
+
setNote(void 0);
|
|
1980
|
+
setConfirming(false);
|
|
1981
|
+
setBusy(true);
|
|
1982
|
+
setFailed(false);
|
|
1983
|
+
try {
|
|
1984
|
+
const response = await fetch(card.probePath, {
|
|
1985
|
+
method: "POST",
|
|
1986
|
+
credentials: "same-origin",
|
|
1987
|
+
headers: {
|
|
1988
|
+
"Content-Type": "application/json",
|
|
1989
|
+
"X-Qoder-Probe-Key": key
|
|
1990
|
+
},
|
|
1991
|
+
body: JSON.stringify({
|
|
1992
|
+
action: "probe",
|
|
1993
|
+
model
|
|
1994
|
+
})
|
|
1995
|
+
});
|
|
1996
|
+
const body = await response.json();
|
|
1997
|
+
if (!response.ok || body.state !== "ok" || body.validation !== "validating" && body.validation !== "non-validating" || !Array.isArray(body.efforts) || !body.efforts.every((effort) => typeof effort === "string")) throw new Error("probe failed");
|
|
1998
|
+
if (mounted.current) {
|
|
1999
|
+
const completed = {
|
|
2000
|
+
id: model,
|
|
2001
|
+
name: model,
|
|
2002
|
+
validation: body.validation,
|
|
2003
|
+
efforts: body.efforts,
|
|
2004
|
+
probedAt: Date.now()
|
|
2005
|
+
};
|
|
2006
|
+
setNote(completed);
|
|
2007
|
+
}
|
|
2008
|
+
refresh().catch(() => {});
|
|
2009
|
+
} catch {
|
|
2010
|
+
if (mounted.current) setFailed(true);
|
|
2011
|
+
} finally {
|
|
2012
|
+
inFlight.current = false;
|
|
2013
|
+
if (mounted.current) setBusy(false);
|
|
2014
|
+
}
|
|
1937
2015
|
};
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2016
|
+
if (!visible) return null;
|
|
2017
|
+
const text = tooltipText(t, model, {
|
|
2018
|
+
busy,
|
|
2019
|
+
result,
|
|
2020
|
+
failed
|
|
2021
|
+
});
|
|
2022
|
+
const disabled = busy || probe?.running === true || key === void 0;
|
|
2023
|
+
const showTooltip = tooltipVisible && !confirming && note === void 0;
|
|
2024
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2025
|
+
style: wrapperStyle,
|
|
1945
2026
|
onMouseEnter: () => {
|
|
1946
|
-
|
|
2027
|
+
setTooltipVisible(true);
|
|
1947
2028
|
},
|
|
1948
2029
|
onMouseLeave: () => {
|
|
1949
|
-
|
|
2030
|
+
setTooltipVisible(false);
|
|
1950
2031
|
},
|
|
1951
|
-
children: [
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
style: {
|
|
1969
|
-
...headerButtonStyle,
|
|
1970
|
-
...headerFocused ? headerFocusStyle : {}
|
|
1971
|
-
},
|
|
1972
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1973
|
-
style: headTextStyle,
|
|
1974
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1975
|
-
style: titleStyle,
|
|
1976
|
-
children: t("quotaSettingsTitle")
|
|
1977
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1978
|
-
style: introStyle,
|
|
1979
|
-
children: t("quotaSettingsIntro")
|
|
1980
|
-
})]
|
|
1981
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2032
|
+
children: [
|
|
2033
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2034
|
+
type: "button",
|
|
2035
|
+
"aria-label": text,
|
|
2036
|
+
"aria-describedby": showTooltip ? tooltipId : void 0,
|
|
2037
|
+
"aria-busy": busy,
|
|
2038
|
+
"aria-expanded": confirming,
|
|
2039
|
+
disabled,
|
|
2040
|
+
onClick: () => {
|
|
2041
|
+
setConfirming(true);
|
|
2042
|
+
},
|
|
2043
|
+
onFocus: () => {
|
|
2044
|
+
setTooltipVisible(true);
|
|
2045
|
+
},
|
|
2046
|
+
onBlur: () => {
|
|
2047
|
+
setTooltipVisible(false);
|
|
2048
|
+
},
|
|
1982
2049
|
style: {
|
|
1983
|
-
...
|
|
1984
|
-
|
|
2050
|
+
...buttonStyle,
|
|
2051
|
+
opacity: disabled && !confirming ? .6 : 1,
|
|
2052
|
+
cursor: disabled ? "default" : "pointer"
|
|
1985
2053
|
},
|
|
1986
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2054
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2055
|
+
width: "16",
|
|
2056
|
+
height: "16",
|
|
2057
|
+
viewBox: "0 0 24 24",
|
|
2058
|
+
fill: "none",
|
|
2059
|
+
stroke: "currentColor",
|
|
2060
|
+
strokeWidth: "1.6",
|
|
2061
|
+
"aria-hidden": "true",
|
|
2062
|
+
focusable: "false",
|
|
2063
|
+
children: [
|
|
2064
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2065
|
+
cx: "12",
|
|
2066
|
+
cy: "12",
|
|
2067
|
+
r: "9"
|
|
2068
|
+
}),
|
|
2069
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2070
|
+
cx: "12",
|
|
2071
|
+
cy: "12",
|
|
2072
|
+
r: "4"
|
|
2073
|
+
}),
|
|
2074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 12 20 4" }),
|
|
2075
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2076
|
+
cx: "12",
|
|
2077
|
+
cy: "12",
|
|
2078
|
+
r: "1"
|
|
2079
|
+
})
|
|
2080
|
+
]
|
|
2081
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2082
|
+
style: labelStyle,
|
|
2083
|
+
children: label
|
|
2084
|
+
})]
|
|
2085
|
+
}),
|
|
2086
|
+
showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2087
|
+
id: tooltipId,
|
|
2088
|
+
role: "tooltip",
|
|
2089
|
+
style: tooltipStyle,
|
|
2090
|
+
children: text
|
|
2091
|
+
}),
|
|
2092
|
+
confirming && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2093
|
+
style: confirmStyle,
|
|
2094
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("probeBubbleBody") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2095
|
+
style: confirmRowStyle,
|
|
2096
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2097
|
+
type: "button",
|
|
2098
|
+
style: confirmButtonStyle,
|
|
2099
|
+
onClick: () => {
|
|
2100
|
+
setConfirming(false);
|
|
2101
|
+
},
|
|
2102
|
+
children: t("cancel")
|
|
2103
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2104
|
+
type: "button",
|
|
2105
|
+
style: primaryButtonStyle,
|
|
2106
|
+
onClick: () => {
|
|
2107
|
+
detect();
|
|
2108
|
+
},
|
|
2109
|
+
children: t("probeConfirmAction")
|
|
2039
2110
|
})]
|
|
2040
|
-
})
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2111
|
+
})]
|
|
2112
|
+
}),
|
|
2113
|
+
note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2114
|
+
role: "status",
|
|
2115
|
+
"aria-live": "polite",
|
|
2116
|
+
style: noteStyle,
|
|
2117
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: noteText(t, note) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2118
|
+
type: "button",
|
|
2119
|
+
style: noteDismissStyle,
|
|
2120
|
+
onClick: () => {
|
|
2121
|
+
setNote(void 0);
|
|
2122
|
+
},
|
|
2123
|
+
children: t("probeNoteDismiss")
|
|
2124
|
+
})]
|
|
2125
|
+
})
|
|
2126
|
+
]
|
|
2047
2127
|
});
|
|
2048
2128
|
}
|
|
2049
|
-
/**
|
|
2050
|
-
function
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
viewBox: "0 0 14 14",
|
|
2055
|
-
fill: "none",
|
|
2056
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
2057
|
-
"aria-hidden": "true",
|
|
2058
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2059
|
-
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
2060
|
-
fill: "currentColor"
|
|
2061
|
-
})
|
|
2062
|
-
});
|
|
2129
|
+
/** Compose the one-line outcome string the note bubble shows. */
|
|
2130
|
+
function noteText(t, result) {
|
|
2131
|
+
if (result.validation === "validating" && result.efforts.length > 0) return t("probeNoteVerified", { levels: result.efforts.join(" / ") });
|
|
2132
|
+
if (result.validation === "non-validating") return t("probeNoteNotValidating");
|
|
2133
|
+
return t("probeNoteUnknown");
|
|
2063
2134
|
}
|
|
2064
|
-
const cardStyle = {
|
|
2065
|
-
listStyle: "none",
|
|
2066
|
-
borderWidth: "0.5px",
|
|
2067
|
-
borderStyle: "solid",
|
|
2068
|
-
borderColor: "var(--dsw-alias-border-l4)",
|
|
2069
|
-
borderRadius: 16,
|
|
2070
|
-
background: "var(--dsw-alias-bg-layer-3)",
|
|
2071
|
-
transition: "border-color .16s, background .16s"
|
|
2072
|
-
};
|
|
2073
|
-
/** Hover, matching the built-in card's `:hover` (inline styles cannot express pseudo-classes). */
|
|
2074
|
-
const cardHoverStyle = { borderColor: "var(--dsw-alias-label-dimmed)" };
|
|
2075
|
-
/** Expanded, matching the built-in card's open state. */
|
|
2076
|
-
const cardOpenStyle = {
|
|
2077
|
-
background: "var(--dsw-alias-bg-layer-2)",
|
|
2078
|
-
borderColor: "var(--dsw-alias-label-dimmed)"
|
|
2079
|
-
};
|
|
2080
|
-
const headerButtonStyle = {
|
|
2081
|
-
boxSizing: "border-box",
|
|
2082
|
-
width: "100%",
|
|
2083
|
-
display: "flex",
|
|
2084
|
-
alignItems: "center",
|
|
2085
|
-
gap: 12,
|
|
2086
|
-
border: 0,
|
|
2087
|
-
borderRadius: 12,
|
|
2088
|
-
padding: "14px 16px",
|
|
2089
|
-
background: "transparent",
|
|
2090
|
-
color: "inherit",
|
|
2091
|
-
font: "inherit",
|
|
2092
|
-
textAlign: "left",
|
|
2093
|
-
cursor: "pointer",
|
|
2094
|
-
appearance: "none"
|
|
2095
|
-
};
|
|
2096
|
-
const headerFocusStyle = {
|
|
2097
|
-
outline: "2px solid var(--dsw-alias-brand-primary)",
|
|
2098
|
-
outlineOffset: -2
|
|
2099
|
-
};
|
|
2100
|
-
const headTextStyle = {
|
|
2101
|
-
display: "flex",
|
|
2102
|
-
flex: 1,
|
|
2103
|
-
minWidth: 0,
|
|
2104
|
-
flexDirection: "column",
|
|
2105
|
-
gap: 4
|
|
2106
|
-
};
|
|
2107
|
-
const titleStyle = {
|
|
2108
|
-
fontSize: 15,
|
|
2109
|
-
lineHeight: 1.4,
|
|
2110
|
-
fontWeight: 600,
|
|
2111
|
-
color: "var(--dsw-alias-label-primary)"
|
|
2112
|
-
};
|
|
2113
|
-
const introStyle = {
|
|
2114
|
-
fontSize: 13,
|
|
2115
|
-
lineHeight: 1.5,
|
|
2116
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
2117
|
-
};
|
|
2118
|
-
/** The variant cards' chevron rule. */
|
|
2119
|
-
const chevronStyle = {
|
|
2120
|
-
flex: "none",
|
|
2121
|
-
display: "flex",
|
|
2122
|
-
color: "var(--dsw-alias-label-tertiary)",
|
|
2123
|
-
transition: "transform .16s"
|
|
2124
|
-
};
|
|
2125
|
-
/** The variant cards' body: hairline top border, inset margins, no extra box. */
|
|
2126
|
-
const cardBodyStyle = {
|
|
2127
|
-
borderTop: ".5px solid var(--dsw-alias-border-l2)",
|
|
2128
|
-
margin: "0 16px",
|
|
2129
|
-
padding: "12px 0 8px"
|
|
2130
|
-
};
|
|
2131
|
-
/**
|
|
2132
|
-
* One settings row: NO box of its own (the bordered rows read as nested
|
|
2133
|
-
* cards, which the user ruled against) — rows are separated by a hairline
|
|
2134
|
-
* bottom rule like the settings shell's own preference lists.
|
|
2135
|
-
*/
|
|
2136
|
-
const rowStyle = {
|
|
2137
|
-
display: "flex",
|
|
2138
|
-
alignItems: "center",
|
|
2139
|
-
gap: 12,
|
|
2140
|
-
borderBottom: ".5px solid var(--dsw-alias-border-l2)",
|
|
2141
|
-
paddingBottom: 10
|
|
2142
|
-
};
|
|
2143
|
-
const rowTextStyle = {
|
|
2144
|
-
display: "flex",
|
|
2145
|
-
flex: 1,
|
|
2146
|
-
minWidth: 0,
|
|
2147
|
-
flexDirection: "column",
|
|
2148
|
-
gap: 2
|
|
2149
|
-
};
|
|
2150
|
-
const labelStyle = {
|
|
2151
|
-
fontSize: 13,
|
|
2152
|
-
fontWeight: 500,
|
|
2153
|
-
lineHeight: 1.5,
|
|
2154
|
-
color: "var(--dsw-alias-label-primary)"
|
|
2155
|
-
};
|
|
2156
|
-
const hintStyle = {
|
|
2157
|
-
fontSize: 12,
|
|
2158
|
-
lineHeight: 1.5,
|
|
2159
|
-
color: "var(--dsw-alias-label-tertiary)"
|
|
2160
|
-
};
|
|
2161
|
-
const switchStyle = {
|
|
2162
|
-
flex: "none",
|
|
2163
|
-
display: "flex",
|
|
2164
|
-
width: 36,
|
|
2165
|
-
height: 20,
|
|
2166
|
-
borderRadius: 10,
|
|
2167
|
-
borderWidth: "1px",
|
|
2168
|
-
borderStyle: "solid",
|
|
2169
|
-
borderColor: "var(--dsw-alias-border-l2)",
|
|
2170
|
-
padding: 1,
|
|
2171
|
-
cursor: "pointer",
|
|
2172
|
-
alignItems: "center",
|
|
2173
|
-
transition: "background .16s"
|
|
2174
|
-
};
|
|
2175
|
-
const knobStyle = {
|
|
2176
|
-
display: "block",
|
|
2177
|
-
width: 16,
|
|
2178
|
-
height: 16,
|
|
2179
|
-
borderRadius: "50%",
|
|
2180
|
-
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
2181
|
-
boxShadow: "0 1px 2px rgba(0,0,0,0.2)"
|
|
2182
|
-
};
|
|
2183
|
-
const pollFieldStyle = {
|
|
2184
|
-
display: "flex",
|
|
2185
|
-
alignItems: "center",
|
|
2186
|
-
gap: 6,
|
|
2187
|
-
flex: "none"
|
|
2188
|
-
};
|
|
2189
|
-
const inputStyle = {
|
|
2190
|
-
boxSizing: "border-box",
|
|
2191
|
-
width: 55,
|
|
2192
|
-
padding: "5px 8px",
|
|
2193
|
-
borderWidth: "1px",
|
|
2194
|
-
borderStyle: "solid",
|
|
2195
|
-
borderColor: "var(--dsw-alias-border-l2)",
|
|
2196
|
-
borderRadius: 8,
|
|
2197
|
-
background: "var(--dsw-alias-bg-layer-2)",
|
|
2198
|
-
color: "var(--dsw-alias-label-primary)",
|
|
2199
|
-
font: "inherit",
|
|
2200
|
-
fontSize: 13,
|
|
2201
|
-
textAlign: "right"
|
|
2202
|
-
};
|
|
2203
2135
|
//#endregion
|
|
2204
2136
|
//#region src/client/quota-merge.ts
|
|
2205
2137
|
/**
|
|
@@ -2441,6 +2373,7 @@ window.__ModuleLoader__.load({
|
|
|
2441
2373
|
(0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSettingsRevision);
|
|
2442
2374
|
const enabled = variantId === "qoder" ? quotaToggles().cn : quotaToggles().global;
|
|
2443
2375
|
const status = quotaStatus(variantId);
|
|
2376
|
+
const signedIn = status?.status === "signed-in";
|
|
2444
2377
|
(0, react.useEffect)(() => {
|
|
2445
2378
|
if (statusPath === void 0 || !enabled) return void 0;
|
|
2446
2379
|
let disposed = false;
|
|
@@ -2464,21 +2397,22 @@ window.__ModuleLoader__.load({
|
|
|
2464
2397
|
if (!disposed) setFailed(true);
|
|
2465
2398
|
}
|
|
2466
2399
|
};
|
|
2400
|
+
const isHidden = () => typeof document !== "undefined" && document.hidden;
|
|
2467
2401
|
const loop = () => {
|
|
2468
|
-
if (
|
|
2402
|
+
if (isHidden()) return;
|
|
2469
2403
|
refresh();
|
|
2470
2404
|
};
|
|
2471
2405
|
timer = window.setInterval(loop, Math.max(6e4, quotaPollMs()));
|
|
2472
2406
|
refresh();
|
|
2473
2407
|
const onVisible = () => {
|
|
2474
|
-
if (!
|
|
2408
|
+
if (!isHidden()) loop();
|
|
2475
2409
|
};
|
|
2476
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
2410
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisible);
|
|
2477
2411
|
return () => {
|
|
2478
2412
|
disposed = true;
|
|
2479
2413
|
controller.abort();
|
|
2480
2414
|
if (timer !== void 0) window.clearInterval(timer);
|
|
2481
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
2415
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisible);
|
|
2482
2416
|
};
|
|
2483
2417
|
}, [
|
|
2484
2418
|
statusPath,
|
|
@@ -2507,7 +2441,11 @@ window.__ModuleLoader__.load({
|
|
|
2507
2441
|
className: "qdp-railButton",
|
|
2508
2442
|
"aria-label": title,
|
|
2509
2443
|
title,
|
|
2510
|
-
|
|
2444
|
+
disabled: !signedIn,
|
|
2445
|
+
onClick: () => {
|
|
2446
|
+
if (!signedIn) return;
|
|
2447
|
+
open?.();
|
|
2448
|
+
},
|
|
2511
2449
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Ring, {
|
|
2512
2450
|
percent: ringPercent,
|
|
2513
2451
|
warn: ringWarn,
|
|
@@ -2519,7 +2457,11 @@ window.__ModuleLoader__.load({
|
|
|
2519
2457
|
className: "qdp-foot",
|
|
2520
2458
|
"aria-label": title,
|
|
2521
2459
|
title,
|
|
2522
|
-
|
|
2460
|
+
disabled: !signedIn,
|
|
2461
|
+
onClick: () => {
|
|
2462
|
+
if (!signedIn) return;
|
|
2463
|
+
open?.();
|
|
2464
|
+
},
|
|
2523
2465
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2524
2466
|
className: "qdp-footTop",
|
|
2525
2467
|
children: [
|
|
@@ -2983,6 +2925,10 @@ window.__ModuleLoader__.load({
|
|
|
2983
2925
|
intro: "Use the models from your Qoder (China) account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
|
|
2984
2926
|
titleAI: "Qoder Global",
|
|
2985
2927
|
introAI: "Use the models from your Qoder Global account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
|
|
2928
|
+
unifiedTitle: "Qoder",
|
|
2929
|
+
unifiedIntro: "Manage Qoder (China) and Qoder Global models, credentials, and sidebar quota displays.",
|
|
2930
|
+
variantTabCN: "China",
|
|
2931
|
+
variantTabGlobal: "Global",
|
|
2986
2932
|
expand: "Expand",
|
|
2987
2933
|
collapse: "Collapse",
|
|
2988
2934
|
loading: "Loading account…",
|
|
@@ -3113,6 +3059,10 @@ window.__ModuleLoader__.load({
|
|
|
3113
3059
|
intro: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder 国内版的模型。",
|
|
3114
3060
|
titleAI: "Qoder Global(国际版)",
|
|
3115
3061
|
introAI: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder Global 的模型。",
|
|
3062
|
+
unifiedTitle: "Qoder",
|
|
3063
|
+
unifiedIntro: "统一管理 Qoder(国内版)与 Qoder Global(国际版)模型、凭证及侧栏额度展示。",
|
|
3064
|
+
variantTabCN: "国内版",
|
|
3065
|
+
variantTabGlobal: "国际版",
|
|
3116
3066
|
expand: "展开",
|
|
3117
3067
|
collapse: "收起",
|
|
3118
3068
|
loading: "正在读取账号…",
|
|
@@ -3299,10 +3249,6 @@ window.__ModuleLoader__.load({
|
|
|
3299
3249
|
en
|
|
3300
3250
|
}), "dsh-qoder-connect: settings copy");
|
|
3301
3251
|
const t = ctx.locale.bind(namespace);
|
|
3302
|
-
const quotaSettingsInjected = {
|
|
3303
|
-
t,
|
|
3304
|
-
signedIn: () => quotaSignInState()
|
|
3305
|
-
};
|
|
3306
3252
|
let quotaScope;
|
|
3307
3253
|
try {
|
|
3308
3254
|
const scope = ctx.settingsScope.bind({ namespace: "qoder-quota" });
|
|
@@ -3319,20 +3265,13 @@ window.__ModuleLoader__.load({
|
|
|
3319
3265
|
}
|
|
3320
3266
|
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
3321
3267
|
name: "settings.plugin.item",
|
|
3322
|
-
key: "qoder
|
|
3268
|
+
key: "qoder",
|
|
3323
3269
|
priority: 50,
|
|
3324
|
-
inject: () => ({
|
|
3325
|
-
...quotaSettingsInjected,
|
|
3326
|
-
scope: quotaScope
|
|
3327
|
-
})
|
|
3328
|
-
}, QuotaSettingsCard));
|
|
3329
|
-
for (const [index, variant] of QODER_CARD_VARIANTS.entries()) ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
3330
|
-
name: "settings.plugin.item",
|
|
3331
|
-
key: variant.id,
|
|
3332
|
-
priority: 60 + index * 10,
|
|
3333
3270
|
inject: () => ({
|
|
3334
3271
|
t,
|
|
3335
|
-
|
|
3272
|
+
scope: quotaScope,
|
|
3273
|
+
signedIn: () => quotaSignInState(),
|
|
3274
|
+
unified: true
|
|
3336
3275
|
})
|
|
3337
3276
|
}, QoderPluginCard));
|
|
3338
3277
|
const QUOTA_PANEL_ID = "qoder-quota-panel";
|