@mars-sea/dsh-commandcode-provider 0.11.16 → 0.12.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 +56 -0
- package/README.md +17 -29
- package/README.zh-CN.md +14 -26
- package/lib/client.js +526 -766
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +422 -499
- package/lib/index.js +1893 -2175
- package/lib/index.js.map +1 -1
- package/package.json +37 -37
package/lib/client.js
CHANGED
|
@@ -40,16 +40,12 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
/**
|
|
41
41
|
* Backoff before re-reading a directory whose FIRST read failed, in millis.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* a live Host to fire, and the namespace only re-mounts on a page reload. So a
|
|
50
|
-
* failure with NOTHING held retries on this bounded ladder; a failure with a
|
|
51
|
-
* held view keeps it (no retry — the page is already showing the last good
|
|
52
|
-
* document, and `tests/settings-scope.test.ts` pins that).
|
|
43
|
+
* Only a failure with NOTHING held retries. With no view the scope keeps its
|
|
44
|
+
* initial `{ status: 'loading', writable: false }` snapshot and the settings
|
|
45
|
+
* page renders fully disabled, while the forwarded invalidation signals need a
|
|
46
|
+
* live Host to fire and the namespace only re-mounts on a reload. A failure
|
|
47
|
+
* with a held view retries nothing — the page already shows the last good
|
|
48
|
+
* document (`tests/settings-scope.test.ts` pins both).
|
|
53
49
|
*/
|
|
54
50
|
const SETTINGS_DESCRIBE_RETRY_MS = [
|
|
55
51
|
1e3,
|
|
@@ -95,10 +91,6 @@ window.__ModuleLoader__.load({
|
|
|
95
91
|
retryHandle;
|
|
96
92
|
retries = 0;
|
|
97
93
|
disposed = false;
|
|
98
|
-
/**
|
|
99
|
-
* @param resolveRemote - Reads the inject-captured `remote.settings` namespace.
|
|
100
|
-
* @param timer - Retry timer seam (see {@link SETTINGS_DESCRIBE_RETRY_MS}).
|
|
101
|
-
*/
|
|
102
94
|
constructor(resolveRemote, timer = REAL_SETTINGS_TIMER) {
|
|
103
95
|
this.resolveRemote = resolveRemote;
|
|
104
96
|
this.timer = timer;
|
|
@@ -229,9 +221,8 @@ window.__ModuleLoader__.load({
|
|
|
229
221
|
};
|
|
230
222
|
/**
|
|
231
223
|
* One namespace's derived scope over the shared mirror, plus its serialized
|
|
232
|
-
* Host writes
|
|
233
|
-
* (`getSnapshot` / `subscribe` / `set` / `unset`)
|
|
234
|
-
* needs no changes for either generation.
|
|
224
|
+
* Host writes — the face `CommandCodeSettingsController` consumes
|
|
225
|
+
* (`getSnapshot` / `subscribe` / `set` / `unset`).
|
|
235
226
|
*/
|
|
236
227
|
var RemoteSettingsScope = class {
|
|
237
228
|
resolveRemote;
|
|
@@ -392,22 +383,17 @@ window.__ModuleLoader__.load({
|
|
|
392
383
|
*
|
|
393
384
|
* Subscribes the two signals that can move the Host view — the forwarded
|
|
394
385
|
* `settings/document-updated` invalidation and (when the client exposes it)
|
|
395
|
-
* `connection/reset` — kicks the first describe read, and derives the
|
|
396
|
-
*
|
|
386
|
+
* `connection/reset` — kicks the first describe read, and derives the namespace
|
|
387
|
+
* row on every mirror change.
|
|
397
388
|
*
|
|
398
|
-
* `resolveRemote`
|
|
399
|
-
* {@link SettingsRemoteResolver})
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* into a served one; a profile that never mounts the namespace leaves the page
|
|
405
|
-
* in its degraded (`unavailable`, read-only) state instead of throwing.
|
|
389
|
+
* `resolveRemote` must be the INJECT-CAPTURED namespace (see
|
|
390
|
+
* {@link SettingsRemoteResolver}), and the caller that owns that inject calls
|
|
391
|
+
* {@link ManagedSettingsScope.refresh} once the namespace lands — that is what
|
|
392
|
+
* turns the first, namespace-less read into a served one. A profile that never
|
|
393
|
+
* mounts the namespace leaves the page in its degraded (`unavailable`,
|
|
394
|
+
* read-only) state instead of throwing.
|
|
406
395
|
*
|
|
407
|
-
* @param context - The client context slice (only the declared `remote` service is read).
|
|
408
396
|
* @param namespace - Settings namespace (profile entry id) this scope binds.
|
|
409
|
-
* @param resolveRemote - Reads the inject-captured `remote.settings` namespace.
|
|
410
|
-
* @returns The scope plus the refresh/dispose handles the entry wires into its fiber.
|
|
411
397
|
*/
|
|
412
398
|
function createSettingsScope(context, namespace, resolveRemote, timer = REAL_SETTINGS_TIMER) {
|
|
413
399
|
const mirror = new SettingsDescribeMirror(resolveRemote, timer);
|
|
@@ -444,38 +430,22 @@ window.__ModuleLoader__.load({
|
|
|
444
430
|
//#endregion
|
|
445
431
|
//#region src/client/settings.ts
|
|
446
432
|
/**
|
|
447
|
-
* Browser controller for the "Command Code" settings page.
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
* (a `settings.section` entry, id `commandcode`). It exists because the
|
|
451
|
-
* Models page renders an unknown-adapter-family card for the `commandcode`
|
|
452
|
-
* provider and deliberately disables its submit — the API key cannot be
|
|
453
|
-
* configured there. This page owns the connection facts the plugin resolves
|
|
454
|
-
* per request:
|
|
433
|
+
* Browser controller for the "Command Code" settings page (a `settings.section`
|
|
434
|
+
* entry, id `commandcode`). React-free: it only produces the state face
|
|
435
|
+
* `section.tsx` renders, so node tests can drive it.
|
|
455
436
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* - API base -> the `llm-commandcode` settings namespace (`apiBase`), same
|
|
461
|
-
* namespace the Models page card addresses.
|
|
462
|
-
* - Timeouts, behavior switches, model visibility -> the same namespace,
|
|
463
|
-
* staged and written on save.
|
|
464
|
-
* - Accounts -> `accounts`, `activeAccount`, `modelAccountRules` and the
|
|
465
|
-
* per-account credentials, committed immediately.
|
|
466
|
-
*
|
|
467
|
-
* The controller mirrors the plugin-card pattern from the harness's own
|
|
468
|
-
* settings UI: it binds the `llm-commandcode` namespace through a
|
|
469
|
-
* `SettingsScope` — this plugin's own binding of `remote.settings` (see
|
|
470
|
-
* `./settings-scope.ts`; the harness's `settingsScope` wrapper existed only
|
|
471
|
-
* through 0.1.6 and was removed with the 0.1.7 settings rewrite, while the
|
|
472
|
-
* wire underneath spans every supported release) — keeps a staged draft of
|
|
473
|
-
* edits, and writes them on save through `scope.set` / the credentials
|
|
474
|
-
* domain. The Host stays the single fact source; the snapshot is republished
|
|
475
|
-
* after each accepted write.
|
|
437
|
+
* Two write paths with different contracts:
|
|
438
|
+
* - The staged form (connection facts, behavior switches, model visibility,
|
|
439
|
+
* plus the Models-page card's default key draft) lands on `save()`.
|
|
440
|
+
* - Account management commits IMMEDIATELY and serially.
|
|
476
441
|
*
|
|
477
|
-
*
|
|
478
|
-
* the
|
|
442
|
+
* The API key is written through the CREDENTIALS domain under the reference the
|
|
443
|
+
* plugin resolves (`apiKeyEnv`), never through the settings document, so the
|
|
444
|
+
* literal cannot leak into a document; the control only reports whether one is
|
|
445
|
+
* configured. Everything else rides the `llm-commandcode` namespace through this
|
|
446
|
+
* plugin's own `SettingsScope` over `remote.settings` (see `./settings-scope.ts`).
|
|
447
|
+
* The Host stays the single fact source; the snapshot is republished after each
|
|
448
|
+
* accepted write.
|
|
479
449
|
*/
|
|
480
450
|
/** The settings namespace the plugin registers (host half, src/index.ts). */
|
|
481
451
|
const COMMANDCODE_NS = "llm-commandcode";
|
|
@@ -500,8 +470,7 @@ window.__ModuleLoader__.load({
|
|
|
500
470
|
* save, and an optional inclusive `bounds` range rejects out-of-range values
|
|
501
471
|
* with a specific reason (the Host schema would reject them at save time with
|
|
502
472
|
* only a generic failure — catching it here names the problem while typing).
|
|
503
|
-
* Decimals pass
|
|
504
|
-
* millisecond value is harmless even if pointless.
|
|
473
|
+
* Decimals pass, like the Host schema's `z.number()`.
|
|
505
474
|
*/
|
|
506
475
|
function numberField$1(field, bounds) {
|
|
507
476
|
return {
|
|
@@ -530,24 +499,6 @@ window.__ModuleLoader__.load({
|
|
|
530
499
|
}
|
|
531
500
|
};
|
|
532
501
|
}
|
|
533
|
-
/** A field limited to fixed string choices; an empty draft clears it. */
|
|
534
|
-
function choiceField(field, choices) {
|
|
535
|
-
return {
|
|
536
|
-
field,
|
|
537
|
-
format: (value) => typeof value === "string" && choices.includes(value) ? value : "",
|
|
538
|
-
parse: (text) => {
|
|
539
|
-
const trimmed = text.trim();
|
|
540
|
-
if (trimmed === "") return { kind: "clear" };
|
|
541
|
-
return choices.includes(trimmed) ? {
|
|
542
|
-
kind: "set",
|
|
543
|
-
value: trimmed
|
|
544
|
-
} : {
|
|
545
|
-
kind: "invalid",
|
|
546
|
-
reason: "format"
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
502
|
/**
|
|
552
503
|
* A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
|
|
553
504
|
* clears it). The component renders a toggle and only ever stages these two
|
|
@@ -575,35 +526,65 @@ window.__ModuleLoader__.load({
|
|
|
575
526
|
}
|
|
576
527
|
};
|
|
577
528
|
}
|
|
578
|
-
const
|
|
579
|
-
/**
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
529
|
+
const MAX_TIMEOUT_SECONDS = 3600;
|
|
530
|
+
/**
|
|
531
|
+
* A duration field the user edits in WHOLE SECONDS.
|
|
532
|
+
*
|
|
533
|
+
* Milliseconds are the wrong unit to show: the defaults are 300000 and the
|
|
534
|
+
* ceiling is 2147483647, so the page asked users to do arithmetic to answer
|
|
535
|
+
* "how long is a reasonable wait". `format` divides, `parse` multiplies, and
|
|
536
|
+
* the stored value never leaves milliseconds.
|
|
537
|
+
*
|
|
538
|
+
* Decimals are refused rather than rounded. "1.5" is a format the control does
|
|
539
|
+
* not accept, and silently storing 1000 or 2000 for it would make the saved
|
|
540
|
+
* profile disagree with the box it was typed in.
|
|
541
|
+
*/
|
|
542
|
+
function secondsField(field, bounds) {
|
|
543
|
+
return {
|
|
544
|
+
field,
|
|
545
|
+
format: (value) => typeof value === "number" ? String(Math.round(value / 1e3)) : "",
|
|
546
|
+
parse: (text) => {
|
|
547
|
+
const trimmed = text.trim();
|
|
548
|
+
if (trimmed === "") return { kind: "clear" };
|
|
549
|
+
const parsed = Number(trimmed);
|
|
550
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return {
|
|
551
|
+
kind: "invalid",
|
|
552
|
+
reason: "format"
|
|
553
|
+
};
|
|
554
|
+
if (parsed < bounds.min) return {
|
|
555
|
+
kind: "invalid",
|
|
556
|
+
reason: "tooSmall"
|
|
557
|
+
};
|
|
558
|
+
if (parsed > bounds.max) return {
|
|
559
|
+
kind: "invalid",
|
|
560
|
+
reason: "tooLarge"
|
|
561
|
+
};
|
|
562
|
+
return {
|
|
563
|
+
kind: "set",
|
|
564
|
+
value: parsed * 1e3
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
587
569
|
/** The fields this page edits inside the `llm-commandcode` namespace. */
|
|
588
570
|
const SECTION_FIELDS = [
|
|
589
571
|
textField("apiBase"),
|
|
590
|
-
|
|
572
|
+
secondsField("requestTimeoutMs", {
|
|
591
573
|
min: 1,
|
|
592
|
-
max:
|
|
574
|
+
max: MAX_TIMEOUT_SECONDS
|
|
593
575
|
}),
|
|
594
|
-
|
|
576
|
+
secondsField("streamIdleTimeoutMs", {
|
|
595
577
|
min: 1,
|
|
596
|
-
max:
|
|
578
|
+
max: MAX_TIMEOUT_SECONDS
|
|
597
579
|
}),
|
|
598
580
|
numberField$1("transportMaxRetries", {
|
|
599
581
|
min: 0,
|
|
600
582
|
max: 50
|
|
601
583
|
}),
|
|
584
|
+
booleanField$1("offloadSeenImagesForCache"),
|
|
602
585
|
booleanField$1("filterModelsByPlan"),
|
|
603
586
|
booleanField$1("webSearch"),
|
|
604
587
|
booleanField$1("showSidebarQuota"),
|
|
605
|
-
booleanField$1("commandGuard"),
|
|
606
|
-
choiceField("commandGuardLevel", COMMAND_GUARD_LEVEL_CHOICES),
|
|
607
588
|
booleanField$1("zdr")
|
|
608
589
|
];
|
|
609
590
|
/** Whether two model-id lists are equal as sets (order-insensitive). */
|
|
@@ -643,17 +624,12 @@ window.__ModuleLoader__.load({
|
|
|
643
624
|
}
|
|
644
625
|
/**
|
|
645
626
|
* Controller bridging the `llm-commandcode` scope and the credentials domain
|
|
646
|
-
* onto the page.
|
|
627
|
+
* onto the page. See the module header for the two write paths.
|
|
647
628
|
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
* per-account models) commits IMMEDIATELY and serially. A new account had to
|
|
653
|
-
* be saved before browser sign-in could target it (the Host refuses a login
|
|
654
|
-
* for a reference the stored `accounts` list does not name), which turned
|
|
655
|
-
* "add an account" into edit → save → sign in; committing each operation
|
|
656
|
-
* removes that dance and keeps unrelated staged edits out of it.
|
|
629
|
+
* Account ops commit immediately, so a new account can be created, signed into
|
|
630
|
+
* and pinned in one gesture — the Host refuses a login for a reference the
|
|
631
|
+
* stored `accounts` list does not name, so a staged row could not be signed
|
|
632
|
+
* into at all.
|
|
657
633
|
*/
|
|
658
634
|
var CommandCodeSettingsController = class {
|
|
659
635
|
scope;
|
|
@@ -663,13 +639,11 @@ window.__ModuleLoader__.load({
|
|
|
663
639
|
listeners = /* @__PURE__ */ new Set();
|
|
664
640
|
disposers = [];
|
|
665
641
|
disposed = false;
|
|
666
|
-
/** The credential reference the default account resolves. */
|
|
667
642
|
credentialRef = DEFAULT_API_KEY_REF;
|
|
668
643
|
/** Host-reported configured/writable state per credential reference. */
|
|
669
644
|
credentialStates = /* @__PURE__ */ new Map();
|
|
670
645
|
/** Staged visible-model allowlist (undefined = no draft). */
|
|
671
646
|
visibleModelsDraft = void 0;
|
|
672
|
-
/** The catalog the model editors offer (Host-side). */
|
|
673
647
|
catalogModels = [];
|
|
674
648
|
catalogFailed = false;
|
|
675
649
|
saving = false;
|
|
@@ -679,10 +653,6 @@ window.__ModuleLoader__.load({
|
|
|
679
653
|
accountQueue = Promise.resolve();
|
|
680
654
|
accountPending = 0;
|
|
681
655
|
accountFailed = void 0;
|
|
682
|
-
/**
|
|
683
|
-
* @param scope - bound scope for the `llm-commandcode` namespace.
|
|
684
|
-
* @param api - credentials wire face.
|
|
685
|
-
*/
|
|
686
656
|
constructor(scope, api) {
|
|
687
657
|
this.scope = scope;
|
|
688
658
|
this.api = api;
|
|
@@ -745,11 +715,10 @@ window.__ModuleLoader__.load({
|
|
|
745
715
|
requestTimeoutMs: this.field("requestTimeoutMs"),
|
|
746
716
|
streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
|
|
747
717
|
transportMaxRetries: this.field("transportMaxRetries"),
|
|
718
|
+
offloadSeenImagesForCache: this.field("offloadSeenImagesForCache"),
|
|
748
719
|
filterModelsByPlan: this.field("filterModelsByPlan"),
|
|
749
720
|
webSearch: this.field("webSearch"),
|
|
750
721
|
showSidebarQuota: this.field("showSidebarQuota"),
|
|
751
|
-
commandGuard: this.field("commandGuard"),
|
|
752
|
-
commandGuardLevel: this.field("commandGuardLevel"),
|
|
753
722
|
zdr: this.field("zdr"),
|
|
754
723
|
sidebarQuota: this.sectionValue("showSidebarQuota") === true,
|
|
755
724
|
activeAccount: typeof active === "string" ? active : "",
|
|
@@ -973,9 +942,8 @@ window.__ModuleLoader__.load({
|
|
|
973
942
|
return target === "default" ? this.credentialRef : target;
|
|
974
943
|
}
|
|
975
944
|
/**
|
|
976
|
-
* The first free `<credentialRef>_<n>` reference
|
|
977
|
-
*
|
|
978
|
-
* consistent with the default slot.
|
|
945
|
+
* The first free `<credentialRef>_<n>` reference, so a renamed `apiKeyEnv`
|
|
946
|
+
* yields `MY_KEY_2`-style refs consistent with the default slot.
|
|
979
947
|
*/
|
|
980
948
|
nextAccountRef() {
|
|
981
949
|
const used = /* @__PURE__ */ new Set([this.credentialRef, ...this.storedExtras().map((extra) => extra.ref)]);
|
|
@@ -1133,10 +1101,10 @@ window.__ModuleLoader__.load({
|
|
|
1133
1101
|
if (changed) this.publish();
|
|
1134
1102
|
}
|
|
1135
1103
|
/**
|
|
1136
|
-
* Fetch the model catalog
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1104
|
+
* Fetch the model catalog through the Host Remote. Runs once at construction;
|
|
1105
|
+
* call again (e.g. from the client entry once the Remote mount lands) to
|
|
1106
|
+
* (re)try — a later success clears a prior failure flag, so the editors
|
|
1107
|
+
* recover without a page reload.
|
|
1140
1108
|
*/
|
|
1141
1109
|
refreshCatalog() {
|
|
1142
1110
|
const models = this.api.models;
|
|
@@ -1155,7 +1123,8 @@ window.__ModuleLoader__.load({
|
|
|
1155
1123
|
shaped.push({
|
|
1156
1124
|
id: entry.id,
|
|
1157
1125
|
name: entry.name,
|
|
1158
|
-
...typeof entry.tier === "string" ? { tier: entry.tier } : {}
|
|
1126
|
+
...typeof entry.tier === "string" ? { tier: entry.tier } : {},
|
|
1127
|
+
...typeof entry.allowance === "number" && Number.isFinite(entry.allowance) ? { allowance: entry.allowance } : {}
|
|
1159
1128
|
});
|
|
1160
1129
|
}
|
|
1161
1130
|
this.catalogModels = shaped;
|
|
@@ -1257,6 +1226,34 @@ window.__ModuleLoader__.load({
|
|
|
1257
1226
|
}
|
|
1258
1227
|
};
|
|
1259
1228
|
//#endregion
|
|
1229
|
+
//#region src/display-format.ts
|
|
1230
|
+
/** Shared, dependency-free formatting for Host commands and browser surfaces. */
|
|
1231
|
+
function formatMoney(value) {
|
|
1232
|
+
return `$${value.toFixed(2)}`;
|
|
1233
|
+
}
|
|
1234
|
+
function formatMoneyExact(value) {
|
|
1235
|
+
return `$${value.toFixed(4)}`;
|
|
1236
|
+
}
|
|
1237
|
+
function formatTokensCompact(value) {
|
|
1238
|
+
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
1239
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
1240
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
1241
|
+
return String(value);
|
|
1242
|
+
}
|
|
1243
|
+
/** The input is already in percent units; callers add the percent sign. */
|
|
1244
|
+
function formatSuccessRate(value) {
|
|
1245
|
+
return String(Number(value.toFixed(2)));
|
|
1246
|
+
}
|
|
1247
|
+
/** One window's fill ratio in [0, 1]; zero when uncapped. */
|
|
1248
|
+
function windowRatio(used, cap) {
|
|
1249
|
+
if (cap <= 0) return 0;
|
|
1250
|
+
return Math.max(0, Math.min(1, used / cap));
|
|
1251
|
+
}
|
|
1252
|
+
/** Local date-time, with the empty state chosen by the caller. */
|
|
1253
|
+
function formatResetAt(ms) {
|
|
1254
|
+
return ms <= 0 ? "" : new Date(ms).toLocaleString();
|
|
1255
|
+
}
|
|
1256
|
+
//#endregion
|
|
1260
1257
|
//#region src/client/usage.ts
|
|
1261
1258
|
const IDLE$1 = {
|
|
1262
1259
|
status: "idle",
|
|
@@ -1339,41 +1336,6 @@ window.__ModuleLoader__.load({
|
|
|
1339
1336
|
for (const listener of this.listeners) listener();
|
|
1340
1337
|
}
|
|
1341
1338
|
};
|
|
1342
|
-
/** Format a dollar amount compactly (2 decimals). */
|
|
1343
|
-
function formatMoney(value) {
|
|
1344
|
-
return `$${value.toFixed(2)}`;
|
|
1345
|
-
}
|
|
1346
|
-
/** Format a dollar amount precisely (4 decimals) for small totals. */
|
|
1347
|
-
function formatMoneyExact(value) {
|
|
1348
|
-
return `$${value.toFixed(4)}`;
|
|
1349
|
-
}
|
|
1350
|
-
/** Format a large token count compactly (1.9M style). */
|
|
1351
|
-
function formatTokensCompact(value) {
|
|
1352
|
-
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
1353
|
-
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
1354
|
-
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
1355
|
-
return String(value);
|
|
1356
|
-
}
|
|
1357
|
-
/**
|
|
1358
|
-
* Format a success-rate percentage (already in percent units, e.g. 99.96):
|
|
1359
|
-
* at most two decimals, trailing zeros trimmed — `100` stays `100`, not
|
|
1360
|
-
* `100.00`, and the raw upstream float `99.965552876334` becomes `99.97`.
|
|
1361
|
-
* The `%` suffix is appended by the caller (the card and the dashboard line
|
|
1362
|
-
* both compose it).
|
|
1363
|
-
*/
|
|
1364
|
-
function formatSuccessRate(value) {
|
|
1365
|
-
return String(Number(value.toFixed(2)));
|
|
1366
|
-
}
|
|
1367
|
-
/** One window's fill ratio in [0, 1]; 0 when uncapped. */
|
|
1368
|
-
function windowRatio(used, cap) {
|
|
1369
|
-
if (cap <= 0) return 0;
|
|
1370
|
-
return Math.max(0, Math.min(1, used / cap));
|
|
1371
|
-
}
|
|
1372
|
-
/** Format a millis timestamp as a local short date-time; empty when unset. */
|
|
1373
|
-
function formatResetAt(ms) {
|
|
1374
|
-
if (ms <= 0) return "";
|
|
1375
|
-
return new Date(ms).toLocaleString();
|
|
1376
|
-
}
|
|
1377
1339
|
/** Host facts drive the usage card, including keys invisible to browser credentials. */
|
|
1378
1340
|
function usageCardState(usage) {
|
|
1379
1341
|
const accounts = usage.report?.accounts ?? [];
|
|
@@ -1402,15 +1364,12 @@ window.__ModuleLoader__.load({
|
|
|
1402
1364
|
};
|
|
1403
1365
|
/**
|
|
1404
1366
|
* Cache over the `commandcode/prices` Remote, with a bounded transient-retry
|
|
1405
|
-
* budget. Public API mirrors
|
|
1406
|
-
* `
|
|
1407
|
-
* restarts the budget when the
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
* poll), and a MANUAL `ensure()` still fetches after that budget is spent. A
|
|
1412
|
-
* PERMANENT failure — the Host serving no such endpoint — is terminal for the
|
|
1413
|
-
* binding and is only cleared by `reload()`.
|
|
1367
|
+
* budget (three retries at 1/2/4 s — bounded, not a poll). Public API mirrors
|
|
1368
|
+
* {@link CommandCodeUsageController}: `state()`, `subscribe` and `ensure()`;
|
|
1369
|
+
* `reload()` additionally drops the cache and restarts the budget when the
|
|
1370
|
+
* Host namespace rebinds. A MANUAL `ensure()` still fetches after the budget
|
|
1371
|
+
* is spent, and a PERMANENT failure — the Host serving no such endpoint — is
|
|
1372
|
+
* terminal for the binding until `reload()`.
|
|
1414
1373
|
*/
|
|
1415
1374
|
var CommandCodePricesController = class {
|
|
1416
1375
|
remote;
|
|
@@ -1444,12 +1403,9 @@ window.__ModuleLoader__.load({
|
|
|
1444
1403
|
}
|
|
1445
1404
|
/**
|
|
1446
1405
|
* Fetch the table unless it is already loaded, permanent, or in flight — and
|
|
1447
|
-
* unless a bounded retry already owns the next attempt.
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
* lands or rebinds, and a manual refresh. Nothing in the composer calls it, so
|
|
1451
|
-
* a mounted readout does not trigger a fetch of its own — it renders whatever
|
|
1452
|
-
* the table's state currently is.
|
|
1406
|
+
* unless a bounded retry already owns the next attempt. Only the client
|
|
1407
|
+
* entry calls this (on rebind and on a manual refresh), so a mounted readout
|
|
1408
|
+
* renders the current state instead of triggering a fetch of its own.
|
|
1453
1409
|
*/
|
|
1454
1410
|
ensure() {
|
|
1455
1411
|
if (this.disposed || this.permanent || this.inFlight || this.current.status === "ready") return;
|
|
@@ -1575,12 +1531,11 @@ window.__ModuleLoader__.load({
|
|
|
1575
1531
|
this.remote = remote;
|
|
1576
1532
|
this.pollMs = pollMs;
|
|
1577
1533
|
}
|
|
1578
|
-
/** Subscribe to state projections.
|
|
1534
|
+
/** Subscribe to state projections. */
|
|
1579
1535
|
subscribe(listener) {
|
|
1580
1536
|
this.listeners.add(listener);
|
|
1581
1537
|
return () => this.listeners.delete(listener);
|
|
1582
1538
|
}
|
|
1583
|
-
/** Build the current panel state face. */
|
|
1584
1539
|
state() {
|
|
1585
1540
|
return {
|
|
1586
1541
|
phase: this.phase,
|
|
@@ -1773,9 +1728,9 @@ window.__ModuleLoader__.load({
|
|
|
1773
1728
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1774
1729
|
}
|
|
1775
1730
|
/**
|
|
1776
|
-
* The per-reason copy for a failed
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
1731
|
+
* The per-reason copy for a failed attempt. Shared by the settings page's login
|
|
1732
|
+
* panel and the Models-page card's login row — the same reason can surface from
|
|
1733
|
+
* either.
|
|
1779
1734
|
*/
|
|
1780
1735
|
function loginFailureCopy(reason, t) {
|
|
1781
1736
|
if (reason === "denied") return t("loginDenied");
|
|
@@ -1788,8 +1743,8 @@ window.__ModuleLoader__.load({
|
|
|
1788
1743
|
}
|
|
1789
1744
|
/**
|
|
1790
1745
|
* The hint text + class for one login panel state, shared by the settings
|
|
1791
|
-
* page's
|
|
1792
|
-
* can never drift apart. Pure: no timers, no state
|
|
1746
|
+
* page's login panel and the Models-page card's login row so both surfaces
|
|
1747
|
+
* can never drift apart. Pure: no timers, no state.
|
|
1793
1748
|
*/
|
|
1794
1749
|
function loginHint(state, t) {
|
|
1795
1750
|
if (state.phase === "starting" || state.phase === "waiting") return {
|
|
@@ -1830,16 +1785,16 @@ window.__ModuleLoader__.load({
|
|
|
1830
1785
|
/** The wire namespace all Command Code endpoints share. */
|
|
1831
1786
|
const REMOTE_NAMESPACE = "commandcode";
|
|
1832
1787
|
/**
|
|
1833
|
-
* Build the validator helpers one endpoint uses. `prefix` names the
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1836
|
-
* bodies.
|
|
1788
|
+
* Build the validator helpers one endpoint uses. `prefix` names the endpoint
|
|
1789
|
+
* in the rejection message (e.g. `commandcode/report result:`), so each wire
|
|
1790
|
+
* file keeps its own diagnostic phrasing while sharing the helper bodies.
|
|
1837
1791
|
*
|
|
1838
1792
|
* The helpers return the reject call directly in the failure branch: since
|
|
1839
1793
|
* `reject` is typed `never`, the ternary's union collapses to the success type
|
|
1840
1794
|
* without relying on TypeScript's control-flow analysis of a never-returning
|
|
1841
1795
|
* call (which only recognizes function declarations, not the destructured
|
|
1842
|
-
* arrow `reject` callers receive from this factory).
|
|
1796
|
+
* arrow `reject` callers receive from this factory). Callers that need the
|
|
1797
|
+
* narrowing in an `if` must therefore assign inside the positive branch.
|
|
1843
1798
|
*/
|
|
1844
1799
|
function makeBoundaryValidator(prefix) {
|
|
1845
1800
|
const reject = (field) => {
|
|
@@ -1867,10 +1822,8 @@ window.__ModuleLoader__.load({
|
|
|
1867
1822
|
}
|
|
1868
1823
|
/**
|
|
1869
1824
|
* Build one strict invocation descriptor. Every Command Code Remote shares the
|
|
1870
|
-
* `commandcode` namespace, the `commandcodeUsage` service, and a strict
|
|
1871
|
-
*
|
|
1872
|
-
* schema differ — so the boilerplate lives here once and each endpoint supplies
|
|
1873
|
-
* only its own facts.
|
|
1825
|
+
* `commandcode` namespace, the `commandcodeUsage` service, and a strict result
|
|
1826
|
+
* — only the endpoint, method, result type symbol, and schema differ.
|
|
1874
1827
|
*/
|
|
1875
1828
|
function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
|
|
1876
1829
|
return {
|
|
@@ -1894,7 +1847,6 @@ window.__ModuleLoader__.load({
|
|
|
1894
1847
|
* so rejection messages name the offending boundary.
|
|
1895
1848
|
*/
|
|
1896
1849
|
const { reject: reject$1, record: record$1, stringField: stringField$1, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
1897
|
-
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
1898
1850
|
/**
|
|
1899
1851
|
* Parse one quota window, or undefined when the frame carries no such window.
|
|
1900
1852
|
*
|
|
@@ -2010,7 +1962,7 @@ window.__ModuleLoader__.load({
|
|
|
2010
1962
|
* separate instance so catalog boundary errors name `commandcode/models`,
|
|
2011
1963
|
* not the report endpoint.
|
|
2012
1964
|
*/
|
|
2013
|
-
const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
|
|
1965
|
+
const { record: catalogRecord, stringField: catalogString, numberField: catalogNumber } = makeBoundaryValidator("commandcode/models result:");
|
|
2014
1966
|
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
2015
1967
|
function parseCatalogModel(value) {
|
|
2016
1968
|
const source = catalogRecord(value, "model");
|
|
@@ -2019,6 +1971,7 @@ window.__ModuleLoader__.load({
|
|
|
2019
1971
|
name: catalogString(source, "name", "model.name")
|
|
2020
1972
|
};
|
|
2021
1973
|
if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
|
|
1974
|
+
if (source.allowance !== void 0) model.allowance = catalogNumber(source, "allowance", "model.allowance");
|
|
2022
1975
|
return model;
|
|
2023
1976
|
}
|
|
2024
1977
|
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
@@ -2170,38 +2123,40 @@ window.__ModuleLoader__.load({
|
|
|
2170
2123
|
]
|
|
2171
2124
|
};
|
|
2172
2125
|
//#endregion
|
|
2126
|
+
//#region src/plan-tiers.ts
|
|
2127
|
+
/** Shared plan-tier presentation facts; safe to inline into the browser bundle. */
|
|
2128
|
+
const PLAN_TIER_ORDER = [
|
|
2129
|
+
"go",
|
|
2130
|
+
"goat",
|
|
2131
|
+
"pro",
|
|
2132
|
+
"provider",
|
|
2133
|
+
"max"
|
|
2134
|
+
];
|
|
2135
|
+
const PLAN_LABELS = {
|
|
2136
|
+
go: "Go",
|
|
2137
|
+
goat: "GOAT",
|
|
2138
|
+
pro: "Pro",
|
|
2139
|
+
provider: "Provider",
|
|
2140
|
+
max: "Max"
|
|
2141
|
+
};
|
|
2142
|
+
Object.fromEntries(PLAN_TIER_ORDER.map((tier, index) => [tier, index]));
|
|
2143
|
+
//#endregion
|
|
2173
2144
|
//#region src/client/model-select.ts
|
|
2174
2145
|
/**
|
|
2175
2146
|
* Model-select helpers for the settings page's model editors (browser half).
|
|
2176
2147
|
*
|
|
2177
|
-
* The routing-rule editor and the visible-models filter both pick catalog
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2148
|
+
* The routing-rule editor and the visible-models filter both pick catalog models
|
|
2149
|
+
* through the same checkbox multi-select dropdown (`ModelMultiSelect` in
|
|
2150
|
+
* section.tsx). The dropdown's data shaping — search filtering, stale-id
|
|
2180
2151
|
* detection, tier grouping — lives here, React-free, so node tests can drive
|
|
2181
2152
|
* it directly.
|
|
2182
2153
|
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
* (tier key → heading label) rather than an import of src/capabilities.ts.
|
|
2186
|
-
* When upstream adds a plan tier, extend BOTH tables.
|
|
2154
|
+
* The shared tier module is dependency-free and inlined into the client bundle;
|
|
2155
|
+
* importing the Host's full capability snapshot here would bloat that bundle.
|
|
2187
2156
|
*
|
|
2188
2157
|
* @module dsh-commandcode-provider/model-select
|
|
2189
2158
|
*/
|
|
2190
2159
|
/**
|
|
2191
|
-
* Minimum plan tier → dropdown section heading. Mirrors the Host-side
|
|
2192
|
-
* `KNOWN_PLANS` values + `PLAN_LABELS` in src/capabilities.ts (kept as a
|
|
2193
|
-
* vendored copy because the client bundle cannot import host modules).
|
|
2194
|
-
* Covers every tier key `KNOWN_PLANS` uses today; an unknown tier key falls
|
|
2195
|
-
* back to the raw key rather than vanishing the row.
|
|
2196
|
-
*/
|
|
2197
|
-
const TIER_HEADINGS = {
|
|
2198
|
-
go: "Go",
|
|
2199
|
-
goat: "GOAT",
|
|
2200
|
-
pro: "Pro",
|
|
2201
|
-
provider: "Provider",
|
|
2202
|
-
max: "Max"
|
|
2203
|
-
};
|
|
2204
|
-
/**
|
|
2205
2160
|
* The dropdown section heading for a catalog model id, or undefined for
|
|
2206
2161
|
* models outside the known plan tiers (unmapped models and stale ids render
|
|
2207
2162
|
* unheaded). `knownPlans` is the Host-side `KNOWN_PLANS` table, threaded in
|
|
@@ -2210,7 +2165,7 @@ window.__ModuleLoader__.load({
|
|
|
2210
2165
|
function tierHeadingFor(modelId, knownPlans) {
|
|
2211
2166
|
const tier = knownPlans[modelId];
|
|
2212
2167
|
if (tier === void 0) return void 0;
|
|
2213
|
-
return
|
|
2168
|
+
return PLAN_LABELS[tier] ?? tier;
|
|
2214
2169
|
}
|
|
2215
2170
|
/**
|
|
2216
2171
|
* Whether `text` matches `query` as a case-insensitive substring over the
|
|
@@ -2222,21 +2177,19 @@ window.__ModuleLoader__.load({
|
|
|
2222
2177
|
return model.id.toLowerCase().includes(needle) || model.name.toLowerCase().includes(needle);
|
|
2223
2178
|
}
|
|
2224
2179
|
/**
|
|
2225
|
-
* Build the dropdown options: the catalog (already in picker order) plus
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2228
|
-
*
|
|
2229
|
-
*
|
|
2230
|
-
* When `query` is non-blank, catalog rows are filtered by
|
|
2231
|
-
* {@link matchesModelQuery}; stale rows are kept only while they match too,
|
|
2232
|
-
* so a search for a live model does not surface unrelated stale ids.
|
|
2180
|
+
* Build the dropdown options: the catalog (already in picker order) plus any
|
|
2181
|
+
* selected ids the catalog no longer carries, flagged stale so a saved
|
|
2182
|
+
* selection never silently loses an entry. Stale rows survive a non-blank
|
|
2183
|
+
* {@link matchesModelQuery} search only while they match too, so a search for
|
|
2184
|
+
* a live model does not surface unrelated stale ids.
|
|
2233
2185
|
*/
|
|
2234
2186
|
function buildModelSelectOptions(catalog, selected, query = "") {
|
|
2235
2187
|
const catalogIds = new Set(catalog.map((model) => model.id));
|
|
2236
2188
|
const options = catalog.filter((model) => matchesModelQuery(model, query)).map((model) => ({
|
|
2237
2189
|
value: model.id,
|
|
2238
2190
|
label: model.name,
|
|
2239
|
-
stale: false
|
|
2191
|
+
stale: false,
|
|
2192
|
+
...model.allowance === void 0 ? {} : { allowance: model.allowance }
|
|
2240
2193
|
}));
|
|
2241
2194
|
const seen = new Set(catalogIds);
|
|
2242
2195
|
for (const id of selected) {
|
|
@@ -2256,12 +2209,10 @@ window.__ModuleLoader__.load({
|
|
|
2256
2209
|
return options;
|
|
2257
2210
|
}
|
|
2258
2211
|
/**
|
|
2259
|
-
* Group dropdown options under plan-tier headings (`tierOf` maps a model id
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2262
|
-
*
|
|
2263
|
-
* repeats, so a catalog interleaving two tiers still renders one section
|
|
2264
|
-
* per tier.
|
|
2212
|
+
* Group dropdown options under plan-tier headings (`tierOf` maps a model id to
|
|
2213
|
+
* its heading, or undefined for unmapped models — see {@link tierHeadingFor}).
|
|
2214
|
+
* Live rows keep their relative order and repeats merge, so a catalog
|
|
2215
|
+
* interleaving two tiers still renders one section per tier.
|
|
2265
2216
|
*/
|
|
2266
2217
|
function groupModelSelectOptions(options, tierOf) {
|
|
2267
2218
|
const groups = [];
|
|
@@ -2291,13 +2242,11 @@ window.__ModuleLoader__.load({
|
|
|
2291
2242
|
}
|
|
2292
2243
|
/**
|
|
2293
2244
|
* Whether the catalog is trustworthy enough to call an unlisted selection
|
|
2294
|
-
* "retired". FALSE while the first fetch is
|
|
2295
|
-
*
|
|
2296
|
-
* stale
|
|
2297
|
-
* empties the allowlist.
|
|
2298
|
-
*
|
|
2299
|
-
* model being retired at once, and the explicit "show all" entry covers the
|
|
2300
|
-
* user who really wants to clear the list.
|
|
2245
|
+
* "retired". FALSE while the first fetch is in flight, after a failure, and
|
|
2246
|
+
* for a loaded-but-empty catalog — an empty list makes every selected id look
|
|
2247
|
+
* stale, which would turn the one-click stale cleanup into a button that
|
|
2248
|
+
* silently empties the allowlist. Users who really want it cleared have the
|
|
2249
|
+
* explicit "show all" entry.
|
|
2301
2250
|
*/
|
|
2302
2251
|
function catalogIsReady(readiness) {
|
|
2303
2252
|
return readiness.catalogIds.length > 0 && !readiness.catalogFailed;
|
|
@@ -2313,7 +2262,7 @@ window.__ModuleLoader__.load({
|
|
|
2313
2262
|
}
|
|
2314
2263
|
//#endregion
|
|
2315
2264
|
//#region package.json
|
|
2316
|
-
var version = "0.
|
|
2265
|
+
var version = "0.12.0";
|
|
2317
2266
|
var repository = {
|
|
2318
2267
|
"type": "git",
|
|
2319
2268
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -2321,17 +2270,15 @@ window.__ModuleLoader__.load({
|
|
|
2321
2270
|
//#endregion
|
|
2322
2271
|
//#region src/client/version.ts
|
|
2323
2272
|
/**
|
|
2324
|
-
* The plugin's own version, read from package.json at build time.
|
|
2325
|
-
*
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
2328
|
-
*
|
|
2329
|
-
* sync. Rendered as a muted footer line on the settings page so a user can
|
|
2330
|
-
* report the exact build they run.
|
|
2273
|
+
* The plugin's own version, read from package.json at build time. The client
|
|
2274
|
+
* bundle inlines the JSON import (rolldown resolves it during the tsdown
|
|
2275
|
+
* build; node tests read it through tsx), so the rendered value always matches
|
|
2276
|
+
* the published package version with no second constant to keep in sync —
|
|
2277
|
+
* rendered as a muted footer line so a user can report the exact build.
|
|
2331
2278
|
*
|
|
2332
2279
|
* @module dsh-commandcode-provider/client/version
|
|
2333
2280
|
*/
|
|
2334
|
-
/** The published package version
|
|
2281
|
+
/** The published package version. */
|
|
2335
2282
|
const PLUGIN_VERSION = version;
|
|
2336
2283
|
/**
|
|
2337
2284
|
* This package's GitHub releases page, derived from the repository field so
|
|
@@ -2351,14 +2298,12 @@ window.__ModuleLoader__.load({
|
|
|
2351
2298
|
*/
|
|
2352
2299
|
const NPM_LATEST_URL = "https://registry.npmjs.org/@mars-sea%2Fdsh-commandcode-provider/latest";
|
|
2353
2300
|
/**
|
|
2354
|
-
* Compare two version strings (`major.minor.patch[-pre]`)
|
|
2355
|
-
*
|
|
2301
|
+
* Compare two version strings (`major.minor.patch[-pre]`): negative when `a`
|
|
2302
|
+
* sorts before `b`, positive when after, zero when equal.
|
|
2356
2303
|
*
|
|
2357
|
-
* Tolerant by design
|
|
2358
|
-
* count as `0`, and semver prerelease rules apply
|
|
2359
|
-
*
|
|
2360
|
-
* shorter identifier list sorts first). Enough for release tags; not a full
|
|
2361
|
-
* semver validator.
|
|
2304
|
+
* Tolerant by design — a leading `v` is stripped, unparsable numeric parts
|
|
2305
|
+
* count as `0`, and semver prerelease rules apply. Enough for release tags,
|
|
2306
|
+
* not a full semver validator.
|
|
2362
2307
|
*/
|
|
2363
2308
|
function compareVersions(a, b) {
|
|
2364
2309
|
const left = splitVersion(a);
|
|
@@ -2494,21 +2439,17 @@ window.__ModuleLoader__.load({
|
|
|
2494
2439
|
* React component for the "Command Code" settings page (browser half).
|
|
2495
2440
|
*
|
|
2496
2441
|
* Renders as a `settings.section` entry — a page at the same settings-nav
|
|
2497
|
-
* level as General / Models / Plugins.
|
|
2498
|
-
* renders this body inside the content column. All copy comes from the
|
|
2442
|
+
* level as General / Models / Plugins. All copy comes from the
|
|
2499
2443
|
* `settings.commandcode` locale namespace; all state comes from the
|
|
2500
2444
|
* `CommandCodeSettingsController` injected by the slot registration.
|
|
2501
2445
|
*
|
|
2502
|
-
* Layout, top to bottom:
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2505
|
-
*
|
|
2506
|
-
*
|
|
2507
|
-
*
|
|
2508
|
-
*
|
|
2509
|
-
* Sections 2–5 are flat groups of rows (title and description left, control
|
|
2510
|
-
* right, as on the harness's own settings pages), a staged form written by
|
|
2511
|
-
* the floating save bar.
|
|
2446
|
+
* Layout, top to bottom: Accounts (one card per account, status, quota,
|
|
2447
|
+
* actions and dedicated models — every operation commits immediately), Models
|
|
2448
|
+
* (plan filter + visible-model allowlist), Privacy & security (ZDR),
|
|
2449
|
+
* Integrations & display (web search, sidebar quota card), and a collapsed
|
|
2450
|
+
* Advanced card (API base + network limits). Everything below Accounts is a
|
|
2451
|
+
* flat group of rows (title/description left, control right) inside the staged
|
|
2452
|
+
* form the floating save bar writes.
|
|
2512
2453
|
*
|
|
2513
2454
|
* Styles are injected once by the client entry (see src/client/index.ts) and
|
|
2514
2455
|
* class-prefixed `cc-` to stay local.
|
|
@@ -2517,7 +2458,8 @@ window.__ModuleLoader__.load({
|
|
|
2517
2458
|
"apiBase",
|
|
2518
2459
|
"requestTimeoutMs",
|
|
2519
2460
|
"streamIdleTimeoutMs",
|
|
2520
|
-
"transportMaxRetries"
|
|
2461
|
+
"transportMaxRetries",
|
|
2462
|
+
"offloadSeenImagesForCache"
|
|
2521
2463
|
];
|
|
2522
2464
|
/** The "customized" tag and reset link a staged field shows once it differs from the default. */
|
|
2523
2465
|
function FieldOverride({ label, state, disabled, t, onReset }) {
|
|
@@ -2645,53 +2587,6 @@ window.__ModuleLoader__.load({
|
|
|
2645
2587
|
})] })
|
|
2646
2588
|
});
|
|
2647
2589
|
}
|
|
2648
|
-
/**
|
|
2649
|
-
* One fixed-choice field rendered as a segmented control. The staged text is
|
|
2650
|
-
* one of `options`' values or `''` (unset → `defaultValue`); picking the
|
|
2651
|
-
* default while unset stages nothing new.
|
|
2652
|
-
*/
|
|
2653
|
-
function SegmentedField({ label, hint, state, disabled, options, defaultValue, className, onEdit, onReset, t }) {
|
|
2654
|
-
const current = state.text === "" ? defaultValue : state.text;
|
|
2655
|
-
const index = Math.max(0, options.findIndex((option) => option.value === current));
|
|
2656
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
2657
|
-
title: label,
|
|
2658
|
-
tag: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverrideTag, {
|
|
2659
|
-
state,
|
|
2660
|
-
t
|
|
2661
|
-
}),
|
|
2662
|
-
description: hint,
|
|
2663
|
-
...className === void 0 ? {} : { className },
|
|
2664
|
-
control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
|
|
2665
|
-
label,
|
|
2666
|
-
state,
|
|
2667
|
-
disabled,
|
|
2668
|
-
t,
|
|
2669
|
-
onReset
|
|
2670
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2671
|
-
className: "cc-segmented",
|
|
2672
|
-
role: "radiogroup",
|
|
2673
|
-
"aria-label": label,
|
|
2674
|
-
style: {
|
|
2675
|
-
"--cc-segment-count": options.length,
|
|
2676
|
-
"--cc-segment-index": index
|
|
2677
|
-
},
|
|
2678
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2679
|
-
className: "cc-segmentIndicator",
|
|
2680
|
-
"aria-hidden": "true"
|
|
2681
|
-
}), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2682
|
-
type: "button",
|
|
2683
|
-
role: "radio",
|
|
2684
|
-
"aria-checked": current === option.value,
|
|
2685
|
-
className: "cc-segment",
|
|
2686
|
-
disabled,
|
|
2687
|
-
onClick: () => {
|
|
2688
|
-
if (current !== option.value) onEdit(option.value);
|
|
2689
|
-
},
|
|
2690
|
-
children: option.label
|
|
2691
|
-
}, option.value))]
|
|
2692
|
-
})] })
|
|
2693
|
-
});
|
|
2694
|
-
}
|
|
2695
2590
|
/** One stat tile in the account card's summary grid. */
|
|
2696
2591
|
function UsageStat({ label, value, sub }) {
|
|
2697
2592
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2760,11 +2655,8 @@ window.__ModuleLoader__.load({
|
|
|
2760
2655
|
]
|
|
2761
2656
|
});
|
|
2762
2657
|
}
|
|
2763
|
-
/**
|
|
2764
|
-
|
|
2765
|
-
* rendered inside an account row's details.
|
|
2766
|
-
*/
|
|
2767
|
-
function AccountReport({ entry, fetchedAt, t, summaryOnly = false, headless = false }) {
|
|
2658
|
+
/** One pool account's facts (identity, totals, credits, window limits). */
|
|
2659
|
+
function AccountReport({ entry, fetchedAt, t, headless = false }) {
|
|
2768
2660
|
const report = entry.report;
|
|
2769
2661
|
const account = report.account;
|
|
2770
2662
|
const accountName = account === void 0 ? "" : account.userName || account.name;
|
|
@@ -2821,7 +2713,7 @@ window.__ModuleLoader__.load({
|
|
|
2821
2713
|
}) : null
|
|
2822
2714
|
]
|
|
2823
2715
|
}) : null,
|
|
2824
|
-
|
|
2716
|
+
report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2825
2717
|
className: "cc-usageStats cc-usageStatsActivity",
|
|
2826
2718
|
children: [
|
|
2827
2719
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
@@ -2845,7 +2737,7 @@ window.__ModuleLoader__.load({
|
|
|
2845
2737
|
})
|
|
2846
2738
|
]
|
|
2847
2739
|
}) : null,
|
|
2848
|
-
|
|
2740
|
+
credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2849
2741
|
className: "cc-usageStats cc-usageStatsBalance",
|
|
2850
2742
|
children: [
|
|
2851
2743
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
@@ -2919,16 +2811,12 @@ window.__ModuleLoader__.load({
|
|
|
2919
2811
|
return t("usageNetworkHint");
|
|
2920
2812
|
}
|
|
2921
2813
|
/**
|
|
2922
|
-
* A checkbox multi-select dropdown for picking catalog models (the
|
|
2923
|
-
*
|
|
2924
|
-
*
|
|
2925
|
-
*
|
|
2926
|
-
*
|
|
2927
|
-
*
|
|
2928
|
-
* under plan-tier headings in picker order. Selected ids the catalog no
|
|
2929
|
-
* longer carries still render — flagged stale — so a saved selection never
|
|
2930
|
-
* silently loses an entry, and the VisibleModelsCard offers a one-click
|
|
2931
|
-
* cleanup.
|
|
2814
|
+
* A checkbox multi-select dropdown for picking catalog models (the routing-rule
|
|
2815
|
+
* rows and the visible-models filter share it). The trigger shows the selection
|
|
2816
|
+
* count; the Menu lists every catalog model, grouping under plan-tier headings
|
|
2817
|
+
* and flagging selected ids the catalog no longer carries as stale, so a saved
|
|
2818
|
+
* selection never silently loses an entry (the VisibleModelsCard offers a
|
|
2819
|
+
* one-click cleanup).
|
|
2932
2820
|
*
|
|
2933
2821
|
* With `deferCommit` the toggles collect in a local draft that is handed to
|
|
2934
2822
|
* `onSelect` once, when the menu closes: the per-account picker writes
|
|
@@ -2975,6 +2863,11 @@ window.__ModuleLoader__.load({
|
|
|
2975
2863
|
className: "cc-badge",
|
|
2976
2864
|
children: t("modelStale")
|
|
2977
2865
|
}) : null,
|
|
2866
|
+
option.allowance !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2867
|
+
className: "cc-badgeMuted",
|
|
2868
|
+
title: t("modelAllowanceTitle"),
|
|
2869
|
+
children: t("modelAllowance", { amount: option.allowance })
|
|
2870
|
+
}) : null,
|
|
2978
2871
|
!selected.includes(option.value) && ownerOf?.(option.value) !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2979
2872
|
className: "cc-badgeMuted",
|
|
2980
2873
|
children: t("accountModelOwner", { name: ownerOf(option.value) })
|
|
@@ -3073,11 +2966,13 @@ window.__ModuleLoader__.load({
|
|
|
3073
2966
|
}
|
|
3074
2967
|
/**
|
|
3075
2968
|
* One titled group of rows. No card surface: like the harness's own settings
|
|
3076
|
-
* pages, a group is a heading over
|
|
2969
|
+
* pages, a group is a heading over its rows. The hairline between sections is
|
|
2970
|
+
* `cc-groupDivided`, so it belongs to every group but the first one on the page
|
|
2971
|
+
* (accounts opens it) and never to the rows inside a group.
|
|
3077
2972
|
*/
|
|
3078
|
-
function SettingsGroup({ title, action, children }) {
|
|
2973
|
+
function SettingsGroup({ title, action, divided = false, children }) {
|
|
3079
2974
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3080
|
-
className: "cc-group",
|
|
2975
|
+
className: divided ? "cc-group cc-groupDivided" : "cc-group",
|
|
3081
2976
|
"aria-label": title,
|
|
3082
2977
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3083
2978
|
className: "cc-groupHead",
|
|
@@ -3098,6 +2993,7 @@ window.__ModuleLoader__.load({
|
|
|
3098
2993
|
function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear }) {
|
|
3099
2994
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
|
|
3100
2995
|
title: t("modelsTitle"),
|
|
2996
|
+
divided: true,
|
|
3101
2997
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
3102
2998
|
id: "cc-filter-models-by-plan",
|
|
3103
2999
|
label: t("filterModelsByPlan"),
|
|
@@ -3118,70 +3014,31 @@ window.__ModuleLoader__.load({
|
|
|
3118
3014
|
});
|
|
3119
3015
|
}
|
|
3120
3016
|
/**
|
|
3121
|
-
* Privacy & security: the
|
|
3122
|
-
*
|
|
3123
|
-
* text to a decision model with no ZDR upstream and can skip an approval the
|
|
3124
|
-
* user would otherwise have seen, so both start off.
|
|
3017
|
+
* Privacy & security: the opt-in that changes where data goes. ZDR restricts
|
|
3018
|
+
* which upstream serves a request, so it starts off.
|
|
3125
3019
|
*/
|
|
3126
3020
|
function PrivacyCard({ state, disabled, t, onEdit, onReset }) {
|
|
3127
|
-
|
|
3128
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
|
|
3021
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsGroup, {
|
|
3129
3022
|
title: t("privacyTitle"),
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
3143
|
-
id: "cc-command-guard",
|
|
3144
|
-
label: t("commandGuard"),
|
|
3145
|
-
hint: t("commandGuardHint"),
|
|
3146
|
-
state: state.commandGuard,
|
|
3147
|
-
disabled,
|
|
3148
|
-
defaultChecked: false,
|
|
3149
|
-
onEdit: (text) => onEdit("commandGuard", text),
|
|
3150
|
-
onReset: () => onReset("commandGuard"),
|
|
3151
|
-
t
|
|
3152
|
-
}),
|
|
3153
|
-
showGuardLevel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SegmentedField, {
|
|
3154
|
-
className: "cc-rowNested",
|
|
3155
|
-
label: t("commandGuardLevel"),
|
|
3156
|
-
hint: t("commandGuardLevelHint"),
|
|
3157
|
-
state: state.commandGuardLevel,
|
|
3158
|
-
disabled,
|
|
3159
|
-
options: [
|
|
3160
|
-
{
|
|
3161
|
-
value: "high",
|
|
3162
|
-
label: t("commandGuardLevelHigh")
|
|
3163
|
-
},
|
|
3164
|
-
{
|
|
3165
|
-
value: "medium",
|
|
3166
|
-
label: t("commandGuardLevelMedium")
|
|
3167
|
-
},
|
|
3168
|
-
{
|
|
3169
|
-
value: "low",
|
|
3170
|
-
label: t("commandGuardLevelLow")
|
|
3171
|
-
}
|
|
3172
|
-
],
|
|
3173
|
-
defaultValue: COMMAND_GUARD_DEFAULT_LEVEL_CHOICE,
|
|
3174
|
-
onEdit: (text) => onEdit("commandGuardLevel", text),
|
|
3175
|
-
onReset: () => onReset("commandGuardLevel"),
|
|
3176
|
-
t
|
|
3177
|
-
}) : null
|
|
3178
|
-
]
|
|
3023
|
+
divided: true,
|
|
3024
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
3025
|
+
id: "cc-zdr",
|
|
3026
|
+
label: t("zdr"),
|
|
3027
|
+
hint: t("zdrHint"),
|
|
3028
|
+
state: state.zdr,
|
|
3029
|
+
disabled,
|
|
3030
|
+
defaultChecked: false,
|
|
3031
|
+
onEdit: (text) => onEdit("zdr", text),
|
|
3032
|
+
onReset: () => onReset("zdr"),
|
|
3033
|
+
t
|
|
3034
|
+
})
|
|
3179
3035
|
});
|
|
3180
3036
|
}
|
|
3181
3037
|
/** Integrations & display: surfaces outside chat that reuse this provider. */
|
|
3182
3038
|
function IntegrationsCard({ state, disabled, t, onEdit, onReset }) {
|
|
3183
3039
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
|
|
3184
3040
|
title: t("integrationsTitle"),
|
|
3041
|
+
divided: true,
|
|
3185
3042
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
3186
3043
|
id: "cc-web-search",
|
|
3187
3044
|
label: t("webSearch"),
|
|
@@ -3215,7 +3072,7 @@ window.__ModuleLoader__.load({
|
|
|
3215
3072
|
const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
|
|
3216
3073
|
const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
|
|
3217
3074
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
3218
|
-
className: "cc-group",
|
|
3075
|
+
className: "cc-group cc-groupDivided",
|
|
3219
3076
|
"aria-label": t("advancedSettings"),
|
|
3220
3077
|
children: [
|
|
3221
3078
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
@@ -3295,6 +3152,17 @@ window.__ModuleLoader__.load({
|
|
|
3295
3152
|
onEdit: (text) => onEdit("transportMaxRetries", text),
|
|
3296
3153
|
onReset: () => onReset("transportMaxRetries"),
|
|
3297
3154
|
t
|
|
3155
|
+
}),
|
|
3156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
3157
|
+
id: "cc-offload-seen-images-for-cache",
|
|
3158
|
+
label: t("offloadSeenImagesForCache"),
|
|
3159
|
+
hint: t("offloadSeenImagesForCacheHint"),
|
|
3160
|
+
state: state.offloadSeenImagesForCache,
|
|
3161
|
+
disabled,
|
|
3162
|
+
defaultChecked: false,
|
|
3163
|
+
onEdit: (text) => onEdit("offloadSeenImagesForCache", text),
|
|
3164
|
+
onReset: () => onReset("offloadSeenImagesForCache"),
|
|
3165
|
+
t
|
|
3298
3166
|
})
|
|
3299
3167
|
]
|
|
3300
3168
|
}) : null,
|
|
@@ -3358,12 +3226,33 @@ window.__ModuleLoader__.load({
|
|
|
3358
3226
|
}
|
|
3359
3227
|
return rows;
|
|
3360
3228
|
}
|
|
3361
|
-
/**
|
|
3229
|
+
/**
|
|
3230
|
+
* The row's status dot. Read top-down, so a problem always outranks "serving":
|
|
3231
|
+
* red for a key the Host rejected, amber while a rate limit is cooling down,
|
|
3232
|
+
* green for the account that is actually serving requests. A standby (or not
|
|
3233
|
+
* yet configured) account draws NOTHING — `cc-tabDotBlank` is an unfilled,
|
|
3234
|
+
* borderless dot that only holds the column so the account names stay aligned
|
|
3235
|
+
* with the rows that do have a verdict. Green therefore means "in use", not
|
|
3236
|
+
* "healthy": a wall of green dots would say nothing.
|
|
3237
|
+
*/
|
|
3362
3238
|
function statusDotClass(row) {
|
|
3363
3239
|
const entry = row.usage;
|
|
3364
|
-
if (
|
|
3240
|
+
if (entry?.mark === "invalid-credential") return "cc-tabDot cc-tabDotError";
|
|
3365
3241
|
if (entry !== void 0 && (entry.mark !== "" || entry.cooldownUntil > 0)) return "cc-tabDot cc-tabDotWarn";
|
|
3366
|
-
return "cc-tabDot cc-tabDotOk";
|
|
3242
|
+
if (row.configured && entry?.active === true) return "cc-tabDot cc-tabDotOk";
|
|
3243
|
+
return "cc-tabDot cc-tabDotBlank";
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* The same verdict as a sentence, for the dot's tooltip. Undefined on a blank
|
|
3247
|
+
* dot: there is no verdict to read out, and a hover hotspot on an invisible
|
|
3248
|
+
* 8px mark would only invite the question it cannot usefully answer.
|
|
3249
|
+
*/
|
|
3250
|
+
function statusDotTitle(row, t) {
|
|
3251
|
+
const entry = row.usage;
|
|
3252
|
+
if (entry?.mark === "invalid-credential") return t("usageInvalidKey");
|
|
3253
|
+
if (entry !== void 0 && (entry.mark !== "" || entry.cooldownUntil > 0)) return t("usageCooldown");
|
|
3254
|
+
if (!row.configured) return t("apiKeyUnset");
|
|
3255
|
+
return entry?.active === true ? t("usageActive") : void 0;
|
|
3367
3256
|
}
|
|
3368
3257
|
/** One compact quota meter for a row's summary line. */
|
|
3369
3258
|
function MiniMeter({ label, limit }) {
|
|
@@ -3587,6 +3476,7 @@ window.__ModuleLoader__.load({
|
|
|
3587
3476
|
children: [
|
|
3588
3477
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3589
3478
|
className: statusDotClass(row),
|
|
3479
|
+
title: statusDotTitle(row, t),
|
|
3590
3480
|
"aria-hidden": "true"
|
|
3591
3481
|
}),
|
|
3592
3482
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -3927,9 +3817,8 @@ window.__ModuleLoader__.load({
|
|
|
3927
3817
|
}
|
|
3928
3818
|
/**
|
|
3929
3819
|
* The unified account card: every account the pool rotates through, one row
|
|
3930
|
-
* each, with its status and quota inline
|
|
3931
|
-
*
|
|
3932
|
-
* facts no longer have to be cross-read across three surfaces.
|
|
3820
|
+
* each, with its status and quota inline — so one account's facts no longer
|
|
3821
|
+
* have to be cross-read across separate surfaces.
|
|
3933
3822
|
*/
|
|
3934
3823
|
function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh }) {
|
|
3935
3824
|
const [adding, setAdding] = (0, react.useState)(false);
|
|
@@ -4278,7 +4167,6 @@ window.__ModuleLoader__.load({
|
|
|
4278
4167
|
}
|
|
4279
4168
|
//#endregion
|
|
4280
4169
|
//#region src/client/login-row.tsx
|
|
4281
|
-
/** The sign-in alternative to pasting a key (settings page + Models card). */
|
|
4282
4170
|
function LoginRow({ state, targetRef, disabled, disabledHint, t, onBegin, onCancel }) {
|
|
4283
4171
|
const { visible: visibleState, busyElsewhere } = loginStateForTarget(state, targetRef);
|
|
4284
4172
|
const busy = visibleState.phase === "starting" || visibleState.phase === "waiting";
|
|
@@ -4329,11 +4217,11 @@ window.__ModuleLoader__.load({
|
|
|
4329
4217
|
* Find the official editor card among the slot outlet's siblings, or null
|
|
4330
4218
|
* while it is closed. The Models page renders the editor as an immediate
|
|
4331
4219
|
* sibling of the outlet wrapper — after it in a provider row (the target of
|
|
4332
|
-
* the row's 编辑 toggle), before it in the first-run setup
|
|
4333
|
-
*
|
|
4334
|
-
*
|
|
4335
|
-
*
|
|
4336
|
-
*
|
|
4220
|
+
* the row's 编辑 toggle), before it in the first-run setup and add-provider
|
|
4221
|
+
* cards, where it is always open. The editor is the only such sibling whose
|
|
4222
|
+
* CSS module class carries the `editor` stem (`<hash>_editor`); the row header
|
|
4223
|
+
* and the add card's provider select never do, so the lookup needs no hash
|
|
4224
|
+
* knowledge.
|
|
4337
4225
|
*/
|
|
4338
4226
|
function adjacentEditorCard(wrapper) {
|
|
4339
4227
|
if (wrapper === null) return null;
|
|
@@ -4358,14 +4246,14 @@ window.__ModuleLoader__.load({
|
|
|
4358
4246
|
apiKeyWritable: snapshot.apiKeyWritable
|
|
4359
4247
|
};
|
|
4360
4248
|
}
|
|
4361
|
-
/**
|
|
4249
|
+
/** Credential-state badge: the solid pill when set, the muted outline when not. */
|
|
4362
4250
|
function StatusBadge({ ok, okLabel, pendingLabel }) {
|
|
4363
4251
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4364
4252
|
className: ok ? "cc-badge" : "cc-badgeMuted",
|
|
4365
4253
|
children: ok ? okLabel : pendingLabel
|
|
4366
4254
|
});
|
|
4367
4255
|
}
|
|
4368
|
-
/**
|
|
4256
|
+
/** The key paste field, with the show/hide toggle over it. */
|
|
4369
4257
|
function CardKeyField({ state, disabled, t, onEdit }) {
|
|
4370
4258
|
const [visible, setVisible] = (0, react.useState)(false);
|
|
4371
4259
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4407,16 +4295,12 @@ window.__ModuleLoader__.load({
|
|
|
4407
4295
|
}
|
|
4408
4296
|
/**
|
|
4409
4297
|
* The slot component body. Dispatched on every Command Code provider card of
|
|
4410
|
-
* the Models page (saved row, first-run setup posture,
|
|
4411
|
-
* draft).
|
|
4298
|
+
* the Models page (saved row, first-run setup posture, add-provider draft).
|
|
4412
4299
|
*
|
|
4413
|
-
* Closed (the official 编辑 toggle off)
|
|
4414
|
-
*
|
|
4415
|
-
*
|
|
4416
|
-
*
|
|
4417
|
-
* watches for it, hides the shell (it carries only the settings.yaml hint and
|
|
4418
|
-
* a disabled apply for this namespace), and shows the real controls — badges,
|
|
4419
|
-
* API-key field, sign-in, discard/save.
|
|
4300
|
+
* Closed (the official 编辑 toggle off) it renders nothing: the row head the
|
|
4301
|
+
* Models page owns already names the provider and shows the credential dot, so
|
|
4302
|
+
* a page full of providers stays compact. Open, it hides that editor shell and
|
|
4303
|
+
* shows the real controls — badges, API-key field, sign-in, discard/save.
|
|
4420
4304
|
*/
|
|
4421
4305
|
function CommandCodeProviderCard(props) {
|
|
4422
4306
|
const { t } = props;
|
|
@@ -4663,15 +4547,14 @@ window.__ModuleLoader__.load({
|
|
|
4663
4547
|
return Math.abs(value) < .01 ? formatMoneyExact(value) : formatMoney(value);
|
|
4664
4548
|
}
|
|
4665
4549
|
/**
|
|
4666
|
-
* What a figure shows when the endpoint that carries it did not report one.
|
|
4667
|
-
*
|
|
4668
|
-
*
|
|
4550
|
+
* What a figure shows when the endpoint that carries it did not report one. A
|
|
4551
|
+
* dash, never a zero: the panel exists to state what the account has spent, so
|
|
4552
|
+
* "we were not told" has to look different from "nothing".
|
|
4669
4553
|
*/
|
|
4670
4554
|
const UNREPORTED = "—";
|
|
4671
4555
|
/** Local reset time; empty when the endpoint reported none. */
|
|
4672
4556
|
function resetText(ms) {
|
|
4673
|
-
|
|
4674
|
-
return new Date(ms).toLocaleString();
|
|
4557
|
+
return formatResetAt(ms);
|
|
4675
4558
|
}
|
|
4676
4559
|
/** Local short date; empty when unset. */
|
|
4677
4560
|
function dateText(ms) {
|
|
@@ -4714,18 +4597,17 @@ window.__ModuleLoader__.load({
|
|
|
4714
4597
|
* Build the monthly credit view from the two endpoints that carry it.
|
|
4715
4598
|
*
|
|
4716
4599
|
* The limit is the PLAN's credit total, not the billing endpoint's
|
|
4717
|
-
* `monthlyCredits`
|
|
4718
|
-
* {@link PanelMonthlyView})
|
|
4719
|
-
*
|
|
4600
|
+
* `monthlyCredits` (that field is a remaining balance — see
|
|
4601
|
+
* {@link PanelMonthlyView}), so a plan that publishes no total still shows its
|
|
4602
|
+
* balances, just without a ratio.
|
|
4720
4603
|
*
|
|
4721
4604
|
* The trap this guards: a MISSING balance is not a consumed one. The billing
|
|
4722
4605
|
* endpoint is one of four the report fetches in parallel, so it fails on its
|
|
4723
|
-
* own while the plan still arrives
|
|
4724
|
-
*
|
|
4725
|
-
*
|
|
4726
|
-
*
|
|
4727
|
-
*
|
|
4728
|
-
* too. Every derived figure stays a placeholder until the balance reported.
|
|
4606
|
+
* own while the plan still arrives, and reading the absent balance as 0 turns
|
|
4607
|
+
* that partial failure into `limit - 0 = limit` — a confident "100% used" for
|
|
4608
|
+
* an account that may have spent nothing. The official CLI draws no meter unless
|
|
4609
|
+
* the credits payload is present at all (`hasCreditsInfo`); every derived figure
|
|
4610
|
+
* here stays a placeholder until the balance reported.
|
|
4729
4611
|
*/
|
|
4730
4612
|
function monthlyView(report) {
|
|
4731
4613
|
const credits = report.credits;
|
|
@@ -4835,10 +4717,10 @@ window.__ModuleLoader__.load({
|
|
|
4835
4717
|
};
|
|
4836
4718
|
}
|
|
4837
4719
|
/**
|
|
4838
|
-
* Every panel string, resolved once per projection through the translator.
|
|
4839
|
-
*
|
|
4840
|
-
*
|
|
4841
|
-
*
|
|
4720
|
+
* Every panel string, resolved once per projection through the translator. One
|
|
4721
|
+
* object with all keys (rather than per-field lookups in the components) keeps
|
|
4722
|
+
* the copy table and the render sites in lockstep: a key cannot be read from
|
|
4723
|
+
* `text` unless {@link PANEL_KEYS} declares it.
|
|
4842
4724
|
*/
|
|
4843
4725
|
function panelStrings(t) {
|
|
4844
4726
|
const out = {};
|
|
@@ -4908,9 +4790,9 @@ window.__ModuleLoader__.load({
|
|
|
4908
4790
|
};
|
|
4909
4791
|
}
|
|
4910
4792
|
/**
|
|
4911
|
-
* How often the panel re-reads the report while a surface is mounted. The
|
|
4912
|
-
*
|
|
4913
|
-
*
|
|
4793
|
+
* How often the panel re-reads the report while a surface is mounted. The quota
|
|
4794
|
+
* windows move slowly and one report costs four upstream calls, so this is a
|
|
4795
|
+
* background freshness tick, not a live meter.
|
|
4914
4796
|
*/
|
|
4915
4797
|
const PANEL_AUTO_REFRESH_MS = 12e4;
|
|
4916
4798
|
/** The default timer (the client bundle runs in a browser; node tests inject one). */
|
|
@@ -4927,21 +4809,18 @@ window.__ModuleLoader__.load({
|
|
|
4927
4809
|
/** Pending timer handle of the running loop. */
|
|
4928
4810
|
let handle;
|
|
4929
4811
|
/**
|
|
4930
|
-
* Start the shared auto refresh. One fetch when the surface appears (the
|
|
4931
|
-
*
|
|
4932
|
-
*
|
|
4933
|
-
* stays mounted.
|
|
4812
|
+
* Start the shared auto refresh. One fetch when the surface appears (the sidebar
|
|
4813
|
+
* row is the point of the panel — it must be current, not wait for a click), then
|
|
4814
|
+
* a tick every {@link PANEL_AUTO_REFRESH_MS} while a surface stays mounted.
|
|
4934
4815
|
*
|
|
4935
|
-
* Reference-counted: the sidebar entry and the dashboard can be mounted at
|
|
4936
|
-
*
|
|
4937
|
-
*
|
|
4938
|
-
*
|
|
4939
|
-
* against the settings page's own refresh.
|
|
4816
|
+
* Reference-counted: the sidebar entry and the dashboard can be mounted at once,
|
|
4817
|
+
* so only the first start fetches and only the last stop halts the loop. Every
|
|
4818
|
+
* tick goes through `usage.refresh()`, which collapses a concurrent fetch onto
|
|
4819
|
+
* the in-flight one. Returns the disposer that drops this surface's reference.
|
|
4940
4820
|
*
|
|
4941
4821
|
* @param usage - the shared usage controller.
|
|
4942
4822
|
* @param isConfigured - whether a credential exists right now (re-read per tick).
|
|
4943
4823
|
* @param timer - timer seam for tests.
|
|
4944
|
-
* @returns the disposer that drops this surface's reference.
|
|
4945
4824
|
*/
|
|
4946
4825
|
function startPanelAutoRefresh(usage, isConfigured, timer = REAL_TIMER) {
|
|
4947
4826
|
references += 1;
|
|
@@ -4972,26 +4851,20 @@ window.__ModuleLoader__.load({
|
|
|
4972
4851
|
//#endregion
|
|
4973
4852
|
//#region src/client/panel-view.tsx
|
|
4974
4853
|
/**
|
|
4975
|
-
* React components for the Command Code plans & quota panel
|
|
4976
|
-
*
|
|
4854
|
+
* React components for the Command Code plans & quota panel: the sidebar footer
|
|
4855
|
+
* card and the dashboard it opens in the center column.
|
|
4977
4856
|
*
|
|
4978
4857
|
* Both render one {@link PanelView} projected by `./panel.ts` — no fact is
|
|
4979
4858
|
* derived here. Strings arrive already localized in `view.text`: the slots
|
|
4980
4859
|
* declare the `panel.commandcode` locale namespace, so the renderer hands this
|
|
4981
|
-
* component a `t` seat that is passed into `buildPanelView
|
|
4982
|
-
*
|
|
4983
|
-
* surfaces re-render without an extra channel).
|
|
4984
|
-
*
|
|
4985
|
-
* The footer card is the panel's home: the sidebar shell renders it in the foot
|
|
4986
|
-
* area directly above the Settings seat, so each quota window's own spend and
|
|
4987
|
-
* limit — the 5-hour and the weekly one — are on screen without opening
|
|
4988
|
-
* anything. Clicking it selects the `main` panel this file also renders — and
|
|
4989
|
-
* unlike a `sidebar.panellist` row, whose button chrome and label the SHELL
|
|
4990
|
-
* owns, this entry owns its whole surface and therefore calls `open()` itself.
|
|
4860
|
+
* component a `t` seat that is passed into `buildPanelView`, and a language
|
|
4861
|
+
* switch mints a new `t` identity, which re-renders both surfaces.
|
|
4991
4862
|
*
|
|
4992
|
-
*
|
|
4993
|
-
*
|
|
4994
|
-
*
|
|
4863
|
+
* The footer card is the panel's home — the shell renders it in the foot area
|
|
4864
|
+
* directly above the Settings seat, and unlike a `sidebar.panellist` row (whose
|
|
4865
|
+
* chrome and label the SHELL owns) this entry owns its whole surface and
|
|
4866
|
+
* therefore calls `open()` itself. Styles ride the stylesheet
|
|
4867
|
+
* `./panel-styles.ts` returns; classes are `ccp-` prefixed.
|
|
4995
4868
|
*
|
|
4996
4869
|
* @module dsh-commandcode-provider/client/panel-view
|
|
4997
4870
|
*/
|
|
@@ -5114,14 +4987,6 @@ window.__ModuleLoader__.load({
|
|
|
5114
4987
|
]
|
|
5115
4988
|
});
|
|
5116
4989
|
}
|
|
5117
|
-
/** One usage tile. */
|
|
5118
|
-
function StatTile({ stat, label }) {
|
|
5119
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
|
|
5120
|
-
label,
|
|
5121
|
-
value: stat.value,
|
|
5122
|
-
sub: stat.sub
|
|
5123
|
-
});
|
|
5124
|
-
}
|
|
5125
4990
|
/** The rotation/credential badge line for one account. */
|
|
5126
4991
|
function markText(account, text) {
|
|
5127
4992
|
if (account.mark === void 0) return "";
|
|
@@ -5268,9 +5133,10 @@ window.__ModuleLoader__.load({
|
|
|
5268
5133
|
children: text("usage")
|
|
5269
5134
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5270
5135
|
className: "ccp-tiles",
|
|
5271
|
-
children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5272
|
-
stat,
|
|
5273
|
-
|
|
5136
|
+
children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
|
|
5137
|
+
label: text(stat.label),
|
|
5138
|
+
value: stat.value,
|
|
5139
|
+
sub: stat.sub
|
|
5274
5140
|
}, stat.label))
|
|
5275
5141
|
})]
|
|
5276
5142
|
}) : null
|
|
@@ -5409,27 +5275,24 @@ window.__ModuleLoader__.load({
|
|
|
5409
5275
|
}
|
|
5410
5276
|
/**
|
|
5411
5277
|
* The sidebar footer card, registered into `sidebar.footer.action` — the list
|
|
5412
|
-
* the shell renders in the
|
|
5413
|
-
*
|
|
5414
|
-
*
|
|
5278
|
+
* the shell renders in the foot area directly ABOVE the Settings seat, so the
|
|
5279
|
+
* panel reads as a bottom-pinned sibling of Settings rather than a global panel
|
|
5280
|
+
* icon at the top of the column.
|
|
5415
5281
|
*
|
|
5416
|
-
* The shell wraps nothing here, so this component owns the surface: the
|
|
5417
|
-
*
|
|
5418
|
-
*
|
|
5419
|
-
*
|
|
5420
|
-
*
|
|
5421
|
-
*
|
|
5422
|
-
*
|
|
5423
|
-
* matching the shell's own rail geometry. `wide` comes from the shell as an
|
|
5424
|
-
* owner prop — unlike the old `sidebar.panellist` row, this slot really does
|
|
5425
|
-
* supply it.
|
|
5282
|
+
* The shell wraps nothing here, so this component owns the surface: the button,
|
|
5283
|
+
* its chrome and its accessible name. Expanded, it draws the title row and one
|
|
5284
|
+
* block per quota window (5-hour, then weekly) — the window's own spend and
|
|
5285
|
+
* limit, its percentage and its bar — and nothing else, because those two
|
|
5286
|
+
* windows are the figures an account runs into, so the period total stays in the
|
|
5287
|
+
* tooltip. In the 56px rail it collapses to a 36px icon button carrying the ring,
|
|
5288
|
+
* matching the shell's own rail geometry; `wide` comes from the shell.
|
|
5426
5289
|
*
|
|
5427
5290
|
* Rendering is gated on the settings page's `showSidebarQuota` toggle, which
|
|
5428
5291
|
* defaults OFF: a fresh install shows no quota surface in the sidebar and runs
|
|
5429
5292
|
* no background poll. The gate reads the STORED document (the settings
|
|
5430
|
-
* controller's `sidebarQuota` fact), so an unsaved draft cannot show the card
|
|
5431
|
-
* while a landed save flips it live. The entry
|
|
5432
|
-
* render decision,
|
|
5293
|
+
* controller's `sidebarQuota` fact), so an unsaved draft cannot show the card
|
|
5294
|
+
* while a landed save flips it live. The entry stays REGISTERED — this is a
|
|
5295
|
+
* render decision, so nothing churns the slot ledger.
|
|
5433
5296
|
*/
|
|
5434
5297
|
function CommandCodeFooterEntry(props) {
|
|
5435
5298
|
const visible = props.useCommandCodeSettings((snapshot) => snapshot.sidebarQuota);
|
|
@@ -5530,13 +5393,13 @@ window.__ModuleLoader__.load({
|
|
|
5530
5393
|
return time.getUTCDay() !== 0 && time.getUTCDay() !== 6 && windows.some(([start, end]) => time.getUTCHours() >= start && time.getUTCHours() < end);
|
|
5531
5394
|
}
|
|
5532
5395
|
/**
|
|
5533
|
-
* All prompt billing buckets determine a request's band, never cumulative
|
|
5396
|
+
* All prompt billing buckets determine a request's band, never cumulative
|
|
5397
|
+
* session input.
|
|
5534
5398
|
*
|
|
5535
5399
|
* A tiered model returns the matching band and does NOT then apply `peak`: the
|
|
5536
|
-
* page publishes those two dimensions independently
|
|
5537
|
-
*
|
|
5538
|
-
*
|
|
5539
|
-
* choice, not a verified upstream rule.
|
|
5400
|
+
* page publishes those two dimensions independently and no row carries both
|
|
5401
|
+
* today, so the combination becoming real is visible before it reaches a user.
|
|
5402
|
+
* This ordering is a latent choice, not a verified upstream rule.
|
|
5540
5403
|
*/
|
|
5541
5404
|
function requestRates(price, at, contextTokens, table) {
|
|
5542
5405
|
const tier = price.contextTiers?.find((tier) => tier.maxContext === void 0 || contextTokens <= tier.maxContext);
|
|
@@ -5572,31 +5435,27 @@ window.__ModuleLoader__.load({
|
|
|
5572
5435
|
/**
|
|
5573
5436
|
* Session-cost view model for the composer readout.
|
|
5574
5437
|
*
|
|
5575
|
-
*
|
|
5576
|
-
*
|
|
5577
|
-
*
|
|
5578
|
-
*
|
|
5579
|
-
*
|
|
5580
|
-
*
|
|
5438
|
+
* JSX-free and React-free, mirroring `./panel.ts`: it turns the session's token
|
|
5439
|
+
* accounting plus the Host's price table into one presentation value AND the
|
|
5440
|
+
* exact text the two surfaces of the harness's token-usage UI receive — the
|
|
5441
|
+
* amount appended to the shipped pill, and the rows appended to the shipped
|
|
5442
|
+
* usage dialog. Node tests therefore drive the whole calculation, and every
|
|
5443
|
+
* user-visible string, without a DOM.
|
|
5581
5444
|
*
|
|
5582
|
-
*
|
|
5583
|
-
*
|
|
5584
|
-
*
|
|
5585
|
-
*
|
|
5586
|
-
*
|
|
5587
|
-
*
|
|
5445
|
+
* It prices the session's DURABLE cumulative buckets, which is what the
|
|
5446
|
+
* composer's own "N tokens" pill reads too (`tokenUsage`), so the two surfaces
|
|
5447
|
+
* can never disagree about how much was used. Dollars are computed here because
|
|
5448
|
+
* Command Code publishes per-token rates and bills against dollar-denominated
|
|
5449
|
+
* windows; the account's own reported `totalCost` is a billing PERIOD figure,
|
|
5450
|
+
* not this session's.
|
|
5588
5451
|
*
|
|
5589
|
-
* Three rules are load-bearing:
|
|
5590
|
-
*
|
|
5591
|
-
*
|
|
5592
|
-
*
|
|
5593
|
-
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
* charged at a guessed multiple of the input rate.
|
|
5597
|
-
* 3. **Unpriceable means invisible.** No usage, no model, no table, an unknown
|
|
5598
|
-
* model, or all-zero buckets renders nothing at all — a confident `$0.00`
|
|
5599
|
-
* would be a lie, and this module never returns one.
|
|
5452
|
+
* Three rules are load-bearing: only Command Code usage is priced (a session
|
|
5453
|
+
* served by another provider must render nothing, never a Command Code
|
|
5454
|
+
* estimate); a missing rate is never invented (the pricing page publishes a
|
|
5455
|
+
* cache-WRITE rate for only some models, so unpriced cache-write tokens are
|
|
5456
|
+
* surfaced as such rather than charged at a guessed multiple of the input rate);
|
|
5457
|
+
* and unpriceable means invisible — a confident `$0.00` would be a lie, and
|
|
5458
|
+
* this module never returns one.
|
|
5600
5459
|
*
|
|
5601
5460
|
* @module dsh-commandcode-provider/client/session-cost
|
|
5602
5461
|
*/
|
|
@@ -5621,15 +5480,12 @@ window.__ModuleLoader__.load({
|
|
|
5621
5480
|
* between its own items.
|
|
5622
5481
|
*/
|
|
5623
5482
|
separator: "·",
|
|
5624
|
-
/** The heading the tooltip leads with. */
|
|
5625
5483
|
panelTitle: "Session cost",
|
|
5626
5484
|
/** Marks an estimate from published rates rather than an invoice. */
|
|
5627
5485
|
approximate: "≈",
|
|
5628
|
-
/** Tooltip line for the unpriced cache-write tokens. */
|
|
5629
5486
|
unpricedCacheWrite: "cache write tokens have no published rate",
|
|
5630
5487
|
/** Tooltip line naming the rate half in force. */
|
|
5631
5488
|
peakRates: "peak rates",
|
|
5632
|
-
/** Tooltip line naming the rate half in force. */
|
|
5633
5489
|
offPeakRates: "off-peak rates",
|
|
5634
5490
|
/** Tooltip line explaining the approximate marker. */
|
|
5635
5491
|
approximateNote: "estimate from published rates, not the provider invoice",
|
|
@@ -5690,8 +5546,8 @@ window.__ModuleLoader__.load({
|
|
|
5690
5546
|
*
|
|
5691
5547
|
* The panel's `money()` convention (2 decimals, 4 below a cent) is right for
|
|
5692
5548
|
* billing windows, but a live session total starts far below a cent, where
|
|
5693
|
-
* `toFixed(4)` would print a flat `$0.0000` —
|
|
5694
|
-
*
|
|
5549
|
+
* `toFixed(4)` would print a flat `$0.0000` — broken rather than small. So a
|
|
5550
|
+
* total under $0.0001 is stated as a bound instead.
|
|
5695
5551
|
*/
|
|
5696
5552
|
function sessionCostAmount(total) {
|
|
5697
5553
|
if (!Number.isFinite(total) || total <= 0) return formatMoney(0);
|
|
@@ -5899,15 +5755,14 @@ window.__ModuleLoader__.load({
|
|
|
5899
5755
|
* and never the outlet's parent, because the parent is the composer footer,
|
|
5900
5756
|
* which holds the `ContextMeter` as well, and that meter's trigger is itself a
|
|
5901
5757
|
* `button[aria-haspopup="dialog"]` rendered AFTER the dock. A parent-scoped
|
|
5902
|
-
* "last trigger wins" would
|
|
5903
|
-
* instead of the token pill.
|
|
5758
|
+
* "last trigger wins" would append the cost to the context ring instead.
|
|
5904
5759
|
*/
|
|
5905
5760
|
const STATS_ROOT = "[data-composer-stats]";
|
|
5906
5761
|
/**
|
|
5907
5762
|
* The shipped token-usage dialog's `<dl>`. Unique WITHIN one `StatsPills`
|
|
5908
|
-
* render
|
|
5909
|
-
* markup,
|
|
5910
|
-
*
|
|
5763
|
+
* render (the per-message turn-usage panel has its own dialog with different
|
|
5764
|
+
* markup), but not across the document once two composers are live — see
|
|
5765
|
+
* {@link resolveDialog}.
|
|
5911
5766
|
*/
|
|
5912
5767
|
const USAGE_DIALOG = "[data-session-stats-usage]";
|
|
5913
5768
|
/**
|
|
@@ -5917,8 +5772,8 @@ window.__ModuleLoader__.load({
|
|
|
5917
5772
|
*/
|
|
5918
5773
|
const DIALOG_TRIGGER = "button[aria-haspopup=\"dialog\"]";
|
|
5919
5774
|
/**
|
|
5920
|
-
* Stable id for the hidden node the appended cost is described by. The pill's
|
|
5921
|
-
*
|
|
5775
|
+
* Stable id for the hidden node the appended cost is described by. The pill's own
|
|
5776
|
+
* `aria-label` is recomputed by the harness on every render and cannot be
|
|
5922
5777
|
* extended, so a description is how the cost reaches assistive technology.
|
|
5923
5778
|
*/
|
|
5924
5779
|
const A11Y_ID = "dsh-commandcode-session-cost";
|
|
@@ -6040,11 +5895,9 @@ window.__ModuleLoader__.load({
|
|
|
6040
5895
|
* registration order. {@link STATS_ROOT} narrows that to the stats row where
|
|
6041
5896
|
* the markup marks it; where it does not, the outlet is already the narrowest
|
|
6042
5897
|
* correct container, because the composer footer's `ContextMeter` sits BESIDE
|
|
6043
|
-
* the outlet rather than inside it.
|
|
6044
|
-
*
|
|
6045
|
-
*
|
|
6046
|
-
* what makes this lookup per-composer, and a second composer can be live at
|
|
6047
|
-
* once (the sidebar can mount an embedded Conversation).
|
|
5898
|
+
* the outlet rather than inside it. A missing scope is "no pill", never a
|
|
5899
|
+
* document-wide search: the outlet is what makes this lookup per-composer, and
|
|
5900
|
+
* two composers can be live at once.
|
|
6048
5901
|
*/
|
|
6049
5902
|
resolvePillButton() {
|
|
6050
5903
|
const scope = this.scope();
|
|
@@ -6176,20 +6029,18 @@ window.__ModuleLoader__.load({
|
|
|
6176
6029
|
*
|
|
6177
6030
|
* The dialog is portaled onto `body`, so unlike the pill it cannot be scoped
|
|
6178
6031
|
* from this entry, and the attribute is unique per DIALOG rather than per
|
|
6179
|
-
* document. While one composer was guaranteed
|
|
6180
|
-
* exact
|
|
6181
|
-
* in the sidebar, so a second composer —
|
|
6182
|
-
*
|
|
6032
|
+
* document. While one composer was guaranteed a document-level lookup was
|
|
6033
|
+
* exact; 0.1.6-alpha.2 ended that guarantee by mounting an embedded
|
|
6034
|
+
* Conversation in the sidebar, so a second composer — its own session, dock and
|
|
6035
|
+
* dialog — can be live and open at the same time.
|
|
6183
6036
|
*
|
|
6184
6037
|
* There is no DOM link from a dialog back to its trigger (the portaled panel
|
|
6185
|
-
* carries no id and no `aria-controls`), so
|
|
6186
|
-
*
|
|
6187
|
-
*
|
|
6188
|
-
*
|
|
6189
|
-
*
|
|
6190
|
-
*
|
|
6191
|
-
* follows, applied one level up: a dialog we cannot prove is ours is left
|
|
6192
|
-
* alone.
|
|
6038
|
+
* carries no id and no `aria-controls`), so document order says nothing about
|
|
6039
|
+
* ownership. The shape confirmation in {@link dialogShapeMatches} would usually
|
|
6040
|
+
* reject the stranger, but it compares against THIS entry's counts, so a
|
|
6041
|
+
* coincidentally matching dialog would be priced with another session's
|
|
6042
|
+
* figures. Ambiguity is therefore answered by declining to decorate at all:
|
|
6043
|
+
* a dialog we cannot prove is ours is left alone.
|
|
6193
6044
|
*/
|
|
6194
6045
|
resolveDialog() {
|
|
6195
6046
|
if (this.dialogHost?.isConnected === true) return this.dialogHost;
|
|
@@ -6231,12 +6082,11 @@ window.__ModuleLoader__.load({
|
|
|
6231
6082
|
/**
|
|
6232
6083
|
* Whether the dialog really holds the rows this session's buckets predict.
|
|
6233
6084
|
*
|
|
6234
|
-
* The shipped labels belong to the `chat` locale
|
|
6235
|
-
*
|
|
6236
|
-
*
|
|
6237
|
-
*
|
|
6238
|
-
*
|
|
6239
|
-
* dialog is not what this view describes, and nothing is decorated.
|
|
6085
|
+
* The shipped labels belong to the `chat` locale and differ per language, so rows
|
|
6086
|
+
* are matched by POSITION, and this is what makes that safe: the row count must
|
|
6087
|
+
* agree, the cache-hit row must be the percentage it is, and every other value
|
|
6088
|
+
* must carry exactly the token count of the bucket predicted for it. A mismatch
|
|
6089
|
+
* means the dialog is not what this view describes, and nothing is decorated.
|
|
6240
6090
|
*/
|
|
6241
6091
|
function dialogShapeMatches(pairs, plan, prices) {
|
|
6242
6092
|
if (pairs.length !== plan.length) return false;
|
|
@@ -6269,13 +6119,13 @@ window.__ModuleLoader__.load({
|
|
|
6269
6119
|
/**
|
|
6270
6120
|
* The dock outlet this entry renders inside, i.e. what it scopes itself from.
|
|
6271
6121
|
*
|
|
6272
|
-
* The OUTLET
|
|
6273
|
-
*
|
|
6274
|
-
*
|
|
6275
|
-
*
|
|
6276
|
-
*
|
|
6277
|
-
*
|
|
6278
|
-
*
|
|
6122
|
+
* The OUTLET, not its parent. The outlet is one `display:contents` div holding
|
|
6123
|
+
* exactly this slot's entries (the shipped `stats` cell and ours), so it is the
|
|
6124
|
+
* narrowest container that is still per-composer. Its parent is NOT: from
|
|
6125
|
+
* 0.1.6-alpha.2 the composer stacks the dock inside a footer that also holds the
|
|
6126
|
+
* `ContextMeter`, whose trigger is another `button[aria-haspopup="dialog"]`
|
|
6127
|
+
* rendered after the dock — scoping to the parent would let the cost land on the
|
|
6128
|
+
* context ring.
|
|
6279
6129
|
*/
|
|
6280
6130
|
const DOCK_ANCHOR = "[data-slot=\"conversation.composer.dock\"]";
|
|
6281
6131
|
/** Module-level constant so the marker's `style` prop never diffs. */
|
|
@@ -6339,30 +6189,24 @@ window.__ModuleLoader__.load({
|
|
|
6339
6189
|
* Stylesheet for the Command Code settings page — and the Models-page provider
|
|
6340
6190
|
* card, which shares its `cc-` classes.
|
|
6341
6191
|
*
|
|
6342
|
-
* Returned as a string
|
|
6343
|
-
*
|
|
6344
|
-
*
|
|
6345
|
-
*
|
|
6346
|
-
*
|
|
6347
|
-
*
|
|
6348
|
-
*
|
|
6349
|
-
*
|
|
6350
|
-
*
|
|
6351
|
-
* hardcoded values. Classes are `cc-` prefixed to stay clear of the panel's
|
|
6352
|
-
* `ccp-` set.
|
|
6192
|
+
* Returned as a string, not injected here, so importing this module has no DOM
|
|
6193
|
+
* side effect: `./index.ts` installs it once under {@link PAGE_CSS_ID}. It is a
|
|
6194
|
+
* module of its own rather than a literal in the entry so `tests/styles.test.ts`
|
|
6195
|
+
* can audit every rule without importing the React tree (which pulls
|
|
6196
|
+
* `*.module.css` through the primitives package and needs a loader registered
|
|
6197
|
+
* first). The rules are GLOBAL CSS, so containment is a contract (issue #48):
|
|
6198
|
+
* every selector is qualified by one of our own `cc-` classes. Colours are
|
|
6199
|
+
* harness theme aliases with neutral fallbacks, and the `cc-` prefix keeps
|
|
6200
|
+
* these clear of the panel's `ccp-` set.
|
|
6353
6201
|
*
|
|
6354
6202
|
* @module dsh-commandcode-provider/client/page-styles
|
|
6355
6203
|
*/
|
|
6356
6204
|
/**
|
|
6357
|
-
* Stylesheet id
|
|
6205
|
+
* Stylesheet id — the `data-plugin-css` value that makes injection idempotent.
|
|
6358
6206
|
*
|
|
6359
|
-
* The
|
|
6360
|
-
* (`PANEL_CSS_ID`
|
|
6361
|
-
*
|
|
6362
|
-
* the plugin inject the same rules twice and would misreport the owner in the
|
|
6363
|
-
* DOM. The two ids must also stay distinct from each other — they key two
|
|
6364
|
-
* separate style tags, and a shared id would make the second injection a no-op
|
|
6365
|
-
* that silently drops one stylesheet.
|
|
6207
|
+
* The prefix must match this package's real name and the panel stylesheet's
|
|
6208
|
+
* (`PANEL_CSS_ID`); the two ids must also stay DISTINCT, since they key two
|
|
6209
|
+
* separate style tags and a shared id would make the second injection a no-op.
|
|
6366
6210
|
*/
|
|
6367
6211
|
const PAGE_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
|
|
6368
6212
|
/**
|
|
@@ -6370,13 +6214,14 @@ window.__ModuleLoader__.load({
|
|
|
6370
6214
|
*
|
|
6371
6215
|
* The metrics are the harness's own settings pages', read out of the 0.1.7
|
|
6372
6216
|
* bundles so the page sits beside General and Models without looking foreign:
|
|
6373
|
-
* a row is
|
|
6374
|
-
*
|
|
6375
|
-
*
|
|
6217
|
+
* a row is 12px of padding, its title 14/22 label-primary and its description
|
|
6218
|
+
* 12/18 label-tertiary, the control on the right. The hairline rhythm is
|
|
6219
|
+
* section-level: a 0.5px border-l2 rule separates the GROUPS (see
|
|
6220
|
+
* `cc-groupDivided`), never the rows inside one, so a group's rows read as one
|
|
6221
|
+
* list. Selectors and buttons are capsules (36px / 28px compact), inputs are
|
|
6376
6222
|
* 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
|
|
6377
6223
|
* border-l4 outline with a 16px radius, and a nested panel is a
|
|
6378
|
-
* bg-module-platform fill with a 12px radius. Errors use state-error-primary
|
|
6379
|
-
* the platform's error colour (label-error is its text-only sibling).
|
|
6224
|
+
* bg-module-platform fill with a 12px radius. Errors use state-error-primary.
|
|
6380
6225
|
*/
|
|
6381
6226
|
const PAGE_CSS = `
|
|
6382
6227
|
.cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex}
|
|
@@ -6384,8 +6229,8 @@ window.__ModuleLoader__.load({
|
|
|
6384
6229
|
.cc-intro{margin:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}
|
|
6385
6230
|
.cc-readOnly{margin:8px 0 0;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));font-size:12px;line-height:18px}
|
|
6386
6231
|
.cc-spacer{flex:1}
|
|
6387
|
-
/* Groups: a heading over hairline-separated rows, no card surface. */
|
|
6388
6232
|
.cc-group{flex-direction:column;display:flex;margin-top:28px}
|
|
6233
|
+
.cc-groupDivided{margin-top:24px;padding-top:20px;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
6389
6234
|
.cc-groupHead{align-items:center;gap:8px;display:flex;min-height:28px;padding-bottom:4px}
|
|
6390
6235
|
.cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
|
|
6391
6236
|
.cc-groupDesc{margin:0 0 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
@@ -6393,10 +6238,8 @@ window.__ModuleLoader__.load({
|
|
|
6393
6238
|
.cc-disclosure{width:100%;padding:0 0 4px;border:0;background:0 0;font:inherit;text-align:left;cursor:pointer;border-radius:6px}
|
|
6394
6239
|
.cc-disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
6395
6240
|
.cc-rows{flex-direction:column;display:flex}
|
|
6396
|
-
.cc-row{align-items:center;gap:8px;display:flex;padding:
|
|
6397
|
-
.cc-
|
|
6398
|
-
.cc-rowNested{padding-left:16px}
|
|
6399
|
-
.cc-rowFlush{padding:0;border-bottom:0}
|
|
6241
|
+
.cc-row{align-items:center;gap:8px;display:flex;padding:12px 0}
|
|
6242
|
+
.cc-rowFlush{padding:0}
|
|
6400
6243
|
.cc-rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:32px;display:flex}
|
|
6401
6244
|
.cc-rowTitleLine{align-items:center;gap:8px;display:flex;min-width:0}
|
|
6402
6245
|
.cc-rowTitle{min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}
|
|
@@ -6407,19 +6250,15 @@ window.__ModuleLoader__.load({
|
|
|
6407
6250
|
.cc-rowControl{flex:none;align-items:center;justify-content:flex-end;gap:8px;display:inline-flex;max-width:60%}
|
|
6408
6251
|
.cc-rowInput{width:200px}
|
|
6409
6252
|
.cc-rowInputWide{width:280px}
|
|
6410
|
-
/* The platform's link button: a compact capsule with no fill until hovered. */
|
|
6411
6253
|
.cc-linkButton{box-sizing:border-box;flex:none;align-items:center;display:inline-flex;height:28px;padding:0 10px;border:0;border-radius:14px;background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
|
|
6412
6254
|
.cc-linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
6413
6255
|
.cc-linkButton:disabled{cursor:default;opacity:.4}
|
|
6414
6256
|
.cc-linkButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
|
|
6415
|
-
/* Tags: the platform Tag's capsule (11/17, weight 500). */
|
|
6416
6257
|
.cc-badges{align-items:center;gap:8px;display:inline-flex}
|
|
6417
|
-
.cc-badge,.cc-badgeMuted{flex:none;align-items:center;display:inline-flex;white-space:nowrap;border-radius:999px;corner-shape:round;padding:1px 8px;font-size:
|
|
6258
|
+
.cc-badge,.cc-badgeMuted{flex:none;align-items:center;display:inline-flex;white-space:nowrap;border-radius:999px;corner-shape:round;padding:1px 8px;font-size:12px;font-weight:500;line-height:17px}
|
|
6418
6259
|
.cc-badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
6419
6260
|
.cc-badgeMuted{border:.5px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-tertiary)}
|
|
6420
6261
|
.cc-badgeWarn{background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary))}
|
|
6421
|
-
/* The stacked field (the Models-page provider card and the account forms):
|
|
6422
|
-
* the platform SettingsForm field's metrics. */
|
|
6423
6262
|
.cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
6424
6263
|
.cc-field+.cc-field{border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
6425
6264
|
.cc-fieldHead{align-items:center;gap:8px;display:flex}
|
|
@@ -6432,10 +6271,7 @@ window.__ModuleLoader__.load({
|
|
|
6432
6271
|
.cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
6433
6272
|
.cc-input:disabled{opacity:.6;cursor:default}
|
|
6434
6273
|
.cc-inputInvalid{border-color:var(--dsw-alias-state-error-primary)}
|
|
6435
|
-
.cc-invalid{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
|
|
6436
6274
|
.cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
|
|
6437
|
-
/* The model multi-select trigger: the platform's selector capsule (the
|
|
6438
|
-
* language / permission pickers on General). */
|
|
6439
6275
|
.cc-modelSelectAnchor{flex-direction:column;align-items:stretch;gap:6px;display:inline-flex;min-width:160px}
|
|
6440
6276
|
.cc-selector{box-sizing:border-box;align-items:center;gap:12px;display:inline-flex;height:36px;padding:0 14px;border:0;border-radius:18px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}
|
|
6441
6277
|
.cc-selector:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -6449,51 +6285,34 @@ window.__ModuleLoader__.load({
|
|
|
6449
6285
|
.cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
|
|
6450
6286
|
.cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
|
|
6451
6287
|
.cc-checkRow:hover{cursor:pointer}
|
|
6452
|
-
/* A hand-drawn box
|
|
6453
|
-
*
|
|
6454
|
-
*
|
|
6455
|
-
*
|
|
6456
|
-
*
|
|
6457
|
-
*
|
|
6458
|
-
*
|
|
6459
|
-
* outline is a border-l3 hairline: border-l2 (#ffffff1f in dark) leaves an
|
|
6460
|
-
* unchecked box all but invisible against the menu surface. */
|
|
6288
|
+
/* A hand-drawn box, not the platform's accent-color checkbox: the native
|
|
6289
|
+
* control's colours come from color-scheme, which the theme sets once at BOOT
|
|
6290
|
+
* and never re-applies in-session, so a native box can paint light-chrome white
|
|
6291
|
+
* on a dark page. The mark rides a brand-primary fill, so it takes the brand
|
|
6292
|
+
* foreground (the only pairing that survives dark mode's near-white brand), and
|
|
6293
|
+
* the outline is a border-l3 hairline because border-l2 leaves an unchecked box
|
|
6294
|
+
* invisible against the menu surface. */
|
|
6461
6295
|
.cc-check{box-sizing:border-box;appearance:none;flex-shrink:0;width:16px;height:16px;margin:0;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;background:0 0;position:relative}
|
|
6462
6296
|
.cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
6463
6297
|
.cc-check:checked::after{content:'';position:absolute;top:3px;left:5px;width:3px;height:7px;border:solid var(--dsw-alias-label-primary-foreground,#fff);border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
|
|
6464
6298
|
.cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
6465
|
-
/* The platform SegmentedControl: a translucent track and one raised pill that
|
|
6466
|
-
* slides under the picked segment. Segments are equal grid tracks, so the
|
|
6467
|
-
* indicator's width and offset follow from the count and the index alone. */
|
|
6468
|
-
.cc-segmented{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;padding:3px;border-radius:9px;background:var(--dsw-alias-interactive-bg-hover)}
|
|
6469
|
-
.cc-segmentIndicator{position:absolute;top:3px;left:3px;width:calc((100% - 6px - 2px * (var(--cc-segment-count) - 1)) / var(--cc-segment-count));height:calc(100% - 6px);border-radius:7px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft,0 1px 2px rgba(0,0,0,.12));transform:translateX(calc(var(--cc-segment-index) * (100% + 2px)));transition:transform .16s ease;pointer-events:none}
|
|
6470
|
-
.cc-segment{box-sizing:border-box;position:relative;z-index:1;height:28px;padding:0 16px;border:0;border-radius:7px;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;font-weight:500;line-height:20px;white-space:nowrap;cursor:pointer;transition:color .12s ease}
|
|
6471
|
-
.cc-segment:hover:not(:disabled),.cc-segment[aria-checked=true]{color:var(--dsw-alias-label-primary)}
|
|
6472
|
-
.cc-segment:disabled{cursor:default;opacity:.4}
|
|
6473
|
-
.cc-segment:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
6474
6299
|
/* Modelled on the platform's own Switch primitive (ui-primitives
|
|
6475
|
-
* Switch.module.css): a 36x20 capsule
|
|
6476
|
-
* 16px thumb across the 32px content box.
|
|
6477
|
-
*
|
|
6478
|
-
*
|
|
6479
|
-
*
|
|
6480
|
-
*
|
|
6481
|
-
*
|
|
6482
|
-
*
|
|
6483
|
-
* thumb vanishes on the checked track in dark mode; the brand foreground is the
|
|
6484
|
-
* token the platform paints on that fill. (3) --dsw-alias-border-l3 as the off
|
|
6485
|
-
* track: border-l2 (#ffffff1f in dark) leaves it indistinguishable from the
|
|
6486
|
-
* page. */
|
|
6300
|
+
* Switch.module.css): a 36x20 capsule padding its track by 2px and sliding a
|
|
6301
|
+
* 16px thumb across the 32px content box. Three things are load-bearing.
|
|
6302
|
+
* (1) corner-shape:round on BOTH track and thumb — the theme sets a global
|
|
6303
|
+
* superellipse corner shape, which squares a capsule's ends off around a round
|
|
6304
|
+
* thumb. (2) No literal colours: brand-primary INVERTS between the modes, so a
|
|
6305
|
+
* hardcoded white thumb vanishes on the checked track in dark mode; the brand
|
|
6306
|
+
* foreground is what the platform paints on that fill. (3) border-l3 as the
|
|
6307
|
+
* off track — border-l2 leaves it indistinguishable from the page. */
|
|
6487
6308
|
.cc-toggle{box-sizing:border-box;appearance:none;flex-shrink:0;width:36px;height:20px;margin:0;padding:2px;border:0;border-radius:10px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative}
|
|
6488
6309
|
.cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
|
|
6489
6310
|
.cc-toggle::after{content:'';display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
|
|
6490
|
-
/* 32px content box minus the 16px thumb. */
|
|
6491
6311
|
.cc-toggle:checked::after{transform:translateX(16px)}
|
|
6492
6312
|
.cc-toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
6493
6313
|
.cc-toggle:disabled{cursor:default;opacity:.5}
|
|
6494
6314
|
.cc-chevron{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:7px;height:7px;margin-right:6px;margin-bottom:3px;transform:rotate(45deg);transition:transform .15s ease}
|
|
6495
6315
|
.cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
|
|
6496
|
-
/* Accounts: one outlined card per account, the Models page's provider card. */
|
|
6497
6316
|
.cc-accountMode{align-items:center;gap:4px;display:flex;margin:-4px 0 12px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
6498
6317
|
.cc-accountList{flex-direction:column;gap:8px;display:flex}
|
|
6499
6318
|
.cc-accountList+.cc-addButton,.cc-accountList+.cc-addPanel{margin-top:8px}
|
|
@@ -6506,10 +6325,11 @@ window.__ModuleLoader__.load({
|
|
|
6506
6325
|
.cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
6507
6326
|
.cc-iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
6508
6327
|
.cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
|
|
6509
|
-
/* Three dots from one element: the dot
|
|
6328
|
+
/* Three dots from one element: the dot plus two box-shadow copies. */
|
|
6510
6329
|
.cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
|
|
6511
6330
|
.cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
|
|
6512
6331
|
.cc-tabDotOk{background:var(--dsw-alias-state-success-primary)}
|
|
6332
|
+
.cc-tabDotBlank{background:0 0;border:0}
|
|
6513
6333
|
.cc-tabDotWarn{background:var(--dsw-alias-state-warn-primary,#d97706)}
|
|
6514
6334
|
.cc-tabDotError{background:var(--dsw-alias-state-error-primary)}
|
|
6515
6335
|
.cc-accountMeters{flex-wrap:wrap;gap:6px 20px;display:flex;padding-left:16px}
|
|
@@ -6528,20 +6348,16 @@ window.__ModuleLoader__.load({
|
|
|
6528
6348
|
.cc-confirmText{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:22px}
|
|
6529
6349
|
.cc-dangerButton{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
|
|
6530
6350
|
.cc-dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
|
|
6531
|
-
/* Adding an account: the Models page's dashed add button, opening a filled
|
|
6532
|
-
* panel in its place. */
|
|
6533
6351
|
.cc-addButton{box-sizing:border-box;align-items:center;justify-content:center;gap:6px;display:flex;width:100%;height:44px;border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer}
|
|
6534
6352
|
.cc-addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
6535
6353
|
.cc-addButton:disabled{cursor:default;opacity:.4}
|
|
6536
6354
|
.cc-addButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
|
|
6537
|
-
/* A plus from two crossed bars. */
|
|
6538
6355
|
.cc-addGlyph{position:relative;width:12px;height:12px}
|
|
6539
6356
|
.cc-addGlyph::before,.cc-addGlyph::after{content:'';position:absolute;background:currentColor;border-radius:1px}
|
|
6540
6357
|
.cc-addGlyph::before{left:0;right:0;top:5.25px;height:1.5px}
|
|
6541
6358
|
.cc-addGlyph::after{top:0;bottom:0;left:5.25px;width:1.5px}
|
|
6542
6359
|
.cc-addPanel{flex-direction:column;gap:12px;display:flex;padding:14px 16px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
|
|
6543
6360
|
.cc-panelTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
|
|
6544
|
-
/* Account report: stat tiles are filled panels, like the platform's editors. */
|
|
6545
6361
|
.cc-accountReport{flex-direction:column;gap:14px;display:flex}
|
|
6546
6362
|
.cc-usageHead{align-items:center;gap:8px;display:flex}
|
|
6547
6363
|
.cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:14px;font-weight:500;line-height:22px}
|
|
@@ -6549,10 +6365,9 @@ window.__ModuleLoader__.load({
|
|
|
6549
6365
|
.cc-usagePlan{flex:none;white-space:nowrap;border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:999px;corner-shape:round;padding:0 7px;font-size:11px;font-weight:500;line-height:17px}
|
|
6550
6366
|
.cc-usagePlanStatus{flex:none;margin:0;white-space:nowrap;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
6551
6367
|
.cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
|
|
6552
|
-
/* Two fixed grids
|
|
6553
|
-
*
|
|
6554
|
-
*
|
|
6555
|
-
* in a narrow details panel. */
|
|
6368
|
+
/* Two fixed grids, not one auto-fit track: auto-fit wrapped the four activity
|
|
6369
|
+
* tiles 3 + 1 at the details' width. The activity row folds to 2 × 2 in a
|
|
6370
|
+
* narrow details panel. */
|
|
6556
6371
|
.cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
|
|
6557
6372
|
.cc-usageStatsActivity{grid-template-columns:repeat(4,minmax(0,1fr))}
|
|
6558
6373
|
.cc-usageStatsBalance{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
@@ -6580,31 +6395,25 @@ window.__ModuleLoader__.load({
|
|
|
6580
6395
|
.cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
|
|
6581
6396
|
.cc-usageBlockedDetail{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px;word-break:break-word}
|
|
6582
6397
|
.cc-version{margin:32px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
6583
|
-
/* The update hint rides the footer version line, quiet until hovered. */
|
|
6584
6398
|
.cc-versionLink{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary));text-decoration:none}
|
|
6585
6399
|
.cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
|
|
6586
|
-
/* The Models-page provider panel root: unstyled by design — the row card the
|
|
6587
|
-
* Models page owns supplies the surface, the panel only stacks its controls. */
|
|
6588
6400
|
.cc-providerCard{flex-direction:column;display:flex}
|
|
6589
6401
|
.cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
|
|
6590
6402
|
.cc-failed{display:inline-block;max-width:260px;min-width:0;color:var(--dsw-alias-state-error-primary);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px}
|
|
6591
|
-
/* The authorization link is the only link-coloured element on a login row. */
|
|
6592
6403
|
.cc-loginLink{color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px}
|
|
6593
6404
|
.cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
|
|
6594
6405
|
.cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
|
|
6595
6406
|
.cc-loginError{color:var(--dsw-alias-state-error-primary)}
|
|
6596
6407
|
/* The floating save bar: a centered capsule pinned to the bottom of the
|
|
6597
|
-
* settings scrollport. The dock is a zero-height sticky strip at the end of
|
|
6598
|
-
*
|
|
6599
|
-
*
|
|
6600
|
-
*
|
|
6601
|
-
*
|
|
6602
|
-
*
|
|
6603
|
-
*
|
|
6604
|
-
*
|
|
6605
|
-
*
|
|
6606
|
-
* are concentric: a 44px capsule around 36px capsule buttons inset by 4px, so
|
|
6607
|
-
* the gap around each button is even. */
|
|
6408
|
+
* settings scrollport. The dock is a zero-height sticky strip at the end of the
|
|
6409
|
+
* page, so the bar overlays the scrolling content instead of taking a row of
|
|
6410
|
+
* its own; while shown, the section reserves room at its end so the last line
|
|
6411
|
+
* can scroll clear. Hidden, it sinks and fades, and visibility (switched after
|
|
6412
|
+
* the transition) takes its buttons out of the tab order. Its surface is an
|
|
6413
|
+
* elevated layer (bg-layer-1 over the settings panel's layer-2) with the
|
|
6414
|
+
* platform's prominent elevation and no stroke, and its corners are concentric
|
|
6415
|
+
* (a 44px capsule around 36px buttons inset by 4px) so the gap around each
|
|
6416
|
+
* button is even. */
|
|
6608
6417
|
.cc-sectionWithBar{padding-bottom:80px}
|
|
6609
6418
|
.cc-saveBarDock{position:sticky;bottom:0;z-index:2;height:0;pointer-events:none}
|
|
6610
6419
|
.cc-saveBar{--cc-saveBar-tone:var(--dsw-alias-state-warn-primary,#d97706);position:absolute;left:50%;bottom:20px;box-sizing:border-box;width:max-content;max-width:calc(100% - 24px);align-items:center;gap:10px;display:flex;height:44px;padding:4px 4px 4px 16px;border:0;border-radius:22px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-prominent,0 12px 32px -8px rgba(0,0,0,.24),0 2px 8px rgba(0,0,0,.08));opacity:0;visibility:hidden;transform:translate(-50%,12px);transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s}
|
|
@@ -6623,7 +6432,7 @@ window.__ModuleLoader__.load({
|
|
|
6623
6432
|
.cc-saveBarGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
6624
6433
|
.cc-saveBarPrimary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
6625
6434
|
.cc-saveBarPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
|
|
6626
|
-
@media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown
|
|
6435
|
+
@media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown{transition:none}}
|
|
6627
6436
|
`;
|
|
6628
6437
|
//#endregion
|
|
6629
6438
|
//#region src/client/panel-styles.ts
|
|
@@ -6631,70 +6440,48 @@ window.__ModuleLoader__.load({
|
|
|
6631
6440
|
* Stylesheet for the Command Code plans & quota panel (the sidebar footer card
|
|
6632
6441
|
* and the dashboard it opens).
|
|
6633
6442
|
*
|
|
6634
|
-
* Returned as a string
|
|
6635
|
-
*
|
|
6636
|
-
*
|
|
6637
|
-
* (`./page-styles.ts`) uses, and removes it again when the plugin's fiber
|
|
6638
|
-
* unwinds.
|
|
6639
|
-
*
|
|
6640
|
-
* These rules are GLOBAL CSS: every selector in here can match markup this
|
|
6641
|
-
* plugin did not render. So each one is either qualified by a `ccp-` class of
|
|
6642
|
-
* our own, or anchored to the sidebar region it has to lay out — and the one
|
|
6643
|
-
* foreign anchor carries its justification in place (see the footer card
|
|
6644
|
-
* block; issue #48). `tests/styles.test.ts` audits that containment for both
|
|
6645
|
-
* stylesheets and simulates it against the real foreign class stems.
|
|
6443
|
+
* Returned as a string, not injected here, so importing this module has no DOM
|
|
6444
|
+
* side effect: `./index.ts` installs it once under {@link PANEL_CSS_ID} and
|
|
6445
|
+
* removes it when the fiber unwinds.
|
|
6646
6446
|
*
|
|
6647
|
-
*
|
|
6648
|
-
*
|
|
6649
|
-
*
|
|
6650
|
-
*
|
|
6447
|
+
* These rules are GLOBAL CSS, so containment is a contract (issue #48): every
|
|
6448
|
+
* selector is qualified by one of our own `ccp-` classes except the sidebar
|
|
6449
|
+
* anchor below, which carries its justification in place. `tests/styles.test.ts`
|
|
6450
|
+
* audits both stylesheets against the real foreign class stems. Colours are
|
|
6451
|
+
* harness theme aliases with neutral fallbacks; the `ccp-` prefix keeps these
|
|
6452
|
+
* clear of the settings page's `cc-` set.
|
|
6651
6453
|
*
|
|
6652
6454
|
* @module dsh-commandcode-provider/client/panel-styles
|
|
6653
6455
|
*/
|
|
6654
6456
|
/**
|
|
6655
|
-
* Stylesheet id
|
|
6457
|
+
* Stylesheet id — the `data-plugin-css` value that makes injection idempotent.
|
|
6656
6458
|
*
|
|
6657
|
-
* The
|
|
6658
|
-
* (`PAGE_CSS_ID`
|
|
6659
|
-
*
|
|
6660
|
-
* the plugin inject the same rules twice and would misreport the owner in the
|
|
6661
|
-
* DOM. The two ids must also stay distinct from each other — they key two
|
|
6662
|
-
* separate style tags, and a shared id would make the second injection a no-op
|
|
6663
|
-
* that silently drops one stylesheet.
|
|
6459
|
+
* The prefix must match this package's real name and the settings-page sheet's
|
|
6460
|
+
* (`PAGE_CSS_ID`); the two ids must also stay DISTINCT, since they key two
|
|
6461
|
+
* separate style tags and a shared id would make the second injection a no-op.
|
|
6664
6462
|
*/
|
|
6665
6463
|
const PANEL_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodePanel.module.css";
|
|
6666
6464
|
/** The panel stylesheet. */
|
|
6667
6465
|
const PANEL_CSS = `
|
|
6668
6466
|
/* ------------------------------------------------- sidebar footer card */
|
|
6669
|
-
/* The shell
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
step and a hairline border.
|
|
6674
|
-
|
|
6675
|
-
The shell's container is a flex ROW whose occupants (this card and ui-cordis's
|
|
6676
|
-
footer chip) each declare a full-width line and shrink-proof flex, so as a row
|
|
6677
|
-
it would overflow the column: this card's flex:0 0 auto cannot shrink, and the
|
|
6678
|
-
chip's width:100% basis would absorb the whole overflow and collapse to
|
|
6679
|
-
nothing. Both were written for a full-width line, which is exactly what a
|
|
6680
|
-
column gives them. Matched by the CSS-module class STEM — never a hashed name
|
|
6681
|
-
— so a dsh that renames it degrades to the shell's own row rather than
|
|
6682
|
-
breaking.
|
|
6467
|
+
/* The shell renders this list ABOVE the Settings seat, so the card is the
|
|
6468
|
+
sidebar's bottom-most content, and supplies no chrome: the entry IS the
|
|
6469
|
+
button. Deliberately quiet — a surface beside Settings should read as part of
|
|
6470
|
+
the column — with one hover step and a hairline border.
|
|
6683
6471
|
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6472
|
+
The shell's container is a flex ROW whose occupants each declare a full-width
|
|
6473
|
+
line and shrink-proof flex, so as a row it would overflow the column: this
|
|
6474
|
+
card cannot shrink and the neighbouring chip would absorb the overflow and
|
|
6475
|
+
collapse to nothing. The first rule is that fix, and THE ANCHOR IS
|
|
6476
|
+
LOAD-BEARING (issue #48): "footerActions" is not a stem this shell owns alone
|
|
6477
|
+
— dsh-client-ui-user-questions renders the ask-user-question dialog's button
|
|
6478
|
+
row as Mbwy4a_footerActions, and the unanchored rule stacked THAT row's
|
|
6689
6479
|
side-by-side buttons on every page. "footArea" is declared by
|
|
6690
|
-
dsh-client-ui-sidebar alone
|
|
6691
|
-
0.1.6-alpha.1 engine, and against our own cc-/ccp-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
than
|
|
6695
|
-
the two: a renamed or restructured shell should degrade to its own row, not
|
|
6696
|
-
lose the fix. tests/styles.test.ts pins both halves of this — the dialog row
|
|
6697
|
-
is no longer matched, the sidebar row still is. */
|
|
6480
|
+
dsh-client-ui-sidebar alone (audited across every client bundle of the
|
|
6481
|
+
0.1.6-alpha.1 engine, and against our own cc-/ccp- names, which collide with
|
|
6482
|
+
no engine class). The DESCENDANT combinator survives a wrapper element
|
|
6483
|
+
appearing between the two, so a restructured shell degrades to its own row
|
|
6484
|
+
rather than losing the fix. tests/styles.test.ts pins both halves. */
|
|
6698
6485
|
[class*="_footArea"] [class*="_footerActions"]{flex-direction:column}
|
|
6699
6486
|
.ccp-foot{box-sizing:border-box;flex:0 0 auto;width:100%;min-width:0;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:1px solid transparent;border-radius:10px;flex-direction:column;gap:6px;margin:0 0 4px;padding:8px;display:flex}
|
|
6700
6487
|
.ccp-foot:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
|
|
@@ -6704,34 +6491,31 @@ window.__ModuleLoader__.load({
|
|
|
6704
6491
|
/* One block per quota window: a head line carrying the window's own spend and
|
|
6705
6492
|
limit, then the FULL-WIDTH bar under it. Stacking the two lets the card show
|
|
6706
6493
|
the dollar figures — the reason this surface exists — without squeezing the
|
|
6707
|
-
bar into what is left beside them. Mirrors the dashboard's
|
|
6494
|
+
bar into what is left beside them. Mirrors the dashboard's window block. */
|
|
6708
6495
|
.ccp-footRow{flex-direction:column;gap:4px;min-width:0;display:flex}
|
|
6709
6496
|
.ccp-footHead{align-items:baseline;gap:8px;min-width:0;display:flex}
|
|
6710
6497
|
.ccp-footLabel{flex:1;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
6711
6498
|
.ccp-footAmount{flex:none;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
6712
6499
|
/* The card's markup must stay PHRASING content — it renders inside the shell's
|
|
6713
|
-
own button — so these bars are spans,
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
container) while the fill collapsed to 0x0 and the bar showed no usage. */
|
|
6500
|
+
own button — so these bars are spans, which makes display:block load-bearing:
|
|
6501
|
+
an inline box ignores width outright, so the fill would collapse to 0x0 and
|
|
6502
|
+
the bar would show no usage. */
|
|
6717
6503
|
.ccp-footBar{display:block;background:var(--dsw-alias-bg-layer-2);border-radius:999px;height:5px;overflow:hidden}
|
|
6718
6504
|
.ccp-footFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
|
|
6719
6505
|
.ccp-footFillWarn{background:var(--dsw-alias-state-error-primary)}
|
|
6720
6506
|
.ccp-footPct{flex:none;width:34px;color:var(--dsw-alias-label-secondary);text-align:right;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
|
|
6721
6507
|
|
|
6722
|
-
/* The 56px rail: one icon button on the shell's own rail
|
|
6723
|
-
|
|
6508
|
+
/* The 56px rail: one icon button on the shell's own 36px rail cell, so the
|
|
6509
|
+
collapsed column keeps a single 18px glyph like its siblings. */
|
|
6724
6510
|
.ccp-railButton{box-sizing:border-box;width:36px;height:36px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:1px solid transparent;border-radius:8px;flex:none;justify-content:center;align-items:center;margin:0 0 4px;padding:0;display:inline-flex}
|
|
6725
6511
|
.ccp-railButton:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
6726
6512
|
.ccp-railButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
6727
6513
|
|
|
6728
|
-
/* The ring glyph
|
|
6514
|
+
/* The ring glyph, sized entirely by its own width/height attribute, so the
|
|
6729
6515
|
footer row, the rail button and the dashboard can each ask for their own. */
|
|
6730
6516
|
.ccp-glyph{flex:none;justify-content:center;align-items:center;display:inline-flex;color:var(--dsw-alias-brand-primary)}
|
|
6731
6517
|
|
|
6732
6518
|
/* ------------------------------------------------------------ dashboard */
|
|
6733
|
-
/* The center column in the layout frame: fill it, scroll the content column,
|
|
6734
|
-
and cap the reading width like the harness's own panels. */
|
|
6735
6519
|
.ccp-main{background:var(--dsw-alias-bg-layer-1);width:100%;height:100%;overflow:auto;display:block}
|
|
6736
6520
|
.ccp-mainInner{max-width:760px;margin:0 auto;padding:24px 20px 40px;flex-direction:column;gap:14px;display:flex;color:var(--dsw-alias-label-primary)}
|
|
6737
6521
|
.ccp-header{align-items:center;gap:10px;display:flex;flex-wrap:wrap}
|
|
@@ -6740,14 +6524,12 @@ window.__ModuleLoader__.load({
|
|
|
6740
6524
|
.ccp-subtitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
6741
6525
|
.ccp-spacer{flex:1}
|
|
6742
6526
|
.ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-variant-numeric:tabular-nums}
|
|
6743
|
-
/* The dashboard's exit:
|
|
6744
|
-
|
|
6745
|
-
centred, so it reads as a dismiss control rather than a text button. */
|
|
6527
|
+
/* The dashboard's exit: the primitive's own size="sm" geometry is kept; only the
|
|
6528
|
+
glyph is enlarged and optically centred, so it reads as a dismiss control. */
|
|
6746
6529
|
.ccp-close{min-width:28px;justify-content:center;padding-left:0;padding-right:0}
|
|
6747
6530
|
.ccp-close span{font-size:16px;line-height:1}
|
|
6748
6531
|
.ccp-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
6749
6532
|
|
|
6750
|
-
/* Notices: the no-key guidance, a blocked report, and a stale-data error. */
|
|
6751
6533
|
.ccp-notice{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:12px 14px;flex-direction:column;gap:4px;display:flex}
|
|
6752
6534
|
.ccp-noticeError{border-color:var(--dsw-alias-state-error-primary)}
|
|
6753
6535
|
.ccp-noticeTitle{margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
@@ -6767,15 +6549,14 @@ window.__ModuleLoader__.load({
|
|
|
6767
6549
|
.ccp-fieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
6768
6550
|
.ccp-planName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:1.5}
|
|
6769
6551
|
|
|
6770
|
-
/*
|
|
6552
|
+
/* The monthly credits and the usage totals share one grid. */
|
|
6771
6553
|
.ccp-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px}
|
|
6772
6554
|
.ccp-tile{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex;min-width:0}
|
|
6773
6555
|
.ccp-tileLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
|
|
6774
6556
|
.ccp-tileValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4;font-variant-numeric:tabular-nums}
|
|
6775
6557
|
.ccp-tileSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
6776
6558
|
|
|
6777
|
-
/* Quota bars: the monthly bar and the two windows
|
|
6778
|
-
label row plus the track. */
|
|
6559
|
+
/* Quota bars: the monthly bar and the two windows. */
|
|
6779
6560
|
.ccp-windows{flex-direction:column;gap:14px;display:flex}
|
|
6780
6561
|
.ccp-window{flex-direction:column;gap:6px;display:flex}
|
|
6781
6562
|
.ccp-windowHead{align-items:baseline;gap:8px;display:flex}
|
|
@@ -6788,7 +6569,6 @@ window.__ModuleLoader__.load({
|
|
|
6788
6569
|
.ccp-barFillWarn{background:var(--dsw-alias-state-error-primary)}
|
|
6789
6570
|
.ccp-windowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
6790
6571
|
|
|
6791
|
-
/* Badges. */
|
|
6792
6572
|
.ccp-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
|
|
6793
6573
|
.ccp-badgeError{background:transparent;color:var(--dsw-alias-state-error-primary)}
|
|
6794
6574
|
.ccp-badgeWarn{background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary))}
|
|
@@ -6815,12 +6595,14 @@ window.__ModuleLoader__.load({
|
|
|
6815
6595
|
apiKeyLocked: "密钥由只读来源提供",
|
|
6816
6596
|
apiBase: "API 地址",
|
|
6817
6597
|
apiBaseHint: "默认 https://api.commandcode.ai,一般无需修改。",
|
|
6818
|
-
requestTimeoutMs: "
|
|
6819
|
-
requestTimeoutMsHint: "等待响应首个字节的超时;默认
|
|
6820
|
-
streamIdleTimeoutMs: "
|
|
6821
|
-
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认
|
|
6598
|
+
requestTimeoutMs: "请求超时(秒)",
|
|
6599
|
+
requestTimeoutMsHint: "等待响应首个字节的超时;默认 300 秒(与官方 CLI 一致)。Provider API 路由要等上游模型吐出第一个 token 才返回响应头,上游慢时仍可能不够;报超时时可按提示调高。",
|
|
6600
|
+
streamIdleTimeoutMs: "流空闲超时(秒)",
|
|
6601
|
+
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300 秒(长思考模型可静默数分钟,默认值刻意放宽)。",
|
|
6822
6602
|
transportMaxRetries: "网络失败重试次数",
|
|
6823
6603
|
transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
|
|
6604
|
+
offloadSeenImagesForCache: "已看过的图片不再重复发送",
|
|
6605
|
+
offloadSeenImagesForCacheHint: "仅 CLI 传输。模型看过图片并回复后,永久用文字占位替代旧图片,以减少新增图片时的缓存回退。模型如需重看旧图,必须重新读取文件或让用户重新附加。默认关闭。",
|
|
6824
6606
|
advancedSettings: "高级设置",
|
|
6825
6607
|
advancedSettingsHint: "API 地址和网络参数,一般无需修改。",
|
|
6826
6608
|
advancedOverriddenOne: "已自定义 1 项",
|
|
@@ -6832,13 +6614,6 @@ window.__ModuleLoader__.load({
|
|
|
6832
6614
|
webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。",
|
|
6833
6615
|
showSidebarQuota: "在侧边栏显示额度卡片",
|
|
6834
6616
|
showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
|
|
6835
|
-
commandGuard: "AI 命令安全预判",
|
|
6836
|
-
commandGuardHint: "命令确认弹窗前,先由 typesafe/jev 判断它是否安全;沙箱提权还会额外判断范围与必要性。三项都足够确定才自动放行,其余照常弹窗。默认关闭。",
|
|
6837
|
-
commandGuardLevel: "自动放行阈值",
|
|
6838
|
-
commandGuardLevelHint: "阈值越高,需要越确定才放行,弹窗越多。高 = 0.95,中 = 0.9(默认),低 = 0.8。判定超过 3 秒未返回会照常弹窗。",
|
|
6839
|
-
commandGuardLevelHigh: "高",
|
|
6840
|
-
commandGuardLevelMedium: "中",
|
|
6841
|
-
commandGuardLevelLow: "低",
|
|
6842
6617
|
zdr: "零数据保留(ZDR)",
|
|
6843
6618
|
zdrHint: "仅使用不留存、不训练的上游;可用模型可能更少且价格通常更高。",
|
|
6844
6619
|
accountsTitle: "账户",
|
|
@@ -6848,6 +6623,8 @@ window.__ModuleLoader__.load({
|
|
|
6848
6623
|
modelSearchPlaceholder: "搜索模型…",
|
|
6849
6624
|
modelSearchEmpty: "没有匹配的模型。",
|
|
6850
6625
|
modelStale: "已下架",
|
|
6626
|
+
modelAllowance: "${amount}/月",
|
|
6627
|
+
modelAllowanceTitle: "该模型每月可用的额度(美元)。套餐内所有模型共用同一个月度额度池,这不是额外赠送的额度。",
|
|
6851
6628
|
visibleModelsTitle: "模型白名单",
|
|
6852
6629
|
visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。保存后,下次打开模型选择器生效。",
|
|
6853
6630
|
visibleModelsPick: "选择要保留的模型…",
|
|
@@ -6974,12 +6751,14 @@ window.__ModuleLoader__.load({
|
|
|
6974
6751
|
apiKeyLocked: "Key provided by a read-only source",
|
|
6975
6752
|
apiBase: "API base URL",
|
|
6976
6753
|
apiBaseHint: "Defaults to https://api.commandcode.ai; usually leave as-is.",
|
|
6977
|
-
requestTimeoutMs: "Request timeout (
|
|
6978
|
-
requestTimeoutMsHint: "Time to wait for the first response byte; default
|
|
6979
|
-
streamIdleTimeoutMs: "Stream idle timeout (
|
|
6980
|
-
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default
|
|
6754
|
+
requestTimeoutMs: "Request timeout (seconds)",
|
|
6755
|
+
requestTimeoutMsHint: "Time to wait for the first response byte; default 300 s (the official CLI’s). The Provider API route withholds headers until the upstream model emits its first token, so a slow upstream can still outlast the default; raise it when a timeout message suggests it.",
|
|
6756
|
+
streamIdleTimeoutMs: "Stream idle timeout (seconds)",
|
|
6757
|
+
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300 s (deliberately generous — long-thinking models can stay silent for minutes).",
|
|
6981
6758
|
transportMaxRetries: "Transport retries",
|
|
6982
6759
|
transportMaxRetriesHint: "How many times a failed connection is retried automatically; default 5 (waits of 0.5+1+2+4+8s, ~15s in total). After that the failure is reported instead of waiting on the exponential backoff. 0 disables the retries. Rate limits and 5xx answers keep their own, longer retry window.",
|
|
6760
|
+
offloadSeenImagesForCache: "Stop replaying images already seen",
|
|
6761
|
+
offloadSeenImagesForCacheHint: "CLI transport only. After this model responds to an image, durably replace older pixels with text to avoid cache rollback when another image arrives. To inspect an old image again, the model must reread its file or the user must attach it again. Off by default.",
|
|
6983
6762
|
advancedSettings: "Advanced",
|
|
6984
6763
|
advancedSettingsHint: "API base URL and network limits; usually leave as-is.",
|
|
6985
6764
|
advancedOverriddenOne: "1 customized",
|
|
@@ -6991,13 +6770,6 @@ window.__ModuleLoader__.load({
|
|
|
6991
6770
|
webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend.",
|
|
6992
6771
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
6993
6772
|
showSidebarQuotaHint: "Show a plan and quota card at the bottom of the sidebar that opens the full dashboard. Off by default; while off, no background usage refresh runs for it.",
|
|
6994
|
-
commandGuard: "AI command guard",
|
|
6995
|
-
commandGuardHint: "Before a shell command asks for approval, typesafe/jev judges whether it is safe. A sandbox escalation also needs separate scope and necessity verdicts; all three must be confident enough to skip the prompt. Off by default.",
|
|
6996
|
-
commandGuardLevel: "Auto-approve threshold",
|
|
6997
|
-
commandGuardLevelHint: "A higher threshold needs more confidence to skip the prompt, so you see more prompts. High = 0.95, Medium = 0.9 (default), Low = 0.8. A judgement that takes longer than 3 seconds shows the normal prompt.",
|
|
6998
|
-
commandGuardLevelHigh: "High",
|
|
6999
|
-
commandGuardLevelMedium: "Medium",
|
|
7000
|
-
commandGuardLevelLow: "Low",
|
|
7001
6773
|
zdr: "Zero data retention (ZDR)",
|
|
7002
6774
|
zdrHint: "Use only upstreams that retain no data and do not train on requests; availability and pricing may differ.",
|
|
7003
6775
|
accountsTitle: "Accounts",
|
|
@@ -7007,6 +6779,8 @@ window.__ModuleLoader__.load({
|
|
|
7007
6779
|
modelSearchPlaceholder: "Search models…",
|
|
7008
6780
|
modelSearchEmpty: "No matching models.",
|
|
7009
6781
|
modelStale: "Retired",
|
|
6782
|
+
modelAllowance: "${amount}/mo",
|
|
6783
|
+
modelAllowanceTitle: "Monthly usage this model can draw, in USD. Every model shares one monthly pool — this is not extra credit on top of the plan.",
|
|
7010
6784
|
visibleModelsTitle: "Model allowlist",
|
|
7011
6785
|
visibleModelsHint: "Check the models you want to keep, and model pickers will list only those. If nothing is checked, every model is shown. After saving, the change applies the next time you open a model picker.",
|
|
7012
6786
|
visibleModelsPick: "Select models to keep…",
|
|
@@ -7124,56 +6898,42 @@ window.__ModuleLoader__.load({
|
|
|
7124
6898
|
};
|
|
7125
6899
|
//#endregion
|
|
7126
6900
|
//#region src/client/index.ts
|
|
7127
|
-
/**
|
|
7128
|
-
function
|
|
7129
|
-
if (typeof document === "undefined") return;
|
|
7130
|
-
if (document.querySelector(`style[data-plugin-css="@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css"]`) !== null) return;
|
|
7131
|
-
const tag = document.createElement("style");
|
|
7132
|
-
tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
|
|
7133
|
-
tag.dataset.pluginCss = PAGE_CSS_ID;
|
|
7134
|
-
tag.textContent = PAGE_CSS;
|
|
7135
|
-
document.head.appendChild(tag);
|
|
7136
|
-
}
|
|
7137
|
-
/**
|
|
7138
|
-
* Install the plans & quota panel's stylesheet and return its disposer, for
|
|
7139
|
-
* `ctx.effect` to own. Keyed by its own `data-plugin-css` id, so the injection
|
|
7140
|
-
* is idempotent even if a second surface asks for it later.
|
|
7141
|
-
*/
|
|
7142
|
-
function injectPanelCss() {
|
|
6901
|
+
/** Install one stylesheet by id and return a disposer for its owning effect. */
|
|
6902
|
+
function injectCss(id, css) {
|
|
7143
6903
|
if (typeof document === "undefined") return () => {};
|
|
7144
|
-
if (document.querySelector(`style[data-plugin-css="
|
|
6904
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`) !== null) return () => {};
|
|
7145
6905
|
const tag = document.createElement("style");
|
|
7146
6906
|
tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
|
|
7147
|
-
tag.dataset.pluginCss =
|
|
7148
|
-
tag.textContent =
|
|
6907
|
+
tag.dataset.pluginCss = id;
|
|
6908
|
+
tag.textContent = css;
|
|
7149
6909
|
document.head.appendChild(tag);
|
|
7150
|
-
return () =>
|
|
7151
|
-
tag.remove();
|
|
7152
|
-
};
|
|
6910
|
+
return () => tag.remove();
|
|
7153
6911
|
}
|
|
7154
6912
|
/**
|
|
7155
6913
|
* Plans & quota panel id. It is the layout's `MainPanelId`: one string shared
|
|
7156
6914
|
* by the `sidebar.footer.action` card and the `main` slot cell, so the card
|
|
7157
6915
|
* selects this panel and nothing else. `dsh-client-ui-layout` is not a
|
|
7158
|
-
* dependency of this bundle
|
|
7159
|
-
*
|
|
6916
|
+
* dependency of this bundle, so its brand is applied at the call site instead
|
|
6917
|
+
* of importing the package.
|
|
7160
6918
|
*/
|
|
7161
6919
|
const PANEL_ID = "commandcode-panel";
|
|
7162
6920
|
/**
|
|
7163
6921
|
* The composer figure's entry id in `conversation.composer.dock`. Its own id,
|
|
7164
|
-
*
|
|
6922
|
+
* never the shipped `stats` cell's: reusing that id would REPLACE the tokens /
|
|
7165
6923
|
* cache-hit / throughput readout rather than inject into it, and that readout
|
|
7166
|
-
* is the harness's to format
|
|
6924
|
+
* is the harness's to format.
|
|
7167
6925
|
*/
|
|
7168
6926
|
const SESSION_COST_ID = "commandcode-session-cost";
|
|
7169
6927
|
/**
|
|
7170
6928
|
* Client plugin body. Gates on `slots`, `locale` and `remote` — never on
|
|
7171
6929
|
* `settingsScope`, whose wrapper service the 0.1.7 settings rewrite removed
|
|
7172
|
-
* (the
|
|
7173
|
-
*
|
|
6930
|
+
* (the scope below speaks the `remote.settings` wire directly), because
|
|
6931
|
+
* gating on a service 0.1.7 does not provide would park EVERY surface below
|
|
6932
|
+
* into silence. `settingsScope` must never reappear in the exported inject
|
|
6933
|
+
* list at the bottom of this file.
|
|
7174
6934
|
*/
|
|
7175
6935
|
function apply(ctx) {
|
|
7176
|
-
|
|
6936
|
+
injectCss(PAGE_CSS_ID, PAGE_CSS);
|
|
7177
6937
|
ctx.effect(() => ctx.locale.register("settings.commandcode", {
|
|
7178
6938
|
zh,
|
|
7179
6939
|
en
|
|
@@ -7422,7 +7182,7 @@ window.__ModuleLoader__.load({
|
|
|
7422
7182
|
ctx.get("layout")?.selectPanel(null);
|
|
7423
7183
|
}
|
|
7424
7184
|
});
|
|
7425
|
-
ctx.effect(() =>
|
|
7185
|
+
ctx.effect(() => injectCss(PANEL_CSS_ID, PANEL_CSS), "dsh-commandcode-provider: panel styles");
|
|
7426
7186
|
try {
|
|
7427
7187
|
ctx.slots.inject("main", () => ctx.slots.register({
|
|
7428
7188
|
name: "main",
|