dsh-qoder-connect 0.1.2 → 0.1.5
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.en.md +8 -0
- package/README.md +156 -148
- package/assets/7.png +0 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +1449 -1217
- package/lib/index.d.ts +40 -1
- package/lib/index.js +280 -7
- package/lib/{variants-C0iZD8AD.js → variants-BEiQve5M.js} +226 -6
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -61,238 +61,711 @@ 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
|
+
autoCheckInCN: false,
|
|
242
|
+
autoCheckInGlobal: false,
|
|
243
|
+
quotaPollMs: POLL_DEFAULT_MS
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const snapshot = scope.getSnapshot();
|
|
247
|
+
const value = snapshot.value ?? {};
|
|
248
|
+
return {
|
|
249
|
+
status: snapshot.status,
|
|
250
|
+
writable: snapshot.writable,
|
|
251
|
+
values: {
|
|
252
|
+
sidebarQuotaCN: value.sidebarQuotaCN === true,
|
|
253
|
+
sidebarQuotaGlobal: value.sidebarQuotaGlobal === true,
|
|
254
|
+
autoCheckInCN: value.autoCheckInCN === true,
|
|
255
|
+
autoCheckInGlobal: value.autoCheckInGlobal === true,
|
|
256
|
+
quotaPollMs: typeof value.quotaPollMs === "number" ? value.quotaPollMs : POLL_DEFAULT_MS
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stable-reference projection cache.
|
|
262
|
+
*
|
|
263
|
+
* React's useSyncExternalStore requires getSnapshot() to return THE SAME
|
|
264
|
+
* reference between renders unless the store actually changed. project()
|
|
265
|
+
* builds a fresh object every call, which re-renders forever and crashes the
|
|
266
|
+
* card with React error #185 ("maximum update depth exceeded") — exactly the
|
|
267
|
+
* crash the slot ledger reported. The cache below returns the last built
|
|
268
|
+
* projection until the underlying scope snapshot (or scope identity) changes,
|
|
269
|
+
* which is the only thing the projection actually derives from.
|
|
270
|
+
*/
|
|
271
|
+
let cachedScope;
|
|
272
|
+
let cachedProjection;
|
|
273
|
+
const UNAVAILABLE = {
|
|
274
|
+
status: "unavailable",
|
|
275
|
+
writable: false,
|
|
276
|
+
values: {
|
|
277
|
+
sidebarQuotaCN: false,
|
|
278
|
+
sidebarQuotaGlobal: false,
|
|
279
|
+
autoCheckInCN: false,
|
|
280
|
+
autoCheckInGlobal: false,
|
|
281
|
+
quotaPollMs: POLL_DEFAULT_MS
|
|
282
|
+
}
|
|
227
283
|
};
|
|
228
|
-
|
|
284
|
+
function stableProject(scope) {
|
|
285
|
+
if (scope === void 0) return UNAVAILABLE;
|
|
286
|
+
const next = project(scope);
|
|
287
|
+
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.autoCheckInCN !== next.values.autoCheckInCN || cachedProjection.values.autoCheckInGlobal !== next.values.autoCheckInGlobal || cachedProjection.values.quotaPollMs !== next.values.quotaPollMs) {
|
|
288
|
+
cachedScope = scope;
|
|
289
|
+
cachedProjection = next;
|
|
290
|
+
}
|
|
291
|
+
return cachedProjection;
|
|
292
|
+
}
|
|
293
|
+
/** One toggle row: label, hint, and a switch drawn to the shell's proportions. */
|
|
294
|
+
function ToggleRow({ label, hint, checked, disabled, disabledHint, onToggle }) {
|
|
295
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
296
|
+
style: rowStyle$1,
|
|
297
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
298
|
+
style: rowTextStyle,
|
|
299
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
300
|
+
style: labelStyle$1,
|
|
301
|
+
children: label
|
|
302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
|
+
style: hintStyle,
|
|
304
|
+
children: disabled === true && disabledHint !== void 0 ? disabledHint : hint
|
|
305
|
+
})]
|
|
306
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
307
|
+
type: "button",
|
|
308
|
+
role: "switch",
|
|
309
|
+
"aria-checked": checked,
|
|
310
|
+
disabled,
|
|
311
|
+
"aria-label": label,
|
|
312
|
+
onClick: () => {
|
|
313
|
+
if (disabled) return;
|
|
314
|
+
onToggle(!checked);
|
|
315
|
+
},
|
|
316
|
+
style: {
|
|
317
|
+
...switchStyle,
|
|
318
|
+
background: checked ? "var(--dsw-alias-brand-primary)" : "var(--dsw-alias-bg-layer-3, rgba(127,127,127,0.2))",
|
|
319
|
+
justifyContent: checked ? "flex-end" : "flex-start",
|
|
320
|
+
opacity: disabled === true ? .45 : 1,
|
|
321
|
+
cursor: disabled === true ? "not-allowed" : "pointer"
|
|
322
|
+
},
|
|
323
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: knobStyle })
|
|
324
|
+
})]
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/** The inner controls for sidebar quota settings. */
|
|
328
|
+
function QuotaSettingsContent({ t = (key) => key, scope, signedIn }) {
|
|
329
|
+
const subscribe = (0, react.useCallback)((onStoreChange) => {
|
|
330
|
+
return scope?.subscribe(onStoreChange) ?? (() => {});
|
|
331
|
+
}, [scope]);
|
|
332
|
+
const projection = (0, react.useSyncExternalStore)(subscribe, () => stableProject(scope));
|
|
333
|
+
const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
|
|
334
|
+
const [probe, setProbe] = (0, react.useState)();
|
|
335
|
+
(0, react.useEffect)(() => {
|
|
336
|
+
let disposed = false;
|
|
337
|
+
const probeOne = async (path) => {
|
|
338
|
+
try {
|
|
339
|
+
const body = await (await fetch(path, { headers: { accept: "application/json" } })).json();
|
|
340
|
+
if (disposed || !isQoderWebStatus(body)) return void 0;
|
|
341
|
+
noteQuotaSignIn(variantOfStatusPath(path), body.status === "signed-in");
|
|
342
|
+
return body.status === "signed-in";
|
|
343
|
+
} catch {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
(async () => {
|
|
348
|
+
const [cn, global] = await Promise.all([probeOne(QODER_STATUS_PATH), probeOne(QODER_GLOBAL_STATUS_PATH)]);
|
|
349
|
+
if (!disposed) setProbe({
|
|
350
|
+
cn: cn === true,
|
|
351
|
+
global: global === true
|
|
352
|
+
});
|
|
353
|
+
})();
|
|
354
|
+
return () => {
|
|
355
|
+
disposed = true;
|
|
356
|
+
};
|
|
357
|
+
}, []);
|
|
358
|
+
if (projection.status === "unavailable") return null;
|
|
359
|
+
const reported = signedIn?.();
|
|
360
|
+
const deriveSigned = (variant, variantId) => {
|
|
361
|
+
if (reported !== void 0) return Boolean(reported[variant]);
|
|
362
|
+
const currentStatus = quotaStatus(variantId);
|
|
363
|
+
if (currentStatus?.status === "signed-out") return false;
|
|
364
|
+
const live = liveSignIn[variant];
|
|
365
|
+
if (probe !== void 0) {
|
|
366
|
+
if (!probe[variant]) return Boolean(live && currentStatus?.status === "signed-in");
|
|
367
|
+
return Boolean(live);
|
|
368
|
+
}
|
|
369
|
+
return Boolean(live && currentStatus?.status === "signed-in");
|
|
370
|
+
};
|
|
371
|
+
const signed = {
|
|
372
|
+
cn: deriveSigned("cn", "qoder"),
|
|
373
|
+
global: deriveSigned("global", "qoder-global")
|
|
374
|
+
};
|
|
375
|
+
const write = (field, value) => {
|
|
376
|
+
if (field === "sidebarQuotaCN" && value === true && !signed.cn) return;
|
|
377
|
+
if (field === "sidebarQuotaGlobal" && value === true && !signed.global) return;
|
|
378
|
+
if (field === "autoCheckInCN" && value === true && !signed.cn) return;
|
|
379
|
+
if (field === "autoCheckInGlobal" && value === true && !signed.global) return;
|
|
380
|
+
scope?.set(field, value);
|
|
381
|
+
};
|
|
382
|
+
const minutes = Math.max(POLL_MIN_MS / 6e4, Math.round(projection.values.quotaPollMs / 6e4));
|
|
383
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
384
|
+
style: {
|
|
385
|
+
display: "flex",
|
|
386
|
+
flexDirection: "column",
|
|
387
|
+
gap: 4
|
|
388
|
+
},
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
391
|
+
label: t("quotaToggleCN"),
|
|
392
|
+
hint: t("quotaToggleHint"),
|
|
393
|
+
checked: projection.values.sidebarQuotaCN,
|
|
394
|
+
disabled: !signed.cn,
|
|
395
|
+
disabledHint: t("quotaSignInRequired"),
|
|
396
|
+
onToggle: (next) => write("sidebarQuotaCN", next)
|
|
397
|
+
}),
|
|
398
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
399
|
+
label: t("quotaToggleGlobal"),
|
|
400
|
+
hint: t("quotaToggleHint"),
|
|
401
|
+
checked: projection.values.sidebarQuotaGlobal,
|
|
402
|
+
disabled: !signed.global,
|
|
403
|
+
disabledHint: t("quotaSignInRequired"),
|
|
404
|
+
onToggle: (next) => write("sidebarQuotaGlobal", next)
|
|
405
|
+
}),
|
|
406
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
407
|
+
label: t("autoCheckInCN"),
|
|
408
|
+
hint: t("autoCheckInHintCN"),
|
|
409
|
+
checked: projection.values.autoCheckInCN,
|
|
410
|
+
disabled: !signed.cn,
|
|
411
|
+
disabledHint: t("quotaSignInRequired"),
|
|
412
|
+
onToggle: (next) => write("autoCheckInCN", next)
|
|
413
|
+
}),
|
|
414
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
415
|
+
label: t("autoCheckInGlobal"),
|
|
416
|
+
hint: t("autoCheckInHintGlobal"),
|
|
417
|
+
checked: projection.values.autoCheckInGlobal,
|
|
418
|
+
disabled: !signed.global,
|
|
419
|
+
disabledHint: t("quotaSignInRequired"),
|
|
420
|
+
onToggle: (next) => write("autoCheckInGlobal", next)
|
|
421
|
+
}),
|
|
422
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
423
|
+
style: {
|
|
424
|
+
...rowStyle$1,
|
|
425
|
+
borderBottom: "none",
|
|
426
|
+
paddingBottom: 0
|
|
427
|
+
},
|
|
428
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
429
|
+
style: rowTextStyle,
|
|
430
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
431
|
+
style: labelStyle$1,
|
|
432
|
+
children: t("quotaPollLabel")
|
|
433
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
434
|
+
style: hintStyle,
|
|
435
|
+
children: t("quotaPollHint")
|
|
436
|
+
})]
|
|
437
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
438
|
+
style: pollFieldStyle,
|
|
439
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
440
|
+
type: "number",
|
|
441
|
+
min: POLL_MIN_MS / 6e4,
|
|
442
|
+
step: 1,
|
|
443
|
+
value: minutes,
|
|
444
|
+
"aria-label": t("quotaPollLabel"),
|
|
445
|
+
onChange: (event) => {
|
|
446
|
+
const mins = Number.parseInt(event.target.value, 10);
|
|
447
|
+
if (Number.isFinite(mins) && mins > 0) write("quotaPollMs", Math.max(POLL_MIN_MS, mins * 6e4));
|
|
448
|
+
},
|
|
449
|
+
style: inputStyle
|
|
450
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
451
|
+
style: hintStyle,
|
|
452
|
+
children: t("quotaPollUnit")
|
|
453
|
+
})]
|
|
454
|
+
})]
|
|
455
|
+
}),
|
|
456
|
+
projection.writable === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
457
|
+
style: hintStyle,
|
|
458
|
+
children: t("quotaSettingsSaveFailed")
|
|
459
|
+
}) : null
|
|
460
|
+
]
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* One settings row: NO box of its own (the bordered rows read as nested
|
|
465
|
+
* cards, which the user ruled against) — rows are separated by a hairline
|
|
466
|
+
* bottom rule like the settings shell's own preference lists.
|
|
467
|
+
*/
|
|
468
|
+
const rowStyle$1 = {
|
|
229
469
|
display: "flex",
|
|
230
|
-
|
|
231
|
-
gap:
|
|
232
|
-
|
|
470
|
+
alignItems: "center",
|
|
471
|
+
gap: 12,
|
|
472
|
+
borderBottom: ".5px solid var(--dsw-alias-border-l2)",
|
|
473
|
+
paddingBottom: 10
|
|
233
474
|
};
|
|
234
|
-
const
|
|
475
|
+
const rowTextStyle = {
|
|
235
476
|
display: "flex",
|
|
477
|
+
flex: 1,
|
|
478
|
+
minWidth: 0,
|
|
236
479
|
flexDirection: "column",
|
|
237
|
-
gap:
|
|
480
|
+
gap: 2
|
|
238
481
|
};
|
|
239
|
-
const
|
|
240
|
-
margin: 0,
|
|
482
|
+
const labelStyle$1 = {
|
|
241
483
|
fontSize: 13,
|
|
484
|
+
fontWeight: 500,
|
|
242
485
|
lineHeight: 1.5,
|
|
243
|
-
fontWeight: 600,
|
|
244
486
|
color: "var(--dsw-alias-label-primary)"
|
|
245
487
|
};
|
|
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 = {
|
|
488
|
+
const hintStyle = {
|
|
255
489
|
fontSize: 12,
|
|
256
490
|
lineHeight: 1.5,
|
|
257
491
|
color: "var(--dsw-alias-label-tertiary)"
|
|
258
492
|
};
|
|
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 = {
|
|
493
|
+
const switchStyle = {
|
|
494
|
+
flex: "none",
|
|
278
495
|
display: "flex",
|
|
496
|
+
width: 36,
|
|
497
|
+
height: 20,
|
|
498
|
+
borderRadius: 10,
|
|
499
|
+
borderWidth: "1px",
|
|
500
|
+
borderStyle: "solid",
|
|
501
|
+
borderColor: "var(--dsw-alias-border-l2)",
|
|
502
|
+
padding: 1,
|
|
503
|
+
cursor: "pointer",
|
|
279
504
|
alignItems: "center",
|
|
280
|
-
|
|
281
|
-
gap: 8,
|
|
282
|
-
flexWrap: "wrap"
|
|
505
|
+
transition: "background .16s"
|
|
283
506
|
};
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
507
|
+
const knobStyle = {
|
|
508
|
+
display: "block",
|
|
509
|
+
width: 16,
|
|
510
|
+
height: 16,
|
|
511
|
+
borderRadius: "50%",
|
|
512
|
+
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
513
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.2)"
|
|
289
514
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
515
|
+
const pollFieldStyle = {
|
|
516
|
+
display: "flex",
|
|
517
|
+
alignItems: "center",
|
|
518
|
+
gap: 6,
|
|
519
|
+
flex: "none"
|
|
520
|
+
};
|
|
521
|
+
const inputStyle = {
|
|
522
|
+
boxSizing: "border-box",
|
|
523
|
+
width: 55,
|
|
524
|
+
padding: "5px 8px",
|
|
525
|
+
borderWidth: "1px",
|
|
526
|
+
borderStyle: "solid",
|
|
527
|
+
borderColor: "var(--dsw-alias-border-l2)",
|
|
528
|
+
borderRadius: 8,
|
|
529
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
530
|
+
color: "var(--dsw-alias-label-primary)",
|
|
531
|
+
font: "inherit",
|
|
532
|
+
fontSize: 13,
|
|
533
|
+
textAlign: "right"
|
|
534
|
+
};
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region src/client/QoderPluginCard.tsx
|
|
537
|
+
/** Qoder status card contributed to Harness Plugin configuration. */
|
|
538
|
+
/** China Qoder; the plugin's primary card and default. */
|
|
539
|
+
const QODER_CN_CARD = {
|
|
540
|
+
id: "qoder",
|
|
541
|
+
titleKey: "title",
|
|
542
|
+
introKey: "intro",
|
|
543
|
+
signedOutKey: "signedOutHint",
|
|
544
|
+
patGuideKey: "patGuide",
|
|
545
|
+
patPlaceholderKey: "patPlaceholder",
|
|
546
|
+
statusPath: QODER_STATUS_PATH,
|
|
547
|
+
probePath: QODER_PROBE_PATH,
|
|
548
|
+
authPath: QODER_AUTH_PATH
|
|
549
|
+
};
|
|
550
|
+
/** International Qoder Global. */
|
|
551
|
+
const QODER_GLOBAL_CARD = {
|
|
552
|
+
id: "qoder-global",
|
|
553
|
+
titleKey: "titleAI",
|
|
554
|
+
introKey: "introAI",
|
|
555
|
+
signedOutKey: "signedOutHintAI",
|
|
556
|
+
patGuideKey: "patGuideAI",
|
|
557
|
+
patPlaceholderKey: "patPlaceholderAI",
|
|
558
|
+
statusPath: QODER_GLOBAL_STATUS_PATH,
|
|
559
|
+
probePath: QODER_GLOBAL_PROBE_PATH,
|
|
560
|
+
authPath: QODER_GLOBAL_AUTH_PATH
|
|
561
|
+
};
|
|
562
|
+
/** Both cards, in display order (China first). */
|
|
563
|
+
const QODER_CARD_VARIANTS = [QODER_CN_CARD, QODER_GLOBAL_CARD];
|
|
564
|
+
const POLL_INTERVAL_MS = 6e4;
|
|
565
|
+
const cardStyle = {
|
|
566
|
+
listStyle: "none",
|
|
567
|
+
borderWidth: "0.5px",
|
|
568
|
+
borderStyle: "solid",
|
|
569
|
+
borderColor: "var(--dsw-alias-border-l4)",
|
|
570
|
+
borderRadius: 16,
|
|
571
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
572
|
+
transition: "border-color .16s, background .16s"
|
|
573
|
+
};
|
|
574
|
+
/** Hover, matching the built-in card's `:hover`. Inline styles cannot express a pseudo-class. */
|
|
575
|
+
const cardHoverStyle = { borderColor: "var(--dsw-alias-label-dimmed)" };
|
|
576
|
+
/** Expanded, matching the built-in card's open state. */
|
|
577
|
+
const cardOpenStyle = {
|
|
578
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
579
|
+
borderColor: "var(--dsw-alias-label-dimmed)"
|
|
580
|
+
};
|
|
581
|
+
const headerStyle = {
|
|
582
|
+
boxSizing: "border-box",
|
|
583
|
+
width: "100%",
|
|
584
|
+
display: "flex",
|
|
585
|
+
alignItems: "center",
|
|
586
|
+
gap: 12,
|
|
587
|
+
border: 0,
|
|
588
|
+
borderRadius: 12,
|
|
589
|
+
padding: "14px 16px",
|
|
590
|
+
background: "transparent",
|
|
591
|
+
color: "inherit",
|
|
592
|
+
font: "inherit",
|
|
593
|
+
textAlign: "left",
|
|
594
|
+
cursor: "pointer",
|
|
595
|
+
appearance: "none"
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* The built-in header's keyboard focus ring.
|
|
599
|
+
*
|
|
600
|
+
* `:focus-visible` is what makes the ring appear for keyboard navigation but not
|
|
601
|
+
* for a mouse click, and an inline style cannot express a pseudo-class — so the
|
|
602
|
+
* component tracks it and applies this instead. Without it the header falls back
|
|
603
|
+
* to the browser's own outline, which is the black box that used to appear on
|
|
604
|
+
* focus where the built-in card shows a brand-coloured ring.
|
|
605
|
+
*/
|
|
606
|
+
const headerFocusStyle = {
|
|
607
|
+
outline: "2px solid var(--dsw-alias-brand-primary)",
|
|
608
|
+
outlineOffset: -2
|
|
609
|
+
};
|
|
610
|
+
const headTextStyle = {
|
|
611
|
+
display: "flex",
|
|
612
|
+
flex: 1,
|
|
613
|
+
minWidth: 0,
|
|
614
|
+
flexDirection: "column",
|
|
615
|
+
gap: 4
|
|
616
|
+
};
|
|
617
|
+
const nameStyle = {
|
|
618
|
+
fontSize: 15,
|
|
619
|
+
lineHeight: 1.4,
|
|
620
|
+
fontWeight: 600,
|
|
621
|
+
color: "var(--dsw-alias-label-primary)"
|
|
622
|
+
};
|
|
623
|
+
const descriptionStyle = {
|
|
624
|
+
fontSize: 13,
|
|
625
|
+
lineHeight: 1.5,
|
|
626
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
627
|
+
};
|
|
628
|
+
/**
|
|
629
|
+
* The disclosure chevron, drawn to match the Settings panel's own card.
|
|
630
|
+
*
|
|
631
|
+
* The built-in card renders `IconChevronDownOutline14` from the client's shared
|
|
632
|
+
* icon catalog, which the shell seeds into the module table. This plugin does
|
|
633
|
+
* not request that catalog, so the same outline is drawn here from the same path
|
|
634
|
+
* data: the text `⌄` glyph this replaces had a different shape, weight, and
|
|
635
|
+
* baseline from the icon the cards beside it use.
|
|
636
|
+
*/
|
|
637
|
+
function ChevronDownIcon() {
|
|
638
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
639
|
+
width: 14,
|
|
640
|
+
height: 14,
|
|
641
|
+
viewBox: "0 0 14 14",
|
|
642
|
+
fill: "none",
|
|
643
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
644
|
+
"aria-hidden": "true",
|
|
645
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
646
|
+
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",
|
|
647
|
+
fill: "currentColor"
|
|
648
|
+
})
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/** The built-in card's chevron rule: tertiary color, and only the rotation animates. */
|
|
652
|
+
const chevronStyle = {
|
|
653
|
+
flex: "none",
|
|
654
|
+
display: "flex",
|
|
655
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
656
|
+
transition: "transform .16s"
|
|
657
|
+
};
|
|
658
|
+
const cardBodyStyle = {
|
|
659
|
+
borderTop: ".5px solid var(--dsw-alias-border-l2)",
|
|
660
|
+
margin: "0 16px",
|
|
661
|
+
padding: "12px 0 8px"
|
|
662
|
+
};
|
|
663
|
+
const bodyStyle = {
|
|
664
|
+
margin: 0,
|
|
665
|
+
fontSize: 13,
|
|
666
|
+
lineHeight: 1.5,
|
|
667
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
668
|
+
};
|
|
669
|
+
const rowStyle = {
|
|
670
|
+
display: "flex",
|
|
671
|
+
alignItems: "center",
|
|
672
|
+
justifyContent: "space-between",
|
|
673
|
+
flexWrap: "wrap",
|
|
674
|
+
gap: 12
|
|
675
|
+
};
|
|
676
|
+
const statusStyle = {
|
|
677
|
+
display: "flex",
|
|
678
|
+
alignItems: "center",
|
|
679
|
+
gap: 8,
|
|
680
|
+
fontSize: 13,
|
|
681
|
+
fontWeight: 500,
|
|
682
|
+
lineHeight: 1.5,
|
|
683
|
+
color: "var(--dsw-alias-label-primary)"
|
|
684
|
+
};
|
|
685
|
+
/** The built-in secondary button: transparent, hairline border, 8px radius. */
|
|
686
|
+
const buttonStyle$1 = {
|
|
687
|
+
boxSizing: "border-box",
|
|
688
|
+
padding: "5px 14px",
|
|
689
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
690
|
+
borderRadius: 8,
|
|
691
|
+
background: "transparent",
|
|
692
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
693
|
+
font: "inherit",
|
|
694
|
+
fontSize: 13,
|
|
695
|
+
lineHeight: 1.5,
|
|
696
|
+
cursor: "pointer"
|
|
697
|
+
};
|
|
698
|
+
const errorStyle = {
|
|
699
|
+
...bodyStyle,
|
|
700
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
701
|
+
};
|
|
702
|
+
const quotaListStyle = {
|
|
703
|
+
display: "flex",
|
|
704
|
+
flexDirection: "column",
|
|
705
|
+
gap: 18,
|
|
706
|
+
paddingTop: 2
|
|
707
|
+
};
|
|
708
|
+
const quotaGroupStyle = {
|
|
709
|
+
display: "flex",
|
|
710
|
+
flexDirection: "column",
|
|
711
|
+
gap: 10
|
|
712
|
+
};
|
|
713
|
+
const quotaTitleStyle = {
|
|
714
|
+
margin: 0,
|
|
715
|
+
fontSize: 13,
|
|
716
|
+
lineHeight: 1.5,
|
|
717
|
+
fontWeight: 600,
|
|
718
|
+
color: "var(--dsw-alias-label-primary)"
|
|
719
|
+
};
|
|
720
|
+
const quotaLabelStyle = {
|
|
721
|
+
display: "flex",
|
|
722
|
+
justifyContent: "space-between",
|
|
723
|
+
gap: 12,
|
|
724
|
+
fontSize: 13,
|
|
725
|
+
lineHeight: 1.5,
|
|
726
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
727
|
+
};
|
|
728
|
+
const modelRateStyle = {
|
|
729
|
+
fontSize: 12,
|
|
730
|
+
lineHeight: 1.5,
|
|
731
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
732
|
+
};
|
|
733
|
+
const contextPreferenceStyle = {
|
|
734
|
+
display: "flex",
|
|
735
|
+
alignItems: "flex-start",
|
|
736
|
+
gap: 9,
|
|
737
|
+
padding: "10px 12px",
|
|
738
|
+
border: ".5px solid var(--dsw-alias-border-l4)",
|
|
739
|
+
borderRadius: 8,
|
|
740
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
741
|
+
color: "var(--dsw-alias-label-primary)",
|
|
742
|
+
fontSize: 13,
|
|
743
|
+
lineHeight: 1.5
|
|
744
|
+
};
|
|
745
|
+
const contextPreferenceCopyStyle = {
|
|
746
|
+
display: "flex",
|
|
747
|
+
flexDirection: "column",
|
|
748
|
+
gap: 2
|
|
749
|
+
};
|
|
750
|
+
const contextPickerRowStyle = {
|
|
751
|
+
display: "flex",
|
|
752
|
+
alignItems: "center",
|
|
753
|
+
justifyContent: "flex-end",
|
|
754
|
+
gap: 8,
|
|
755
|
+
flexWrap: "wrap"
|
|
756
|
+
};
|
|
757
|
+
const progressTrackStyle = {
|
|
758
|
+
height: 8,
|
|
759
|
+
overflow: "hidden",
|
|
760
|
+
borderRadius: 999,
|
|
761
|
+
background: "var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.08))"
|
|
762
|
+
};
|
|
763
|
+
/**
|
|
764
|
+
* The PAT entry row: the password field takes the row's flexible width, the
|
|
765
|
+
* Save button keeps its own, and the whole block sits inside the card body
|
|
766
|
+
* without a nested box (the same rule the settings rows follow).
|
|
767
|
+
*/
|
|
768
|
+
const patRowStyle = {
|
|
296
769
|
display: "flex",
|
|
297
770
|
alignItems: "center",
|
|
298
771
|
gap: 8,
|
|
@@ -353,13 +826,51 @@ window.__ModuleLoader__.load({
|
|
|
353
826
|
*/
|
|
354
827
|
const primaryButtonStyle$1 = {
|
|
355
828
|
...buttonStyle$1,
|
|
356
|
-
|
|
829
|
+
borderWidth: "1px",
|
|
830
|
+
borderStyle: "solid",
|
|
831
|
+
borderColor: "var(--dsw-alias-button-primary-fill)",
|
|
357
832
|
background: "var(--dsw-alias-button-primary-fill)",
|
|
358
833
|
color: "var(--dsw-alias-label-primary-foreground)"
|
|
359
834
|
};
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
835
|
+
const segmentedContainerStyle = {
|
|
836
|
+
display: "flex",
|
|
837
|
+
alignItems: "center",
|
|
838
|
+
background: "var(--dsw-alias-bg-layer-1, rgba(20, 20, 20, 0.6))",
|
|
839
|
+
borderWidth: "1px",
|
|
840
|
+
borderStyle: "solid",
|
|
841
|
+
borderColor: "var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.08))",
|
|
842
|
+
borderRadius: 8,
|
|
843
|
+
padding: 3,
|
|
844
|
+
gap: 4,
|
|
845
|
+
marginTop: 14,
|
|
846
|
+
marginBottom: 16
|
|
847
|
+
};
|
|
848
|
+
function segmentedTabItemStyle(active) {
|
|
849
|
+
return {
|
|
850
|
+
flex: 1,
|
|
851
|
+
display: "flex",
|
|
852
|
+
alignItems: "center",
|
|
853
|
+
justifyContent: "center",
|
|
854
|
+
gap: 8,
|
|
855
|
+
padding: "6px 12px",
|
|
856
|
+
borderRadius: 6,
|
|
857
|
+
borderWidth: "1px",
|
|
858
|
+
borderStyle: "solid",
|
|
859
|
+
borderColor: active ? "var(--dsw-alias-border-l4, rgba(255, 255, 255, 0.18))" : "transparent",
|
|
860
|
+
background: active ? "var(--dsw-alias-bg-layer-3, rgba(255, 255, 255, 0.08))" : "transparent",
|
|
861
|
+
color: active ? "var(--dsw-alias-label-primary, #fff)" : "var(--dsw-alias-label-tertiary, #8c8c8c)",
|
|
862
|
+
fontWeight: active ? 500 : 400,
|
|
863
|
+
fontSize: 13,
|
|
864
|
+
lineHeight: "18px",
|
|
865
|
+
cursor: "pointer",
|
|
866
|
+
appearance: "none",
|
|
867
|
+
outline: "none",
|
|
868
|
+
transition: "all .16s ease"
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function progressFillStyle(percent) {
|
|
872
|
+
return {
|
|
873
|
+
width: `${Math.max(0, Math.min(100, percent))}%`,
|
|
363
874
|
height: "100%",
|
|
364
875
|
borderRadius: "inherit",
|
|
365
876
|
background: "var(--dsw-alias-brand-primary, #1677ff)"
|
|
@@ -373,11 +884,11 @@ window.__ModuleLoader__.load({
|
|
|
373
884
|
*/
|
|
374
885
|
function dotStyle(status) {
|
|
375
886
|
return {
|
|
376
|
-
width:
|
|
377
|
-
height:
|
|
887
|
+
width: 8,
|
|
888
|
+
height: 8,
|
|
378
889
|
borderRadius: "50%",
|
|
379
890
|
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, #
|
|
891
|
+
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
892
|
};
|
|
382
893
|
}
|
|
383
894
|
function formatNumber(value) {
|
|
@@ -397,7 +908,6 @@ window.__ModuleLoader__.load({
|
|
|
397
908
|
if (!Number.isNaN(parsed)) return formatTime(parsed);
|
|
398
909
|
return time;
|
|
399
910
|
}
|
|
400
|
-
/** Localize where the credential in effect came from. */
|
|
401
911
|
function patSourceText(source, t) {
|
|
402
912
|
if (source === "env") return t("patSourceEnv");
|
|
403
913
|
if (source === "cli") return t("patSourceCli");
|
|
@@ -549,72 +1059,168 @@ window.__ModuleLoader__.load({
|
|
|
549
1059
|
]
|
|
550
1060
|
});
|
|
551
1061
|
}
|
|
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
1062
|
function formatTokens(tokens) {
|
|
558
1063
|
if (tokens >= 1e6 && tokens % 1e6 === 0) return `${tokens / 1e6}M`;
|
|
559
1064
|
if (tokens >= 1e3 && tokens % 1e3 === 0) return `${tokens / 1e3}K`;
|
|
560
1065
|
return String(tokens);
|
|
561
1066
|
}
|
|
1067
|
+
function CheckInLogTable({ logs = [], t, onCheckIn, onRefresh, onClear, busy, checkingIn, clearing, disabled, notice }) {
|
|
1068
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1069
|
+
style: quotaListStyle,
|
|
1070
|
+
children: [
|
|
1071
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1072
|
+
style: {
|
|
1073
|
+
display: "flex",
|
|
1074
|
+
alignItems: "center",
|
|
1075
|
+
justifyContent: "space-between",
|
|
1076
|
+
flexWrap: "wrap",
|
|
1077
|
+
gap: 8
|
|
1078
|
+
},
|
|
1079
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1080
|
+
style: quotaTitleStyle,
|
|
1081
|
+
children: t("tabCheckIn")
|
|
1082
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1083
|
+
style: {
|
|
1084
|
+
display: "flex",
|
|
1085
|
+
alignItems: "center",
|
|
1086
|
+
gap: 6
|
|
1087
|
+
},
|
|
1088
|
+
children: [
|
|
1089
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1090
|
+
type: "button",
|
|
1091
|
+
style: buttonStyle$1,
|
|
1092
|
+
disabled: disabled || busy || checkingIn,
|
|
1093
|
+
onClick: onCheckIn,
|
|
1094
|
+
children: checkingIn ? t("checkInChecking") : t("checkInNow")
|
|
1095
|
+
}),
|
|
1096
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1097
|
+
type: "button",
|
|
1098
|
+
style: buttonStyle$1,
|
|
1099
|
+
disabled: busy || checkingIn,
|
|
1100
|
+
onClick: onRefresh,
|
|
1101
|
+
children: busy ? t("checkInRefreshing") : t("checkInRefresh")
|
|
1102
|
+
}),
|
|
1103
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1104
|
+
type: "button",
|
|
1105
|
+
style: buttonStyle$1,
|
|
1106
|
+
disabled: busy || clearing || !logs || logs.length === 0,
|
|
1107
|
+
onClick: onClear,
|
|
1108
|
+
children: clearing ? t("checkInClearing") : t("checkInClear")
|
|
1109
|
+
})
|
|
1110
|
+
]
|
|
1111
|
+
})]
|
|
1112
|
+
}),
|
|
1113
|
+
notice === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1114
|
+
style: bodyStyle,
|
|
1115
|
+
children: notice
|
|
1116
|
+
}),
|
|
1117
|
+
!logs || logs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1118
|
+
style: descriptionStyle,
|
|
1119
|
+
children: t("checkInLogEmpty")
|
|
1120
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1121
|
+
style: {
|
|
1122
|
+
display: "flex",
|
|
1123
|
+
flexDirection: "column",
|
|
1124
|
+
gap: 6
|
|
1125
|
+
},
|
|
1126
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1127
|
+
style: {
|
|
1128
|
+
display: "flex",
|
|
1129
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.15))",
|
|
1130
|
+
paddingBottom: 6,
|
|
1131
|
+
fontSize: 12,
|
|
1132
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1133
|
+
},
|
|
1134
|
+
children: [
|
|
1135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1136
|
+
style: { flex: 2 },
|
|
1137
|
+
children: t("checkInLogTime")
|
|
1138
|
+
}),
|
|
1139
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1140
|
+
style: { flex: 3 },
|
|
1141
|
+
children: t("checkInLogResult")
|
|
1142
|
+
}),
|
|
1143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1144
|
+
style: {
|
|
1145
|
+
flex: 1,
|
|
1146
|
+
textAlign: "right"
|
|
1147
|
+
},
|
|
1148
|
+
children: t("checkInLogAmount")
|
|
1149
|
+
})
|
|
1150
|
+
]
|
|
1151
|
+
}), logs.map((log) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1152
|
+
style: {
|
|
1153
|
+
display: "flex",
|
|
1154
|
+
alignItems: "center",
|
|
1155
|
+
padding: "6px 0",
|
|
1156
|
+
fontSize: 13,
|
|
1157
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.08))"
|
|
1158
|
+
},
|
|
1159
|
+
children: [
|
|
1160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1161
|
+
style: {
|
|
1162
|
+
flex: 2,
|
|
1163
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1164
|
+
},
|
|
1165
|
+
children: formatTime(log.timestamp)
|
|
1166
|
+
}),
|
|
1167
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1168
|
+
style: {
|
|
1169
|
+
flex: 3,
|
|
1170
|
+
display: "flex",
|
|
1171
|
+
alignItems: "center",
|
|
1172
|
+
gap: 6
|
|
1173
|
+
},
|
|
1174
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
1175
|
+
width: 6,
|
|
1176
|
+
height: 6,
|
|
1177
|
+
borderRadius: "50%",
|
|
1178
|
+
flexShrink: 0,
|
|
1179
|
+
background: log.status === "claimed" ? "var(--dsw-alias-status-success, #52c41a)" : log.status === "already-claimed" ? "var(--dsw-alias-status-info, #1890ff)" : log.status === "no-campaign" ? "var(--dsw-alias-label-tertiary, #999)" : "var(--dsw-alias-status-error, #f5222d)"
|
|
1180
|
+
} }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: log.status === "claimed" ? t("autoCheckInStatusClaimed", { amount: log.amount ?? 100 }) : log.status === "already-claimed" ? t("autoCheckInStatusAlready") : log.status === "no-campaign" ? t("autoCheckInStatusNoCampaign") : t("autoCheckInStatusError", { message: log.message ?? "" }) })]
|
|
1181
|
+
}),
|
|
1182
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1183
|
+
style: {
|
|
1184
|
+
flex: 1,
|
|
1185
|
+
textAlign: "right",
|
|
1186
|
+
fontWeight: 600,
|
|
1187
|
+
color: log.amount ? "var(--dsw-alias-brand-primary)" : "inherit"
|
|
1188
|
+
},
|
|
1189
|
+
children: log.amount ? `+${log.amount}` : "-"
|
|
1190
|
+
})
|
|
1191
|
+
]
|
|
1192
|
+
}, log.id))]
|
|
1193
|
+
})
|
|
1194
|
+
]
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
562
1197
|
/** Render Qoder PAT state, quota, catalog, and context capacities as one expandable card. */
|
|
563
|
-
function QoderPluginCard(
|
|
1198
|
+
function QoderPluginCard(props) {
|
|
1199
|
+
const { t, scope, signedIn, variant, unified } = props;
|
|
564
1200
|
if (t === void 0) throw new Error("Qoder plugin card requires its translation function");
|
|
1201
|
+
const isUnified = unified === true;
|
|
1202
|
+
const liveSignIn = (0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSignInState);
|
|
1203
|
+
const [activeVariantId, setActiveVariantId] = (0, react.useState)("qoder");
|
|
1204
|
+
const currentVariant = isUnified ? activeVariantId === "qoder" ? QODER_CN_CARD : QODER_GLOBAL_CARD : variant ?? QODER_CN_CARD;
|
|
565
1205
|
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
1206
|
const [hovered, setHovered] = (0, react.useState)(false);
|
|
568
|
-
/** Keyboard focus on the header, reproducing the built-in's `:focus-visible` ring. */
|
|
569
1207
|
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
1208
|
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
|
-
*/
|
|
1209
|
+
const [signedInState, setSignedInState] = (0, react.useState)();
|
|
593
1210
|
const [readFailure, setReadFailure] = (0, react.useState)();
|
|
594
1211
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
595
|
-
/** The PAT draft in the input box. Never persisted; lives only in component state. */
|
|
596
1212
|
const [patDraft, setPatDraft] = (0, react.useState)("");
|
|
597
|
-
/** Whether the signed-in card is showing the replace-PAT entry. */
|
|
598
1213
|
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
1214
|
const [patBusy, setPatBusy] = (0, react.useState)(false);
|
|
601
|
-
/** Why the most recent PAT save/clear failed, when it did. */
|
|
602
1215
|
const [patError, setPatError] = (0, react.useState)();
|
|
603
|
-
/** The most recent PAT outcome the card should report beside the entry. */
|
|
604
1216
|
const [patNotice, setPatNotice] = (0, react.useState)();
|
|
605
|
-
/** The password input, so 「更换 PAT」 can focus and pre-clear it. */
|
|
606
1217
|
const patInput = (0, react.useRef)(null);
|
|
607
1218
|
const [tab, setTab] = (0, react.useState)("status");
|
|
1219
|
+
const [checkingIn, setCheckingIn] = (0, react.useState)(false);
|
|
1220
|
+
const [clearingLogs, setClearingLogs] = (0, react.useState)(false);
|
|
1221
|
+
const [checkInNotice, setCheckInNotice] = (0, react.useState)();
|
|
608
1222
|
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
1223
|
const readSeq = (0, react.useRef)(0);
|
|
617
|
-
/** Manual requests in flight, so unmount can abort them like the poll's. */
|
|
618
1224
|
const manualControllers = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
619
1225
|
(0, react.useEffect)(() => {
|
|
620
1226
|
mounted.current = true;
|
|
@@ -624,39 +1230,17 @@ window.__ModuleLoader__.load({
|
|
|
624
1230
|
manualControllers.current.clear();
|
|
625
1231
|
};
|
|
626
1232
|
}, []);
|
|
627
|
-
/** Register a manual request's controller so unmount aborts it. */
|
|
628
1233
|
const trackController = (0, react.useCallback)(() => {
|
|
629
1234
|
const controller = new AbortController();
|
|
630
1235
|
manualControllers.current.add(controller);
|
|
631
1236
|
return controller;
|
|
632
1237
|
}, []);
|
|
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
1238
|
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
1239
|
const refresh = (0, react.useCallback)(async (signal) => {
|
|
656
1240
|
const seq = ++readSeq.current;
|
|
657
1241
|
const current = () => mounted.current && signal?.aborted !== true && seq === readSeq.current;
|
|
658
1242
|
try {
|
|
659
|
-
const response = await fetch(
|
|
1243
|
+
const response = await fetch(currentVariant.statusPath, {
|
|
660
1244
|
headers: { accept: "application/json" },
|
|
661
1245
|
credentials: "same-origin",
|
|
662
1246
|
...signal === void 0 ? {} : { signal }
|
|
@@ -666,8 +1250,13 @@ window.__ModuleLoader__.load({
|
|
|
666
1250
|
if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
|
|
667
1251
|
if (!current()) return false;
|
|
668
1252
|
setStatus(value);
|
|
669
|
-
if (value.status === "signed-in")
|
|
670
|
-
|
|
1253
|
+
if (value.status === "signed-in") {
|
|
1254
|
+
setSignedInState(true);
|
|
1255
|
+
noteQuotaStatus(currentVariant.id, value);
|
|
1256
|
+
} else if (value.status === "signed-out") {
|
|
1257
|
+
setSignedInState(false);
|
|
1258
|
+
noteQuotaStatus(currentVariant.id, value);
|
|
1259
|
+
}
|
|
671
1260
|
setReadFailure(void 0);
|
|
672
1261
|
return true;
|
|
673
1262
|
} catch (error) {
|
|
@@ -681,17 +1270,28 @@ window.__ModuleLoader__.load({
|
|
|
681
1270
|
}
|
|
682
1271
|
return false;
|
|
683
1272
|
}
|
|
684
|
-
}, [
|
|
1273
|
+
}, [currentVariant.statusPath, t]);
|
|
685
1274
|
(0, react.useEffect)(() => {
|
|
686
1275
|
if (!open) return;
|
|
1276
|
+
setStatus(void 0);
|
|
1277
|
+
setSignedInState(void 0);
|
|
1278
|
+
setReadFailure(void 0);
|
|
1279
|
+
setPatDraft("");
|
|
1280
|
+
setReplacing(false);
|
|
1281
|
+
setPatError(void 0);
|
|
1282
|
+
setPatNotice(void 0);
|
|
687
1283
|
const controller = new AbortController();
|
|
688
1284
|
refresh(controller.signal);
|
|
689
1285
|
return () => {
|
|
690
1286
|
controller.abort();
|
|
691
1287
|
};
|
|
692
|
-
}, [
|
|
1288
|
+
}, [
|
|
1289
|
+
open,
|
|
1290
|
+
currentVariant.statusPath,
|
|
1291
|
+
refresh
|
|
1292
|
+
]);
|
|
693
1293
|
(0, react.useEffect)(() => {
|
|
694
|
-
if (!open ||
|
|
1294
|
+
if (!open || signedInState === false) return;
|
|
695
1295
|
const controller = new AbortController();
|
|
696
1296
|
const timer = window.setInterval(() => {
|
|
697
1297
|
refresh(controller.signal);
|
|
@@ -703,7 +1303,7 @@ window.__ModuleLoader__.load({
|
|
|
703
1303
|
}, [
|
|
704
1304
|
open,
|
|
705
1305
|
refresh,
|
|
706
|
-
|
|
1306
|
+
signedInState
|
|
707
1307
|
]);
|
|
708
1308
|
const manualRefresh = async () => {
|
|
709
1309
|
setBusy(true);
|
|
@@ -729,7 +1329,7 @@ window.__ModuleLoader__.load({
|
|
|
729
1329
|
setBusy(true);
|
|
730
1330
|
const controller = trackController();
|
|
731
1331
|
try {
|
|
732
|
-
const response = await fetch(
|
|
1332
|
+
const response = await fetch(currentVariant.probePath, {
|
|
733
1333
|
method: "POST",
|
|
734
1334
|
headers: {
|
|
735
1335
|
"Content-Type": "application/json",
|
|
@@ -753,11 +1353,88 @@ window.__ModuleLoader__.load({
|
|
|
753
1353
|
manualControllers.current.delete(controller);
|
|
754
1354
|
}
|
|
755
1355
|
}, [
|
|
1356
|
+
currentVariant.probePath,
|
|
1357
|
+
refresh,
|
|
1358
|
+
status,
|
|
1359
|
+
t,
|
|
1360
|
+
trackController
|
|
1361
|
+
]);
|
|
1362
|
+
const manualCheckIn = (0, react.useCallback)(async () => {
|
|
1363
|
+
const key = status?.status === "signed-in" ? status.probeKey : void 0;
|
|
1364
|
+
if (key === void 0) return;
|
|
1365
|
+
setCheckingIn(true);
|
|
1366
|
+
setCheckInNotice(void 0);
|
|
1367
|
+
const controller = trackController();
|
|
1368
|
+
try {
|
|
1369
|
+
const response = await fetch(currentVariant.probePath, {
|
|
1370
|
+
method: "POST",
|
|
1371
|
+
headers: {
|
|
1372
|
+
"Content-Type": "application/json",
|
|
1373
|
+
"X-Qoder-Probe-Key": key
|
|
1374
|
+
},
|
|
1375
|
+
credentials: "same-origin",
|
|
1376
|
+
signal: controller.signal,
|
|
1377
|
+
body: JSON.stringify({ action: "checkin" })
|
|
1378
|
+
});
|
|
1379
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1380
|
+
const result = await response.json();
|
|
1381
|
+
if (result.state === "claimed") setCheckInNotice(t("autoCheckInStatusClaimed", { amount: result.amount ?? 100 }));
|
|
1382
|
+
else if (result.state === "already-claimed") setCheckInNotice(t("autoCheckInStatusAlready"));
|
|
1383
|
+
else if (result.state === "no-campaign") setCheckInNotice(t("autoCheckInStatusNoCampaign"));
|
|
1384
|
+
else if (result.reason) setCheckInNotice(t("autoCheckInStatusError", { message: result.reason }));
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
|
|
1387
|
+
} finally {
|
|
1388
|
+
manualControllers.current.delete(controller);
|
|
1389
|
+
if (mounted.current) setCheckingIn(false);
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
await refresh(controller.signal);
|
|
1393
|
+
} finally {
|
|
1394
|
+
manualControllers.current.delete(controller);
|
|
1395
|
+
}
|
|
1396
|
+
}, [
|
|
1397
|
+
currentVariant.probePath,
|
|
1398
|
+
refresh,
|
|
1399
|
+
status,
|
|
1400
|
+
t,
|
|
1401
|
+
trackController
|
|
1402
|
+
]);
|
|
1403
|
+
const clearCheckInLogs = (0, react.useCallback)(async () => {
|
|
1404
|
+
const key = status?.status === "signed-in" ? status.probeKey : void 0;
|
|
1405
|
+
if (key === void 0) return;
|
|
1406
|
+
setClearingLogs(true);
|
|
1407
|
+
setCheckInNotice(void 0);
|
|
1408
|
+
const controller = trackController();
|
|
1409
|
+
try {
|
|
1410
|
+
const response = await fetch(currentVariant.probePath, {
|
|
1411
|
+
method: "POST",
|
|
1412
|
+
headers: {
|
|
1413
|
+
"Content-Type": "application/json",
|
|
1414
|
+
"X-Qoder-Probe-Key": key
|
|
1415
|
+
},
|
|
1416
|
+
credentials: "same-origin",
|
|
1417
|
+
signal: controller.signal,
|
|
1418
|
+
body: JSON.stringify({ action: "clear-checkin-logs" })
|
|
1419
|
+
});
|
|
1420
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (mounted.current && controller.signal.aborted !== true) setCheckInNotice(error instanceof Error ? error.message : t("requestFailed"));
|
|
1423
|
+
} finally {
|
|
1424
|
+
manualControllers.current.delete(controller);
|
|
1425
|
+
if (mounted.current) setClearingLogs(false);
|
|
1426
|
+
}
|
|
1427
|
+
try {
|
|
1428
|
+
await refresh(controller.signal);
|
|
1429
|
+
} finally {
|
|
1430
|
+
manualControllers.current.delete(controller);
|
|
1431
|
+
}
|
|
1432
|
+
}, [
|
|
1433
|
+
currentVariant.probePath,
|
|
756
1434
|
refresh,
|
|
757
1435
|
status,
|
|
758
1436
|
t,
|
|
759
|
-
trackController
|
|
760
|
-
variant.probePath
|
|
1437
|
+
trackController
|
|
761
1438
|
]);
|
|
762
1439
|
/**
|
|
763
1440
|
* Run one probe-route control action and refresh the card's state afterwards.
|
|
@@ -772,7 +1449,7 @@ window.__ModuleLoader__.load({
|
|
|
772
1449
|
setBusy(true);
|
|
773
1450
|
const controller = trackController();
|
|
774
1451
|
try {
|
|
775
|
-
const response = await fetch(
|
|
1452
|
+
const response = await fetch(currentVariant.probePath, {
|
|
776
1453
|
method: "POST",
|
|
777
1454
|
headers: {
|
|
778
1455
|
"Content-Type": "application/json",
|
|
@@ -799,11 +1476,11 @@ window.__ModuleLoader__.load({
|
|
|
799
1476
|
if (mounted.current) setBusy(false);
|
|
800
1477
|
}
|
|
801
1478
|
}, [
|
|
1479
|
+
currentVariant.probePath,
|
|
802
1480
|
refresh,
|
|
803
1481
|
status,
|
|
804
1482
|
t,
|
|
805
|
-
trackController
|
|
806
|
-
variant.probePath
|
|
1483
|
+
trackController
|
|
807
1484
|
]);
|
|
808
1485
|
/**
|
|
809
1486
|
* Validate and store the pasted PAT (or overwrite the stored one).
|
|
@@ -823,7 +1500,7 @@ window.__ModuleLoader__.load({
|
|
|
823
1500
|
setPatNotice(void 0);
|
|
824
1501
|
const controller = trackController();
|
|
825
1502
|
try {
|
|
826
|
-
const response = await fetch(
|
|
1503
|
+
const response = await fetch(currentVariant.authPath, {
|
|
827
1504
|
method: "POST",
|
|
828
1505
|
headers: {
|
|
829
1506
|
"Content-Type": "application/json",
|
|
@@ -850,6 +1527,7 @@ window.__ModuleLoader__.load({
|
|
|
850
1527
|
setPatDraft("");
|
|
851
1528
|
setReplacing(false);
|
|
852
1529
|
setPatNotice(t("patSaved"));
|
|
1530
|
+
noteQuotaSignIn(currentVariant.id, true);
|
|
853
1531
|
await refresh(controller.signal);
|
|
854
1532
|
} catch (error) {
|
|
855
1533
|
if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
|
|
@@ -859,12 +1537,12 @@ window.__ModuleLoader__.load({
|
|
|
859
1537
|
}
|
|
860
1538
|
}, [
|
|
861
1539
|
authKey,
|
|
1540
|
+
currentVariant.authPath,
|
|
862
1541
|
patBusy,
|
|
863
1542
|
patDraft,
|
|
864
1543
|
refresh,
|
|
865
1544
|
t,
|
|
866
|
-
trackController
|
|
867
|
-
variant.authPath
|
|
1545
|
+
trackController
|
|
868
1546
|
]);
|
|
869
1547
|
/** Remove the stored PAT; the host answer is `{ ok: true }`, then the card re-reads. */
|
|
870
1548
|
const clearPat = (0, react.useCallback)(async () => {
|
|
@@ -876,7 +1554,7 @@ window.__ModuleLoader__.load({
|
|
|
876
1554
|
setReplacing(false);
|
|
877
1555
|
const controller = trackController();
|
|
878
1556
|
try {
|
|
879
|
-
const response = await fetch(
|
|
1557
|
+
const response = await fetch(currentVariant.authPath, {
|
|
880
1558
|
method: "POST",
|
|
881
1559
|
headers: {
|
|
882
1560
|
"Content-Type": "application/json",
|
|
@@ -894,6 +1572,14 @@ window.__ModuleLoader__.load({
|
|
|
894
1572
|
return;
|
|
895
1573
|
}
|
|
896
1574
|
setPatDraft("");
|
|
1575
|
+
const signedOutDoc = {
|
|
1576
|
+
status: "signed-out",
|
|
1577
|
+
authKey: key
|
|
1578
|
+
};
|
|
1579
|
+
setStatus(signedOutDoc);
|
|
1580
|
+
setSignedInState(false);
|
|
1581
|
+
noteQuotaStatus(currentVariant.id, signedOutDoc);
|
|
1582
|
+
noteQuotaSignIn(currentVariant.id, false);
|
|
897
1583
|
await refresh(controller.signal);
|
|
898
1584
|
} catch (error) {
|
|
899
1585
|
if (mounted.current && controller.signal.aborted !== true) setPatError(t("patSaveFailed", { message: error instanceof Error ? error.message : t("requestFailed") }));
|
|
@@ -903,11 +1589,11 @@ window.__ModuleLoader__.load({
|
|
|
903
1589
|
}
|
|
904
1590
|
}, [
|
|
905
1591
|
authKey,
|
|
1592
|
+
currentVariant.authPath,
|
|
906
1593
|
patBusy,
|
|
907
1594
|
refresh,
|
|
908
1595
|
t,
|
|
909
|
-
trackController
|
|
910
|
-
variant.authPath
|
|
1596
|
+
trackController
|
|
911
1597
|
]);
|
|
912
1598
|
/**
|
|
913
1599
|
* 更换 PAT: open the entry over the signed-in document, pre-cleared and
|
|
@@ -925,7 +1611,6 @@ window.__ModuleLoader__.load({
|
|
|
925
1611
|
patInput.current?.focus();
|
|
926
1612
|
});
|
|
927
1613
|
}, []);
|
|
928
|
-
/** The PAT entry: guide line, password input, Save. Shared by both arms. */
|
|
929
1614
|
const patEntry = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
930
1615
|
style: {
|
|
931
1616
|
display: "flex",
|
|
@@ -935,7 +1620,7 @@ window.__ModuleLoader__.load({
|
|
|
935
1620
|
children: [
|
|
936
1621
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
937
1622
|
style: bodyStyle,
|
|
938
|
-
children: t(
|
|
1623
|
+
children: t(currentVariant.patGuideKey)
|
|
939
1624
|
}),
|
|
940
1625
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
941
1626
|
style: patRowStyle,
|
|
@@ -944,7 +1629,7 @@ window.__ModuleLoader__.load({
|
|
|
944
1629
|
ref: patInput,
|
|
945
1630
|
type: "password",
|
|
946
1631
|
value: patDraft,
|
|
947
|
-
placeholder: t(
|
|
1632
|
+
placeholder: t(currentVariant.patPlaceholderKey),
|
|
948
1633
|
"aria-label": t("patHeading"),
|
|
949
1634
|
disabled: patBusy,
|
|
950
1635
|
onChange: (event) => {
|
|
@@ -990,9 +1675,9 @@ window.__ModuleLoader__.load({
|
|
|
990
1675
|
})
|
|
991
1676
|
]
|
|
992
1677
|
});
|
|
993
|
-
const
|
|
1678
|
+
const cardTitle = isUnified ? t("unifiedTitle") : t(currentVariant.titleKey);
|
|
1679
|
+
const cardIntro = isUnified ? t("unifiedIntro") : t(currentVariant.introKey);
|
|
994
1680
|
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
1681
|
const patSummaryLine = (pat) => {
|
|
997
1682
|
const parts = [
|
|
998
1683
|
patSourceText(pat.source, t),
|
|
@@ -1004,11 +1689,16 @@ window.__ModuleLoader__.load({
|
|
|
1004
1689
|
children: parts.join(" · ")
|
|
1005
1690
|
});
|
|
1006
1691
|
};
|
|
1692
|
+
const reported = signedIn?.();
|
|
1693
|
+
const cnSignedIn = reported !== void 0 ? reported.cn : liveSignIn.cn;
|
|
1694
|
+
const globalSignedIn = reported !== void 0 ? reported.global : liveSignIn.global;
|
|
1695
|
+
const cnDotStatus = isUnified && activeVariantId === "qoder" ? status === void 0 ? "loading" : status.status : cnSignedIn ? "signed-in" : "signed-out";
|
|
1696
|
+
const globalDotStatus = isUnified && activeVariantId === "qoder-global" ? status === void 0 ? "loading" : status.status : globalSignedIn ? "signed-in" : "signed-out";
|
|
1007
1697
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
1008
1698
|
style: {
|
|
1009
|
-
...cardStyle
|
|
1010
|
-
...hovered ? cardHoverStyle
|
|
1011
|
-
...open ? cardOpenStyle
|
|
1699
|
+
...cardStyle,
|
|
1700
|
+
...hovered ? cardHoverStyle : {},
|
|
1701
|
+
...open ? cardOpenStyle : {}
|
|
1012
1702
|
},
|
|
1013
1703
|
onMouseEnter: () => {
|
|
1014
1704
|
setHovered(true);
|
|
@@ -1020,10 +1710,10 @@ window.__ModuleLoader__.load({
|
|
|
1020
1710
|
type: "button",
|
|
1021
1711
|
style: {
|
|
1022
1712
|
...headerStyle,
|
|
1023
|
-
...headerFocused ? headerFocusStyle
|
|
1713
|
+
...headerFocused ? headerFocusStyle : {}
|
|
1024
1714
|
},
|
|
1025
1715
|
"aria-expanded": open,
|
|
1026
|
-
"aria-label": `${t(open ? "collapse" : "expand")}: ${
|
|
1716
|
+
"aria-label": `${t(open ? "collapse" : "expand")}: ${cardTitle}`,
|
|
1027
1717
|
onClick: () => {
|
|
1028
1718
|
setOpen(!open);
|
|
1029
1719
|
},
|
|
@@ -1040,30 +1730,60 @@ window.__ModuleLoader__.load({
|
|
|
1040
1730
|
setHeaderFocused(false);
|
|
1041
1731
|
},
|
|
1042
1732
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1043
|
-
style: headTextStyle
|
|
1733
|
+
style: headTextStyle,
|
|
1044
1734
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1045
1735
|
style: nameStyle,
|
|
1046
|
-
children:
|
|
1736
|
+
children: cardTitle
|
|
1047
1737
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1048
1738
|
style: descriptionStyle,
|
|
1049
|
-
children:
|
|
1739
|
+
children: cardIntro
|
|
1050
1740
|
})]
|
|
1051
1741
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1052
1742
|
style: {
|
|
1053
|
-
...chevronStyle
|
|
1743
|
+
...chevronStyle,
|
|
1054
1744
|
transform: open ? "rotate(180deg)" : "none"
|
|
1055
1745
|
},
|
|
1056
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon
|
|
1746
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, {})
|
|
1057
1747
|
})]
|
|
1058
1748
|
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1059
|
-
style: cardBodyStyle
|
|
1749
|
+
style: cardBodyStyle,
|
|
1060
1750
|
children: [
|
|
1751
|
+
isUnified ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaSettingsContent, {
|
|
1752
|
+
t,
|
|
1753
|
+
scope,
|
|
1754
|
+
signedIn
|
|
1755
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1756
|
+
style: segmentedContainerStyle,
|
|
1757
|
+
role: "tablist",
|
|
1758
|
+
"aria-label": "Qoder Version Selection",
|
|
1759
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1760
|
+
type: "button",
|
|
1761
|
+
role: "tab",
|
|
1762
|
+
"aria-selected": activeVariantId === "qoder",
|
|
1763
|
+
style: segmentedTabItemStyle(activeVariantId === "qoder"),
|
|
1764
|
+
onClick: () => setActiveVariantId("qoder"),
|
|
1765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1766
|
+
style: dotStyle(cnDotStatus),
|
|
1767
|
+
"aria-hidden": "true"
|
|
1768
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabCN") })]
|
|
1769
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1770
|
+
type: "button",
|
|
1771
|
+
role: "tab",
|
|
1772
|
+
"aria-selected": activeVariantId === "qoder-global",
|
|
1773
|
+
style: segmentedTabItemStyle(activeVariantId === "qoder-global"),
|
|
1774
|
+
onClick: () => setActiveVariantId("qoder-global"),
|
|
1775
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1776
|
+
style: dotStyle(globalDotStatus),
|
|
1777
|
+
"aria-hidden": "true"
|
|
1778
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("variantTabGlobal") })]
|
|
1779
|
+
})]
|
|
1780
|
+
})] }) : null,
|
|
1061
1781
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1062
1782
|
style: quotaTitleStyle,
|
|
1063
1783
|
children: t("accountHeading")
|
|
1064
1784
|
}),
|
|
1065
1785
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1066
|
-
style: rowStyle
|
|
1786
|
+
style: rowStyle,
|
|
1067
1787
|
children: [
|
|
1068
1788
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1069
1789
|
style: statusStyle,
|
|
@@ -1100,7 +1820,7 @@ window.__ModuleLoader__.load({
|
|
|
1100
1820
|
})] })
|
|
1101
1821
|
]
|
|
1102
1822
|
}),
|
|
1103
|
-
readFailure === void 0 ||
|
|
1823
|
+
readFailure === void 0 || signedInState === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1104
1824
|
style: errorStyle,
|
|
1105
1825
|
children: t("statusRefreshFailed", { message: readFailure })
|
|
1106
1826
|
}),
|
|
@@ -1112,7 +1832,7 @@ window.__ModuleLoader__.load({
|
|
|
1112
1832
|
children: t("jobTokenRefreshed", { time: formatTime(status.jobTokenRefreshedAt) })
|
|
1113
1833
|
}),
|
|
1114
1834
|
status.catalog === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1115
|
-
style: rowStyle
|
|
1835
|
+
style: rowStyle,
|
|
1116
1836
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1117
1837
|
style: bodyStyle,
|
|
1118
1838
|
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")
|
|
@@ -1136,7 +1856,8 @@ window.__ModuleLoader__.load({
|
|
|
1136
1856
|
children: [
|
|
1137
1857
|
"status",
|
|
1138
1858
|
"context",
|
|
1139
|
-
"details"
|
|
1859
|
+
"details",
|
|
1860
|
+
"checkin"
|
|
1140
1861
|
].map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1141
1862
|
type: "button",
|
|
1142
1863
|
role: "tab",
|
|
@@ -1148,7 +1869,7 @@ window.__ModuleLoader__.load({
|
|
|
1148
1869
|
...tabStyle,
|
|
1149
1870
|
...tab === id ? tabActiveStyle : {}
|
|
1150
1871
|
},
|
|
1151
|
-
children: t(id === "status" ? "tabStatus" : id === "context" ? "tabContext" : "tabDetails")
|
|
1872
|
+
children: t(id === "status" ? "tabStatus" : id === "context" ? "tabContext" : id === "details" ? "tabDetails" : "tabCheckIn")
|
|
1152
1873
|
}, id))
|
|
1153
1874
|
}),
|
|
1154
1875
|
tab === "status" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1156,7 +1877,7 @@ window.__ModuleLoader__.load({
|
|
|
1156
1877
|
children: [status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1157
1878
|
style: quotaListStyle,
|
|
1158
1879
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1159
|
-
style: rowStyle
|
|
1880
|
+
style: rowStyle,
|
|
1160
1881
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1161
1882
|
style: quotaTitleStyle,
|
|
1162
1883
|
children: t("creditsHeading")
|
|
@@ -1186,7 +1907,7 @@ window.__ModuleLoader__.load({
|
|
|
1186
1907
|
});
|
|
1187
1908
|
}
|
|
1188
1909
|
})
|
|
1189
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1910
|
+
}) : tab === "details" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1190
1911
|
style: tabPanelStyle,
|
|
1191
1912
|
children: status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1192
1913
|
style: quotaListStyle,
|
|
@@ -1202,11 +1923,31 @@ window.__ModuleLoader__.load({
|
|
|
1202
1923
|
t
|
|
1203
1924
|
}, `${account.packageName}-${String(index)}`))]
|
|
1204
1925
|
})
|
|
1926
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1927
|
+
style: tabPanelStyle,
|
|
1928
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckInLogTable, {
|
|
1929
|
+
logs: status.checkIn?.logs,
|
|
1930
|
+
t,
|
|
1931
|
+
busy,
|
|
1932
|
+
checkingIn,
|
|
1933
|
+
clearing: clearingLogs,
|
|
1934
|
+
disabled: status.status !== "signed-in",
|
|
1935
|
+
...checkInNotice === void 0 ? {} : { notice: checkInNotice },
|
|
1936
|
+
onCheckIn: () => {
|
|
1937
|
+
manualCheckIn();
|
|
1938
|
+
},
|
|
1939
|
+
onRefresh: () => {
|
|
1940
|
+
manualRefresh();
|
|
1941
|
+
},
|
|
1942
|
+
onClear: () => {
|
|
1943
|
+
clearCheckInLogs();
|
|
1944
|
+
}
|
|
1945
|
+
})
|
|
1205
1946
|
})
|
|
1206
1947
|
] }) : null,
|
|
1207
1948
|
status?.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1208
1949
|
style: status.reason === void 0 ? bodyStyle : errorStyle,
|
|
1209
|
-
children: status.reason ?? t(
|
|
1950
|
+
children: status.reason ?? t(currentVariant.signedOutKey)
|
|
1210
1951
|
}), authKey === void 0 ? null : patEntry()] }) : null,
|
|
1211
1952
|
status?.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1212
1953
|
style: errorStyle,
|
|
@@ -1278,7 +2019,7 @@ window.__ModuleLoader__.load({
|
|
|
1278
2019
|
* it names the feature, so it should read as an annotation attached to the icon
|
|
1279
2020
|
* rather than compete with the adjacent model selector.
|
|
1280
2021
|
*/
|
|
1281
|
-
const labelStyle
|
|
2022
|
+
const labelStyle = {
|
|
1282
2023
|
fontSize: 11,
|
|
1283
2024
|
lineHeight: "16px",
|
|
1284
2025
|
color: "var(--dsw-alias-label-tertiary)"
|
|
@@ -1288,918 +2029,364 @@ window.__ModuleLoader__.load({
|
|
|
1288
2029
|
position: "absolute",
|
|
1289
2030
|
left: "50%",
|
|
1290
2031
|
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
|
|
2032
|
+
zIndex: 1e3,
|
|
2033
|
+
transform: "translateX(-50%)",
|
|
2034
|
+
padding: "4px 8px",
|
|
2035
|
+
borderRadius: 6,
|
|
2036
|
+
background: "var(--dsw-specific-tip, #1f2329)",
|
|
2037
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
2038
|
+
color: "var(--dsw-alias-label-primary, #fff)",
|
|
2039
|
+
fontSize: 12,
|
|
2040
|
+
lineHeight: "18px",
|
|
2041
|
+
whiteSpace: "nowrap",
|
|
2042
|
+
pointerEvents: "none"
|
|
1655
2043
|
};
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2044
|
+
/** Confirmation bubble: same anchor, but interactive and allowed to wrap. */
|
|
2045
|
+
const confirmStyle = {
|
|
2046
|
+
position: "absolute",
|
|
2047
|
+
right: 0,
|
|
2048
|
+
bottom: "calc(100% + 8px)",
|
|
2049
|
+
zIndex: 1001,
|
|
2050
|
+
display: "flex",
|
|
2051
|
+
flexDirection: "column",
|
|
2052
|
+
gap: 8,
|
|
2053
|
+
width: 260,
|
|
2054
|
+
padding: "10px 12px",
|
|
2055
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
2056
|
+
borderRadius: 8,
|
|
2057
|
+
background: "var(--dsw-alias-bg-layer-1, #fff)",
|
|
2058
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
2059
|
+
color: "var(--dsw-alias-label-primary)",
|
|
2060
|
+
fontSize: 12,
|
|
2061
|
+
lineHeight: "18px"
|
|
2062
|
+
};
|
|
2063
|
+
const confirmRowStyle = {
|
|
2064
|
+
display: "flex",
|
|
2065
|
+
justifyContent: "flex-end",
|
|
2066
|
+
gap: 8
|
|
2067
|
+
};
|
|
2068
|
+
const confirmButtonStyle = {
|
|
2069
|
+
padding: "3px 10px",
|
|
2070
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
2071
|
+
borderRadius: 6,
|
|
2072
|
+
background: "transparent",
|
|
2073
|
+
color: "inherit",
|
|
2074
|
+
font: "inherit",
|
|
2075
|
+
fontSize: 12,
|
|
2076
|
+
cursor: "pointer"
|
|
1659
2077
|
};
|
|
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
2078
|
/**
|
|
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.
|
|
2079
|
+
* Primary action inside the confirmation bubble.
|
|
1727
2080
|
*
|
|
1728
|
-
*
|
|
1729
|
-
* in
|
|
2081
|
+
* The fill and its text colour must come as a pair: `brand-primary` resolves to
|
|
2082
|
+
* a light accent in this theme, so hardcoding `color: #fff` on top of it renders
|
|
2083
|
+
* white-on-white. `button-primary-fill` + `label-primary-foreground` is the
|
|
2084
|
+
* theme's own pair for exactly this, and is what `dsh-codex-connect` uses for
|
|
2085
|
+
* the same job.
|
|
1730
2086
|
*/
|
|
1731
|
-
const
|
|
1732
|
-
|
|
1733
|
-
|
|
2087
|
+
const primaryButtonStyle = {
|
|
2088
|
+
...confirmButtonStyle,
|
|
2089
|
+
border: "1px solid var(--dsw-alias-button-primary-fill)",
|
|
2090
|
+
background: "var(--dsw-alias-button-primary-fill)",
|
|
2091
|
+
color: "var(--dsw-alias-label-primary-foreground)"
|
|
1734
2092
|
};
|
|
1735
2093
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
2094
|
+
* Result note: a single line + a dismiss button, anchored to the control's
|
|
2095
|
+
* right side. Smaller than the confirmation bubble because it carries an
|
|
2096
|
+
* *outcome*, not a *decision* — the work is done, the user only has to read
|
|
2097
|
+
* and dismiss.
|
|
1739
2098
|
*/
|
|
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
|
|
2099
|
+
const noteStyle = {
|
|
2100
|
+
position: "absolute",
|
|
2101
|
+
right: 0,
|
|
2102
|
+
bottom: "calc(100% + 8px)",
|
|
2103
|
+
zIndex: 1001,
|
|
2104
|
+
display: "flex",
|
|
2105
|
+
alignItems: "center",
|
|
2106
|
+
gap: 12,
|
|
2107
|
+
padding: "6px 10px",
|
|
2108
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
2109
|
+
borderRadius: 8,
|
|
2110
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
2111
|
+
boxShadow: "var(--dsw-shadow-lv2)",
|
|
2112
|
+
color: "var(--dsw-alias-label-primary)",
|
|
2113
|
+
fontSize: 12,
|
|
2114
|
+
lineHeight: "18px",
|
|
2115
|
+
whiteSpace: "nowrap"
|
|
1760
2116
|
};
|
|
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
2117
|
/**
|
|
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).
|
|
2118
|
+
* The note's dismiss action. Outlined rather than bare text: inside an already
|
|
2119
|
+
* bordered bubble, an unbordered word does not read as something you can click.
|
|
2120
|
+
* Matches the outlined pill convention the plugin's other secondary actions use.
|
|
1778
2121
|
*/
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2122
|
+
const noteDismissStyle = {
|
|
2123
|
+
padding: "2px 8px",
|
|
2124
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
2125
|
+
borderRadius: 6,
|
|
2126
|
+
background: "transparent",
|
|
2127
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
2128
|
+
font: "inherit",
|
|
2129
|
+
fontSize: 12,
|
|
2130
|
+
lineHeight: "18px",
|
|
2131
|
+
cursor: "pointer"
|
|
2132
|
+
};
|
|
1787
2133
|
/**
|
|
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.
|
|
2134
|
+
* The feature's static inline label. Deliberately not a state readout — see the
|
|
2135
|
+
* module comment.
|
|
1803
2136
|
*/
|
|
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
|
-
};
|
|
2137
|
+
function useLabel(t) {
|
|
2138
|
+
return t("probeLabel");
|
|
2139
|
+
}
|
|
2140
|
+
/** Pick the model's recorded observation out of the probe section. */
|
|
2141
|
+
function resultFor(status, model) {
|
|
2142
|
+
if (status.status !== "signed-in") return void 0;
|
|
2143
|
+
return status.probe?.results.find((result) => result.id === model);
|
|
1830
2144
|
}
|
|
1831
2145
|
/**
|
|
1832
|
-
*
|
|
2146
|
+
* The one-line tooltip: current state first, then what a click does — the same
|
|
2147
|
+
* two-part shape Fast Mode uses.
|
|
1833
2148
|
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1838
|
-
*
|
|
1839
|
-
*
|
|
1840
|
-
* which is the only thing the projection actually derives from.
|
|
2149
|
+
* A recorded result outranks a remembered failure. `failed` only means "the last
|
|
2150
|
+
* run from this control did not complete"; the host can record a result for the
|
|
2151
|
+
* same model at any time (a detection started from the settings card, another
|
|
2152
|
+
* conversation, or a finished sweep), and the levels the user paid for are the
|
|
2153
|
+
* more useful answer than the stale failure. Failure copy is what remains when
|
|
2154
|
+
* there is no result to report.
|
|
1841
2155
|
*/
|
|
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);
|
|
2156
|
+
function tooltipText(t, model, state) {
|
|
2157
|
+
if (state.busy) return t("probeRunning", { model });
|
|
2158
|
+
const result = state.result;
|
|
2159
|
+
if (result !== void 0) {
|
|
2160
|
+
if (result.validation === "validating" && result.efforts.length > 0) return t("probeTooltipVerified", { levels: result.efforts.join(" / ") });
|
|
2161
|
+
if (result.validation === "non-validating") return t("probeTooltipNotValidating");
|
|
2162
|
+
return t("probeTooltipRetry");
|
|
1861
2163
|
}
|
|
1862
|
-
return
|
|
2164
|
+
if (state.failed) return t("probeTooltipRetry");
|
|
2165
|
+
return t("probeTooltipIdle", { model });
|
|
1863
2166
|
}
|
|
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
|
-
});
|
|
2167
|
+
/** Model-independent shell: resolves the selection, then delegates per model. */
|
|
2168
|
+
function QoderProbeControl({ directory, t }) {
|
|
2169
|
+
const subscribe = (0, react.useCallback)((listener) => directory.subscribe(listener), [directory]);
|
|
2170
|
+
const snapshot = (0, react.useCallback)(() => directory.getSnapshot(), [directory]);
|
|
2171
|
+
const selection = (0, react.useSyncExternalStore)(subscribe, snapshot, snapshot).current;
|
|
2172
|
+
const card = selection == null ? void 0 : cardVariantFor(selection.provider);
|
|
2173
|
+
const key = card === void 0 || selection == null ? void 0 : `${card.id}:${selection.model}`;
|
|
2174
|
+
return card === void 0 || selection == null || key === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelProbe, {
|
|
2175
|
+
model: selection.model,
|
|
2176
|
+
card,
|
|
2177
|
+
label: useLabel(t),
|
|
2178
|
+
t
|
|
2179
|
+
}, key);
|
|
1894
2180
|
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
const
|
|
1898
|
-
const
|
|
1899
|
-
const [
|
|
1900
|
-
const [
|
|
1901
|
-
const [
|
|
1902
|
-
const
|
|
2181
|
+
function ModelProbe({ model, card, label, t }) {
|
|
2182
|
+
const [status, setStatus] = (0, react.useState)();
|
|
2183
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
2184
|
+
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
2185
|
+
const [tooltipVisible, setTooltipVisible] = (0, react.useState)(false);
|
|
2186
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
2187
|
+
const [note, setNote] = (0, react.useState)();
|
|
2188
|
+
const inFlight = (0, react.useRef)(false);
|
|
2189
|
+
const mounted = (0, react.useRef)(false);
|
|
2190
|
+
const readSeq = (0, react.useRef)(0);
|
|
2191
|
+
const tooltipId = (0, react.useId)();
|
|
2192
|
+
const refresh = (0, react.useCallback)(async (signal) => {
|
|
2193
|
+
const seq = ++readSeq.current;
|
|
2194
|
+
const response = await fetch(card.statusPath, {
|
|
2195
|
+
credentials: "same-origin",
|
|
2196
|
+
headers: { accept: "application/json" },
|
|
2197
|
+
...signal === void 0 ? {} : { signal }
|
|
2198
|
+
});
|
|
2199
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
2200
|
+
const value = await response.json().catch(() => void 0);
|
|
2201
|
+
if (!isQoderWebStatus(value)) throw new Error(t("statusResponseInvalid"));
|
|
2202
|
+
if (mounted.current && !signal?.aborted && seq === readSeq.current) setStatus(value);
|
|
2203
|
+
}, [card.statusPath, t]);
|
|
1903
2204
|
(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
|
-
}
|
|
2205
|
+
mounted.current = true;
|
|
2206
|
+
const controller = new AbortController();
|
|
2207
|
+
const load = () => {
|
|
2208
|
+
refresh(controller.signal).catch(() => {});
|
|
1914
2209
|
};
|
|
1915
|
-
(
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
cn: cn === true,
|
|
1919
|
-
global: global === true
|
|
1920
|
-
});
|
|
1921
|
-
})();
|
|
2210
|
+
load();
|
|
2211
|
+
const timer = window.setInterval(load, RECONCILE_MS);
|
|
2212
|
+
window.addEventListener("focus", load);
|
|
1922
2213
|
return () => {
|
|
1923
|
-
|
|
2214
|
+
mounted.current = false;
|
|
2215
|
+
controller.abort();
|
|
2216
|
+
window.clearInterval(timer);
|
|
2217
|
+
window.removeEventListener("focus", load);
|
|
1924
2218
|
};
|
|
1925
|
-
}, [
|
|
1926
|
-
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2219
|
+
}, [refresh]);
|
|
2220
|
+
const probe = status?.status === "signed-in" ? status.probe : void 0;
|
|
2221
|
+
const key = status?.status === "signed-in" ? status.probeKey : void 0;
|
|
2222
|
+
const result = status === void 0 ? void 0 : resultFor(status, model);
|
|
2223
|
+
const visible = probe?.candidates.includes(model) === true || result !== void 0;
|
|
2224
|
+
(0, react.useEffect)(() => {
|
|
2225
|
+
if (result !== void 0) setFailed(false);
|
|
2226
|
+
}, [result]);
|
|
2227
|
+
(0, react.useEffect)(() => {
|
|
2228
|
+
setConfirming(false);
|
|
2229
|
+
setNote(void 0);
|
|
2230
|
+
}, [model]);
|
|
2231
|
+
const detect = async () => {
|
|
2232
|
+
if (key === void 0 || inFlight.current || probe?.running === true) return;
|
|
2233
|
+
inFlight.current = true;
|
|
2234
|
+
setNote(void 0);
|
|
2235
|
+
setConfirming(false);
|
|
2236
|
+
setBusy(true);
|
|
2237
|
+
setFailed(false);
|
|
2238
|
+
try {
|
|
2239
|
+
const response = await fetch(card.probePath, {
|
|
2240
|
+
method: "POST",
|
|
2241
|
+
credentials: "same-origin",
|
|
2242
|
+
headers: {
|
|
2243
|
+
"Content-Type": "application/json",
|
|
2244
|
+
"X-Qoder-Probe-Key": key
|
|
2245
|
+
},
|
|
2246
|
+
body: JSON.stringify({
|
|
2247
|
+
action: "probe",
|
|
2248
|
+
model
|
|
2249
|
+
})
|
|
2250
|
+
});
|
|
2251
|
+
const body = await response.json();
|
|
2252
|
+
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");
|
|
2253
|
+
if (mounted.current) {
|
|
2254
|
+
const completed = {
|
|
2255
|
+
id: model,
|
|
2256
|
+
name: model,
|
|
2257
|
+
validation: body.validation,
|
|
2258
|
+
efforts: body.efforts,
|
|
2259
|
+
probedAt: Date.now()
|
|
2260
|
+
};
|
|
2261
|
+
setNote(completed);
|
|
2262
|
+
}
|
|
2263
|
+
refresh().catch(() => {});
|
|
2264
|
+
} catch {
|
|
2265
|
+
if (mounted.current) setFailed(true);
|
|
2266
|
+
} finally {
|
|
2267
|
+
inFlight.current = false;
|
|
2268
|
+
if (mounted.current) setBusy(false);
|
|
2269
|
+
}
|
|
1937
2270
|
};
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2271
|
+
if (!visible) return null;
|
|
2272
|
+
const text = tooltipText(t, model, {
|
|
2273
|
+
busy,
|
|
2274
|
+
result,
|
|
2275
|
+
failed
|
|
2276
|
+
});
|
|
2277
|
+
const disabled = busy || probe?.running === true || key === void 0;
|
|
2278
|
+
const showTooltip = tooltipVisible && !confirming && note === void 0;
|
|
2279
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2280
|
+
style: wrapperStyle,
|
|
1945
2281
|
onMouseEnter: () => {
|
|
1946
|
-
|
|
2282
|
+
setTooltipVisible(true);
|
|
1947
2283
|
},
|
|
1948
2284
|
onMouseLeave: () => {
|
|
1949
|
-
|
|
2285
|
+
setTooltipVisible(false);
|
|
1950
2286
|
},
|
|
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", {
|
|
2287
|
+
children: [
|
|
2288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2289
|
+
type: "button",
|
|
2290
|
+
"aria-label": text,
|
|
2291
|
+
"aria-describedby": showTooltip ? tooltipId : void 0,
|
|
2292
|
+
"aria-busy": busy,
|
|
2293
|
+
"aria-expanded": confirming,
|
|
2294
|
+
disabled,
|
|
2295
|
+
onClick: () => {
|
|
2296
|
+
setConfirming(true);
|
|
2297
|
+
},
|
|
2298
|
+
onFocus: () => {
|
|
2299
|
+
setTooltipVisible(true);
|
|
2300
|
+
},
|
|
2301
|
+
onBlur: () => {
|
|
2302
|
+
setTooltipVisible(false);
|
|
2303
|
+
},
|
|
1982
2304
|
style: {
|
|
1983
|
-
...
|
|
1984
|
-
|
|
2305
|
+
...buttonStyle,
|
|
2306
|
+
opacity: disabled && !confirming ? .6 : 1,
|
|
2307
|
+
cursor: disabled ? "default" : "pointer"
|
|
1985
2308
|
},
|
|
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
|
-
|
|
2309
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2310
|
+
width: "16",
|
|
2311
|
+
height: "16",
|
|
2312
|
+
viewBox: "0 0 24 24",
|
|
2313
|
+
fill: "none",
|
|
2314
|
+
stroke: "currentColor",
|
|
2315
|
+
strokeWidth: "1.6",
|
|
2316
|
+
"aria-hidden": "true",
|
|
2317
|
+
focusable: "false",
|
|
2318
|
+
children: [
|
|
2319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2320
|
+
cx: "12",
|
|
2321
|
+
cy: "12",
|
|
2322
|
+
r: "9"
|
|
2323
|
+
}),
|
|
2324
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2325
|
+
cx: "12",
|
|
2326
|
+
cy: "12",
|
|
2327
|
+
r: "4"
|
|
2328
|
+
}),
|
|
2329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 12 20 4" }),
|
|
2330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2331
|
+
cx: "12",
|
|
2332
|
+
cy: "12",
|
|
2333
|
+
r: "1"
|
|
2334
|
+
})
|
|
2335
|
+
]
|
|
2336
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2337
|
+
style: labelStyle,
|
|
2338
|
+
children: label
|
|
2339
|
+
})]
|
|
2340
|
+
}),
|
|
2341
|
+
showTooltip && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2342
|
+
id: tooltipId,
|
|
2343
|
+
role: "tooltip",
|
|
2344
|
+
style: tooltipStyle,
|
|
2345
|
+
children: text
|
|
2346
|
+
}),
|
|
2347
|
+
confirming && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2348
|
+
style: confirmStyle,
|
|
2349
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("probeBubbleBody") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2350
|
+
style: confirmRowStyle,
|
|
2351
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2352
|
+
type: "button",
|
|
2353
|
+
style: confirmButtonStyle,
|
|
2354
|
+
onClick: () => {
|
|
2355
|
+
setConfirming(false);
|
|
2356
|
+
},
|
|
2357
|
+
children: t("cancel")
|
|
2358
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2359
|
+
type: "button",
|
|
2360
|
+
style: primaryButtonStyle,
|
|
2361
|
+
onClick: () => {
|
|
2362
|
+
detect();
|
|
2363
|
+
},
|
|
2364
|
+
children: t("probeConfirmAction")
|
|
2039
2365
|
})]
|
|
2040
|
-
})
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2366
|
+
})]
|
|
2367
|
+
}),
|
|
2368
|
+
note === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2369
|
+
role: "status",
|
|
2370
|
+
"aria-live": "polite",
|
|
2371
|
+
style: noteStyle,
|
|
2372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: noteText(t, note) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2373
|
+
type: "button",
|
|
2374
|
+
style: noteDismissStyle,
|
|
2375
|
+
onClick: () => {
|
|
2376
|
+
setNote(void 0);
|
|
2377
|
+
},
|
|
2378
|
+
children: t("probeNoteDismiss")
|
|
2379
|
+
})]
|
|
2380
|
+
})
|
|
2381
|
+
]
|
|
2047
2382
|
});
|
|
2048
2383
|
}
|
|
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
|
-
});
|
|
2384
|
+
/** Compose the one-line outcome string the note bubble shows. */
|
|
2385
|
+
function noteText(t, result) {
|
|
2386
|
+
if (result.validation === "validating" && result.efforts.length > 0) return t("probeNoteVerified", { levels: result.efforts.join(" / ") });
|
|
2387
|
+
if (result.validation === "non-validating") return t("probeNoteNotValidating");
|
|
2388
|
+
return t("probeNoteUnknown");
|
|
2063
2389
|
}
|
|
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
2390
|
//#endregion
|
|
2204
2391
|
//#region src/client/quota-merge.ts
|
|
2205
2392
|
/**
|
|
@@ -2441,6 +2628,7 @@ window.__ModuleLoader__.load({
|
|
|
2441
2628
|
(0, react.useSyncExternalStore)(onQuotaSettingsChange, quotaSettingsRevision);
|
|
2442
2629
|
const enabled = variantId === "qoder" ? quotaToggles().cn : quotaToggles().global;
|
|
2443
2630
|
const status = quotaStatus(variantId);
|
|
2631
|
+
const signedIn = status?.status === "signed-in";
|
|
2444
2632
|
(0, react.useEffect)(() => {
|
|
2445
2633
|
if (statusPath === void 0 || !enabled) return void 0;
|
|
2446
2634
|
let disposed = false;
|
|
@@ -2464,21 +2652,22 @@ window.__ModuleLoader__.load({
|
|
|
2464
2652
|
if (!disposed) setFailed(true);
|
|
2465
2653
|
}
|
|
2466
2654
|
};
|
|
2655
|
+
const isHidden = () => typeof document !== "undefined" && document.hidden;
|
|
2467
2656
|
const loop = () => {
|
|
2468
|
-
if (
|
|
2657
|
+
if (isHidden()) return;
|
|
2469
2658
|
refresh();
|
|
2470
2659
|
};
|
|
2471
2660
|
timer = window.setInterval(loop, Math.max(6e4, quotaPollMs()));
|
|
2472
2661
|
refresh();
|
|
2473
2662
|
const onVisible = () => {
|
|
2474
|
-
if (!
|
|
2663
|
+
if (!isHidden()) loop();
|
|
2475
2664
|
};
|
|
2476
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
2665
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisible);
|
|
2477
2666
|
return () => {
|
|
2478
2667
|
disposed = true;
|
|
2479
2668
|
controller.abort();
|
|
2480
2669
|
if (timer !== void 0) window.clearInterval(timer);
|
|
2481
|
-
document.removeEventListener("visibilitychange", onVisible);
|
|
2670
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisible);
|
|
2482
2671
|
};
|
|
2483
2672
|
}, [
|
|
2484
2673
|
statusPath,
|
|
@@ -2507,7 +2696,11 @@ window.__ModuleLoader__.load({
|
|
|
2507
2696
|
className: "qdp-railButton",
|
|
2508
2697
|
"aria-label": title,
|
|
2509
2698
|
title,
|
|
2510
|
-
|
|
2699
|
+
disabled: !signedIn,
|
|
2700
|
+
onClick: () => {
|
|
2701
|
+
if (!signedIn) return;
|
|
2702
|
+
open?.();
|
|
2703
|
+
},
|
|
2511
2704
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Ring, {
|
|
2512
2705
|
percent: ringPercent,
|
|
2513
2706
|
warn: ringWarn,
|
|
@@ -2519,7 +2712,11 @@ window.__ModuleLoader__.load({
|
|
|
2519
2712
|
className: "qdp-foot",
|
|
2520
2713
|
"aria-label": title,
|
|
2521
2714
|
title,
|
|
2522
|
-
|
|
2715
|
+
disabled: !signedIn,
|
|
2716
|
+
onClick: () => {
|
|
2717
|
+
if (!signedIn) return;
|
|
2718
|
+
open?.();
|
|
2719
|
+
},
|
|
2523
2720
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2524
2721
|
className: "qdp-footTop",
|
|
2525
2722
|
children: [
|
|
@@ -2983,6 +3180,10 @@ window.__ModuleLoader__.load({
|
|
|
2983
3180
|
intro: "Use the models from your Qoder (China) account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
|
|
2984
3181
|
titleAI: "Qoder Global",
|
|
2985
3182
|
introAI: "Use the models from your Qoder Global account directly in DSH — paste a Personal Access Token, no browser sign-in flow.",
|
|
3183
|
+
unifiedTitle: "Qoder",
|
|
3184
|
+
unifiedIntro: "Manage Qoder (China) and Qoder Global models, credentials, and sidebar quota displays.",
|
|
3185
|
+
variantTabCN: "China",
|
|
3186
|
+
variantTabGlobal: "Global",
|
|
2986
3187
|
expand: "Expand",
|
|
2987
3188
|
collapse: "Collapse",
|
|
2988
3189
|
loading: "Loading account…",
|
|
@@ -3012,6 +3213,17 @@ window.__ModuleLoader__.load({
|
|
|
3012
3213
|
tabStatus: "Status",
|
|
3013
3214
|
tabContext: "Context window",
|
|
3014
3215
|
tabDetails: "Credit details",
|
|
3216
|
+
tabCheckIn: "Check-in log",
|
|
3217
|
+
checkInLogTime: "Check-in time",
|
|
3218
|
+
checkInLogResult: "Result",
|
|
3219
|
+
checkInLogAmount: "Credits",
|
|
3220
|
+
checkInLogEmpty: "No check-in logs recorded yet.",
|
|
3221
|
+
checkInNow: "Check in now",
|
|
3222
|
+
checkInChecking: "Checking in…",
|
|
3223
|
+
checkInRefresh: "Refresh",
|
|
3224
|
+
checkInRefreshing: "Refreshing…",
|
|
3225
|
+
checkInClear: "Clear logs",
|
|
3226
|
+
checkInClearing: "Clearing…",
|
|
3015
3227
|
creditsDetailHeading: "By package",
|
|
3016
3228
|
creditsUsed: "Used this cycle: {percent}%",
|
|
3017
3229
|
creditsTotalUnlimited: "Total: Unlimited",
|
|
@@ -3074,6 +3286,14 @@ window.__ModuleLoader__.load({
|
|
|
3074
3286
|
quotaToggleCN: "Show China credit card",
|
|
3075
3287
|
quotaToggleGlobal: "Show global credit card",
|
|
3076
3288
|
quotaToggleHint: "Show this account’s remaining credit in the sidebar footer.",
|
|
3289
|
+
autoCheckInCN: "Qoder (China) daily auto check-in (100 Credits)",
|
|
3290
|
+
autoCheckInGlobal: "Qoder Global (International) daily auto check-in",
|
|
3291
|
+
autoCheckInHintCN: "Automatically check in daily at 10:00 (UTC+8) to claim 100 Credits for China account.",
|
|
3292
|
+
autoCheckInHintGlobal: "Automatically check in daily at 10:00 (UTC+8) to claim available perks for Global account.",
|
|
3293
|
+
autoCheckInStatusClaimed: "Checked in today (+{amount} Credits)",
|
|
3294
|
+
autoCheckInStatusAlready: "Already checked in today",
|
|
3295
|
+
autoCheckInStatusNoCampaign: "No active benefit campaign today",
|
|
3296
|
+
autoCheckInStatusError: "Auto check-in error: {message}",
|
|
3077
3297
|
quotaSignInRequired: "Save a PAT for this variant first to enable this card.",
|
|
3078
3298
|
quotaPollLabel: "Refresh interval",
|
|
3079
3299
|
quotaPollHint: "Applies to both quota cards. Longer is kinder to the billing endpoint.",
|
|
@@ -3113,6 +3333,10 @@ window.__ModuleLoader__.load({
|
|
|
3113
3333
|
intro: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder 国内版的模型。",
|
|
3114
3334
|
titleAI: "Qoder Global(国际版)",
|
|
3115
3335
|
introAI: "保存个人访问令牌(PAT)后,即可在 DSH 中直接使用 Qoder Global 的模型。",
|
|
3336
|
+
unifiedTitle: "Qoder",
|
|
3337
|
+
unifiedIntro: "统一管理 Qoder(国内版)与 Qoder Global(国际版)模型、凭证及侧栏额度展示。",
|
|
3338
|
+
variantTabCN: "国内版",
|
|
3339
|
+
variantTabGlobal: "国际版",
|
|
3116
3340
|
expand: "展开",
|
|
3117
3341
|
collapse: "收起",
|
|
3118
3342
|
loading: "正在读取账号…",
|
|
@@ -3142,6 +3366,17 @@ window.__ModuleLoader__.load({
|
|
|
3142
3366
|
tabStatus: "状态",
|
|
3143
3367
|
tabContext: "上下文窗口",
|
|
3144
3368
|
tabDetails: "额度明细",
|
|
3369
|
+
tabCheckIn: "签到日志",
|
|
3370
|
+
checkInLogTime: "签到时间",
|
|
3371
|
+
checkInLogResult: "签到结果",
|
|
3372
|
+
checkInLogAmount: "获得额度",
|
|
3373
|
+
checkInLogEmpty: "暂无签到日志记录。",
|
|
3374
|
+
checkInNow: "立即签到",
|
|
3375
|
+
checkInChecking: "正在签到…",
|
|
3376
|
+
checkInRefresh: "刷新",
|
|
3377
|
+
checkInRefreshing: "正在刷新…",
|
|
3378
|
+
checkInClear: "清空日志",
|
|
3379
|
+
checkInClearing: "正在清空…",
|
|
3145
3380
|
creditsDetailHeading: "按套餐",
|
|
3146
3381
|
creditsUsed: "本轮已用 {percent}%",
|
|
3147
3382
|
creditsTotalUnlimited: "合计:不限额",
|
|
@@ -3204,6 +3439,14 @@ window.__ModuleLoader__.load({
|
|
|
3204
3439
|
quotaToggleCN: "展示国内版额度",
|
|
3205
3440
|
quotaToggleGlobal: "展示国际版额度",
|
|
3206
3441
|
quotaToggleHint: "在侧栏底部展示该账号的剩余额度。",
|
|
3442
|
+
autoCheckInCN: "Qoder(国内版)每日自动签到",
|
|
3443
|
+
autoCheckInGlobal: "Qoder Global(国际版)每日自动签到",
|
|
3444
|
+
autoCheckInHintCN: "每天 10:00 (UTC+8) 自动签到领取 100 Credits 算力额度。",
|
|
3445
|
+
autoCheckInHintGlobal: "每天 10:00 (UTC+8) 自动检测并领取国际版可用福利额度。",
|
|
3446
|
+
autoCheckInStatusClaimed: "今日已自动签到(+{amount} Credits)",
|
|
3447
|
+
autoCheckInStatusAlready: "今日已完成签到",
|
|
3448
|
+
autoCheckInStatusNoCampaign: "今日无可用签到福利活动",
|
|
3449
|
+
autoCheckInStatusError: "自动签到出错:{message}",
|
|
3207
3450
|
quotaSignInRequired: "请先为该变体保存 PAT,再开启此开关。",
|
|
3208
3451
|
quotaPollLabel: "刷新间隔",
|
|
3209
3452
|
quotaPollHint: "对两张额度卡片同时生效。间隔越长对计费接口越友好。",
|
|
@@ -3299,10 +3542,6 @@ window.__ModuleLoader__.load({
|
|
|
3299
3542
|
en
|
|
3300
3543
|
}), "dsh-qoder-connect: settings copy");
|
|
3301
3544
|
const t = ctx.locale.bind(namespace);
|
|
3302
|
-
const quotaSettingsInjected = {
|
|
3303
|
-
t,
|
|
3304
|
-
signedIn: () => quotaSignInState()
|
|
3305
|
-
};
|
|
3306
3545
|
let quotaScope;
|
|
3307
3546
|
try {
|
|
3308
3547
|
const scope = ctx.settingsScope.bind({ namespace: "qoder-quota" });
|
|
@@ -3319,20 +3558,13 @@ window.__ModuleLoader__.load({
|
|
|
3319
3558
|
}
|
|
3320
3559
|
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
3321
3560
|
name: "settings.plugin.item",
|
|
3322
|
-
key: "qoder
|
|
3561
|
+
key: "qoder",
|
|
3323
3562
|
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
3563
|
inject: () => ({
|
|
3334
3564
|
t,
|
|
3335
|
-
|
|
3565
|
+
scope: quotaScope,
|
|
3566
|
+
signedIn: () => quotaSignInState(),
|
|
3567
|
+
unified: true
|
|
3336
3568
|
})
|
|
3337
3569
|
}, QoderPluginCard));
|
|
3338
3570
|
const QUOTA_PANEL_ID = "qoder-quota-panel";
|