@mars-sea/dsh-commandcode-provider 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +28 -2
- package/README.zh-CN.md +28 -2
- package/lib/client.js +619 -95
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +236 -23
- package/lib/index.js +491 -95
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -105,7 +105,8 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
textField("workingDir"),
|
|
106
106
|
numberField$1("requestTimeoutMs"),
|
|
107
107
|
numberField$1("streamIdleTimeoutMs"),
|
|
108
|
-
booleanField$1("filterModelsByPlan")
|
|
108
|
+
booleanField$1("filterModelsByPlan"),
|
|
109
|
+
textField("activeAccount")
|
|
109
110
|
];
|
|
110
111
|
/**
|
|
111
112
|
* Controller bridging the `llm-commandcode` scope and the credentials domain
|
|
@@ -121,11 +122,18 @@ window.__ModuleLoader__.load({
|
|
|
121
122
|
disposers = [];
|
|
122
123
|
disposed = false;
|
|
123
124
|
defaultWorkingDir;
|
|
124
|
-
credential
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
/** The credential reference the default account resolves. */
|
|
126
|
+
credentialRef = DEFAULT_API_KEY_REF;
|
|
127
|
+
/** Host-reported configured/writable state per credential reference. */
|
|
128
|
+
credentialStates = /* @__PURE__ */ new Map();
|
|
129
|
+
/** Staged account additions (not yet saved). */
|
|
130
|
+
addedAccounts = [];
|
|
131
|
+
/** Staged removals of stored extra accounts, by credential reference. */
|
|
132
|
+
removedRefs = /* @__PURE__ */ new Set();
|
|
133
|
+
/** Staged label drafts, by credential reference. */
|
|
134
|
+
labelDrafts = /* @__PURE__ */ new Map();
|
|
135
|
+
/** Staged key drafts, by credential reference (blank = keep stored key). */
|
|
136
|
+
keyDrafts = /* @__PURE__ */ new Map();
|
|
129
137
|
saving = false;
|
|
130
138
|
failed = false;
|
|
131
139
|
/**
|
|
@@ -139,6 +147,7 @@ window.__ModuleLoader__.load({
|
|
|
139
147
|
this.api = api;
|
|
140
148
|
this.disposers.push(scope.subscribe(() => {
|
|
141
149
|
this.recomputeCredentialRef();
|
|
150
|
+
this.describeAll();
|
|
142
151
|
this.publish();
|
|
143
152
|
}));
|
|
144
153
|
if (hostDescription !== void 0) {
|
|
@@ -153,7 +162,7 @@ window.__ModuleLoader__.load({
|
|
|
153
162
|
}));
|
|
154
163
|
}
|
|
155
164
|
this.recomputeCredentialRef();
|
|
156
|
-
this.
|
|
165
|
+
this.describeAll();
|
|
157
166
|
}
|
|
158
167
|
/** Release every subscription held on external sources. Idempotent. */
|
|
159
168
|
dispose() {
|
|
@@ -172,13 +181,9 @@ window.__ModuleLoader__.load({
|
|
|
172
181
|
recomputeCredentialRef() {
|
|
173
182
|
const snapshot = this.scope.getSnapshot();
|
|
174
183
|
const named = typeof snapshot.value?.apiKeyEnv === "string" && snapshot.value.apiKeyEnv.length > 0 ? snapshot.value.apiKeyEnv : DEFAULT_API_KEY_REF;
|
|
175
|
-
if (named === this.
|
|
176
|
-
this.
|
|
177
|
-
|
|
178
|
-
configured: false,
|
|
179
|
-
writable: true
|
|
180
|
-
};
|
|
181
|
-
this.readCredential();
|
|
184
|
+
if (named === this.credentialRef) return;
|
|
185
|
+
this.credentialRef = named;
|
|
186
|
+
this.credentialStates.delete(named);
|
|
182
187
|
}
|
|
183
188
|
/** Subscribe to state projections. @returns the disposer. */
|
|
184
189
|
subscribe(listener) {
|
|
@@ -189,11 +194,14 @@ window.__ModuleLoader__.load({
|
|
|
189
194
|
state() {
|
|
190
195
|
const snapshot = this.scope.getSnapshot();
|
|
191
196
|
const plan = this.plan();
|
|
197
|
+
const credential = this.credentialStates.get(this.credentialRef);
|
|
198
|
+
const accounts = this.effectiveAccounts();
|
|
192
199
|
return {
|
|
193
200
|
available: snapshot.status === "ready",
|
|
194
201
|
writable: snapshot.writable,
|
|
195
|
-
apiKeyConfigured:
|
|
196
|
-
|
|
202
|
+
apiKeyConfigured: credential?.configured ?? false,
|
|
203
|
+
anyAccountConfigured: (credential?.configured ?? false) || accounts.some((account) => account.configured),
|
|
204
|
+
apiKeyWritable: credential?.writable ?? true,
|
|
197
205
|
apiKey: {
|
|
198
206
|
text: this.staged.get("apiKey")?.text ?? "",
|
|
199
207
|
clear: false,
|
|
@@ -206,12 +214,60 @@ window.__ModuleLoader__.load({
|
|
|
206
214
|
requestTimeoutMs: this.field("requestTimeoutMs"),
|
|
207
215
|
streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
|
|
208
216
|
filterModelsByPlan: this.field("filterModelsByPlan"),
|
|
209
|
-
|
|
217
|
+
activeAccount: this.field("activeAccount"),
|
|
218
|
+
accounts,
|
|
219
|
+
accountsRemoving: [...this.removedRefs],
|
|
220
|
+
dirty: plan.length > 0 || this.accountsDirty(),
|
|
210
221
|
invalid: plan.some((item) => item.run === void 0),
|
|
211
222
|
saving: this.saving,
|
|
212
223
|
failed: this.failed
|
|
213
224
|
};
|
|
214
225
|
}
|
|
226
|
+
/** Stage a new extra account (saved on the next `save()`). */
|
|
227
|
+
addAccount() {
|
|
228
|
+
const used = /* @__PURE__ */ new Set([
|
|
229
|
+
this.credentialRef,
|
|
230
|
+
...this.storedExtras().map((extra) => extra.ref),
|
|
231
|
+
...this.addedAccounts.map((extra) => extra.ref)
|
|
232
|
+
]);
|
|
233
|
+
let n = 2;
|
|
234
|
+
while (used.has(`COMMANDCODE_API_KEY_${n}`)) n += 1;
|
|
235
|
+
const index = this.storedExtras().length + this.addedAccounts.length + 2;
|
|
236
|
+
this.addedAccounts.push({
|
|
237
|
+
label: `Account ${index}`,
|
|
238
|
+
ref: `COMMANDCODE_API_KEY_${n}`
|
|
239
|
+
});
|
|
240
|
+
this.failed = false;
|
|
241
|
+
this.describeAll();
|
|
242
|
+
this.publish();
|
|
243
|
+
}
|
|
244
|
+
/** Stage one extra account's removal (or drop an unsaved addition). */
|
|
245
|
+
removeAccount(id) {
|
|
246
|
+
const addedIndex = this.addedAccounts.findIndex((extra) => extra.ref === id);
|
|
247
|
+
if (addedIndex >= 0) this.addedAccounts.splice(addedIndex, 1);
|
|
248
|
+
else this.removedRefs.add(id);
|
|
249
|
+
this.labelDrafts.delete(id);
|
|
250
|
+
this.keyDrafts.delete(id);
|
|
251
|
+
const stagedActive = this.staged.get("activeAccount");
|
|
252
|
+
if ((stagedActive !== void 0 ? stagedActive.clear ? "" : stagedActive.text : typeof this.sectionValue("activeAccount") === "string" ? this.sectionValue("activeAccount") : "") === id) this.staged.set("activeAccount", {
|
|
253
|
+
text: "",
|
|
254
|
+
clear: true
|
|
255
|
+
});
|
|
256
|
+
this.failed = false;
|
|
257
|
+
this.publish();
|
|
258
|
+
}
|
|
259
|
+
/** Stage one extra account's label draft. */
|
|
260
|
+
editAccountLabel(id, text) {
|
|
261
|
+
this.labelDrafts.set(id, text);
|
|
262
|
+
this.failed = false;
|
|
263
|
+
this.publish();
|
|
264
|
+
}
|
|
265
|
+
/** Stage one extra account's key draft (blank keeps the stored key). */
|
|
266
|
+
editAccountKey(id, text) {
|
|
267
|
+
this.keyDrafts.set(id, text);
|
|
268
|
+
this.failed = false;
|
|
269
|
+
this.publish();
|
|
270
|
+
}
|
|
215
271
|
/** Stage one field's draft text. */
|
|
216
272
|
edit(field, text) {
|
|
217
273
|
this.staged.set(field, {
|
|
@@ -239,15 +295,17 @@ window.__ModuleLoader__.load({
|
|
|
239
295
|
}
|
|
240
296
|
/** Discard every staged edit. */
|
|
241
297
|
discard() {
|
|
242
|
-
if (this.staged.size === 0 && !this.failed) return;
|
|
298
|
+
if (this.staged.size === 0 && !this.accountsStaged() && !this.failed) return;
|
|
243
299
|
this.staged.clear();
|
|
300
|
+
this.clearAccountStaging();
|
|
244
301
|
this.failed = false;
|
|
245
302
|
this.publish();
|
|
246
303
|
}
|
|
247
304
|
/** Write every staged edit, then re-read the Host's accepted state. */
|
|
248
305
|
async save() {
|
|
249
306
|
const plan = this.plan();
|
|
250
|
-
|
|
307
|
+
const accountRuns = this.accountPlan();
|
|
308
|
+
if (plan.length === 0 && accountRuns.length === 0 || this.saving) return;
|
|
251
309
|
const runs = [];
|
|
252
310
|
for (const item of plan) {
|
|
253
311
|
if (item.run === void 0) return;
|
|
@@ -257,12 +315,34 @@ window.__ModuleLoader__.load({
|
|
|
257
315
|
this.failed = false;
|
|
258
316
|
this.publish();
|
|
259
317
|
let landed = true;
|
|
260
|
-
for (const run of runs)
|
|
318
|
+
for (const run of [...runs, ...accountRuns]) if (!await run()) {
|
|
319
|
+
landed = false;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
261
322
|
this.saving = false;
|
|
262
323
|
this.failed = !landed;
|
|
263
|
-
if (landed)
|
|
324
|
+
if (landed) {
|
|
325
|
+
this.staged.clear();
|
|
326
|
+
this.clearAccountStaging();
|
|
327
|
+
} else this.reconcileAccountStaging();
|
|
264
328
|
this.publish();
|
|
265
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Drop account staging the stored section already reflects: additions whose
|
|
332
|
+
* ref is now stored, removals whose ref is gone, and label drafts matching
|
|
333
|
+
* the stored label. Key drafts are kept — a landed key write is idempotent
|
|
334
|
+
* on retry, and the draft carries the user's intent when it was the
|
|
335
|
+
* accounts write that failed.
|
|
336
|
+
*/
|
|
337
|
+
reconcileAccountStaging() {
|
|
338
|
+
const stored = new Set(this.storedExtras().map((extra) => extra.ref));
|
|
339
|
+
this.addedAccounts = this.addedAccounts.filter((extra) => !stored.has(extra.ref));
|
|
340
|
+
for (const ref of [...this.removedRefs]) if (!stored.has(ref)) this.removedRefs.delete(ref);
|
|
341
|
+
for (const [ref, text] of [...this.labelDrafts]) {
|
|
342
|
+
const storedLabel = this.storedExtras().find((extra) => extra.ref === ref)?.label;
|
|
343
|
+
if (storedLabel === void 0 || storedLabel === text.trim()) this.labelDrafts.delete(ref);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
266
346
|
spec(field) {
|
|
267
347
|
const spec = this.specs.get(field);
|
|
268
348
|
if (spec === void 0) throw new Error(`commandcode settings page has no field ${field}`);
|
|
@@ -349,38 +429,136 @@ window.__ModuleLoader__.load({
|
|
|
349
429
|
await this.scope.set(field, value);
|
|
350
430
|
return this.userLayer()?.[field] === value;
|
|
351
431
|
}
|
|
352
|
-
/** Write the staged key, then re-read whether the Host
|
|
432
|
+
/** Write the staged default key, then re-read whether the Host holds it. */
|
|
353
433
|
async writeKey(value) {
|
|
434
|
+
return this.writeKeyTo(this.credentialRef, value);
|
|
435
|
+
}
|
|
436
|
+
/** Write one account's key, then re-read the Host's credential states. */
|
|
437
|
+
async writeKeyTo(ref, value) {
|
|
354
438
|
try {
|
|
355
439
|
if (!(await this.api.credentials.set({
|
|
356
|
-
ref
|
|
440
|
+
ref,
|
|
357
441
|
value
|
|
358
442
|
})).result.ok) return false;
|
|
359
443
|
} catch {
|
|
360
444
|
return false;
|
|
361
445
|
}
|
|
362
|
-
await this.
|
|
363
|
-
return this.
|
|
446
|
+
await this.describeAll();
|
|
447
|
+
return this.credentialStates.get(ref)?.configured ?? false;
|
|
364
448
|
}
|
|
365
|
-
/** Ask the credentials domain about
|
|
366
|
-
async
|
|
367
|
-
const
|
|
449
|
+
/** Ask the credentials domain about every reference this page writes. */
|
|
450
|
+
async describeAll() {
|
|
451
|
+
const refs = [
|
|
452
|
+
this.credentialRef,
|
|
453
|
+
...this.storedExtras().map((extra) => extra.ref),
|
|
454
|
+
...this.addedAccounts.map((extra) => extra.ref)
|
|
455
|
+
];
|
|
368
456
|
let response;
|
|
369
457
|
try {
|
|
370
|
-
response = await this.api.credentials.describe({ refs
|
|
458
|
+
response = await this.api.credentials.describe({ refs });
|
|
371
459
|
} catch {
|
|
372
460
|
return;
|
|
373
461
|
}
|
|
374
462
|
if (!response.result.ok) return;
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
ref
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
463
|
+
let changed = false;
|
|
464
|
+
for (const ref of refs) {
|
|
465
|
+
const view = response.result.value.credentials[ref];
|
|
466
|
+
const next = {
|
|
467
|
+
configured: view?.configured ?? false,
|
|
468
|
+
writable: view?.writable ?? true
|
|
469
|
+
};
|
|
470
|
+
const prev = this.credentialStates.get(ref);
|
|
471
|
+
if (prev === void 0 || prev.configured !== next.configured || prev.writable !== next.writable) {
|
|
472
|
+
this.credentialStates.set(ref, next);
|
|
473
|
+
changed = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (changed) this.publish();
|
|
477
|
+
}
|
|
478
|
+
/** The stored extra accounts from the settings section (`accounts`). */
|
|
479
|
+
storedExtras() {
|
|
480
|
+
const raw = this.scope.getSnapshot().value?.accounts;
|
|
481
|
+
if (!Array.isArray(raw)) return [];
|
|
482
|
+
const out = [];
|
|
483
|
+
for (const entry of raw) {
|
|
484
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
485
|
+
const record = entry;
|
|
486
|
+
const ref = record.apiKeyEnv;
|
|
487
|
+
if (typeof ref !== "string" || ref === "") continue;
|
|
488
|
+
const label = record.label;
|
|
489
|
+
out.push({
|
|
490
|
+
label: typeof label === "string" && label !== "" ? label : ref,
|
|
491
|
+
ref
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
496
|
+
/** Every extra account row: stored (minus staged removals) + staged adds. */
|
|
497
|
+
effectiveAccounts() {
|
|
498
|
+
const stored = this.storedExtras().filter((extra) => !this.removedRefs.has(extra.ref)).map((extra) => ({
|
|
499
|
+
...extra,
|
|
500
|
+
added: false
|
|
501
|
+
}));
|
|
502
|
+
const added = this.addedAccounts.map((extra) => ({
|
|
503
|
+
...extra,
|
|
504
|
+
added: true
|
|
505
|
+
}));
|
|
506
|
+
return [...stored, ...added].map((extra) => ({
|
|
507
|
+
id: extra.ref,
|
|
508
|
+
ref: extra.ref,
|
|
509
|
+
label: this.labelDrafts.get(extra.ref) ?? extra.label,
|
|
510
|
+
keyText: this.keyDrafts.get(extra.ref) ?? "",
|
|
511
|
+
configured: this.credentialStates.get(extra.ref)?.configured ?? false,
|
|
512
|
+
writable: this.credentialStates.get(extra.ref)?.writable ?? true,
|
|
513
|
+
added: extra.added
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
/** Whether any account-level staging (add/remove/label/key) exists. */
|
|
517
|
+
accountsStaged() {
|
|
518
|
+
return this.addedAccounts.length > 0 || this.removedRefs.size > 0 || this.labelDrafts.size > 0 || this.keyDrafts.size > 0;
|
|
519
|
+
}
|
|
520
|
+
/** Whether the staged account edits differ from the stored section. */
|
|
521
|
+
accountsDirty() {
|
|
522
|
+
if (this.addedAccounts.length > 0 || this.removedRefs.size > 0) return true;
|
|
523
|
+
for (const [ref, text] of this.labelDrafts) {
|
|
524
|
+
const base = this.storedExtras().find((extra) => extra.ref === ref)?.label;
|
|
525
|
+
if (base !== void 0 && text.trim() !== "" && text !== base) return true;
|
|
526
|
+
}
|
|
527
|
+
for (const text of this.keyDrafts.values()) if (text.trim() !== "") return true;
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
/** Reset every account-level staged edit. */
|
|
531
|
+
clearAccountStaging() {
|
|
532
|
+
this.addedAccounts = [];
|
|
533
|
+
this.removedRefs.clear();
|
|
534
|
+
this.labelDrafts.clear();
|
|
535
|
+
this.keyDrafts.clear();
|
|
536
|
+
}
|
|
537
|
+
/** The account-level writes a save performs (empty when nothing staged). */
|
|
538
|
+
accountPlan() {
|
|
539
|
+
if (!this.accountsDirty()) return [];
|
|
540
|
+
const runs = [];
|
|
541
|
+
for (const [ref, text] of this.keyDrafts) {
|
|
542
|
+
const value = text.trim();
|
|
543
|
+
if (value !== "" && !this.removedRefs.has(ref)) runs.push(() => this.writeKeyTo(ref, value));
|
|
544
|
+
}
|
|
545
|
+
runs.push(() => this.writeAccounts());
|
|
546
|
+
return runs;
|
|
547
|
+
}
|
|
548
|
+
/** Persist the staged accounts list into the settings section. */
|
|
549
|
+
async writeAccounts() {
|
|
550
|
+
const base = [...this.storedExtras().filter((extra) => !this.removedRefs.has(extra.ref)), ...this.addedAccounts];
|
|
551
|
+
const seen = /* @__PURE__ */ new Set();
|
|
552
|
+
const list = base.filter((extra) => !seen.has(extra.ref) && (seen.add(extra.ref), true)).map((extra) => {
|
|
553
|
+
const draft = this.labelDrafts.get(extra.ref)?.trim();
|
|
554
|
+
return {
|
|
555
|
+
label: draft !== void 0 && draft !== "" ? draft : extra.label,
|
|
556
|
+
apiKeyEnv: extra.ref
|
|
557
|
+
};
|
|
558
|
+
});
|
|
559
|
+
await this.scope.set("accounts", list);
|
|
560
|
+
const after = this.storedExtras();
|
|
561
|
+
return after.length === list.length && list.every((item, index) => after[index]?.ref === item.apiKeyEnv);
|
|
384
562
|
}
|
|
385
563
|
publish() {
|
|
386
564
|
if (this.disposed) return;
|
|
@@ -594,6 +772,25 @@ window.__ModuleLoader__.load({
|
|
|
594
772
|
}
|
|
595
773
|
return report;
|
|
596
774
|
}
|
|
775
|
+
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
776
|
+
function parseAccountUsage(value) {
|
|
777
|
+
const source = record(value, "account");
|
|
778
|
+
return {
|
|
779
|
+
id: stringField(source, "id", "account.id"),
|
|
780
|
+
label: stringField(source, "label", "account.label"),
|
|
781
|
+
configured: booleanField(source, "configured", "account.configured"),
|
|
782
|
+
active: booleanField(source, "active", "account.active"),
|
|
783
|
+
mark: stringField(source, "mark", "account.mark"),
|
|
784
|
+
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
785
|
+
report: parseUsageReport(source.report)
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
789
|
+
function parseAccountsReport(value) {
|
|
790
|
+
const accounts = record(value, "result").accounts;
|
|
791
|
+
if (!Array.isArray(accounts)) reject("accounts");
|
|
792
|
+
return { accounts: accounts.map(parseAccountUsage) };
|
|
793
|
+
}
|
|
597
794
|
/** The Client-face contribution mounted on `ctx.remote`. */
|
|
598
795
|
const USAGE_REMOTE_CONTRIBUTION = {
|
|
599
796
|
package: USAGE_REMOTE_PACKAGE,
|
|
@@ -606,8 +803,8 @@ window.__ModuleLoader__.load({
|
|
|
606
803
|
parameters: [],
|
|
607
804
|
result: {
|
|
608
805
|
mode: "strict",
|
|
609
|
-
typeSymbol: `${USAGE_REMOTE_PACKAGE}#
|
|
610
|
-
schema: { parse:
|
|
806
|
+
typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeAccountsReport`,
|
|
807
|
+
schema: { parse: parseAccountsReport }
|
|
611
808
|
}
|
|
612
809
|
}]
|
|
613
810
|
};
|
|
@@ -815,38 +1012,55 @@ window.__ModuleLoader__.load({
|
|
|
815
1012
|
]
|
|
816
1013
|
});
|
|
817
1014
|
}
|
|
1015
|
+
/** One account's rotation state as a short badge next to its label. */
|
|
1016
|
+
function AccountMark({ entry, t }) {
|
|
1017
|
+
if (entry.active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1018
|
+
className: "cc-usagePlan",
|
|
1019
|
+
children: t("usageActive")
|
|
1020
|
+
});
|
|
1021
|
+
if (entry.mark === "invalid-credential") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1022
|
+
className: "cc-usagePlanStatus",
|
|
1023
|
+
children: t("usageInvalidKey")
|
|
1024
|
+
});
|
|
1025
|
+
if (entry.cooldownUntil > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1026
|
+
className: "cc-usagePlanStatus",
|
|
1027
|
+
children: [
|
|
1028
|
+
t("usageCooldown"),
|
|
1029
|
+
" ",
|
|
1030
|
+
formatResetAt(entry.cooldownUntil)
|
|
1031
|
+
]
|
|
1032
|
+
});
|
|
1033
|
+
if (entry.mark === "rate-limit") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1034
|
+
className: "cc-usagePlanStatus",
|
|
1035
|
+
children: t("usageCooldown")
|
|
1036
|
+
});
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
818
1039
|
/**
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
* arrives through the `commandcode/report` Remote; the API key never leaves
|
|
822
|
-
* the Host.
|
|
1040
|
+
* One pool account's facts (identity, totals, credits, window limits)
|
|
1041
|
+
* rendered inside the account-usage card.
|
|
823
1042
|
*/
|
|
824
|
-
function
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}, [
|
|
828
|
-
apiKeyConfigured,
|
|
829
|
-
usage.status,
|
|
830
|
-
onRefresh
|
|
831
|
-
]);
|
|
832
|
-
const loading = usage.status === "loading";
|
|
833
|
-
const report = usage.report;
|
|
834
|
-
const account = report?.account;
|
|
1043
|
+
function AccountReport({ entry, t, onRemove }) {
|
|
1044
|
+
const report = entry.report;
|
|
1045
|
+
const account = report.account;
|
|
835
1046
|
const accountName = account === void 0 ? "" : account.userName || account.name;
|
|
836
|
-
const credits = report
|
|
837
|
-
const plan = report
|
|
1047
|
+
const credits = report.credits;
|
|
1048
|
+
const plan = report.plan;
|
|
838
1049
|
const planName = plan?.name ?? "";
|
|
839
1050
|
const planStatus = plan !== void 0 && plan.status !== "" && plan.status !== "active" ? plan.status : "";
|
|
840
1051
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
841
|
-
className: "cc-
|
|
842
|
-
"aria-label": t("usageTitle"),
|
|
1052
|
+
className: "cc-accountReport",
|
|
843
1053
|
children: [
|
|
844
1054
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
845
1055
|
className: "cc-usageHead",
|
|
846
1056
|
children: [
|
|
847
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1057
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
848
1058
|
className: "cc-usageTitle",
|
|
849
|
-
children:
|
|
1059
|
+
children: entry.label
|
|
1060
|
+
}),
|
|
1061
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMark, {
|
|
1062
|
+
entry,
|
|
1063
|
+
t
|
|
850
1064
|
}),
|
|
851
1065
|
accountName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
852
1066
|
className: "cc-usageAccount",
|
|
@@ -860,29 +1074,20 @@ window.__ModuleLoader__.load({
|
|
|
860
1074
|
className: "cc-usagePlanStatus",
|
|
861
1075
|
children: planStatus
|
|
862
1076
|
}) : null,
|
|
863
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1077
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
|
|
1078
|
+
onRemove !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
864
1079
|
type: "button",
|
|
865
1080
|
className: "cc-usageRefresh",
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
})
|
|
1081
|
+
onClick: onRemove,
|
|
1082
|
+
children: t("accountRemove")
|
|
1083
|
+
}) : null
|
|
870
1084
|
]
|
|
871
1085
|
}),
|
|
872
|
-
!
|
|
1086
|
+
!entry.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
873
1087
|
className: "cc-usageHint",
|
|
874
|
-
children: t("
|
|
1088
|
+
children: t("usageUnconfigured")
|
|
875
1089
|
}) : null,
|
|
876
|
-
|
|
877
|
-
className: "cc-usageHint",
|
|
878
|
-
children: t("usageLoading")
|
|
879
|
-
}) : null,
|
|
880
|
-
usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
881
|
-
className: "cc-usageError",
|
|
882
|
-
role: "status",
|
|
883
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
|
|
884
|
-
}) : null,
|
|
885
|
-
report?.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1090
|
+
report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
886
1091
|
className: "cc-usageStats",
|
|
887
1092
|
children: [
|
|
888
1093
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
@@ -935,25 +1140,17 @@ window.__ModuleLoader__.load({
|
|
|
935
1140
|
t
|
|
936
1141
|
})]
|
|
937
1142
|
}) : null,
|
|
938
|
-
|
|
1143
|
+
plan !== void 0 && plan.currentPeriodEnd > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
939
1144
|
className: "cc-usageMeta",
|
|
940
1145
|
children: [
|
|
941
|
-
|
|
1146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
942
1147
|
className: "cc-usageUpdated",
|
|
943
1148
|
children: [
|
|
944
1149
|
t("usagePeriodEnd"),
|
|
945
1150
|
" ",
|
|
946
1151
|
new Date(plan.currentPeriodEnd).toLocaleDateString()
|
|
947
1152
|
]
|
|
948
|
-
})
|
|
949
|
-
usage.fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
950
|
-
className: "cc-usageUpdated",
|
|
951
|
-
children: [
|
|
952
|
-
t("usageUpdated"),
|
|
953
|
-
" ",
|
|
954
|
-
new Date(usage.fetchedAt).toLocaleTimeString()
|
|
955
|
-
]
|
|
956
|
-
}) : null,
|
|
1153
|
+
}),
|
|
957
1154
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
|
|
958
1155
|
report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
959
1156
|
className: "cc-usagePartial",
|
|
@@ -961,10 +1158,280 @@ window.__ModuleLoader__.load({
|
|
|
961
1158
|
children: t("usagePartial")
|
|
962
1159
|
}) : null
|
|
963
1160
|
]
|
|
1161
|
+
}) : report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1162
|
+
className: "cc-usageMeta",
|
|
1163
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1164
|
+
className: "cc-usagePartial",
|
|
1165
|
+
title: report.failures.join("; "),
|
|
1166
|
+
children: t("usagePartial")
|
|
1167
|
+
})]
|
|
964
1168
|
}) : null
|
|
965
1169
|
]
|
|
966
1170
|
});
|
|
967
1171
|
}
|
|
1172
|
+
/** The status dot on an account tab: cooling/invalid warn, everything else ok. */
|
|
1173
|
+
function AccountTabDot({ entry }) {
|
|
1174
|
+
const cls = entry.mark === "invalid-credential" ? "cc-tabDot cc-tabDotError" : entry.mark !== "" || entry.cooldownUntil > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk";
|
|
1175
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cls });
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* The account-usage card: the `/commandcode` dashboard's facts rendered as
|
|
1179
|
+
* a native settings card. With several accounts the card is a carousel — a
|
|
1180
|
+
* tab strip (label + status dot) switches between accounts so the page stays
|
|
1181
|
+
* short; each account's report carries its own remove affordance (the
|
|
1182
|
+
* default account is not removable). Accounts staged for removal in the
|
|
1183
|
+
* management card are hidden here immediately. Data arrives through the
|
|
1184
|
+
* `commandcode/report` Remote; the API keys never leave the Host.
|
|
1185
|
+
*/
|
|
1186
|
+
function UsageCard({ t, usage, apiKeyConfigured, removingIds, removableIds, canManage, onRefresh, onRemoveAccount }) {
|
|
1187
|
+
(0, react.useEffect)(() => {
|
|
1188
|
+
if (apiKeyConfigured && usage.status === "idle") onRefresh();
|
|
1189
|
+
}, [
|
|
1190
|
+
apiKeyConfigured,
|
|
1191
|
+
usage.status,
|
|
1192
|
+
onRefresh
|
|
1193
|
+
]);
|
|
1194
|
+
const loading = usage.status === "loading";
|
|
1195
|
+
const report = usage.report;
|
|
1196
|
+
const [locallyRemoved, setLocallyRemoved] = (0, react.useState)([]);
|
|
1197
|
+
(0, react.useEffect)(() => {
|
|
1198
|
+
setLocallyRemoved([]);
|
|
1199
|
+
}, [usage.fetchedAt]);
|
|
1200
|
+
const hidden = /* @__PURE__ */ new Set([...removingIds, ...locallyRemoved]);
|
|
1201
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1202
|
+
const entries = (report?.accounts ?? []).filter((entry) => {
|
|
1203
|
+
if (hidden.has(entry.id)) return false;
|
|
1204
|
+
if (seenIds.has(entry.id)) return false;
|
|
1205
|
+
seenIds.add(entry.id);
|
|
1206
|
+
return true;
|
|
1207
|
+
});
|
|
1208
|
+
const [selectedId, setSelectedId] = (0, react.useState)(void 0);
|
|
1209
|
+
const selected = entries.find((entry) => entry.id === selectedId) ?? entries.find((entry) => entry.active) ?? entries[0];
|
|
1210
|
+
const removeSelected = canManage && selected !== void 0 && removableIds.includes(selected.id) ? () => {
|
|
1211
|
+
const id = selected.id;
|
|
1212
|
+
setLocallyRemoved((prev) => [...prev, id]);
|
|
1213
|
+
onRemoveAccount(id);
|
|
1214
|
+
} : void 0;
|
|
1215
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1216
|
+
className: "cc-usageCard",
|
|
1217
|
+
"aria-label": t("usageTitle"),
|
|
1218
|
+
children: [
|
|
1219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1220
|
+
className: "cc-usageHead",
|
|
1221
|
+
children: [
|
|
1222
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1223
|
+
className: "cc-usageTitle",
|
|
1224
|
+
children: t("usageTitle")
|
|
1225
|
+
}),
|
|
1226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
|
|
1227
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1228
|
+
type: "button",
|
|
1229
|
+
className: "cc-usageRefresh",
|
|
1230
|
+
disabled: loading || !apiKeyConfigured,
|
|
1231
|
+
onClick: onRefresh,
|
|
1232
|
+
children: loading ? t("usageRefreshing") : t("usageRefresh")
|
|
1233
|
+
})
|
|
1234
|
+
]
|
|
1235
|
+
}),
|
|
1236
|
+
!apiKeyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1237
|
+
className: "cc-usageHint",
|
|
1238
|
+
children: t("usageNoKey")
|
|
1239
|
+
}) : null,
|
|
1240
|
+
apiKeyConfigured && report === void 0 && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1241
|
+
className: "cc-usageHint",
|
|
1242
|
+
children: t("usageLoading")
|
|
1243
|
+
}) : null,
|
|
1244
|
+
usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1245
|
+
className: "cc-usageError",
|
|
1246
|
+
role: "status",
|
|
1247
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
|
|
1248
|
+
}) : null,
|
|
1249
|
+
entries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1250
|
+
className: "cc-tabs",
|
|
1251
|
+
role: "tablist",
|
|
1252
|
+
"aria-label": t("accountsTitle"),
|
|
1253
|
+
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1254
|
+
type: "button",
|
|
1255
|
+
role: "tab",
|
|
1256
|
+
"aria-selected": selected?.id === entry.id,
|
|
1257
|
+
className: selected?.id === entry.id ? "cc-tab cc-tabActive" : "cc-tab",
|
|
1258
|
+
onClick: () => setSelectedId(entry.id),
|
|
1259
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTabDot, { entry }), entry.label]
|
|
1260
|
+
}, entry.id))
|
|
1261
|
+
}) : null,
|
|
1262
|
+
selected !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountReport, {
|
|
1263
|
+
entry: selected,
|
|
1264
|
+
t,
|
|
1265
|
+
onRemove: removeSelected
|
|
1266
|
+
}, selected.id) : null,
|
|
1267
|
+
report !== void 0 && usage.fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1268
|
+
className: "cc-usageMeta",
|
|
1269
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1270
|
+
className: "cc-usageUpdated",
|
|
1271
|
+
children: [
|
|
1272
|
+
t("usageUpdated"),
|
|
1273
|
+
" ",
|
|
1274
|
+
new Date(usage.fetchedAt).toLocaleTimeString()
|
|
1275
|
+
]
|
|
1276
|
+
})]
|
|
1277
|
+
}) : null
|
|
1278
|
+
]
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* One extra account row: label, key, configured badge. Saved accounts are
|
|
1283
|
+
* removed from the usage card above; a NOT-YET-SAVED addition never appears
|
|
1284
|
+
* there (the usage report is Host-side), so it keeps its own remove button —
|
|
1285
|
+
* otherwise the only way to undo a mistaken Add would be discarding every
|
|
1286
|
+
* other staged edit.
|
|
1287
|
+
*/
|
|
1288
|
+
function AccountRow({ account, disabled, t, onLabel, onKey, onRemove }) {
|
|
1289
|
+
const locked = !account.writable;
|
|
1290
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1291
|
+
className: "cc-field",
|
|
1292
|
+
children: [
|
|
1293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1294
|
+
className: "cc-fieldHead",
|
|
1295
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1296
|
+
className: "cc-label",
|
|
1297
|
+
htmlFor: `cc-account-label-${account.id}`,
|
|
1298
|
+
children: t("accountLabel")
|
|
1299
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1300
|
+
className: "cc-badges",
|
|
1301
|
+
children: [
|
|
1302
|
+
account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1303
|
+
className: "cc-badge",
|
|
1304
|
+
children: t("unsaved")
|
|
1305
|
+
}) : null,
|
|
1306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1307
|
+
className: account.configured ? "cc-badge" : "cc-badgeMuted",
|
|
1308
|
+
children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
|
|
1309
|
+
}),
|
|
1310
|
+
account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1311
|
+
type: "button",
|
|
1312
|
+
className: "cc-reset",
|
|
1313
|
+
disabled,
|
|
1314
|
+
onClick: onRemove,
|
|
1315
|
+
children: t("accountRemove")
|
|
1316
|
+
}) : null
|
|
1317
|
+
]
|
|
1318
|
+
})]
|
|
1319
|
+
}),
|
|
1320
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1321
|
+
id: `cc-account-label-${account.id}`,
|
|
1322
|
+
className: "cc-input",
|
|
1323
|
+
type: "text",
|
|
1324
|
+
value: account.label,
|
|
1325
|
+
disabled,
|
|
1326
|
+
onChange: (event) => onLabel(event.target.value)
|
|
1327
|
+
}),
|
|
1328
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1329
|
+
id: `cc-account-key-${account.id}`,
|
|
1330
|
+
className: "cc-input",
|
|
1331
|
+
type: "password",
|
|
1332
|
+
autoComplete: "off",
|
|
1333
|
+
placeholder: t("accountKey"),
|
|
1334
|
+
value: account.keyText,
|
|
1335
|
+
disabled: disabled || locked,
|
|
1336
|
+
onChange: (event) => onKey(event.target.value)
|
|
1337
|
+
}),
|
|
1338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1339
|
+
className: "cc-hint",
|
|
1340
|
+
children: locked ? t("apiKeyLocked") : t("accountKeyHint")
|
|
1341
|
+
})
|
|
1342
|
+
]
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
/** The multi-account card: the active-account selector + extra accounts in rotation order + add button. */
|
|
1346
|
+
function AccountsCard({ t, state, disabled, onAdd, onRemove, onLabel, onKey, onActive, onActiveReset }) {
|
|
1347
|
+
const active = state.activeAccount;
|
|
1348
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1349
|
+
className: "cc-card",
|
|
1350
|
+
"aria-label": t("accountsTitle"),
|
|
1351
|
+
children: [
|
|
1352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
+
className: "cc-field",
|
|
1354
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1355
|
+
className: "cc-fieldHead",
|
|
1356
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1357
|
+
className: "cc-label",
|
|
1358
|
+
children: t("accountsTitle")
|
|
1359
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1360
|
+
className: "cc-badges",
|
|
1361
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1362
|
+
type: "button",
|
|
1363
|
+
className: "cc-reset",
|
|
1364
|
+
disabled,
|
|
1365
|
+
onClick: onAdd,
|
|
1366
|
+
children: t("accountAdd")
|
|
1367
|
+
})
|
|
1368
|
+
})]
|
|
1369
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1370
|
+
className: "cc-hint",
|
|
1371
|
+
children: t("accountsHint")
|
|
1372
|
+
})]
|
|
1373
|
+
}),
|
|
1374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1375
|
+
className: "cc-field",
|
|
1376
|
+
children: [
|
|
1377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1378
|
+
className: "cc-fieldHead",
|
|
1379
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1380
|
+
className: "cc-label",
|
|
1381
|
+
htmlFor: "cc-active-account",
|
|
1382
|
+
children: t("activeAccount")
|
|
1383
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1384
|
+
className: "cc-badges",
|
|
1385
|
+
children: [active.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1386
|
+
className: "cc-badge",
|
|
1387
|
+
children: t("overridden")
|
|
1388
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1389
|
+
type: "button",
|
|
1390
|
+
className: "cc-reset",
|
|
1391
|
+
disabled,
|
|
1392
|
+
onClick: onActiveReset,
|
|
1393
|
+
children: t("reset")
|
|
1394
|
+
})]
|
|
1395
|
+
})]
|
|
1396
|
+
}),
|
|
1397
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1398
|
+
id: "cc-active-account",
|
|
1399
|
+
className: "cc-input",
|
|
1400
|
+
value: active.text,
|
|
1401
|
+
disabled,
|
|
1402
|
+
onChange: (event) => onActive(event.target.value),
|
|
1403
|
+
children: [
|
|
1404
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1405
|
+
value: "",
|
|
1406
|
+
children: t("activeAccountAuto")
|
|
1407
|
+
}),
|
|
1408
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1409
|
+
value: "default",
|
|
1410
|
+
children: t("accountDefault")
|
|
1411
|
+
}),
|
|
1412
|
+
state.accounts.filter((account) => !account.added).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1413
|
+
value: account.ref,
|
|
1414
|
+
children: account.label
|
|
1415
|
+
}, account.id))
|
|
1416
|
+
]
|
|
1417
|
+
}),
|
|
1418
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1419
|
+
className: "cc-hint",
|
|
1420
|
+
children: t("activeAccountHint")
|
|
1421
|
+
})
|
|
1422
|
+
]
|
|
1423
|
+
}),
|
|
1424
|
+
state.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountRow, {
|
|
1425
|
+
account,
|
|
1426
|
+
disabled,
|
|
1427
|
+
t,
|
|
1428
|
+
onLabel: (text) => onLabel(account.id, text),
|
|
1429
|
+
onKey: (text) => onKey(account.id, text),
|
|
1430
|
+
onRemove: () => onRemove(account.id)
|
|
1431
|
+
}, account.id))
|
|
1432
|
+
]
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
968
1435
|
/** The settings page body: connection facts for the Command Code provider. */
|
|
969
1436
|
function CommandCodeSettingsPage(props) {
|
|
970
1437
|
const { t } = props;
|
|
@@ -992,8 +1459,23 @@ window.__ModuleLoader__.load({
|
|
|
992
1459
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
993
1460
|
t,
|
|
994
1461
|
usage,
|
|
995
|
-
apiKeyConfigured: state.
|
|
996
|
-
|
|
1462
|
+
apiKeyConfigured: state.anyAccountConfigured,
|
|
1463
|
+
removingIds: state.accountsRemoving,
|
|
1464
|
+
removableIds: state.accounts.map((account) => account.id),
|
|
1465
|
+
canManage: state.writable,
|
|
1466
|
+
onRefresh: props.refreshUsage,
|
|
1467
|
+
onRemoveAccount: props.removeAccount
|
|
1468
|
+
}),
|
|
1469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
|
|
1470
|
+
t,
|
|
1471
|
+
state,
|
|
1472
|
+
disabled,
|
|
1473
|
+
onAdd: props.addAccount,
|
|
1474
|
+
onRemove: props.removeAccount,
|
|
1475
|
+
onLabel: props.editAccountLabel,
|
|
1476
|
+
onKey: props.editAccountKey,
|
|
1477
|
+
onActive: (text) => props.edit("activeAccount", text),
|
|
1478
|
+
onActiveReset: () => props.resetField("activeAccount")
|
|
997
1479
|
}),
|
|
998
1480
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
999
1481
|
className: "cc-card",
|
|
@@ -1112,6 +1594,17 @@ window.__ModuleLoader__.load({
|
|
|
1112
1594
|
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
|
|
1113
1595
|
filterModelsByPlan: "隐藏套餐外模型",
|
|
1114
1596
|
filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
|
|
1597
|
+
accountsTitle: "多账户轮换",
|
|
1598
|
+
accountsHint: "当前账户达到用量限额(429)或密钥失效(401)时,请求自动切换到下一个账户;全部耗尽时会提示最早的重置时间。",
|
|
1599
|
+
accountAdd: "添加账户",
|
|
1600
|
+
accountRemove: "移除",
|
|
1601
|
+
accountLabel: "账户备注名",
|
|
1602
|
+
accountKey: "API 密钥",
|
|
1603
|
+
accountKeyHint: "该账户的 API 密钥。留空保存不会覆盖已存储的密钥。",
|
|
1604
|
+
accountDefault: "默认账户",
|
|
1605
|
+
activeAccount: "当前使用账户",
|
|
1606
|
+
activeAccountAuto: "自动(第一个可用账户)",
|
|
1607
|
+
activeAccountHint: "手动指定优先使用的账户,保存后下次请求即生效;所选账户耗尽时仍会自动切换到其他可用账户。",
|
|
1115
1608
|
overridden: "已覆盖",
|
|
1116
1609
|
reset: "重置",
|
|
1117
1610
|
invalidNumber: "无效数字",
|
|
@@ -1144,7 +1637,11 @@ window.__ModuleLoader__.load({
|
|
|
1144
1637
|
usageReset: "重置于",
|
|
1145
1638
|
usagePartial: "部分端点数据不可用",
|
|
1146
1639
|
usageUpdated: "更新于",
|
|
1147
|
-
usagePeriodEnd: "账期截止"
|
|
1640
|
+
usagePeriodEnd: "账期截止",
|
|
1641
|
+
usageActive: "当前使用",
|
|
1642
|
+
usageCooldown: "限额冷却中",
|
|
1643
|
+
usageInvalidKey: "密钥无效",
|
|
1644
|
+
usageUnconfigured: "该账户尚未配置 API 密钥。"
|
|
1148
1645
|
};
|
|
1149
1646
|
const en = {
|
|
1150
1647
|
nav: "Command Code",
|
|
@@ -1165,6 +1662,17 @@ window.__ModuleLoader__.load({
|
|
|
1165
1662
|
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
|
|
1166
1663
|
filterModelsByPlan: "Hide out-of-plan models",
|
|
1167
1664
|
filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
|
|
1665
|
+
accountsTitle: "Account rotation",
|
|
1666
|
+
accountsHint: "When the active account hits its usage limit (429) or its key fails (401), requests switch to the next account; when every account is exhausted the error names the earliest window reset.",
|
|
1667
|
+
accountAdd: "Add account",
|
|
1668
|
+
accountRemove: "Remove",
|
|
1669
|
+
accountLabel: "Account label",
|
|
1670
|
+
accountKey: "API key",
|
|
1671
|
+
accountKeyHint: "This account’s API key. Saving with the field blank keeps the stored key.",
|
|
1672
|
+
accountDefault: "Default account",
|
|
1673
|
+
activeAccount: "Active account",
|
|
1674
|
+
activeAccountAuto: "Auto (first usable account)",
|
|
1675
|
+
activeAccountHint: "Pin the preferred account; applies to the next request after saving. If the selected account is exhausted, requests still rotate to another usable account.",
|
|
1168
1676
|
overridden: "Overridden",
|
|
1169
1677
|
reset: "Reset",
|
|
1170
1678
|
invalidNumber: "Invalid number",
|
|
@@ -1197,7 +1705,11 @@ window.__ModuleLoader__.load({
|
|
|
1197
1705
|
usageReset: "Resets",
|
|
1198
1706
|
usagePartial: "Some endpoint data unavailable",
|
|
1199
1707
|
usageUpdated: "Updated",
|
|
1200
|
-
usagePeriodEnd: "Period ends"
|
|
1708
|
+
usagePeriodEnd: "Period ends",
|
|
1709
|
+
usageActive: "Active",
|
|
1710
|
+
usageCooldown: "Cooling down",
|
|
1711
|
+
usageInvalidKey: "Invalid key",
|
|
1712
|
+
usageUnconfigured: "No API key configured for this account yet."
|
|
1201
1713
|
};
|
|
1202
1714
|
//#endregion
|
|
1203
1715
|
//#region src/client/index.ts
|
|
@@ -1260,6 +1772,14 @@ window.__ModuleLoader__.load({
|
|
|
1260
1772
|
.cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
|
|
1261
1773
|
.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1262
1774
|
.cc-usageMeta{align-items:center;gap:8px;display:flex}
|
|
1775
|
+
.cc-accountReport{flex-direction:column;gap:12px;display:flex}
|
|
1776
|
+
.cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
|
|
1777
|
+
.cc-tab{align-items:center;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex;gap:6px}
|
|
1778
|
+
.cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
|
|
1779
|
+
.cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
1780
|
+
.cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
|
|
1781
|
+
.cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
|
|
1782
|
+
.cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
|
|
1263
1783
|
.cc-usageMetaSpacer{flex:1}
|
|
1264
1784
|
.cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1265
1785
|
.cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
|
|
@@ -1341,10 +1861,14 @@ window.__ModuleLoader__.load({
|
|
|
1341
1861
|
resetField: (field) => controller.resetField(field),
|
|
1342
1862
|
save: () => void controller.save().then(() => {
|
|
1343
1863
|
const settled = controller.state();
|
|
1344
|
-
if (!settled.failed && settled.
|
|
1864
|
+
if (!settled.failed && settled.anyAccountConfigured) usageController.refresh();
|
|
1345
1865
|
}),
|
|
1346
1866
|
discard: () => controller.discard(),
|
|
1347
|
-
refreshUsage: () => void usageController.refresh()
|
|
1867
|
+
refreshUsage: () => void usageController.refresh(),
|
|
1868
|
+
addAccount: () => controller.addAccount(),
|
|
1869
|
+
removeAccount: (id) => controller.removeAccount(id),
|
|
1870
|
+
editAccountLabel: (id, text) => controller.editAccountLabel(id, text),
|
|
1871
|
+
editAccountKey: (id, text) => controller.editAccountKey(id, text)
|
|
1348
1872
|
});
|
|
1349
1873
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1350
1874
|
name: "settings.section",
|