@mars-sea/dsh-commandcode-provider 0.11.12 → 0.11.13

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/lib/client.js CHANGED
@@ -471,7 +471,10 @@ window.__ModuleLoader__.load({
471
471
  * so the control only reports whether one is configured.
472
472
  * - API base -> the `llm-commandcode` settings namespace (`apiBase`), same
473
473
  * namespace the Models page card addresses.
474
- * - Working dir, request/stream timeouts -> the same namespace.
474
+ * - Timeouts, behavior switches, model visibility -> the same namespace,
475
+ * staged and written on save.
476
+ * - Accounts -> `accounts`, `activeAccount`, `modelAccountRules` and the
477
+ * per-account credentials, committed immediately.
475
478
  *
476
479
  * The controller mirrors the plugin-card pattern from the harness's own
477
480
  * settings UI: it binds the `llm-commandcode` namespace through a
@@ -539,6 +542,24 @@ window.__ModuleLoader__.load({
539
542
  }
540
543
  };
541
544
  }
545
+ /** A field limited to fixed string choices; an empty draft clears it. */
546
+ function choiceField(field, choices) {
547
+ return {
548
+ field,
549
+ format: (value) => typeof value === "string" && choices.includes(value) ? value : "",
550
+ parse: (text) => {
551
+ const trimmed = text.trim();
552
+ if (trimmed === "") return { kind: "clear" };
553
+ return choices.includes(trimmed) ? {
554
+ kind: "set",
555
+ value: trimmed
556
+ } : {
557
+ kind: "invalid",
558
+ reason: "format"
559
+ };
560
+ }
561
+ };
562
+ }
542
563
  /**
543
564
  * A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
544
565
  * clears it). The component renders a toggle and only ever stages these two
@@ -567,10 +588,17 @@ window.__ModuleLoader__.load({
567
588
  };
568
589
  }
569
590
  const MAX_TIMEOUT_MS = 2147483647;
591
+ /** The command guard's auto-approve levels, strictest first. */
592
+ const COMMAND_GUARD_LEVEL_CHOICES = [
593
+ "high",
594
+ "medium",
595
+ "low"
596
+ ];
597
+ /** The level an unset `commandGuardLevel` reads as on the Host. */
598
+ const COMMAND_GUARD_DEFAULT_LEVEL_CHOICE = "medium";
570
599
  /** The fields this page edits inside the `llm-commandcode` namespace. */
571
600
  const SECTION_FIELDS = [
572
601
  textField("apiBase"),
573
- textField("workingDir"),
574
602
  numberField$1("requestTimeoutMs", {
575
603
  min: 1,
576
604
  max: MAX_TIMEOUT_MS
@@ -587,16 +615,8 @@ window.__ModuleLoader__.load({
587
615
  booleanField$1("webSearch"),
588
616
  booleanField$1("showSidebarQuota"),
589
617
  booleanField$1("commandGuard"),
590
- numberField$1("commandGuardThreshold", {
591
- min: .5,
592
- max: 1
593
- }),
594
- numberField$1("commandGuardTimeoutMs", {
595
- min: 200,
596
- max: 1e4
597
- }),
598
- booleanField$1("zdr"),
599
- textField("activeAccount")
618
+ choiceField("commandGuardLevel", COMMAND_GUARD_LEVEL_CHOICES),
619
+ booleanField$1("zdr")
600
620
  ];
601
621
  /** Whether two model-id lists are equal as sets (order-insensitive). */
602
622
  function sameModels(a, b) {
@@ -605,17 +625,47 @@ window.__ModuleLoader__.load({
605
625
  return b.every((id) => set.has(id));
606
626
  }
607
627
  /**
608
- * Order-sensitive content fingerprint of the stored routing rules. Stored rule
609
- * ids are positional (`rule-<index>`), so equality of this fingerprint across a
610
- * save is exactly the statement "no rules write landed and no row shifted".
628
+ * Fold routing rules into one model list per account with the runtime's
629
+ * first-match-wins order (`matchModelRule()` in src/accounts.ts): a model
630
+ * claimed by an earlier rule is ignored by every later one. Account order is
631
+ * first appearance, which keeps a rewrite's rule order stable.
611
632
  */
612
- function ruleFingerprint(rules) {
613
- return JSON.stringify(rules.map((rule) => [rule.models, rule.account]));
633
+ function accountModelMap(rules) {
634
+ const claimed = /* @__PURE__ */ new Set();
635
+ const map = /* @__PURE__ */ new Map();
636
+ for (const rule of rules) {
637
+ const list = map.get(rule.account) ?? [];
638
+ for (const model of rule.models) {
639
+ if (claimed.has(model)) continue;
640
+ claimed.add(model);
641
+ list.push(model);
642
+ }
643
+ map.set(rule.account, list);
644
+ }
645
+ return map;
646
+ }
647
+ /** Serialize a per-account model map back into `modelAccountRules`. */
648
+ function rulesFromMap(map) {
649
+ const out = [];
650
+ for (const [account, models] of map) if (models.length > 0) out.push({
651
+ models: [...models],
652
+ account
653
+ });
654
+ return out;
614
655
  }
615
656
  /**
616
657
  * Controller bridging the `llm-commandcode` scope and the credentials domain
617
- * onto the page. Public API mirrors the harness's CardForm actions, so the
618
- * component stays thin.
658
+ * onto the page.
659
+ *
660
+ * Two write paths with different contracts:
661
+ * - The staged form (connection, behavior and model-visibility fields, plus
662
+ * the Models-page card's default key draft) lands on `save()`.
663
+ * - Account management (create, rename, remove, key replacement, pinning and
664
+ * per-account models) commits IMMEDIATELY and serially. A new account had to
665
+ * be saved before browser sign-in could target it (the Host refuses a login
666
+ * for a reference the stored `accounts` list does not name), which turned
667
+ * "add an account" into edit → save → sign in; committing each operation
668
+ * removes that dance and keeps unrelated staged edits out of it.
619
669
  */
620
670
  var CommandCodeSettingsController = class {
621
671
  scope;
@@ -629,34 +679,6 @@ window.__ModuleLoader__.load({
629
679
  credentialRef = DEFAULT_API_KEY_REF;
630
680
  /** Host-reported configured/writable state per credential reference. */
631
681
  credentialStates = /* @__PURE__ */ new Map();
632
- /** Staged account additions (not yet saved). */
633
- addedAccounts = [];
634
- /** Staged removals of stored extra accounts, by credential reference. */
635
- removedRefs = /* @__PURE__ */ new Set();
636
- /** Staged label drafts, by credential reference. */
637
- labelDrafts = /* @__PURE__ */ new Map();
638
- /** Staged key drafts, by credential reference (blank = keep stored key). */
639
- keyDrafts = /* @__PURE__ */ new Map();
640
- /** Credential references staged for removal on the next save. */
641
- keyClears = /* @__PURE__ */ new Set();
642
- /** Staged model→account routing rules (not yet saved). */
643
- addedRules = [];
644
- /** Staged edits to stored routing rules, by stored row id. */
645
- ruleDrafts = /* @__PURE__ */ new Map();
646
- /**
647
- * Stored routing rule rows staged for removal, by stored row id with a
648
- * content snapshot. The snapshot makes reconcile content-based: stored
649
- * row ids are positional (`rule-N`) and shift after any write, so an
650
- * id-only check would misread a landed removal as pending (and a retry
651
- * would delete the wrong row).
652
- */
653
- removedRuleIds = /* @__PURE__ */ new Map();
654
- /**
655
- * Fingerprint of the stored routing rules when the last `save()` started;
656
- * `undefined` outside a save. Lets reconcile distinguish a landed rules
657
- * write from a save that failed before reaching it.
658
- */
659
- rulesBeforeSave = void 0;
660
682
  /** Staged visible-model allowlist (undefined = no draft). */
661
683
  visibleModelsDraft = void 0;
662
684
  /** The catalog the model editors offer (Host-side). */
@@ -665,6 +687,10 @@ window.__ModuleLoader__.load({
665
687
  saving = false;
666
688
  failed = false;
667
689
  savedCount = 0;
690
+ /** Tail of the serial account-operation queue. */
691
+ accountQueue = Promise.resolve();
692
+ accountPending = 0;
693
+ accountFailed = void 0;
668
694
  /**
669
695
  * @param scope - bound scope for the `llm-commandcode` namespace.
670
696
  * @param api - credentials wire face.
@@ -691,9 +717,9 @@ window.__ModuleLoader__.load({
691
717
  }
692
718
  /**
693
719
  * The credential reference the section names, or the provider default. A
694
- * user who renamed `apiKeyEnv` in `settings.yaml` (or the composition
695
- * config) gets a page that addresses the renamed ref instead of silently
696
- * writing the default — mirroring the Models page's `refFor()`.
720
+ * user who renamed `apiKeyEnv` in the profile Config gets a page that
721
+ * addresses the renamed ref instead of silently writing the default —
722
+ * mirroring the Models page's `refFor()`.
697
723
  */
698
724
  recomputeCredentialRef() {
699
725
  const snapshot = this.scope.getSnapshot();
@@ -713,6 +739,7 @@ window.__ModuleLoader__.load({
713
739
  const plan = this.plan();
714
740
  const credential = this.credentialStates.get(this.credentialRef);
715
741
  const accounts = this.effectiveAccounts();
742
+ const active = this.sectionValue("activeAccount");
716
743
  return {
717
744
  available: snapshot.status === "ready",
718
745
  writable: snapshot.writable,
@@ -726,9 +753,7 @@ window.__ModuleLoader__.load({
726
753
  invalid: false,
727
754
  invalidReason: void 0
728
755
  },
729
- apiKeyClearStaged: this.keyClears.has(this.credentialRef),
730
756
  apiBase: this.field("apiBase"),
731
- workingDir: this.field("workingDir"),
732
757
  requestTimeoutMs: this.field("requestTimeoutMs"),
733
758
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
734
759
  transportMaxRetries: this.field("transportMaxRetries"),
@@ -736,164 +761,31 @@ window.__ModuleLoader__.load({
736
761
  webSearch: this.field("webSearch"),
737
762
  showSidebarQuota: this.field("showSidebarQuota"),
738
763
  commandGuard: this.field("commandGuard"),
739
- commandGuardThreshold: this.field("commandGuardThreshold"),
740
- commandGuardTimeoutMs: this.field("commandGuardTimeoutMs"),
764
+ commandGuardLevel: this.field("commandGuardLevel"),
741
765
  zdr: this.field("zdr"),
742
766
  sidebarQuota: this.sectionValue("showSidebarQuota") === true,
743
- activeAccount: this.field("activeAccount"),
767
+ activeAccount: typeof active === "string" ? active : "",
744
768
  accounts,
745
- accountsRemoving: [...this.removedRefs],
746
- rules: this.effectiveRules(),
769
+ accountModels: Object.fromEntries(accountModelMap(this.storedRules())),
770
+ accountBusy: this.accountPending > 0,
771
+ accountFailed: this.accountFailed,
747
772
  visibleModels: this.effectiveVisibleModels(),
748
773
  catalogModels: this.catalogModels,
749
774
  catalogFailed: this.catalogFailed,
750
- dirty: plan.length > 0 || this.accountsDirty() || this.rulesDirty() || this.visibleModelsDirty(),
775
+ dirty: plan.length > 0 || this.visibleModelsDirty(),
751
776
  invalid: plan.some((item) => item.run === void 0),
752
777
  saving: this.saving,
753
778
  failed: this.failed,
754
779
  savedCount: this.savedCount
755
780
  };
756
781
  }
757
- /** Stage a new extra account (saved on the next `save()`). */
758
- addAccount() {
759
- const used = /* @__PURE__ */ new Set([
760
- this.credentialRef,
761
- ...this.storedExtras().map((extra) => extra.ref),
762
- ...this.addedAccounts.map((extra) => extra.ref)
763
- ]);
764
- let n = 2;
765
- while (used.has(`${this.credentialRef}_${n}`)) n += 1;
766
- const index = this.storedExtras().length + this.addedAccounts.length + 2;
767
- this.addedAccounts.push({
768
- label: `Account ${index}`,
769
- ref: `${this.credentialRef}_${n}`
770
- });
771
- this.failed = false;
772
- this.describeAll();
773
- this.publish();
774
- }
775
- /** Stage one extra account's removal (or drop an unsaved addition). */
776
- removeAccount(id) {
777
- const addedIndex = this.addedAccounts.findIndex((extra) => extra.ref === id);
778
- if (addedIndex >= 0) this.addedAccounts.splice(addedIndex, 1);
779
- else this.removedRefs.add(id);
780
- this.labelDrafts.delete(id);
781
- this.keyDrafts.delete(id);
782
- const stagedActive = this.staged.get("activeAccount");
783
- if ((stagedActive !== void 0 ? stagedActive.clear ? "" : stagedActive.text : typeof this.sectionValue("activeAccount") === "string" ? this.sectionValue("activeAccount") : "") === id) this.staged.set("activeAccount", {
784
- text: "",
785
- clear: true
786
- });
787
- this.failed = false;
788
- this.publish();
789
- }
790
- /** Stage one extra account's label draft. */
791
- editAccountLabel(id, text) {
792
- this.labelDrafts.set(id, text);
793
- this.failed = false;
794
- this.publish();
795
- }
796
- /** Stage one extra account's key draft (blank keeps the stored key). */
797
- editAccountKey(id, text) {
798
- this.keyDrafts.set(id, text);
799
- this.keyClears.delete(id);
800
- this.failed = false;
801
- this.publish();
802
- }
803
- /**
804
- * Toggle the staged removal of one account's stored key: the next save
805
- * unsets the credential so the account reports unconfigured and falls back
806
- * to its other key sources. Only meaningful while a key is actually
807
- * stored. `target` is `'default'` (the implicit first account) or an extra
808
- * account's credential reference.
809
- */
810
- toggleKeyClear(target) {
811
- const ref = target === "default" ? this.credentialRef : target;
812
- if (this.keyClears.has(ref)) this.keyClears.delete(ref);
813
- else {
814
- if (this.credentialStates.get(ref)?.configured !== true) return;
815
- this.keyDrafts.delete(ref);
816
- if (ref === this.credentialRef) this.staged.delete("apiKey");
817
- this.keyClears.add(ref);
818
- }
819
- this.failed = false;
820
- this.describeAll();
821
- this.publish();
822
- }
823
- /** Stage a new model → account routing rule (saved on the next `save()`). */
824
- addRule() {
825
- this.addedRules.push({
826
- models: [],
827
- account: "default"
828
- });
829
- this.failed = false;
830
- this.publish();
831
- }
832
- /** Stage one routing rule's removal (or drop an unsaved addition). */
833
- removeRule(id) {
834
- const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
835
- if (addedIndex >= 0) this.addedRules.splice(addedIndex, 1);
836
- else {
837
- const stored = this.storedRules().find((rule) => rule.id === id);
838
- this.removedRuleIds.set(id, stored === void 0 ? {
839
- models: [],
840
- account: ""
841
- } : {
842
- models: [...stored.models],
843
- account: stored.account
844
- });
845
- }
846
- this.ruleDrafts.delete(id);
847
- this.failed = false;
848
- this.publish();
849
- }
850
- /** Stage one routing rule's selected model ids (multi-select). */
851
- editRuleModels(id, models) {
852
- const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
853
- if (addedIndex >= 0) this.addedRules[addedIndex] = {
854
- ...this.addedRules[addedIndex],
855
- models
856
- };
857
- else {
858
- const current = this.ruleDrafts.get(id) ?? this.storedRules().find((rule) => rule.id === id) ?? {
859
- models: [],
860
- account: "default"
861
- };
862
- this.ruleDrafts.set(id, {
863
- ...current,
864
- models
865
- });
866
- }
867
- this.failed = false;
868
- this.publish();
869
- }
870
- /** Stage one routing rule's target account draft. */
871
- editRuleAccount(id, text) {
872
- const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
873
- if (addedIndex >= 0) this.addedRules[addedIndex] = {
874
- ...this.addedRules[addedIndex],
875
- account: text
876
- };
877
- else {
878
- const current = this.ruleDrafts.get(id) ?? this.storedRules().find((rule) => rule.id === id) ?? {
879
- models: [],
880
- account: "default"
881
- };
882
- this.ruleDrafts.set(id, {
883
- ...current,
884
- account: text
885
- });
886
- }
887
- this.failed = false;
888
- this.publish();
889
- }
890
782
  /** Stage one field's draft text. */
891
783
  edit(field, text) {
784
+ if (field !== "apiKey") this.spec(field);
892
785
  this.staged.set(field, {
893
786
  text,
894
787
  clear: false
895
788
  });
896
- if (field === "apiKey") this.keyClears.delete(this.credentialRef);
897
789
  this.failed = false;
898
790
  this.publish();
899
791
  }
@@ -915,11 +807,9 @@ window.__ModuleLoader__.load({
915
807
  }
916
808
  /** Discard every staged edit. */
917
809
  discard() {
918
- if (this.staged.size === 0 && !this.accountsStaged() && !this.rulesStaged() && !this.visibleModelsStaged() && !this.failed) return;
810
+ if (this.staged.size === 0 && this.visibleModelsDraft === void 0 && !this.failed) return;
919
811
  this.staged.clear();
920
- this.clearAccountStaging();
921
- this.clearRuleStaging();
922
- this.clearVisibleModelsStaging();
812
+ this.visibleModelsDraft = void 0;
923
813
  this.failed = false;
924
814
  this.publish();
925
815
  }
@@ -934,10 +824,8 @@ window.__ModuleLoader__.load({
934
824
  /** Write every staged edit, then re-read the Host's accepted state. */
935
825
  async save() {
936
826
  const plan = this.plan();
937
- const accountRuns = this.accountPlan();
938
- const ruleRuns = this.rulesPlan();
939
827
  const visibleRuns = this.visibleModelsPlan();
940
- if (plan.length === 0 && accountRuns.length === 0 && ruleRuns.length === 0 && visibleRuns.length === 0 || this.saving) return;
828
+ if (plan.length === 0 && visibleRuns.length === 0 || this.saving) return;
941
829
  const runs = [];
942
830
  for (const item of plan) {
943
831
  if (item.run === void 0) return;
@@ -945,15 +833,9 @@ window.__ModuleLoader__.load({
945
833
  }
946
834
  this.saving = true;
947
835
  this.failed = false;
948
- this.rulesBeforeSave = ruleFingerprint(this.storedRules());
949
836
  this.publish();
950
837
  let landed = true;
951
- for (const run of [
952
- ...runs,
953
- ...accountRuns,
954
- ...ruleRuns,
955
- ...visibleRuns
956
- ]) {
838
+ for (const run of [...runs, ...visibleRuns]) {
957
839
  let ok = false;
958
840
  try {
959
841
  ok = await run();
@@ -970,32 +852,148 @@ window.__ModuleLoader__.load({
970
852
  if (landed) {
971
853
  this.savedCount += 1;
972
854
  this.staged.clear();
973
- this.clearAccountStaging();
974
- this.clearRuleStaging();
975
- this.clearVisibleModelsStaging();
976
- } else {
977
- this.reconcileAccountStaging();
978
- this.reconcileRuleStaging();
979
- this.reconcileVisibleModelsStaging();
980
- }
855
+ this.visibleModelsDraft = void 0;
856
+ } else this.reconcileStaging();
857
+ this.publish();
858
+ }
859
+ /** Stage the visible-model allowlist (multi-select). */
860
+ editVisibleModels(models) {
861
+ this.visibleModelsDraft = [...models];
862
+ this.failed = false;
863
+ this.publish();
864
+ }
865
+ /** Stage "show all models" (clears the allowlist). */
866
+ clearVisibleModels() {
867
+ this.visibleModelsDraft = [];
868
+ this.failed = false;
981
869
  this.publish();
982
870
  }
983
871
  /**
984
- * Drop account staging the stored section already reflects: additions whose
985
- * ref is now stored, removals whose ref is gone, and label drafts that the
986
- * stored label proves landed. Key drafts are kept — a landed key write is
987
- * idempotent on retry, and the draft carries the user's intent when it was
988
- * the accounts write that failed.
872
+ * Create one extra account now and return its credential reference, or
873
+ * undefined when the account could not be stored. With `key` the key lands
874
+ * first, so a stored row never names a reference whose key write failed;
875
+ * without it the row is stored keyless so browser sign-in can target it.
989
876
  */
990
- reconcileAccountStaging() {
991
- const stored = new Set(this.storedExtras().map((extra) => extra.ref));
992
- this.addedAccounts = this.addedAccounts.filter((extra) => !stored.has(extra.ref));
993
- for (const ref of [...this.removedRefs]) if (!stored.has(ref)) this.removedRefs.delete(ref);
994
- for (const [ref, text] of [...this.labelDrafts]) {
995
- const storedLabel = this.storedExtras().find((extra) => extra.ref === ref)?.label;
996
- if (storedLabel !== void 0 && storedLabel === text.trim()) this.labelDrafts.delete(ref);
997
- }
998
- for (const ref of [...this.keyClears]) if (this.credentialStates.get(ref)?.configured !== true) this.keyClears.delete(ref);
877
+ async createAccount(input) {
878
+ let created;
879
+ await this.runAccountOp("create", async () => {
880
+ const ref = this.nextAccountRef();
881
+ const key = input.key?.trim() ?? "";
882
+ if (key !== "" && !await this.writeKeyTo(ref, key)) return false;
883
+ const label = input.label.trim() === "" ? ref : input.label.trim();
884
+ let ok = false;
885
+ try {
886
+ ok = await this.writeAccountList([...this.rawStoredAccounts(), {
887
+ label,
888
+ apiKeyEnv: ref
889
+ }]);
890
+ } catch {
891
+ ok = false;
892
+ }
893
+ if (!ok) {
894
+ if (key !== "") await this.unsetKey(ref);
895
+ return false;
896
+ }
897
+ created = ref;
898
+ return true;
899
+ });
900
+ return created;
901
+ }
902
+ /** Rename one stored extra account now. */
903
+ renameAccount(ref, label) {
904
+ const next = label.trim();
905
+ return this.runAccountOp("rename", async () => {
906
+ if (next === "") return false;
907
+ const list = this.rawStoredAccounts().map((entry) => entry.apiKeyEnv === ref ? {
908
+ ...entry,
909
+ label: next
910
+ } : { ...entry });
911
+ return this.writeAccountList(list);
912
+ });
913
+ }
914
+ /**
915
+ * Remove one stored extra account now: its stored key, its dedicated
916
+ * models and a pin naming it go with it, so nothing orphaned remains.
917
+ */
918
+ removeAccount(ref) {
919
+ return this.runAccountOp("remove", async () => {
920
+ if (this.credentialStates.get(ref)?.configured === true && !await this.unsetKey(ref)) return false;
921
+ const list = this.rawStoredAccounts().filter((entry) => entry.apiKeyEnv !== ref).map((entry) => ({ ...entry }));
922
+ if (!await this.writeAccountList(list)) return false;
923
+ const map = accountModelMap(this.storedRules());
924
+ if (map.has(ref)) {
925
+ map.delete(ref);
926
+ if (!await this.writeRules(rulesFromMap(map))) return false;
927
+ }
928
+ if (this.sectionValue("activeAccount") === ref) return this.clear("activeAccount");
929
+ return true;
930
+ });
931
+ }
932
+ /** Store a replacement key now; `target` is `'default'` or an extra account's reference. */
933
+ setAccountKey(target, key) {
934
+ const value = key.trim();
935
+ return this.runAccountOp("key", async () => {
936
+ if (value === "") return false;
937
+ return this.writeKeyTo(this.refFor(target), value);
938
+ });
939
+ }
940
+ /** Remove a stored key now; `target` is `'default'` or an extra account's reference. */
941
+ clearAccountKey(target) {
942
+ return this.runAccountOp("key", () => this.unsetKey(this.refFor(target)));
943
+ }
944
+ /** Pin the serving account now (`''` returns to automatic rotation). */
945
+ setActiveAccount(id) {
946
+ return this.runAccountOp("active", () => id === "" ? this.clear("activeAccount") : this.store("activeAccount", id));
947
+ }
948
+ /**
949
+ * Replace one account's dedicated models now. A model belongs to one
950
+ * account at a time, so each selected model is moved out of any other
951
+ * account's list — the stored rules then carry no shadowed entries.
952
+ */
953
+ setAccountModels(target, models) {
954
+ return this.runAccountOp("models", async () => {
955
+ const chosen = [...new Set(models.filter((id) => id !== ""))];
956
+ const taken = new Set(chosen);
957
+ const map = accountModelMap(this.storedRules());
958
+ for (const [account, list] of map) if (account !== target) map.set(account, list.filter((id) => !taken.has(id)));
959
+ map.set(target, chosen);
960
+ return this.writeRules(rulesFromMap(map));
961
+ });
962
+ }
963
+ /** Run one account operation after every earlier one, tracking busy/failure. */
964
+ runAccountOp(op, run) {
965
+ this.accountPending += 1;
966
+ this.accountFailed = void 0;
967
+ this.publish();
968
+ const result = this.accountQueue.then(async () => {
969
+ if (!this.scope.getSnapshot().writable) return false;
970
+ try {
971
+ return await run();
972
+ } catch {
973
+ return false;
974
+ }
975
+ });
976
+ this.accountQueue = result;
977
+ return result.then((ok) => {
978
+ this.accountPending -= 1;
979
+ if (!ok) this.accountFailed = op;
980
+ this.publish();
981
+ return ok;
982
+ });
983
+ }
984
+ refFor(target) {
985
+ return target === "default" ? this.credentialRef : target;
986
+ }
987
+ /**
988
+ * The first free `<credentialRef>_<n>` reference. Derived from the default
989
+ * reference's name, so a renamed `apiKeyEnv` yields `MY_KEY_2`-style refs
990
+ * consistent with the default slot.
991
+ */
992
+ nextAccountRef() {
993
+ const used = /* @__PURE__ */ new Set([this.credentialRef, ...this.storedExtras().map((extra) => extra.ref)]);
994
+ let n = 2;
995
+ while (used.has(`${this.credentialRef}_${n}`)) n += 1;
996
+ return `${this.credentialRef}_${n}`;
999
997
  }
1000
998
  spec(field) {
1001
999
  const spec = this.specs.get(field);
@@ -1048,7 +1046,7 @@ window.__ModuleLoader__.load({
1048
1046
  const value = staged.text.trim();
1049
1047
  if (value !== "") plan.push({
1050
1048
  field,
1051
- run: () => this.writeKey(value)
1049
+ run: () => this.writeKeyTo(this.credentialRef, value)
1052
1050
  });
1053
1051
  continue;
1054
1052
  }
@@ -1077,7 +1075,16 @@ window.__ModuleLoader__.load({
1077
1075
  }
1078
1076
  return plan;
1079
1077
  }
1078
+ /** Drop staged drafts a partially landed save already stored. */
1079
+ reconcileStaging() {
1080
+ for (const [field, staged] of [...this.staged]) {
1081
+ if (field === "apiKey" || staged.clear) continue;
1082
+ if (staged.text === this.spec(field).format(this.sectionValue(field))) this.staged.delete(field);
1083
+ }
1084
+ if (this.visibleModelsDraft !== void 0 && sameModels(this.visibleModelsDraft, this.storedVisibleModels())) this.visibleModelsDraft = void 0;
1085
+ }
1080
1086
  async clear(field) {
1087
+ if (!this.stored(field)) return true;
1081
1088
  await this.scope.unset(field);
1082
1089
  return !this.stored(field);
1083
1090
  }
@@ -1085,10 +1092,6 @@ window.__ModuleLoader__.load({
1085
1092
  await this.scope.set(field, value);
1086
1093
  return this.userLayer()?.[field] === value;
1087
1094
  }
1088
- /** Write the staged default key, then re-read whether the Host holds it. */
1089
- async writeKey(value) {
1090
- return this.writeKeyTo(this.credentialRef, value);
1091
- }
1092
1095
  /** Write one account's key, then re-read the Host's credential states. */
1093
1096
  async writeKeyTo(ref, value) {
1094
1097
  try {
@@ -1096,16 +1099,29 @@ window.__ModuleLoader__.load({
1096
1099
  } catch {
1097
1100
  return false;
1098
1101
  }
1099
- await this.describeAll();
1102
+ await this.describeAll([ref]);
1100
1103
  return this.credentialStates.get(ref)?.configured ?? false;
1101
1104
  }
1102
- /** Ask the credentials domain about every reference this page writes. */
1103
- async describeAll() {
1104
- const refs = [
1105
+ /** Unset one stored credential, then re-read the Host's credential states. */
1106
+ async unsetKey(ref) {
1107
+ try {
1108
+ if (!(await this.api.credentials.unset(ref)).ok) return false;
1109
+ } catch {
1110
+ return false;
1111
+ }
1112
+ await this.describeAll([ref]);
1113
+ return this.credentialStates.get(ref)?.configured !== true;
1114
+ }
1115
+ /**
1116
+ * Ask the credentials domain about every reference this page writes, plus
1117
+ * `extra` — a key written for an account whose row is not stored yet.
1118
+ */
1119
+ async describeAll(extra = []) {
1120
+ const refs = [.../* @__PURE__ */ new Set([
1105
1121
  this.credentialRef,
1106
- ...this.storedExtras().map((extra) => extra.ref),
1107
- ...this.addedAccounts.map((extra) => extra.ref)
1108
- ];
1122
+ ...this.storedExtras().map((account) => account.ref),
1123
+ ...extra
1124
+ ])];
1109
1125
  let response;
1110
1126
  try {
1111
1127
  response = await this.api.credentials.describe(refs);
@@ -1168,20 +1184,19 @@ window.__ModuleLoader__.load({
1168
1184
  return raw.filter((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
1169
1185
  }
1170
1186
  /**
1171
- * The stored extra accounts from the settings section (`accounts`): the rows
1172
- * this page can address, i.e. the ones carrying a credential reference.
1173
- *
1174
- * Entries the page cannot name are deliberately NOT listed here but are also
1175
- * never dropped — `writeAccounts()` rebuilds the stored list from
1176
- * {@link rawStoredAccounts} and only rewrites the reference-carrying entries
1177
- * it manages (see the note there), so a literal-key entry stays in the
1178
- * document and simply has no row.
1187
+ * The stored extra accounts this page can address: the rows carrying a
1188
+ * credential reference. Entries it cannot name (a composition entry with a
1189
+ * literal `apiKey`, or an unknown shape) are never dropped — every list
1190
+ * write starts from {@link rawStoredAccounts} and copies them verbatim,
1191
+ * because the settings layer replaces the whole array.
1179
1192
  */
1180
1193
  storedExtras() {
1181
1194
  const out = [];
1195
+ const seen = /* @__PURE__ */ new Set();
1182
1196
  for (const record of this.rawStoredAccounts()) {
1183
1197
  const ref = record.apiKeyEnv;
1184
- if (typeof ref !== "string" || ref === "") continue;
1198
+ if (typeof ref !== "string" || ref === "" || seen.has(ref)) continue;
1199
+ seen.add(ref);
1185
1200
  const label = record.label;
1186
1201
  out.push({
1187
1202
  label: typeof label === "string" && label !== "" ? label : ref,
@@ -1190,197 +1205,43 @@ window.__ModuleLoader__.load({
1190
1205
  }
1191
1206
  return out;
1192
1207
  }
1193
- /** Every extra account row: stored (minus staged removals) + staged adds. */
1194
1208
  effectiveAccounts() {
1195
- const stored = this.storedExtras().filter((extra) => !this.removedRefs.has(extra.ref)).map((extra) => ({
1196
- ...extra,
1197
- added: false
1198
- }));
1199
- const added = this.addedAccounts.map((extra) => ({
1200
- ...extra,
1201
- added: true
1202
- }));
1203
- return [...stored, ...added].map((extra) => ({
1209
+ return this.storedExtras().map((extra) => ({
1204
1210
  id: extra.ref,
1205
1211
  ref: extra.ref,
1206
- label: this.labelDrafts.get(extra.ref) ?? extra.label,
1207
- keyText: this.keyDrafts.get(extra.ref) ?? "",
1212
+ label: extra.label,
1208
1213
  configured: this.credentialStates.get(extra.ref)?.configured ?? false,
1209
- writable: this.credentialStates.get(extra.ref)?.writable ?? true,
1210
- added: extra.added,
1211
- clearStaged: this.keyClears.has(extra.ref)
1214
+ writable: this.credentialStates.get(extra.ref)?.writable ?? true
1212
1215
  }));
1213
1216
  }
1214
- /** Whether any account-level staging (add/remove/label/key/clear) exists. */
1215
- accountsStaged() {
1216
- return this.addedAccounts.length > 0 || this.removedRefs.size > 0 || this.labelDrafts.size > 0 || this.keyDrafts.size > 0 || this.keyClears.size > 0;
1217
- }
1218
- /** Whether the staged account edits differ from the stored section. */
1219
- accountsDirty() {
1220
- if (this.addedAccounts.length > 0 || this.removedRefs.size > 0) return true;
1221
- for (const [ref, text] of this.labelDrafts) {
1222
- const base = this.storedExtras().find((extra) => extra.ref === ref)?.label;
1223
- if (base !== void 0 && text.trim() !== "" && text !== base) return true;
1224
- }
1225
- for (const text of this.keyDrafts.values()) if (text.trim() !== "") return true;
1226
- for (const ref of this.keyClears) if (this.credentialStates.get(ref)?.configured === true) return true;
1227
- return false;
1228
- }
1229
- /** Reset every account-level staged edit. */
1230
- clearAccountStaging() {
1231
- this.addedAccounts = [];
1232
- this.removedRefs.clear();
1233
- this.labelDrafts.clear();
1234
- this.keyDrafts.clear();
1235
- this.keyClears.clear();
1236
- }
1237
- /** Unset one stored credential, then re-read the Host's credential states. */
1238
- async unsetKey(ref) {
1239
- try {
1240
- if (!(await this.api.credentials.unset(ref)).ok) return false;
1241
- } catch {
1242
- return false;
1243
- }
1244
- await this.describeAll();
1245
- return this.credentialStates.get(ref)?.configured !== true;
1246
- }
1247
- /** The account-level writes a save performs (empty when nothing staged). */
1248
- accountPlan() {
1249
- if (!this.accountsDirty()) return [];
1250
- const runs = [];
1251
- for (const ref of this.keyClears) if (this.credentialStates.get(ref)?.configured === true) runs.push(() => this.unsetKey(ref));
1252
- for (const [ref, text] of this.keyDrafts) {
1253
- const value = text.trim();
1254
- if (value !== "" && !this.removedRefs.has(ref) && !this.keyClears.has(ref)) runs.push(() => this.writeKeyTo(ref, value));
1255
- }
1256
- runs.push(() => this.writeAccounts());
1257
- return runs;
1258
- }
1259
- /**
1260
- * Persist the staged accounts list into the settings section.
1261
- *
1262
- * The stored list is the base — NOT a list rebuilt from this page's rows.
1263
- * A composition-config entry may carry a literal `apiKey` (or a shape this
1264
- * page does not know), and the settings layer replaces the whole array, so a
1265
- * rebuilt list would silently delete every entry the page cannot name along
1266
- * with the literal keys of the entries it can. Entries are therefore carried
1267
- * over verbatim and only the reference-carrying rows are rewritten (label
1268
- * draft applied, staged removals dropped, staged additions appended).
1269
- */
1270
- async writeAccounts() {
1271
- const removed = this.removedRefs;
1272
- const written = /* @__PURE__ */ new Set();
1273
- const list = [];
1274
- for (const entry of this.rawStoredAccounts()) {
1275
- const ref = entry.apiKeyEnv;
1276
- if (typeof ref !== "string" || ref === "") {
1277
- list.push({ ...entry });
1278
- continue;
1279
- }
1280
- if (removed.has(ref) || written.has(ref)) continue;
1281
- written.add(ref);
1282
- list.push(this.accountEntry(ref, entry));
1283
- }
1284
- for (const extra of this.addedAccounts) {
1285
- if (written.has(extra.ref)) continue;
1286
- written.add(extra.ref);
1287
- list.push(this.accountEntry(extra.ref, extra));
1288
- }
1217
+ /** Persist a full accounts list and verify the Host stored it. */
1218
+ async writeAccountList(list) {
1289
1219
  await this.scope.set("accounts", list);
1290
1220
  const after = this.rawStoredAccounts();
1291
- return after.length === list.length && list.every((item, index) => after[index]?.apiKeyEnv === item.apiKeyEnv);
1292
- }
1293
- /**
1294
- * One written account entry: the stored/added facts plus the label draft.
1295
- * `fallback` contributes the non-managed fields (a stored entry's literal
1296
- * `apiKey`, or any future field) so a rewrite never strips them.
1297
- */
1298
- accountEntry(ref, fallback) {
1299
- const draft = this.labelDrafts.get(ref)?.trim();
1300
- const base = "ref" in fallback ? { label: fallback.label } : { ...fallback };
1301
- const storedLabel = base.label;
1302
- return {
1303
- ...base,
1304
- label: draft !== void 0 && draft !== "" ? draft : typeof storedLabel === "string" && storedLabel !== "" ? storedLabel : ref,
1305
- apiKeyEnv: ref
1306
- };
1221
+ return after.length === list.length && list.every((item, index) => after[index]?.apiKeyEnv === item.apiKeyEnv && after[index]?.label === item.label);
1307
1222
  }
1308
1223
  /** The stored routing rules from the settings section (`modelAccountRules`). */
1309
1224
  storedRules() {
1310
1225
  const raw = this.scope.getSnapshot().value?.modelAccountRules;
1311
1226
  if (!Array.isArray(raw)) return [];
1312
1227
  const out = [];
1313
- for (const [index, entry] of raw.entries()) {
1228
+ for (const entry of raw) {
1314
1229
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
1315
1230
  const record = entry;
1316
- const models = record.models;
1317
- const account = record.account;
1318
- const modelsList = Array.isArray(models) && models.every((m) => typeof m === "string") ? models.filter((m) => m !== "") : [];
1319
- if (modelsList.length === 0) continue;
1231
+ const models = Array.isArray(record.models) ? record.models.filter((m) => typeof m === "string" && m !== "") : [];
1232
+ if (models.length === 0) continue;
1320
1233
  out.push({
1321
- id: `rule-${index}`,
1322
- models: modelsList,
1323
- account: typeof account === "string" && account !== "" ? account : "default"
1234
+ models,
1235
+ account: typeof record.account === "string" && record.account !== "" ? record.account : "default"
1324
1236
  });
1325
1237
  }
1326
1238
  return out;
1327
1239
  }
1328
- /** Every routing rule row: stored (minus staged removals, with drafts) + staged adds. */
1329
- effectiveRules() {
1330
- const stored = this.storedRules().filter((rule) => !this.removedRuleIds.has(rule.id)).map((rule) => {
1331
- const draft = this.ruleDrafts.get(rule.id);
1332
- return {
1333
- id: rule.id,
1334
- models: draft?.models ?? rule.models,
1335
- account: draft?.account ?? rule.account,
1336
- added: false
1337
- };
1338
- });
1339
- const added = this.addedRules.map((rule, index) => ({
1340
- id: `new-${index}`,
1341
- models: rule.models,
1342
- account: rule.account,
1343
- added: true
1344
- }));
1345
- return [...stored, ...added];
1346
- }
1347
- /** Whether any routing-rule staging (add/remove/edit) exists. */
1348
- rulesStaged() {
1349
- return this.addedRules.length > 0 || this.removedRuleIds.size > 0 || this.ruleDrafts.size > 0;
1350
- }
1351
- /** Whether the staged routing rules differ from the stored section. */
1352
- rulesDirty() {
1353
- if (this.addedRules.length > 0 || this.removedRuleIds.size > 0) return true;
1354
- for (const [id, draft] of this.ruleDrafts) {
1355
- const base = this.storedRules().find((rule) => rule.id === id);
1356
- if (base === void 0) continue;
1357
- if (draft.models.length > 0 && !sameModels(draft.models, base.models)) return true;
1358
- if (draft.account !== base.account) return true;
1359
- }
1360
- return false;
1361
- }
1362
- /** Reset every routing-rule staged edit. */
1363
- clearRuleStaging() {
1364
- this.addedRules = [];
1365
- this.ruleDrafts.clear();
1366
- this.removedRuleIds.clear();
1367
- }
1368
- /**
1369
- * Drop rule staging the stored section already reflects (partial-save
1370
- * retry). Stored row ids are positional (`rule-N`) and shift after any
1371
- * write, so every check here is content-based, never id-based:
1372
- * - landed additions (in `addedRules`, already in stored) are dropped, or
1373
- * a retry would persist them twice;
1374
- * - staged removals whose snapshot row is gone from stored have landed
1375
- * (drop them); a removal whose snapshot still matches a stored row is
1376
- * still pending (keep it).
1377
- */
1378
- reconcileRuleStaging() {
1379
- const stored = this.storedRules();
1380
- this.addedRules = this.addedRules.filter((added) => !stored.some((rule) => sameModels(added.models, rule.models) && added.account === rule.account));
1381
- for (const [id, snapshot] of [...this.removedRuleIds]) if (!stored.some((rule) => sameModels(snapshot.models, rule.models) && snapshot.account === rule.account)) this.removedRuleIds.delete(id);
1382
- if (this.rulesBeforeSave !== void 0 && ruleFingerprint(stored) === this.rulesBeforeSave) return;
1383
- this.ruleDrafts.clear();
1240
+ /** Persist routing rules and verify the Host stored them. */
1241
+ async writeRules(list) {
1242
+ await this.scope.set("modelAccountRules", list);
1243
+ const after = this.storedRules();
1244
+ return after.length === list.length && list.every((item, index) => after[index] !== void 0 && sameModels(after[index].models, item.models) && after[index].account === item.account);
1384
1245
  }
1385
1246
  /** The stored visible-model allowlist (`visibleModels`); empty = show all. */
1386
1247
  storedVisibleModels() {
@@ -1388,79 +1249,19 @@ window.__ModuleLoader__.load({
1388
1249
  if (!Array.isArray(raw)) return [];
1389
1250
  return raw.filter((m) => typeof m === "string" && m !== "");
1390
1251
  }
1391
- /** Effective visible-model allowlist: staged draft or stored value. */
1392
1252
  effectiveVisibleModels() {
1393
1253
  return this.visibleModelsDraft ?? this.storedVisibleModels();
1394
1254
  }
1395
- /** Whether the staged visible-model selection differs from stored. */
1396
1255
  visibleModelsDirty() {
1397
1256
  return this.visibleModelsDraft !== void 0 && !sameModels(this.visibleModelsDraft, this.storedVisibleModels());
1398
1257
  }
1399
- /** Whether any visible-model staging exists. */
1400
- visibleModelsStaged() {
1401
- return this.visibleModelsDraft !== void 0;
1402
- }
1403
- /** Reset the visible-model staged edit. */
1404
- clearVisibleModelsStaging() {
1405
- this.visibleModelsDraft = void 0;
1406
- }
1407
- /** Drop visible-model staging the stored section already reflects. */
1408
- reconcileVisibleModelsStaging() {
1409
- if (this.visibleModelsDraft !== void 0 && sameModels(this.visibleModelsDraft, this.storedVisibleModels())) this.visibleModelsDraft = void 0;
1410
- }
1411
- /** The visible-model writes a save performs (empty when nothing staged). */
1412
1258
  visibleModelsPlan() {
1413
1259
  if (!this.visibleModelsDirty()) return [];
1414
- return [() => this.writeVisibleModels()];
1415
- }
1416
- /** Persist the staged visible-model allowlist into the settings section. */
1417
- async writeVisibleModels() {
1418
- const list = this.visibleModelsDraft ?? [];
1419
- await this.scope.set("visibleModels", list);
1420
- return sameModels(this.storedVisibleModels(), list);
1421
- }
1422
- /** Stage the visible-model allowlist (multi-select). */
1423
- editVisibleModels(models) {
1424
- this.visibleModelsDraft = [...models];
1425
- this.failed = false;
1426
- this.publish();
1427
- }
1428
- /** Stage "show all models" (clears the allowlist). */
1429
- clearVisibleModels() {
1430
- this.visibleModelsDraft = [];
1431
- this.failed = false;
1432
- this.publish();
1433
- }
1434
- /** The routing-rule writes a save performs (empty when nothing staged). */
1435
- rulesPlan() {
1436
- if (!this.rulesDirty()) return [];
1437
- return [() => this.writeRules()];
1438
- }
1439
- /** Persist the staged routing rules into the settings section. */
1440
- async writeRules() {
1441
- const list = [...this.storedRules().filter((rule) => {
1442
- const snapshot = this.removedRuleIds.get(rule.id);
1443
- return snapshot === void 0 || !sameModels(snapshot.models, rule.models) || snapshot.account !== rule.account;
1444
- }).map((rule) => {
1445
- const draft = this.ruleDrafts.get(rule.id);
1446
- return {
1447
- models: draft !== void 0 && draft.models.length > 0 ? draft.models : rule.models,
1448
- account: draft?.account !== void 0 && draft.account !== "" ? draft.account : rule.account
1449
- };
1450
- }), ...this.addedRules.map((rule) => ({
1451
- models: rule.models,
1452
- account: rule.account
1453
- }))].filter((rule) => rule.models.length > 0);
1454
- const seen = /* @__PURE__ */ new Set();
1455
- const deduped = list.filter((rule) => {
1456
- const key = `${rule.account}\u0001${[...rule.models].sort().join("")}`;
1457
- if (seen.has(key)) return false;
1458
- seen.add(key);
1459
- return true;
1460
- });
1461
- await this.scope.set("modelAccountRules", deduped);
1462
- const after = this.storedRules();
1463
- return after.length === deduped.length && deduped.every((item, index) => after[index] !== void 0 && sameModels(after[index].models, item.models) && after[index].account === item.account);
1260
+ return [async () => {
1261
+ const list = this.visibleModelsDraft ?? [];
1262
+ await this.scope.set("visibleModels", list);
1263
+ return sameModels(this.storedVisibleModels(), list);
1264
+ }];
1464
1265
  }
1465
1266
  publish() {
1466
1267
  if (this.disposed) return;
@@ -2381,55 +2182,6 @@ window.__ModuleLoader__.load({
2381
2182
  ]
2382
2183
  };
2383
2184
  //#endregion
2384
- //#region src/client/login-row.tsx
2385
- /** The sign-in alternative to pasting a key (settings page + Models card). */
2386
- function LoginRow({ state, targetRef, disabled, disabledHint, t, onBegin, onCancel }) {
2387
- const { visible: visibleState, busyElsewhere } = loginStateForTarget(state, targetRef);
2388
- const busy = visibleState.phase === "starting" || visibleState.phase === "waiting";
2389
- const hint = loginHint(visibleState, t);
2390
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2391
- className: "cc-field",
2392
- children: [
2393
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2394
- className: "cc-fieldHead",
2395
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2396
- className: "cc-label",
2397
- children: t("loginTitle")
2398
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2399
- className: "cc-badges",
2400
- children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2401
- type: "button",
2402
- className: "cc-reset",
2403
- onClick: onCancel,
2404
- children: t("loginCancel")
2405
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2406
- type: "button",
2407
- className: "cc-reset",
2408
- disabled: disabled || busyElsewhere,
2409
- onClick: onBegin,
2410
- children: t("loginButton")
2411
- })
2412
- })]
2413
- }),
2414
- visibleState.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2415
- className: "cc-hint",
2416
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2417
- className: "cc-loginLink",
2418
- href: visibleState.authUrl,
2419
- target: "_blank",
2420
- rel: "noreferrer",
2421
- children: t("loginOpenLink")
2422
- })
2423
- }) : null,
2424
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2425
- className: hint.className,
2426
- title: hint.title,
2427
- children: disabled && !busy && disabledHint !== void 0 ? disabledHint : hint.text
2428
- })
2429
- ]
2430
- });
2431
- }
2432
- //#endregion
2433
2185
  //#region src/client/model-select.ts
2434
2186
  /**
2435
2187
  * Model-select helpers for the settings page's model editors (browser half).
@@ -2573,7 +2325,7 @@ window.__ModuleLoader__.load({
2573
2325
  }
2574
2326
  //#endregion
2575
2327
  //#region package.json
2576
- var version = "0.11.12";
2328
+ var version = "0.11.13";
2577
2329
  var repository = {
2578
2330
  "type": "git",
2579
2331
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2759,69 +2511,112 @@ window.__ModuleLoader__.load({
2759
2511
  * `settings.commandcode` locale namespace; all state comes from the
2760
2512
  * `CommandCodeSettingsController` injected by the slot registration.
2761
2513
  *
2762
- * The layout mirrors the harness's settings pages: a max-width content
2763
- * column, labelled fields with hints, a reset affordance, and a
2764
- * save/discard footer. Rarely touched connection facts (API base, working
2765
- * directory, timeouts, plan filter) fold into a collapsed Advanced card
2766
- * (`AdvancedSection`) so the page leads with the key and the usage facts.
2767
- * Styles are injected once by the client entry
2768
- * (see src/client/index.ts) and class-prefixed `cc-` to stay local.
2514
+ * Layout, top to bottom:
2515
+ * 1. Accounts — one card per account with its status, quota, actions and
2516
+ * dedicated models. Every account operation commits immediately.
2517
+ * 2. Models — the plan filter and the visible-model allowlist.
2518
+ * 3. Privacy & security — zero data retention and the command guard.
2519
+ * 4. Integrations & display — web search and the sidebar quota card.
2520
+ * 5. Advanced (collapsed) — API base and the network limits.
2521
+ * Sections 2–5 are flat groups of rows (title and description left, control
2522
+ * right, as on the harness's own settings pages), a staged form written by
2523
+ * the floating save bar.
2524
+ *
2525
+ * Styles are injected once by the client entry (see src/client/index.ts) and
2526
+ * class-prefixed `cc-` to stay local.
2769
2527
  */
2770
2528
  const ADVANCED_FIELDS = [
2771
2529
  "apiBase",
2772
- "workingDir",
2773
2530
  "requestTimeoutMs",
2774
2531
  "streamIdleTimeoutMs",
2775
- "transportMaxRetries",
2776
- "filterModelsByPlan",
2777
- "webSearch",
2778
- "showSidebarQuota",
2779
- "commandGuard",
2780
- "commandGuardThreshold",
2781
- "commandGuardTimeoutMs",
2782
- "zdr"
2532
+ "transportMaxRetries"
2783
2533
  ];
2784
- /** One labelled field row in the page body. */
2785
- function Field({ id, label, hint, state, disabled, numeric, placeholder, onEdit, onReset, t }) {
2534
+ /** The "customized" tag and reset link a staged field shows once it differs from the default. */
2535
+ function FieldOverride({ label, state, disabled, t, onReset }) {
2536
+ if (!state.overridden && !state.clear) return null;
2537
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2538
+ type: "button",
2539
+ className: "cc-linkButton",
2540
+ disabled,
2541
+ onClick: onReset,
2542
+ "aria-label": `${label} — ${t("reset")}`,
2543
+ children: t("reset")
2544
+ });
2545
+ }
2546
+ /**
2547
+ * One settings row, laid out like the harness's own General page: the title
2548
+ * and description on the left, the control on the right, a hairline between
2549
+ * rows.
2550
+ */
2551
+ function SettingRow({ title, titleFor, titleId, tag, description, error, control, className }) {
2786
2552
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2787
- className: "cc-field",
2788
- children: [
2789
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2790
- className: "cc-fieldHead",
2791
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2792
- className: "cc-label",
2793
- htmlFor: id,
2794
- children: label
2795
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2796
- className: "cc-badges",
2797
- children: [state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2798
- className: "cc-badge",
2799
- children: t("overridden")
2800
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2801
- type: "button",
2802
- className: "cc-reset",
2803
- disabled,
2804
- onClick: onReset,
2805
- "aria-label": `${label} — ${t("reset")}`,
2806
- children: t("reset")
2807
- })]
2808
- })]
2809
- }),
2810
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2811
- id,
2812
- className: state.invalid ? "cc-input cc-inputInvalid" : "cc-input",
2813
- type: "text",
2814
- inputMode: numeric ? "numeric" : void 0,
2815
- value: state.text,
2816
- placeholder,
2817
- disabled,
2818
- onChange: (event) => onEdit(event.target.value)
2819
- }),
2820
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2821
- className: state.invalid ? "cc-invalid" : "cc-hint",
2822
- children: state.invalid ? invalidCopy(state.invalidReason, t) : hint
2823
- })
2824
- ]
2553
+ className: className === void 0 ? "cc-row" : `cc-row ${className}`,
2554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2555
+ className: "cc-rowText",
2556
+ children: [
2557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2558
+ className: "cc-rowTitleLine",
2559
+ children: [titleFor !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2560
+ className: "cc-rowTitle",
2561
+ id: titleId,
2562
+ htmlFor: titleFor,
2563
+ children: title
2564
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2565
+ className: "cc-rowTitle",
2566
+ id: titleId,
2567
+ children: title
2568
+ }), tag]
2569
+ }),
2570
+ error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2571
+ className: "cc-rowError",
2572
+ children: error
2573
+ }) : null,
2574
+ error === void 0 && description !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2575
+ className: "cc-rowDesc",
2576
+ children: description
2577
+ }) : null
2578
+ ]
2579
+ }), control !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2580
+ className: "cc-rowControl",
2581
+ children: control
2582
+ }) : null]
2583
+ });
2584
+ }
2585
+ /** The tag a row title carries while its field overrides the default. */
2586
+ function OverrideTag({ state, t }) {
2587
+ return state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2588
+ className: "cc-badge",
2589
+ children: t("overridden")
2590
+ }) : null;
2591
+ }
2592
+ /** One text/number field row. */
2593
+ function Field({ id, label, hint, state, disabled, numeric, wide, placeholder, onEdit, onReset, t }) {
2594
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
2595
+ title: label,
2596
+ titleFor: id,
2597
+ tag: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverrideTag, {
2598
+ state,
2599
+ t
2600
+ }),
2601
+ description: hint,
2602
+ error: state.invalid ? invalidCopy(state.invalidReason, t) : void 0,
2603
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
2604
+ label,
2605
+ state,
2606
+ disabled,
2607
+ t,
2608
+ onReset
2609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2610
+ id,
2611
+ className: `cc-input ${wide ? "cc-rowInputWide" : "cc-rowInput"}${state.invalid ? " cc-inputInvalid" : ""}`,
2612
+ type: "text",
2613
+ inputMode: numeric ? "numeric" : void 0,
2614
+ "aria-invalid": state.invalid || void 0,
2615
+ value: state.text,
2616
+ placeholder,
2617
+ disabled,
2618
+ onChange: (event) => onEdit(event.target.value)
2619
+ })] })
2825
2620
  });
2826
2621
  }
2827
2622
  /** The per-field error copy for a staged draft's failure reason. */
@@ -2831,299 +2626,82 @@ window.__ModuleLoader__.load({
2831
2626
  return t("invalidNumber");
2832
2627
  }
2833
2628
  /**
2834
- * The collapsed "Advanced" card: API base, working dir, both timeouts, the
2835
- * plan filter, the web-search backend and the sidebar quota card live here so
2836
- * the page leads with the facts a user actually touches. Starts collapsed on
2837
- * every visit; expands on demand. While collapsed, a badge names the
2838
- * customized count so a nonzero override stays visible (a number field's error
2839
- * blocks save and must be reachable).
2840
- */
2841
- function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
2842
- const [expanded, setExpanded] = (0, react.useState)(false);
2843
- const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
2844
- const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
2845
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2846
- className: "cc-card",
2847
- children: [
2848
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2849
- type: "button",
2850
- className: "cc-advancedHead",
2851
- "aria-expanded": expanded,
2852
- "aria-controls": "cc-advanced-body",
2853
- onClick: () => setExpanded((value) => !value),
2854
- children: [
2855
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2856
- className: "cc-advancedTitle",
2857
- children: t("advancedSettings")
2858
- }),
2859
- overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2860
- className: "cc-badge",
2861
- children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany", { count: overridden })
2862
- }) : null,
2863
- !expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2864
- className: "cc-badge cc-badgeWarn",
2865
- children: t("advancedInvalid")
2866
- }) : null,
2867
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-advancedSpacer" }),
2868
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2869
- className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
2870
- "aria-hidden": "true"
2871
- })
2872
- ]
2873
- }),
2874
- expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2875
- id: "cc-advanced-body",
2876
- className: "cc-advancedBody",
2877
- children: [
2878
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2879
- className: "cc-hint",
2880
- children: t("advancedSettingsHint")
2881
- }),
2882
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2883
- id: "cc-api-base",
2884
- label: t("apiBase"),
2885
- hint: t("apiBaseHint"),
2886
- state: state.apiBase,
2887
- disabled,
2888
- onEdit: (text) => onEdit("apiBase", text),
2889
- onReset: () => onReset("apiBase"),
2890
- t
2891
- }),
2892
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2893
- id: "cc-working-dir",
2894
- label: t("workingDir"),
2895
- hint: t("workingDirHint"),
2896
- state: state.workingDir,
2897
- disabled,
2898
- onEdit: (text) => onEdit("workingDir", text),
2899
- onReset: () => onReset("workingDir"),
2900
- t
2901
- }),
2902
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2903
- id: "cc-request-timeout",
2904
- label: t("requestTimeoutMs"),
2905
- hint: t("requestTimeoutMsHint"),
2906
- state: state.requestTimeoutMs,
2907
- disabled,
2908
- numeric: true,
2909
- onEdit: (text) => onEdit("requestTimeoutMs", text),
2910
- onReset: () => onReset("requestTimeoutMs"),
2911
- t
2912
- }),
2913
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2914
- id: "cc-stream-idle-timeout",
2915
- label: t("streamIdleTimeoutMs"),
2916
- hint: t("streamIdleTimeoutMsHint"),
2917
- state: state.streamIdleTimeoutMs,
2918
- disabled,
2919
- numeric: true,
2920
- onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
2921
- onReset: () => onReset("streamIdleTimeoutMs"),
2922
- t
2923
- }),
2924
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2925
- id: "cc-transport-max-retries",
2926
- label: t("transportMaxRetries"),
2927
- hint: t("transportMaxRetriesHint"),
2928
- state: state.transportMaxRetries,
2929
- disabled,
2930
- numeric: true,
2931
- onEdit: (text) => onEdit("transportMaxRetries", text),
2932
- onReset: () => onReset("transportMaxRetries"),
2933
- t
2934
- }),
2935
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2936
- id: "cc-filter-models-by-plan",
2937
- label: t("filterModelsByPlan"),
2938
- hint: t("filterModelsByPlanHint"),
2939
- state: state.filterModelsByPlan,
2940
- disabled,
2941
- defaultChecked: true,
2942
- onEdit: (text) => onEdit("filterModelsByPlan", text),
2943
- onReset: () => onReset("filterModelsByPlan"),
2944
- t
2945
- }),
2946
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2947
- id: "cc-web-search",
2948
- label: t("webSearch"),
2949
- hint: t("webSearchHint"),
2950
- state: state.webSearch,
2951
- disabled,
2952
- defaultChecked: true,
2953
- onEdit: (text) => onEdit("webSearch", text),
2954
- onReset: () => onReset("webSearch"),
2955
- t
2956
- }),
2957
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2958
- id: "cc-show-sidebar-quota",
2959
- label: t("showSidebarQuota"),
2960
- hint: t("showSidebarQuotaHint"),
2961
- state: state.showSidebarQuota,
2962
- disabled,
2963
- defaultChecked: false,
2964
- onEdit: (text) => onEdit("showSidebarQuota", text),
2965
- onReset: () => onReset("showSidebarQuota"),
2966
- t
2967
- }),
2968
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2969
- id: "cc-command-guard",
2970
- label: t("commandGuard"),
2971
- hint: t("commandGuardHint"),
2972
- state: state.commandGuard,
2973
- disabled,
2974
- defaultChecked: false,
2975
- onEdit: (text) => onEdit("commandGuard", text),
2976
- onReset: () => onReset("commandGuard"),
2977
- t
2978
- }),
2979
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2980
- id: "cc-command-guard-threshold",
2981
- label: t("commandGuardThreshold"),
2982
- hint: t("commandGuardThresholdHint"),
2983
- state: state.commandGuardThreshold,
2984
- disabled,
2985
- onEdit: (text) => onEdit("commandGuardThreshold", text),
2986
- onReset: () => onReset("commandGuardThreshold"),
2987
- t
2988
- }),
2989
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2990
- id: "cc-command-guard-timeout",
2991
- label: t("commandGuardTimeoutMs"),
2992
- hint: t("commandGuardTimeoutMsHint"),
2993
- state: state.commandGuardTimeoutMs,
2994
- disabled,
2995
- numeric: true,
2996
- onEdit: (text) => onEdit("commandGuardTimeoutMs", text),
2997
- onReset: () => onReset("commandGuardTimeoutMs"),
2998
- t
2999
- }),
3000
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3001
- id: "cc-zdr",
3002
- label: t("zdr"),
3003
- hint: t("zdrHint"),
3004
- state: state.zdr,
3005
- disabled,
3006
- defaultChecked: false,
3007
- onEdit: (text) => onEdit("zdr", text),
3008
- onReset: () => onReset("zdr"),
3009
- t
3010
- })
3011
- ]
3012
- }) : null,
3013
- expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3014
- className: "cc-invalid",
3015
- role: "status",
3016
- children: t("advancedInvalid")
3017
- }) : null
3018
- ]
3019
- });
3020
- }
3021
- /**
3022
- * One boolean field row rendered as a toggle. The staged text is `'true'` /
2629
+ * One boolean field row rendered as a switch. The staged text is `'true'` /
3023
2630
  * `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
3024
2631
  * boolean field spec parses back into a real boolean on save.
3025
2632
  */
3026
2633
  function ToggleField({ id, label, hint, state, disabled, defaultChecked, onEdit, onReset, t }) {
3027
2634
  const checked = state.text === "" ? defaultChecked : state.text === "true";
3028
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3029
- className: "cc-field",
3030
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3031
- className: "cc-fieldHead",
3032
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3033
- className: "cc-label",
3034
- "aria-hidden": "true",
3035
- children: label
3036
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3037
- className: "cc-badges",
3038
- children: [state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3039
- className: "cc-badge",
3040
- children: t("overridden")
3041
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3042
- type: "button",
3043
- className: "cc-reset",
3044
- disabled,
3045
- onClick: onReset,
3046
- "aria-label": `${label} — ${t("reset")}`,
3047
- children: t("reset")
3048
- })]
3049
- })]
3050
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3051
- className: "cc-toggleRow",
3052
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3053
- id,
3054
- className: "cc-toggle",
3055
- type: "checkbox",
3056
- role: "switch",
3057
- checked,
3058
- disabled,
3059
- onChange: (event) => onEdit(event.target.checked ? "true" : "false")
3060
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3061
- className: "cc-hint",
3062
- children: hint
3063
- })]
3064
- })]
2635
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
2636
+ title: label,
2637
+ titleFor: id,
2638
+ tag: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverrideTag, {
2639
+ state,
2640
+ t
2641
+ }),
2642
+ description: hint,
2643
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
2644
+ label,
2645
+ state,
2646
+ disabled,
2647
+ t,
2648
+ onReset
2649
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2650
+ id,
2651
+ className: "cc-toggle",
2652
+ type: "checkbox",
2653
+ role: "switch",
2654
+ checked,
2655
+ disabled,
2656
+ onChange: (event) => onEdit(event.target.checked ? "true" : "false")
2657
+ })] })
3065
2658
  });
3066
2659
  }
3067
2660
  /**
3068
- * The API-key control: write-only, reports configured state, never echoes the
3069
- * key. The input is masked by default with a Show/Hide toggle so a pasted key
3070
- * can be spot-checked without leaving the field, and a stored key can be
3071
- * staged for removal (the next save unsets it) when it is bad or unwanted.
2661
+ * One fixed-choice field rendered as a segmented control. The staged text is
2662
+ * one of `options`' values or `''` (unset → `defaultValue`); picking the
2663
+ * default while unset stages nothing new.
3072
2664
  */
3073
- function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, clearStaged, showLabel, hideLabel, clearLabel, clearStagedLabel, undoClearLabel, onEdit, onToggleClear }) {
3074
- const [visible, setVisible] = (0, react.useState)(false);
3075
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3076
- className: "cc-field",
3077
- children: [
3078
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3079
- className: "cc-fieldHead",
3080
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3081
- className: "cc-label",
3082
- htmlFor: "cc-api-key",
3083
- children: label
3084
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3085
- className: "cc-badges",
3086
- children: [
3087
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3088
- className: configured ? "cc-badge" : "cc-badgeMuted",
3089
- children: configured ? configuredLabel : unconfiguredLabel
3090
- }),
3091
- clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3092
- className: "cc-badge cc-badgeWarn",
3093
- children: clearStagedLabel
3094
- }) : null,
3095
- configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3096
- type: "button",
3097
- className: "cc-reset",
3098
- disabled,
3099
- onClick: onToggleClear,
3100
- children: clearStaged ? undoClearLabel : clearLabel
3101
- }) : null,
3102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3103
- type: "button",
3104
- className: "cc-reset",
3105
- disabled,
3106
- onClick: () => setVisible((value) => !value),
3107
- children: visible ? hideLabel : showLabel
3108
- })
3109
- ]
3110
- })]
3111
- }),
3112
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3113
- id: "cc-api-key",
3114
- className: "cc-input",
3115
- type: visible ? "text" : "password",
3116
- autoComplete: "off",
3117
- spellCheck: false,
3118
- value: state.text,
2665
+ function SegmentedField({ label, hint, state, disabled, options, defaultValue, className, onEdit, onReset, t }) {
2666
+ const current = state.text === "" ? defaultValue : state.text;
2667
+ const index = Math.max(0, options.findIndex((option) => option.value === current));
2668
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
2669
+ title: label,
2670
+ tag: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverrideTag, {
2671
+ state,
2672
+ t
2673
+ }),
2674
+ description: hint,
2675
+ ...className === void 0 ? {} : { className },
2676
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
2677
+ label,
2678
+ state,
2679
+ disabled,
2680
+ t,
2681
+ onReset
2682
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2683
+ className: "cc-segmented",
2684
+ role: "radiogroup",
2685
+ "aria-label": label,
2686
+ style: {
2687
+ "--cc-segment-count": options.length,
2688
+ "--cc-segment-index": index
2689
+ },
2690
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2691
+ className: "cc-segmentIndicator",
2692
+ "aria-hidden": "true"
2693
+ }), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2694
+ type: "button",
2695
+ role: "radio",
2696
+ "aria-checked": current === option.value,
2697
+ className: "cc-segment",
3119
2698
  disabled,
3120
- onChange: (event) => onEdit(event.target.value)
3121
- }),
3122
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3123
- className: "cc-hint",
3124
- children: hint
3125
- })
3126
- ]
2699
+ onClick: () => {
2700
+ if (current !== option.value) onEdit(option.value);
2701
+ },
2702
+ children: option.label
2703
+ }, option.value))]
2704
+ })] })
3127
2705
  });
3128
2706
  }
3129
2707
  /** One stat tile in the account card's summary grid. */
@@ -3194,35 +2772,11 @@ window.__ModuleLoader__.load({
3194
2772
  ]
3195
2773
  });
3196
2774
  }
3197
- /** One account's rotation state as a short badge next to its label. */
3198
- function AccountMark({ entry, t }) {
3199
- if (entry.active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3200
- className: "cc-usagePlan",
3201
- children: t("usageActive")
3202
- });
3203
- if (entry.mark === "invalid-credential") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3204
- className: "cc-usagePlanStatus",
3205
- children: t("usageInvalidKey")
3206
- });
3207
- if (entry.cooldownUntil > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3208
- className: "cc-usagePlanStatus",
3209
- children: [
3210
- t("usageCooldown"),
3211
- " ",
3212
- formatResetAt(entry.cooldownUntil)
3213
- ]
3214
- });
3215
- if (entry.mark === "rate-limit") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3216
- className: "cc-usagePlanStatus",
3217
- children: t("usageCooldown")
3218
- });
3219
- return null;
3220
- }
3221
2775
  /**
3222
2776
  * One pool account's facts (identity, totals, credits, window limits)
3223
- * rendered inside the account-usage card.
2777
+ * rendered inside an account row's details.
3224
2778
  */
3225
- function AccountReport({ entry, fetchedAt, t, onRemove }) {
2779
+ function AccountReport({ entry, fetchedAt, t, summaryOnly = false, headless = false }) {
3226
2780
  const report = entry.report;
3227
2781
  const account = report.account;
3228
2782
  const accountName = account === void 0 ? "" : account.userName || account.name;
@@ -3235,17 +2789,13 @@ window.__ModuleLoader__.load({
3235
2789
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3236
2790
  className: "cc-accountReport",
3237
2791
  children: [
3238
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2792
+ !headless ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3239
2793
  className: "cc-usageHead",
3240
2794
  children: [
3241
2795
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3242
2796
  className: "cc-usageTitle",
3243
2797
  children: entry.label
3244
2798
  }),
3245
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountMark, {
3246
- entry,
3247
- t
3248
- }),
3249
2799
  accountName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3250
2800
  className: "cc-usageAccount",
3251
2801
  children: accountName
@@ -3253,20 +2803,13 @@ window.__ModuleLoader__.load({
3253
2803
  planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3254
2804
  className: "cc-usagePlan",
3255
2805
  children: planName
3256
- }) : null,
3257
- planStatus !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3258
- className: "cc-usagePlanStatus",
3259
- children: planStatus
3260
- }) : null,
3261
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
3262
- onRemove !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3263
- type: "button",
3264
- className: "cc-usageRefresh",
3265
- onClick: onRemove,
3266
- children: t("accountRemove")
3267
2806
  }) : null
3268
2807
  ]
3269
- }),
2808
+ }) : null,
2809
+ planStatus !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2810
+ className: "cc-usagePlanStatus",
2811
+ children: planStatus
2812
+ }) : null,
3270
2813
  !entry.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3271
2814
  className: "cc-usageHint",
3272
2815
  children: t("usageUnconfigured")
@@ -3290,8 +2833,8 @@ window.__ModuleLoader__.load({
3290
2833
  }) : null
3291
2834
  ]
3292
2835
  }) : null,
3293
- report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3294
- className: "cc-usageStats",
2836
+ !summaryOnly && report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2837
+ className: "cc-usageStats cc-usageStatsActivity",
3295
2838
  children: [
3296
2839
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
3297
2840
  label: t("usageRequests"),
@@ -3314,8 +2857,8 @@ window.__ModuleLoader__.load({
3314
2857
  })
3315
2858
  ]
3316
2859
  }) : null,
3317
- credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3318
- className: "cc-usageStats",
2860
+ !summaryOnly && credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2861
+ className: "cc-usageStats cc-usageStatsBalance",
3319
2862
  children: [
3320
2863
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
3321
2864
  label: t("usageMonthly"),
@@ -3387,360 +2930,6 @@ window.__ModuleLoader__.load({
3387
2930
  if (reason === "invalid-response") return t("usageInvalidResponseHint");
3388
2931
  return t("usageNetworkHint");
3389
2932
  }
3390
- /** The status dot on an account tab: cooling/invalid warn, everything else ok. */
3391
- function AccountTabDot({ entry }) {
3392
- const cls = entry.mark === "invalid-credential" ? "cc-tabDot cc-tabDotError" : entry.mark !== "" || entry.cooldownUntil > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk";
3393
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3394
- className: cls,
3395
- "aria-hidden": "true"
3396
- });
3397
- }
3398
- /**
3399
- * The account-usage card: the `/commandcode` dashboard's facts rendered as
3400
- * a native settings card. With several accounts the card is a carousel — a
3401
- * tab strip (label + status dot) switches between accounts so the page stays
3402
- * short; each account's report carries its own remove affordance (the
3403
- * default account is not removable). Accounts staged for removal in the
3404
- * management card are hidden here immediately. Data arrives through the
3405
- * `commandcode/report` Remote; the API keys never leave the Host.
3406
- */
3407
- function UsageCard({ t, usage, removingIds, removableIds, canManage, onRefresh, onRemoveAccount }) {
3408
- const { loading, shouldRefresh, noKey } = usageCardState(usage);
3409
- (0, react.useEffect)(() => {
3410
- if (shouldRefresh) onRefresh();
3411
- }, [shouldRefresh, onRefresh]);
3412
- const report = usage.report;
3413
- const [locallyRemoved, setLocallyRemoved] = (0, react.useState)([]);
3414
- (0, react.useEffect)(() => {
3415
- setLocallyRemoved([]);
3416
- }, [usage.fetchedAt]);
3417
- const hidden = /* @__PURE__ */ new Set([...removingIds, ...locallyRemoved]);
3418
- const seenIds = /* @__PURE__ */ new Set();
3419
- const entries = (report?.accounts ?? []).filter((entry) => {
3420
- if (hidden.has(entry.id)) return false;
3421
- if (seenIds.has(entry.id)) return false;
3422
- seenIds.add(entry.id);
3423
- return true;
3424
- });
3425
- const [selectedId, setSelectedId] = (0, react.useState)(void 0);
3426
- const selected = entries.find((entry) => entry.id === selectedId) ?? entries.find((entry) => entry.active) ?? entries[0];
3427
- const removeSelected = canManage && selected !== void 0 && removableIds.includes(selected.id) ? () => {
3428
- const id = selected.id;
3429
- setLocallyRemoved((prev) => [...prev, id]);
3430
- onRemoveAccount(id);
3431
- } : void 0;
3432
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3433
- className: "cc-usageCard",
3434
- "aria-label": t("usageTitle"),
3435
- children: [
3436
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3437
- className: "cc-usageHead",
3438
- children: [
3439
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3440
- className: "cc-usageTitle",
3441
- children: t("usageTitle")
3442
- }),
3443
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
3444
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3445
- type: "button",
3446
- className: "cc-usageRefresh",
3447
- disabled: loading,
3448
- onClick: onRefresh,
3449
- children: loading ? t("usageRefreshing") : t("usageRefresh")
3450
- })
3451
- ]
3452
- }),
3453
- noKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3454
- className: "cc-usageHint",
3455
- children: t("usageNoKey")
3456
- }) : null,
3457
- report === void 0 && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3458
- className: "cc-usageHint",
3459
- children: t("usageLoading")
3460
- }) : null,
3461
- usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3462
- className: "cc-usageError",
3463
- role: "status",
3464
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
3465
- }) : null,
3466
- entries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3467
- className: "cc-tabs",
3468
- "aria-label": t("accountsTitle"),
3469
- children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3470
- type: "button",
3471
- "aria-pressed": selected?.id === entry.id,
3472
- className: selected?.id === entry.id ? "cc-tab cc-tabActive" : "cc-tab",
3473
- onClick: () => setSelectedId(entry.id),
3474
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTabDot, { entry }), entry.label]
3475
- }, entry.id))
3476
- }) : null,
3477
- selected !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountReport, {
3478
- entry: selected,
3479
- fetchedAt: usage.fetchedAt,
3480
- t,
3481
- onRemove: removeSelected
3482
- }, selected.id) : null
3483
- ]
3484
- });
3485
- }
3486
- /**
3487
- * One extra account row: label, key, configured badge. Saved accounts are
3488
- * removed from the usage card above; a NOT-YET-SAVED addition never appears
3489
- * there (the usage report is Host-side), so it keeps its own remove button —
3490
- * otherwise the only way to undo a mistaken Add would be discarding every
3491
- * other staged edit.
3492
- */
3493
- function AccountRow({ account, disabled, t, onLabel, onKey, onToggleClear, onRemove }) {
3494
- const locked = !account.writable;
3495
- const [keyVisible, setKeyVisible] = (0, react.useState)(false);
3496
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3497
- className: "cc-field",
3498
- children: [
3499
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3500
- className: "cc-fieldHead",
3501
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3502
- className: "cc-label",
3503
- htmlFor: `cc-account-label-${account.id}`,
3504
- children: t("accountLabel")
3505
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3506
- className: "cc-badges",
3507
- children: [
3508
- account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3509
- className: "cc-badge",
3510
- children: t("unsaved")
3511
- }) : null,
3512
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3513
- className: account.configured ? "cc-badge" : "cc-badgeMuted",
3514
- children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
3515
- }),
3516
- account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3517
- className: "cc-badge cc-badgeWarn",
3518
- children: t("usageKeyClearStaged")
3519
- }) : null,
3520
- account.configured && !account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3521
- type: "button",
3522
- className: "cc-reset",
3523
- disabled,
3524
- onClick: onToggleClear,
3525
- children: t("usageKeyClear")
3526
- }) : null,
3527
- account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3528
- type: "button",
3529
- className: "cc-reset",
3530
- disabled,
3531
- onClick: onToggleClear,
3532
- children: t("usageUndoKeyClear")
3533
- }) : null,
3534
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3535
- type: "button",
3536
- className: "cc-reset",
3537
- disabled,
3538
- onClick: () => setKeyVisible((value) => !value),
3539
- children: keyVisible ? t("hide") : t("show")
3540
- }),
3541
- account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3542
- type: "button",
3543
- className: "cc-reset",
3544
- disabled,
3545
- onClick: onRemove,
3546
- children: t("accountRemove")
3547
- }) : null
3548
- ]
3549
- })]
3550
- }),
3551
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3552
- id: `cc-account-label-${account.id}`,
3553
- className: "cc-input",
3554
- type: "text",
3555
- value: account.label,
3556
- disabled,
3557
- onChange: (event) => onLabel(event.target.value)
3558
- }),
3559
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3560
- id: `cc-account-key-${account.id}`,
3561
- className: "cc-input",
3562
- type: keyVisible ? "text" : "password",
3563
- autoComplete: "off",
3564
- spellCheck: false,
3565
- placeholder: t("accountKey"),
3566
- value: account.keyText,
3567
- disabled: disabled || locked,
3568
- onChange: (event) => onKey(event.target.value)
3569
- }),
3570
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3571
- className: "cc-hint",
3572
- children: locked ? t("apiKeyLocked") : t("accountKeyHint")
3573
- })
3574
- ]
3575
- });
3576
- }
3577
- /** The multi-account card: the active-account selector + extra accounts in rotation order + add button. */
3578
- function AccountsCard({ t, state, login, disabled, onAdd, onRemove, onLabel, onKey, onToggleClear, onActive, onActiveReset, onBeginLogin, onCancelLogin }) {
3579
- const active = state.activeAccount;
3580
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3581
- className: "cc-card",
3582
- "aria-label": t("accountsTitle"),
3583
- children: [
3584
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3585
- className: "cc-field",
3586
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3587
- className: "cc-fieldHead",
3588
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3589
- className: "cc-label",
3590
- children: t("accountsTitle")
3591
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3592
- className: "cc-badges",
3593
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3594
- type: "button",
3595
- className: "cc-reset",
3596
- disabled,
3597
- onClick: onAdd,
3598
- children: t("accountAdd")
3599
- })
3600
- })]
3601
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3602
- className: "cc-hint",
3603
- children: t("accountsHint")
3604
- })]
3605
- }),
3606
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3607
- className: "cc-field",
3608
- children: [
3609
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3610
- className: "cc-fieldHead",
3611
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3612
- className: "cc-label",
3613
- htmlFor: "cc-active-account",
3614
- children: t("activeAccount")
3615
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3616
- className: "cc-badges",
3617
- children: [active.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3618
- className: "cc-badge",
3619
- children: t("overridden")
3620
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3621
- type: "button",
3622
- className: "cc-reset",
3623
- disabled,
3624
- onClick: onActiveReset,
3625
- children: t("reset")
3626
- })]
3627
- })]
3628
- }),
3629
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3630
- id: "cc-active-account",
3631
- className: "cc-input",
3632
- value: active.text,
3633
- disabled,
3634
- onChange: (event) => onActive(event.target.value),
3635
- children: [
3636
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3637
- value: "",
3638
- children: t("activeAccountAuto")
3639
- }),
3640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3641
- value: "default",
3642
- children: t("accountDefault")
3643
- }),
3644
- state.accounts.filter((account) => !account.added).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3645
- value: account.ref,
3646
- children: account.label
3647
- }, account.id))
3648
- ]
3649
- }),
3650
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3651
- className: "cc-hint",
3652
- children: t("activeAccountHint")
3653
- })
3654
- ]
3655
- }),
3656
- state.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3657
- className: "cc-accountEditor",
3658
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountRow, {
3659
- account,
3660
- disabled: disabled || login.targetRef === account.ref && (login.phase === "starting" || login.phase === "waiting"),
3661
- t,
3662
- onLabel: (text) => onLabel(account.id, text),
3663
- onKey: (text) => onKey(account.id, text),
3664
- onToggleClear: () => onToggleClear(account.id),
3665
- onRemove: () => onRemove(account.id)
3666
- }), account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3667
- className: "cc-accountLoginHint",
3668
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3669
- className: "cc-hint",
3670
- children: t("accountLoginAfterSave")
3671
- })
3672
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginRow, {
3673
- state: login,
3674
- targetRef: account.ref,
3675
- disabled: disabled || !account.writable || state.dirty,
3676
- disabledHint: state.dirty ? t("loginSaveBefore") : void 0,
3677
- t,
3678
- onBegin: () => onBeginLogin(account.ref),
3679
- onCancel: onCancelLogin
3680
- })]
3681
- }, account.id))
3682
- ]
3683
- });
3684
- }
3685
- /** One model → account routing rule row. */
3686
- function RuleRow({ rule, accounts, catalog, disabled, t, onModels, onAccount, onRemove }) {
3687
- const targets = [{
3688
- value: "default",
3689
- label: t("accountDefault")
3690
- }, ...accounts.filter((account) => !account.added).map((account) => ({
3691
- value: account.ref,
3692
- label: account.label
3693
- }))];
3694
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3695
- className: "cc-field",
3696
- children: [
3697
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3698
- className: "cc-fieldHead",
3699
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3700
- className: "cc-label",
3701
- htmlFor: `cc-rule-model-${rule.id}`,
3702
- children: t("ruleModel")
3703
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3704
- className: "cc-badges",
3705
- children: [rule.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3706
- className: "cc-badge",
3707
- children: t("unsaved")
3708
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3709
- type: "button",
3710
- className: "cc-reset",
3711
- disabled,
3712
- onClick: onRemove,
3713
- children: t("ruleRemove")
3714
- })]
3715
- })]
3716
- }),
3717
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
3718
- id: `cc-rule-model-${rule.id}`,
3719
- selected: rule.models,
3720
- catalog,
3721
- disabled,
3722
- t,
3723
- onSelect: onModels
3724
- }),
3725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
3726
- id: `cc-rule-account-${rule.id}`,
3727
- className: "cc-input",
3728
- value: rule.account,
3729
- disabled,
3730
- onChange: (event) => onAccount(event.target.value),
3731
- "aria-label": t("ruleAccount"),
3732
- children: targets.map((target) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3733
- value: target.value,
3734
- children: target.label
3735
- }, target.value))
3736
- }),
3737
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3738
- className: "cc-hint",
3739
- children: t("ruleHint")
3740
- })
3741
- ]
3742
- });
3743
- }
3744
2933
  /**
3745
2934
  * A checkbox multi-select dropdown for picking catalog models (the
3746
2935
  * routing-rule rows and the visible-models filter share it). The trigger
@@ -3752,13 +2941,26 @@ window.__ModuleLoader__.load({
3752
2941
  * longer carries still render — flagged stale — so a saved selection never
3753
2942
  * silently loses an entry, and the VisibleModelsCard offers a one-click
3754
2943
  * cleanup.
2944
+ *
2945
+ * With `deferCommit` the toggles collect in a local draft that is handed to
2946
+ * `onSelect` once, when the menu closes: the per-account picker writes
2947
+ * immediately, and one write per click would be a burst of document writes.
3755
2948
  */
3756
- function ModelMultiSelect({ id, selected, catalog, disabled, t, onSelect }) {
2949
+ function ModelMultiSelect({ id, selected: committed, catalog, disabled, deferCommit = false, ownerOf, t, onSelect }) {
3757
2950
  const [open, setOpen] = (0, react.useState)(false);
3758
2951
  const [query, setQuery] = (0, react.useState)("");
2952
+ const [draft, setDraft] = (0, react.useState)(void 0);
2953
+ const selected = draft ?? committed;
3759
2954
  (0, react.useEffect)(() => {
3760
2955
  if (open) setQuery("");
3761
2956
  }, [open]);
2957
+ const close = () => {
2958
+ setOpen(false);
2959
+ if (draft !== void 0) {
2960
+ setDraft(void 0);
2961
+ if (draft.length !== committed.length || draft.some((modelId) => !committed.includes(modelId))) onSelect(draft);
2962
+ }
2963
+ };
3762
2964
  const tiers = (0, react.useMemo)(() => Object.fromEntries(catalog.flatMap((model) => model.tier === void 0 ? [] : [[model.id, model.tier]])), [catalog]);
3763
2965
  const options = buildModelSelectOptions(catalog, selected, query);
3764
2966
  const items = groupModelSelectOptions(options, (modelId) => tierHeadingFor(modelId, tiers)).flatMap((group) => [...group.heading === void 0 ? [] : [{
@@ -3784,15 +2986,21 @@ window.__ModuleLoader__.load({
3784
2986
  option.stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3785
2987
  className: "cc-badge",
3786
2988
  children: t("modelStale")
2989
+ }) : null,
2990
+ !selected.includes(option.value) && ownerOf?.(option.value) !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2991
+ className: "cc-badgeMuted",
2992
+ children: t("accountModelOwner", { name: ownerOf(option.value) })
3787
2993
  }) : null
3788
2994
  ]
3789
2995
  })
3790
2996
  }))]);
3791
2997
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3792
2998
  open,
3793
- onClose: () => setOpen(false),
2999
+ onClose: close,
3794
3000
  onSelect: (modelId) => {
3795
- onSelect(toggleModelSelection(selected, modelId));
3001
+ const next = toggleModelSelection(selected, modelId);
3002
+ if (deferCommit) setDraft(next);
3003
+ else onSelect(next);
3796
3004
  },
3797
3005
  selectedIds: selected,
3798
3006
  items,
@@ -3807,14 +3015,14 @@ window.__ModuleLoader__.load({
3807
3015
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3808
3016
  id,
3809
3017
  type: "button",
3810
- className: "cc-input cc-ruleTrigger",
3018
+ className: "cc-selector",
3811
3019
  disabled: disabled || catalog.length === 0,
3812
- onClick: () => setOpen((value) => !value),
3020
+ onClick: () => open ? close() : setOpen(true),
3813
3021
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3814
- className: "cc-ruleTriggerText",
3815
- children: selected.length === 0 ? t("ruleModelPick") : t("ruleModelCount", { count: selected.length })
3022
+ className: "cc-selectorText",
3023
+ children: selected.length === 0 ? t("modelPick") : t("modelCount", { count: selected.length })
3816
3024
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3817
- className: "cc-ruleCaret",
3025
+ className: "cc-selectorCaret",
3818
3026
  "aria-hidden": "true"
3819
3027
  })]
3820
3028
  }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -3830,133 +3038,1014 @@ window.__ModuleLoader__.load({
3830
3038
  })
3831
3039
  });
3832
3040
  }
3833
- /** The model → account routing card: rules in list order (first match wins). */
3834
- function RulesCard({ t, state, disabled, onAdd, onRemove, onModels, onAccount }) {
3835
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3836
- className: "cc-card",
3837
- "aria-label": t("rulesTitle"),
3041
+ /** The visible-model filter row: an allowlist over the catalog. Empty = show all. */
3042
+ function VisibleModelsRow({ t, state, disabled, onSelect, onClear }) {
3043
+ const count = state.visibleModels.length;
3044
+ const pickT = (key, params) => {
3045
+ if (key === "modelPick") return t("visibleModelsPick");
3046
+ if (key === "modelCount") return t("visibleModelsCount", params);
3047
+ return t(key, params);
3048
+ };
3049
+ const readiness = {
3050
+ catalogIds: state.catalogModels.map((model) => model.id),
3051
+ catalogFailed: state.catalogFailed
3052
+ };
3053
+ const staleIds = staleModelIds(state.visibleModels, readiness);
3054
+ const catalogReady = catalogIsReady(readiness);
3055
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
3056
+ title: t("visibleModelsTitle"),
3057
+ titleFor: "cc-visible-models",
3058
+ description: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("visibleModelsHint") }), staleIds.length > 0 && catalogReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("visibleModelsStaleHint", { count: staleIds.length }) }) : null] }),
3059
+ error: state.catalogFailed ? t("modelCatalogFailed") : void 0,
3060
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3061
+ catalogReady && staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3062
+ type: "button",
3063
+ className: "cc-linkButton",
3064
+ disabled,
3065
+ onClick: () => onSelect(state.visibleModels.filter((id) => !staleIds.includes(id))),
3066
+ children: t("visibleModelsCleanStale", { count: staleIds.length })
3067
+ }) : null,
3068
+ count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3069
+ type: "button",
3070
+ className: "cc-linkButton",
3071
+ disabled,
3072
+ onClick: onClear,
3073
+ children: t("visibleModelsShowAll")
3074
+ }) : null,
3075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
3076
+ id: "cc-visible-models",
3077
+ selected: state.visibleModels,
3078
+ catalog: state.catalogModels,
3079
+ disabled,
3080
+ t: pickT,
3081
+ onSelect
3082
+ })
3083
+ ] })
3084
+ });
3085
+ }
3086
+ /**
3087
+ * One titled group of rows. No card surface: like the harness's own settings
3088
+ * pages, a group is a heading over hairline-separated rows.
3089
+ */
3090
+ function SettingsGroup({ title, action, children }) {
3091
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3092
+ className: "cc-group",
3093
+ "aria-label": title,
3094
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3095
+ className: "cc-groupHead",
3096
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3097
+ className: "cc-groupTitle",
3098
+ children: title
3099
+ }), action]
3100
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3101
+ className: "cc-rows",
3102
+ children
3103
+ })]
3104
+ });
3105
+ }
3106
+ /**
3107
+ * Models: the plan filter and the allowlist sit together because both decide
3108
+ * what the model picker lists (the allowlist applies after the plan filter).
3109
+ */
3110
+ function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear }) {
3111
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
3112
+ title: t("modelsTitle"),
3113
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3114
+ id: "cc-filter-models-by-plan",
3115
+ label: t("filterModelsByPlan"),
3116
+ hint: t("filterModelsByPlanHint"),
3117
+ state: state.filterModelsByPlan,
3118
+ disabled,
3119
+ defaultChecked: true,
3120
+ onEdit: (text) => onEdit("filterModelsByPlan", text),
3121
+ onReset: () => onReset("filterModelsByPlan"),
3122
+ t
3123
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsRow, {
3124
+ t,
3125
+ state,
3126
+ disabled,
3127
+ onSelect,
3128
+ onClear
3129
+ })]
3130
+ });
3131
+ }
3132
+ /**
3133
+ * Privacy & security: the two opt-ins that change where data goes. ZDR
3134
+ * restricts which upstream serves a request; the command guard sends command
3135
+ * text to a decision model with no ZDR upstream and can skip an approval the
3136
+ * user would otherwise have seen, so both start off.
3137
+ */
3138
+ function PrivacyCard({ state, disabled, t, onEdit, onReset }) {
3139
+ const showGuardLevel = state.commandGuard.text === "true" || state.commandGuardLevel.overridden || state.commandGuardLevel.clear;
3140
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
3141
+ title: t("privacyTitle"),
3142
+ children: [
3143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3144
+ id: "cc-zdr",
3145
+ label: t("zdr"),
3146
+ hint: t("zdrHint"),
3147
+ state: state.zdr,
3148
+ disabled,
3149
+ defaultChecked: false,
3150
+ onEdit: (text) => onEdit("zdr", text),
3151
+ onReset: () => onReset("zdr"),
3152
+ t
3153
+ }),
3154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3155
+ id: "cc-command-guard",
3156
+ label: t("commandGuard"),
3157
+ hint: t("commandGuardHint"),
3158
+ state: state.commandGuard,
3159
+ disabled,
3160
+ defaultChecked: false,
3161
+ onEdit: (text) => onEdit("commandGuard", text),
3162
+ onReset: () => onReset("commandGuard"),
3163
+ t
3164
+ }),
3165
+ showGuardLevel ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SegmentedField, {
3166
+ className: "cc-rowNested",
3167
+ label: t("commandGuardLevel"),
3168
+ hint: t("commandGuardLevelHint"),
3169
+ state: state.commandGuardLevel,
3170
+ disabled,
3171
+ options: [
3172
+ {
3173
+ value: "high",
3174
+ label: t("commandGuardLevelHigh")
3175
+ },
3176
+ {
3177
+ value: "medium",
3178
+ label: t("commandGuardLevelMedium")
3179
+ },
3180
+ {
3181
+ value: "low",
3182
+ label: t("commandGuardLevelLow")
3183
+ }
3184
+ ],
3185
+ defaultValue: COMMAND_GUARD_DEFAULT_LEVEL_CHOICE,
3186
+ onEdit: (text) => onEdit("commandGuardLevel", text),
3187
+ onReset: () => onReset("commandGuardLevel"),
3188
+ t
3189
+ }) : null
3190
+ ]
3191
+ });
3192
+ }
3193
+ /** Integrations & display: surfaces outside chat that reuse this provider. */
3194
+ function IntegrationsCard({ state, disabled, t, onEdit, onReset }) {
3195
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
3196
+ title: t("integrationsTitle"),
3197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3198
+ id: "cc-web-search",
3199
+ label: t("webSearch"),
3200
+ hint: t("webSearchHint"),
3201
+ state: state.webSearch,
3202
+ disabled,
3203
+ defaultChecked: true,
3204
+ onEdit: (text) => onEdit("webSearch", text),
3205
+ onReset: () => onReset("webSearch"),
3206
+ t
3207
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
3208
+ id: "cc-show-sidebar-quota",
3209
+ label: t("showSidebarQuota"),
3210
+ hint: t("showSidebarQuotaHint"),
3211
+ state: state.showSidebarQuota,
3212
+ disabled,
3213
+ defaultChecked: false,
3214
+ onEdit: (text) => onEdit("showSidebarQuota", text),
3215
+ onReset: () => onReset("showSidebarQuota"),
3216
+ t
3217
+ })]
3218
+ });
3219
+ }
3220
+ /**
3221
+ * The collapsed "Advanced" card: the API base and the network limits. Starts
3222
+ * collapsed on every visit; while collapsed, a badge names the customized
3223
+ * count and an invalid number (which blocks save) is surfaced on the header.
3224
+ */
3225
+ function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
3226
+ const [expanded, setExpanded] = (0, react.useState)(false);
3227
+ const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
3228
+ const invalid = ADVANCED_FIELDS.some((field) => state[field].invalid);
3229
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3230
+ className: "cc-group",
3231
+ "aria-label": t("advancedSettings"),
3232
+ children: [
3233
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3234
+ type: "button",
3235
+ className: "cc-groupHead cc-disclosure",
3236
+ "aria-expanded": expanded,
3237
+ "aria-controls": "cc-advanced-body",
3238
+ onClick: () => setExpanded((value) => !value),
3239
+ children: [
3240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3241
+ className: "cc-groupTitle",
3242
+ children: t("advancedSettings")
3243
+ }),
3244
+ overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3245
+ className: "cc-badge",
3246
+ children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany", { count: overridden })
3247
+ }) : null,
3248
+ !expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3249
+ className: "cc-badge cc-badgeWarn",
3250
+ children: t("advancedInvalid")
3251
+ }) : null,
3252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3254
+ className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
3255
+ "aria-hidden": "true"
3256
+ })
3257
+ ]
3258
+ }),
3259
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3260
+ id: "cc-advanced-body",
3261
+ className: "cc-rows",
3262
+ children: [
3263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3264
+ className: "cc-groupDesc",
3265
+ children: t("advancedSettingsHint")
3266
+ }),
3267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3268
+ id: "cc-api-base",
3269
+ label: t("apiBase"),
3270
+ hint: t("apiBaseHint"),
3271
+ state: state.apiBase,
3272
+ disabled,
3273
+ wide: true,
3274
+ onEdit: (text) => onEdit("apiBase", text),
3275
+ onReset: () => onReset("apiBase"),
3276
+ t
3277
+ }),
3278
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3279
+ id: "cc-request-timeout",
3280
+ label: t("requestTimeoutMs"),
3281
+ hint: t("requestTimeoutMsHint"),
3282
+ state: state.requestTimeoutMs,
3283
+ disabled,
3284
+ numeric: true,
3285
+ onEdit: (text) => onEdit("requestTimeoutMs", text),
3286
+ onReset: () => onReset("requestTimeoutMs"),
3287
+ t
3288
+ }),
3289
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3290
+ id: "cc-stream-idle-timeout",
3291
+ label: t("streamIdleTimeoutMs"),
3292
+ hint: t("streamIdleTimeoutMsHint"),
3293
+ state: state.streamIdleTimeoutMs,
3294
+ disabled,
3295
+ numeric: true,
3296
+ onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
3297
+ onReset: () => onReset("streamIdleTimeoutMs"),
3298
+ t
3299
+ }),
3300
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3301
+ id: "cc-transport-max-retries",
3302
+ label: t("transportMaxRetries"),
3303
+ hint: t("transportMaxRetriesHint"),
3304
+ state: state.transportMaxRetries,
3305
+ disabled,
3306
+ numeric: true,
3307
+ onEdit: (text) => onEdit("transportMaxRetries", text),
3308
+ onReset: () => onReset("transportMaxRetries"),
3309
+ t
3310
+ })
3311
+ ]
3312
+ }) : null,
3313
+ expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3314
+ className: "cc-rowError",
3315
+ role: "status",
3316
+ children: t("advancedInvalid")
3317
+ }) : null
3318
+ ]
3319
+ });
3320
+ }
3321
+ /**
3322
+ * Merge the controller's accounts with the usage report. The default slot is
3323
+ * always first; report entries this page cannot manage (literal keys from the
3324
+ * composition config) still render, read-only, so the list names every
3325
+ * account the pool rotates through.
3326
+ */
3327
+ function accountRows(state, usage, t) {
3328
+ const report = new Map((usage.report?.accounts ?? []).map((entry) => [entry.id, entry]));
3329
+ const defaultUsage = report.get("default");
3330
+ const rows = [{
3331
+ id: "default",
3332
+ label: t("accountDefault"),
3333
+ ref: void 0,
3334
+ isDefault: true,
3335
+ managed: true,
3336
+ configured: state.apiKeyConfigured || defaultUsage?.configured === true,
3337
+ storedKey: state.apiKeyConfigured,
3338
+ keyWritable: state.apiKeyWritable,
3339
+ usage: defaultUsage
3340
+ }];
3341
+ for (const account of state.accounts) {
3342
+ const entry = report.get(account.id);
3343
+ rows.push({
3344
+ id: account.id,
3345
+ label: account.label,
3346
+ ref: account.ref,
3347
+ isDefault: false,
3348
+ managed: true,
3349
+ configured: account.configured || entry?.configured === true,
3350
+ storedKey: account.configured,
3351
+ keyWritable: account.writable,
3352
+ usage: entry
3353
+ });
3354
+ }
3355
+ const known = new Set(rows.map((row) => row.id));
3356
+ for (const entry of usage.report?.accounts ?? []) {
3357
+ if (known.has(entry.id)) continue;
3358
+ known.add(entry.id);
3359
+ rows.push({
3360
+ id: entry.id,
3361
+ label: entry.label,
3362
+ ref: void 0,
3363
+ isDefault: false,
3364
+ managed: false,
3365
+ configured: entry.configured,
3366
+ storedKey: false,
3367
+ keyWritable: false,
3368
+ usage: entry
3369
+ });
3370
+ }
3371
+ return rows;
3372
+ }
3373
+ /** The row's status dot: error for a rejected key, warning while cooling down. */
3374
+ function statusDotClass(row) {
3375
+ const entry = row.usage;
3376
+ if (!row.configured || entry?.mark === "invalid-credential") return "cc-tabDot cc-tabDotError";
3377
+ if (entry !== void 0 && (entry.mark !== "" || entry.cooldownUntil > 0)) return "cc-tabDot cc-tabDotWarn";
3378
+ return "cc-tabDot cc-tabDotOk";
3379
+ }
3380
+ /** One compact quota meter for a row's summary line. */
3381
+ function MiniMeter({ label, limit }) {
3382
+ const ratio = windowRatio(limit.used, limit.cap);
3383
+ const percent = limit.cap > 0 ? `${Math.round(ratio * 100)}%` : formatMoney(limit.used);
3384
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3385
+ className: "cc-miniMeter",
3386
+ title: limit.cap > 0 ? `${formatMoney(limit.used)} / ${formatMoney(limit.cap)}` : void 0,
3387
+ children: [
3388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3389
+ className: "cc-miniMeterLabel",
3390
+ children: label
3391
+ }),
3392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3393
+ className: "cc-miniMeterTrack",
3394
+ "aria-hidden": "true",
3395
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3396
+ className: limit.exceeded ? "cc-miniMeterFill cc-usageBarFillWarn" : "cc-miniMeterFill",
3397
+ style: { width: `${ratio * 100}%` }
3398
+ })
3399
+ }),
3400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3401
+ className: "cc-miniMeterValue",
3402
+ children: percent
3403
+ })
3404
+ ]
3405
+ });
3406
+ }
3407
+ /**
3408
+ * The monthly credit balance for a row's summary line: the billing endpoint's
3409
+ * `monthlyCredits` is what is LEFT, shown as the amount alone.
3410
+ */
3411
+ function MonthlyBalance({ label, remaining }) {
3412
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3413
+ className: "cc-miniMeter",
3414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3415
+ className: "cc-miniMeterLabel",
3416
+ children: label
3417
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3418
+ className: "cc-miniMeterValue",
3419
+ children: formatMoney(Math.max(0, remaining))
3420
+ })]
3421
+ });
3422
+ }
3423
+ /** A single-input inline form (paste key, rename) with confirm/cancel. */
3424
+ function InlineInput({ id, label, secret, initial, placeholder, confirmLabel, disabled, t, onSubmit, onCancel }) {
3425
+ const [value, setValue] = (0, react.useState)(initial ?? "");
3426
+ const [visible, setVisible] = (0, react.useState)(false);
3427
+ const ready = value.trim() !== "";
3428
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
3429
+ className: "cc-inlineForm",
3430
+ onSubmit: (event) => {
3431
+ event.preventDefault();
3432
+ if (ready && !disabled) onSubmit(value);
3433
+ },
3434
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3435
+ id,
3436
+ className: "cc-input",
3437
+ type: secret && !visible ? "password" : "text",
3438
+ "aria-label": label,
3439
+ autoComplete: "off",
3440
+ spellCheck: false,
3441
+ autoFocus: true,
3442
+ placeholder,
3443
+ value,
3444
+ disabled,
3445
+ onChange: (event) => setValue(event.target.value),
3446
+ onKeyDown: (event) => {
3447
+ if (event.key === "Escape") onCancel();
3448
+ }
3449
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3450
+ className: "cc-inlineActions",
3451
+ children: [
3452
+ secret && value !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3453
+ type: "button",
3454
+ className: "cc-reset",
3455
+ onClick: () => setVisible((shown) => !shown),
3456
+ children: visible ? t("hide") : t("show")
3457
+ }) : null,
3458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3460
+ variant: "ghost",
3461
+ size: "sm",
3462
+ type: "button",
3463
+ onClick: onCancel,
3464
+ children: t("cancel")
3465
+ }),
3466
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3467
+ variant: "primary",
3468
+ size: "sm",
3469
+ type: "submit",
3470
+ disabled: !ready || disabled,
3471
+ children: confirmLabel
3472
+ })
3473
+ ]
3474
+ })]
3475
+ });
3476
+ }
3477
+ /** A destructive confirmation bar shown in place of a menu action. */
3478
+ function ConfirmBar({ text, confirmLabel, disabled, t, onConfirm, onCancel }) {
3479
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3480
+ className: "cc-confirmBar",
3481
+ role: "alertdialog",
3482
+ "aria-label": text,
3483
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3484
+ className: "cc-confirmText",
3485
+ children: text
3486
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3487
+ className: "cc-inlineActions",
3488
+ children: [
3489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3491
+ variant: "ghost",
3492
+ size: "sm",
3493
+ onClick: onCancel,
3494
+ children: t("cancel")
3495
+ }),
3496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3497
+ variant: "outline",
3498
+ size: "sm",
3499
+ className: "cc-dangerButton",
3500
+ disabled,
3501
+ onClick: onConfirm,
3502
+ children: confirmLabel
3503
+ })
3504
+ ]
3505
+ })]
3506
+ });
3507
+ }
3508
+ /** One account row: identity, status, quota summary, actions and expandable details. */
3509
+ function AccountItem({ row, rows, state, usage, login, disabled, pinned, t, actions }) {
3510
+ const [expanded, setExpanded] = (0, react.useState)(false);
3511
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
3512
+ const [mode, setMode] = (0, react.useState)(void 0);
3513
+ const entry = row.usage;
3514
+ const report = entry?.report;
3515
+ const accountName = report?.account === void 0 ? "" : report.account.userName || report.account.name;
3516
+ const planName = report?.plan?.name ?? "";
3517
+ const credits = report?.credits;
3518
+ const monthlyReported = credits !== void 0 && credits.monthlyReported !== false;
3519
+ const multi = rows.length > 1;
3520
+ const loginTarget = row.ref;
3521
+ const { visible: loginState, busyElsewhere } = loginStateForTarget(login, loginTarget);
3522
+ const loginBusy = loginState.phase === "starting" || loginState.phase === "waiting";
3523
+ const showLogin = loginState.phase !== "idle";
3524
+ const locked = disabled || !row.managed;
3525
+ const keyActions = !locked && row.keyWritable;
3526
+ const items = [];
3527
+ if (row.managed && multi) items.push(pinned ? {
3528
+ id: "unpin",
3529
+ label: t("accountActionUnpin"),
3530
+ disabled: locked
3531
+ } : {
3532
+ id: "pin",
3533
+ label: t("accountActionPin"),
3534
+ disabled: locked || !row.configured
3535
+ });
3536
+ if (row.managed) {
3537
+ if (items.length > 0) items.push({
3538
+ type: "separator",
3539
+ id: "sep-key"
3540
+ });
3541
+ items.push({
3542
+ id: "login",
3543
+ label: t("accountActionLogin"),
3544
+ disabled: !keyActions || loginBusy || busyElsewhere
3545
+ }, {
3546
+ id: "key",
3547
+ label: t("accountActionKey"),
3548
+ disabled: !keyActions
3549
+ });
3550
+ if (!row.isDefault) items.push({
3551
+ id: "rename",
3552
+ label: t("accountActionRename"),
3553
+ disabled: locked
3554
+ });
3555
+ if (row.isDefault && row.storedKey) items.push({
3556
+ type: "separator",
3557
+ id: "sep-danger"
3558
+ }, {
3559
+ id: "clearKey",
3560
+ label: t("accountActionClearKey"),
3561
+ disabled: !keyActions,
3562
+ danger: true
3563
+ });
3564
+ if (!row.isDefault) items.push({
3565
+ type: "separator",
3566
+ id: "sep-danger"
3567
+ }, {
3568
+ id: "remove",
3569
+ label: t("accountActionRemove"),
3570
+ disabled: locked,
3571
+ danger: true
3572
+ });
3573
+ }
3574
+ const onAction = (id) => {
3575
+ setMenuOpen(false);
3576
+ if (id === "pin") actions.setActive(row.id);
3577
+ else if (id === "unpin") actions.setActive("");
3578
+ else if (id === "login") actions.beginLogin(loginTarget);
3579
+ else if (id === "key" || id === "rename" || id === "remove" || id === "clearKey") setMode(id);
3580
+ };
3581
+ const models = state.accountModels[row.id] ?? [];
3582
+ const ownerOf = (modelId) => {
3583
+ for (const [account, list] of Object.entries(state.accountModels)) {
3584
+ if (account === row.id || !list.includes(modelId)) continue;
3585
+ return rows.find((candidate) => candidate.id === account)?.label ?? account;
3586
+ }
3587
+ };
3588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3589
+ className: row.usage?.active ? "cc-accountItem cc-accountItemActive" : "cc-accountItem",
3590
+ children: [
3591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3592
+ className: "cc-accountHead",
3593
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3594
+ type: "button",
3595
+ className: "cc-accountToggle",
3596
+ "aria-expanded": expanded,
3597
+ "aria-controls": `cc-account-${row.id}-details`,
3598
+ onClick: () => setExpanded((value) => !value),
3599
+ children: [
3600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3601
+ className: statusDotClass(row),
3602
+ "aria-hidden": "true"
3603
+ }),
3604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3605
+ className: "cc-accountName",
3606
+ children: row.label
3607
+ }),
3608
+ accountName !== "" && accountName !== row.label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3609
+ className: "cc-usageAccount",
3610
+ children: accountName
3611
+ }) : null,
3612
+ planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3613
+ className: "cc-usagePlan",
3614
+ children: planName
3615
+ }) : null,
3616
+ entry?.active && multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3617
+ className: "cc-badge",
3618
+ children: t("usageActive")
3619
+ }) : null,
3620
+ pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3621
+ className: "cc-badge",
3622
+ children: t("accountStatusPinned")
3623
+ }) : null,
3624
+ !row.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3625
+ className: "cc-badgeMuted",
3626
+ children: t("apiKeyUnset")
3627
+ }) : null,
3628
+ entry?.mark === "invalid-credential" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3629
+ className: "cc-usagePlanStatus",
3630
+ children: t("usageInvalidKey")
3631
+ }) : null,
3632
+ entry !== void 0 && entry.mark !== "invalid-credential" && (entry.cooldownUntil > 0 || entry.mark === "rate-limit") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3633
+ className: "cc-usagePlanStatus",
3634
+ children: [t("usageCooldown"), entry.cooldownUntil > 0 ? ` ${formatResetAt(entry.cooldownUntil)}` : ""]
3635
+ }) : null,
3636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3637
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3638
+ className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
3639
+ "aria-hidden": "true"
3640
+ })
3641
+ ]
3642
+ }), items.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
3643
+ open: menuOpen,
3644
+ onClose: () => setMenuOpen(false),
3645
+ onSelect: onAction,
3646
+ items,
3647
+ align: "end",
3648
+ portal: true,
3649
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3650
+ type: "button",
3651
+ className: "cc-iconButton",
3652
+ "aria-label": `${row.label} — ${t("accountActions")}`,
3653
+ title: t("accountActions"),
3654
+ "aria-haspopup": "menu",
3655
+ "aria-expanded": menuOpen,
3656
+ onClick: () => setMenuOpen((value) => !value),
3657
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3658
+ className: "cc-kebab",
3659
+ "aria-hidden": "true"
3660
+ })
3661
+ })
3662
+ }) : null]
3663
+ }),
3664
+ credits !== void 0 && (credits.fiveHour !== void 0 || credits.weekly !== void 0 || monthlyReported) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3665
+ className: "cc-accountMeters",
3666
+ children: [
3667
+ credits.fiveHour !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniMeter, {
3668
+ label: t("usageFiveHour"),
3669
+ limit: credits.fiveHour
3670
+ }) : null,
3671
+ credits.weekly !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniMeter, {
3672
+ label: t("usageWeekly"),
3673
+ limit: credits.weekly
3674
+ }) : null,
3675
+ monthlyReported ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthlyBalance, {
3676
+ label: t("usageMonthlyLeft"),
3677
+ remaining: credits.monthlyCredits
3678
+ }) : null
3679
+ ]
3680
+ }) : null,
3681
+ !row.configured && row.managed && mode === void 0 && !showLogin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3682
+ className: "cc-accountSetup",
3683
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3684
+ className: "cc-hint",
3685
+ children: row.keyWritable ? t("accountNoKeyHint") : t("apiKeyLocked")
3686
+ }), keyActions ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3687
+ className: "cc-inlineActions",
3688
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3689
+ variant: "primary",
3690
+ size: "sm",
3691
+ disabled: loginBusy || busyElsewhere,
3692
+ onClick: () => actions.beginLogin(loginTarget),
3693
+ children: t("accountAddLogin")
3694
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3695
+ variant: "ghost",
3696
+ size: "sm",
3697
+ onClick: () => setMode("key"),
3698
+ children: t("accountAddPaste")
3699
+ })]
3700
+ }) : null]
3701
+ }) : null,
3702
+ showLogin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginStatus, {
3703
+ state: loginState,
3704
+ t,
3705
+ onCancel: actions.cancelLogin
3706
+ }) : null,
3707
+ mode === "key" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
3708
+ id: `cc-account-${row.id}-key`,
3709
+ label: t("accountKeyPlaceholder"),
3710
+ secret: true,
3711
+ placeholder: t("accountKeyPlaceholder"),
3712
+ confirmLabel: t("accountApply"),
3713
+ disabled: disabled || state.accountBusy,
3714
+ t,
3715
+ onCancel: () => setMode(void 0),
3716
+ onSubmit: (value) => {
3717
+ actions.setKey(row.id, value).then((ok) => {
3718
+ if (ok) setMode(void 0);
3719
+ });
3720
+ }
3721
+ }) : null,
3722
+ mode === "rename" && row.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
3723
+ id: `cc-account-${row.id}-rename`,
3724
+ label: t("accountActionRename"),
3725
+ initial: row.label,
3726
+ confirmLabel: t("accountApply"),
3727
+ disabled: disabled || state.accountBusy,
3728
+ t,
3729
+ onCancel: () => setMode(void 0),
3730
+ onSubmit: (value) => {
3731
+ actions.rename(row.ref, value).then((ok) => {
3732
+ if (ok) setMode(void 0);
3733
+ });
3734
+ }
3735
+ }) : null,
3736
+ mode === "remove" && row.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmBar, {
3737
+ text: t("accountRemoveConfirm", { name: row.label }),
3738
+ confirmLabel: t("accountConfirmRemove"),
3739
+ disabled: disabled || state.accountBusy,
3740
+ t,
3741
+ onCancel: () => setMode(void 0),
3742
+ onConfirm: () => {
3743
+ actions.remove(row.ref).then((ok) => {
3744
+ if (ok) setMode(void 0);
3745
+ });
3746
+ }
3747
+ }) : null,
3748
+ mode === "clearKey" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmBar, {
3749
+ text: t("accountClearKeyConfirm"),
3750
+ confirmLabel: t("accountConfirmClear"),
3751
+ disabled: disabled || state.accountBusy,
3752
+ t,
3753
+ onCancel: () => setMode(void 0),
3754
+ onConfirm: () => {
3755
+ actions.clearKey(row.id).then((ok) => {
3756
+ if (ok) setMode(void 0);
3757
+ });
3758
+ }
3759
+ }) : null,
3760
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3761
+ id: `cc-account-${row.id}-details`,
3762
+ className: "cc-accountDetails",
3763
+ children: [entry !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountReport, {
3764
+ entry,
3765
+ fetchedAt: usage.fetchedAt,
3766
+ t,
3767
+ headless: true
3768
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3769
+ className: "cc-usageHint",
3770
+ children: usage.status === "loading" ? t("usageLoading") : t("usageUnconfigured")
3771
+ }), multi && row.managed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
3772
+ className: "cc-rowFlush",
3773
+ title: t("accountModels"),
3774
+ titleFor: `cc-account-${row.id}-models`,
3775
+ description: t("accountModelsHint"),
3776
+ error: state.catalogFailed ? t("modelCatalogFailed") : void 0,
3777
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
3778
+ id: `cc-account-${row.id}-models`,
3779
+ selected: models,
3780
+ catalog: state.catalogModels,
3781
+ disabled: disabled || state.accountBusy,
3782
+ deferCommit: true,
3783
+ ownerOf,
3784
+ t,
3785
+ onSelect: (ids) => void actions.setModels(row.id, ids)
3786
+ })
3787
+ }) : null]
3788
+ }) : null
3789
+ ]
3790
+ });
3791
+ }
3792
+ /** The live status of one browser sign-in: link while waiting, outcome after. */
3793
+ function LoginStatus({ state, t, onCancel }) {
3794
+ const hint = loginHint(state, t);
3795
+ const busy = state.phase === "starting" || state.phase === "waiting";
3796
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3797
+ className: "cc-loginStatus",
3798
+ role: "status",
3799
+ children: [
3800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3801
+ className: hint.className,
3802
+ title: hint.title,
3803
+ children: hint.text
3804
+ }),
3805
+ state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
3806
+ className: "cc-loginLink",
3807
+ href: state.authUrl,
3808
+ target: "_blank",
3809
+ rel: "noreferrer",
3810
+ children: t("loginOpenLink")
3811
+ }) : null,
3812
+ busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3813
+ type: "button",
3814
+ className: "cc-reset",
3815
+ onClick: onCancel,
3816
+ children: t("loginCancel")
3817
+ }) : null
3818
+ ]
3819
+ });
3820
+ }
3821
+ /**
3822
+ * The add-account panel. Both paths store the account at once: pasting a key
3823
+ * writes key then row; browser sign-in stores a keyless row first (the Host
3824
+ * only signs in to a reference the stored list names) and drops it again if
3825
+ * the sign-in does not complete, so a failed attempt leaves no empty account.
3826
+ */
3827
+ function AddAccountPanel({ state, login, disabled, t, actions, pendingRef, setPendingRef, onClose }) {
3828
+ const [label, setLabel] = (0, react.useState)("");
3829
+ const [pasting, setPasting] = (0, react.useState)(false);
3830
+ const [loginFailure, setLoginFailure] = (0, react.useState)(void 0);
3831
+ const fallbackName = t("accountNameN", { n: state.accounts.length + 2 });
3832
+ const pending = pendingRef === void 0 ? void 0 : loginStateForTarget(login, pendingRef).visible;
3833
+ const busy = state.accountBusy || pending?.phase === "starting" || pending?.phase === "waiting";
3834
+ const loginBusyElsewhere = login.phase === "starting" || login.phase === "waiting";
3835
+ (0, react.useEffect)(() => {
3836
+ if (pendingRef === void 0 || login.targetRef !== pendingRef) return;
3837
+ if (login.phase === "success") {
3838
+ const named = label.trim() === "" && login.userName !== void 0 && login.userName !== "";
3839
+ setPendingRef(void 0);
3840
+ (named ? actions.rename(pendingRef, login.userName) : Promise.resolve(true)).then(onClose);
3841
+ return;
3842
+ }
3843
+ if (login.phase === "failed" || login.phase === "unavailable") {
3844
+ setLoginFailure(loginHint(login, t).text);
3845
+ setPendingRef(void 0);
3846
+ actions.remove(pendingRef);
3847
+ }
3848
+ }, [
3849
+ login,
3850
+ pendingRef,
3851
+ setPendingRef,
3852
+ label,
3853
+ actions,
3854
+ onClose,
3855
+ t
3856
+ ]);
3857
+ const startLogin = async () => {
3858
+ setLoginFailure(void 0);
3859
+ const ref = await actions.create({ label: label.trim() === "" ? fallbackName : label });
3860
+ if (ref === void 0) return;
3861
+ setPendingRef(ref);
3862
+ actions.beginLogin(ref);
3863
+ };
3864
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3865
+ className: "cc-addPanel",
3866
+ "aria-label": t("accountAdd"),
3867
+ children: [
3868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3869
+ className: "cc-panelTitle",
3870
+ children: t("accountAdd")
3871
+ }),
3872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3873
+ className: "cc-input",
3874
+ type: "text",
3875
+ "aria-label": t("accountNamePlaceholder"),
3876
+ placeholder: t("accountNamePlaceholder"),
3877
+ value: label,
3878
+ disabled: disabled || busy,
3879
+ onChange: (event) => setLabel(event.target.value)
3880
+ }),
3881
+ pending !== void 0 && pending.phase !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginStatus, {
3882
+ state: pending,
3883
+ t,
3884
+ onCancel: actions.cancelLogin
3885
+ }) : null,
3886
+ loginFailure !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3887
+ className: "cc-loginError",
3888
+ children: loginFailure
3889
+ }) : null,
3890
+ pasting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
3891
+ id: "cc-add-account-key",
3892
+ label: t("accountKeyPlaceholder"),
3893
+ secret: true,
3894
+ placeholder: t("accountKeyPlaceholder"),
3895
+ confirmLabel: t("accountAddConfirm"),
3896
+ disabled: disabled || busy,
3897
+ t,
3898
+ onCancel: () => setPasting(false),
3899
+ onSubmit: (key) => {
3900
+ actions.create({
3901
+ label: label.trim() === "" ? fallbackName : label,
3902
+ key
3903
+ }).then((ref) => {
3904
+ if (ref !== void 0) onClose();
3905
+ });
3906
+ }
3907
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3908
+ className: "cc-hint",
3909
+ children: t("accountAddHint")
3910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3911
+ className: "cc-inlineActions",
3912
+ children: [
3913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3914
+ variant: "primary",
3915
+ size: "sm",
3916
+ disabled: disabled || busy || loginBusyElsewhere,
3917
+ onClick: () => void startLogin(),
3918
+ children: t("accountAddLogin")
3919
+ }),
3920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3921
+ variant: "ghost",
3922
+ size: "sm",
3923
+ disabled: disabled || busy,
3924
+ onClick: () => setPasting(true),
3925
+ children: t("accountAddPaste")
3926
+ }),
3927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
3929
+ variant: "ghost",
3930
+ size: "sm",
3931
+ disabled: busy,
3932
+ onClick: onClose,
3933
+ children: t("cancel")
3934
+ })
3935
+ ]
3936
+ })] })
3937
+ ]
3938
+ });
3939
+ }
3940
+ /**
3941
+ * The unified account card: every account the pool rotates through, one row
3942
+ * each, with its status and quota inline. Replaces the former split between
3943
+ * a credentials card, a usage card and a routing-rules card, so one account's
3944
+ * facts no longer have to be cross-read across three surfaces.
3945
+ */
3946
+ function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh }) {
3947
+ const [adding, setAdding] = (0, react.useState)(false);
3948
+ const [pendingRef, setPendingRef] = (0, react.useState)(void 0);
3949
+ const closeAdd = (0, react.useCallback)(() => setAdding(false), []);
3950
+ const { loading, shouldRefresh } = usageCardState(usage);
3951
+ (0, react.useEffect)(() => {
3952
+ if (shouldRefresh) onRefresh();
3953
+ }, [shouldRefresh, onRefresh]);
3954
+ const rows = accountRows(state, usage, t).filter((row) => row.ref === void 0 || row.ref !== pendingRef);
3955
+ const multi = rows.length > 1;
3956
+ const pinnedRow = rows.find((row) => row.id === state.activeAccount);
3957
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3958
+ className: "cc-group",
3959
+ "aria-label": t("accountsTitle"),
3838
3960
  children: [
3839
3961
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3840
- className: "cc-field",
3962
+ className: "cc-groupHead",
3841
3963
  children: [
3842
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3843
- className: "cc-fieldHead",
3844
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3845
- className: "cc-label",
3846
- children: t("rulesTitle")
3847
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3848
- className: "cc-badges",
3849
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3850
- type: "button",
3851
- className: "cc-reset",
3852
- disabled,
3853
- onClick: onAdd,
3854
- children: t("ruleAdd")
3855
- })
3856
- })]
3857
- }),
3858
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3859
- className: "cc-hint",
3860
- children: t("rulesHint")
3964
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3965
+ className: "cc-groupTitle",
3966
+ children: t("accountsTitle")
3861
3967
  }),
3862
- state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3863
- className: "cc-invalid",
3864
- children: t("rulesCatalogFailed")
3865
- }) : null
3968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
3969
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3970
+ type: "button",
3971
+ className: "cc-linkButton",
3972
+ disabled: loading,
3973
+ onClick: onRefresh,
3974
+ children: loading ? t("usageRefreshing") : t("usageRefresh")
3975
+ })
3866
3976
  ]
3867
3977
  }),
3868
- state.rules.map((rule) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RuleRow, {
3869
- rule,
3870
- accounts: state.accounts,
3871
- catalog: state.catalogModels,
3978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3979
+ className: "cc-groupDesc",
3980
+ children: multi ? t("accountsRotationHint") : t("accountsHint")
3981
+ }),
3982
+ multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3983
+ className: "cc-accountMode",
3984
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: pinnedRow !== void 0 ? t("accountModePinned", { name: pinnedRow.label }) : t("accountModeAuto") }), pinnedRow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3985
+ type: "button",
3986
+ className: "cc-reset",
3987
+ disabled: disabled || state.accountBusy,
3988
+ onClick: () => void actions.setActive(""),
3989
+ children: t("accountActionUnpin")
3990
+ }) : null]
3991
+ }) : null,
3992
+ usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3993
+ className: "cc-rowError",
3994
+ role: "status",
3995
+ children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""]
3996
+ }) : null,
3997
+ state.accountFailed !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3998
+ className: "cc-rowError",
3999
+ role: "status",
4000
+ children: t("accountOpFailed")
4001
+ }) : null,
4002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4003
+ className: "cc-accountList",
4004
+ children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountItem, {
4005
+ row,
4006
+ rows,
4007
+ state,
4008
+ usage,
4009
+ login,
4010
+ disabled,
4011
+ pinned: state.activeAccount === row.id,
4012
+ t,
4013
+ actions
4014
+ }, row.id))
4015
+ }),
4016
+ adding ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddAccountPanel, {
4017
+ state,
4018
+ login,
3872
4019
  disabled,
3873
4020
  t,
3874
- onModels: (ids) => onModels(rule.id, ids),
3875
- onAccount: (text) => onAccount(rule.id, text),
3876
- onRemove: () => onRemove(rule.id)
3877
- }, rule.id)),
3878
- state.rules.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3879
- className: "cc-hint",
3880
- children: t("rulesEmpty")
3881
- }) : null
4021
+ actions,
4022
+ pendingRef,
4023
+ setPendingRef,
4024
+ onClose: closeAdd
4025
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4026
+ type: "button",
4027
+ className: "cc-addButton",
4028
+ disabled,
4029
+ onClick: () => setAdding(true),
4030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4031
+ className: "cc-addGlyph",
4032
+ "aria-hidden": "true"
4033
+ }), t("accountAdd")]
4034
+ })
3882
4035
  ]
3883
4036
  });
3884
4037
  }
3885
- /** The visible-model filter card: an allowlist over the catalog. Empty = show all. */
3886
- function VisibleModelsCard({ t, state, disabled, onSelect, onClear }) {
3887
- const count = state.visibleModels.length;
3888
- const pickT = (key, params) => {
3889
- if (key === "ruleModelPick") return t("visibleModelsPick");
3890
- if (key === "ruleModelCount") return t("visibleModelsCount", params);
3891
- return t(key, params);
3892
- };
3893
- const readiness = {
3894
- catalogIds: state.catalogModels.map((model) => model.id),
3895
- catalogFailed: state.catalogFailed
3896
- };
3897
- const staleIds = staleModelIds(state.visibleModels, readiness);
3898
- const catalogReady = catalogIsReady(readiness);
3899
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3900
- className: "cc-card",
3901
- "aria-label": t("visibleModelsTitle"),
3902
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3903
- className: "cc-field",
3904
- children: [
3905
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3906
- className: "cc-fieldHead",
3907
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3908
- className: "cc-label",
3909
- children: t("visibleModelsTitle")
3910
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3911
- className: "cc-badges",
3912
- children: [catalogReady && staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3913
- type: "button",
3914
- className: "cc-reset",
3915
- disabled,
3916
- onClick: () => onSelect(state.visibleModels.filter((id) => !staleIds.includes(id))),
3917
- children: t("visibleModelsCleanStale", { count: staleIds.length })
3918
- }) : null, count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3919
- type: "button",
3920
- className: "cc-reset",
3921
- disabled,
3922
- onClick: onClear,
3923
- children: t("visibleModelsShowAll")
3924
- }) : null]
3925
- })]
3926
- }),
3927
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3928
- className: "cc-hint",
3929
- children: t("visibleModelsHint")
3930
- }),
3931
- state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3932
- className: "cc-invalid",
3933
- children: t("rulesCatalogFailed")
3934
- }) : null,
3935
- staleIds.length > 0 && catalogReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3936
- className: "cc-hint",
3937
- children: t("visibleModelsStaleHint", { count: staleIds.length })
3938
- }) : null,
3939
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
3940
- id: "cc-visible-models",
3941
- selected: state.visibleModels,
3942
- catalog: state.catalogModels,
3943
- disabled,
3944
- t: pickT,
3945
- onSelect
3946
- })
3947
- ]
3948
- })
3949
- });
3950
- }
3951
4038
  /**
3952
- * Show the "Saved ✓" affordance for a short window after each accepted save.
4039
+ * Show the save bar's "saved" confirmation for a short window after each accepted save.
3953
4040
  * The controller only counts saves (`savedCount`); the flash timing lives
3954
4041
  * here so the state machine stays timer-free.
3955
4042
  */
3956
4043
  function useSavedFlash(tick) {
3957
4044
  const [visible, setVisible] = (0, react.useState)(false);
4045
+ const seen = (0, react.useRef)(tick);
3958
4046
  (0, react.useEffect)(() => {
3959
- if (tick === 0) return;
4047
+ if (tick === seen.current) return;
4048
+ seen.current = tick;
3960
4049
  setVisible(true);
3961
4050
  const timer = setTimeout(() => setVisible(false), 2500);
3962
4051
  return () => clearTimeout(timer);
@@ -3986,29 +4075,157 @@ window.__ModuleLoader__.load({
3986
4075
  }, []);
3987
4076
  return available;
3988
4077
  }
3989
- /** The settings page body: connection facts for the Command Code provider. */
4078
+ /**
4079
+ * What the save bar says. It exists only while there is something to act on
4080
+ * or to confirm: pending edits, a save in flight, a failure, or the brief
4081
+ * confirmation after a save landed. Account operations never reach it, they
4082
+ * commit on their own.
4083
+ */
4084
+ function saveBarView(state, savedVisible, t) {
4085
+ if (state.failed) return {
4086
+ visible: true,
4087
+ tone: "error",
4088
+ message: t("saveFailed"),
4089
+ actions: state.dirty
4090
+ };
4091
+ if (state.dirty || state.saving) return state.invalid ? {
4092
+ visible: true,
4093
+ tone: "error",
4094
+ message: t("saveInvalid"),
4095
+ actions: true
4096
+ } : {
4097
+ visible: true,
4098
+ tone: "pending",
4099
+ message: t("unsavedChanges"),
4100
+ actions: true
4101
+ };
4102
+ if (savedVisible) return {
4103
+ visible: true,
4104
+ tone: "success",
4105
+ message: t("saved"),
4106
+ actions: false
4107
+ };
4108
+ return {
4109
+ visible: false,
4110
+ tone: "success",
4111
+ message: "",
4112
+ actions: false
4113
+ };
4114
+ }
4115
+ /** The save bar's leading status mark: a soft dot while pending, a glyph once there is an outcome. */
4116
+ function SaveBarIcon({ tone }) {
4117
+ if (tone === "pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4118
+ className: "cc-saveBarIcon",
4119
+ "aria-hidden": "true",
4120
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-saveBarPulse" })
4121
+ });
4122
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4123
+ className: "cc-saveBarIcon",
4124
+ "aria-hidden": "true",
4125
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
4126
+ viewBox: "0 0 16 16",
4127
+ width: "16",
4128
+ height: "16",
4129
+ fill: "none",
4130
+ stroke: "currentColor",
4131
+ strokeWidth: "1.6",
4132
+ strokeLinecap: "round",
4133
+ strokeLinejoin: "round",
4134
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4135
+ cx: "8",
4136
+ cy: "8",
4137
+ r: "6.5"
4138
+ }), tone === "success" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 8.2l1.9 1.9 3.7-3.9" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 4.8v3.6M8 11.1v.1" })]
4139
+ })
4140
+ });
4141
+ }
4142
+ /**
4143
+ * The floating save bar: pinned to the bottom of the settings scrollport and
4144
+ * slid out of view when there is nothing to save. It stays mounted so it can
4145
+ * animate out, and keeps showing its last message while it does (the state
4146
+ * that hid it has no message of its own).
4147
+ */
4148
+ function SaveBar({ view, state, t, onDiscard, onSave }) {
4149
+ const last = (0, react.useRef)(view);
4150
+ if (view.visible) last.current = view;
4151
+ const shown = view.visible ? view : last.current;
4152
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4153
+ className: "cc-saveBarDock",
4154
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4155
+ className: `cc-saveBar cc-saveBar-${shown.tone}${view.visible ? " cc-saveBarShown" : ""}`,
4156
+ role: "region",
4157
+ "aria-label": t("save"),
4158
+ "aria-hidden": !view.visible,
4159
+ children: [
4160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBarIcon, { tone: shown.tone }),
4161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4162
+ className: "cc-saveBarText",
4163
+ role: "status",
4164
+ "aria-live": "polite",
4165
+ title: shown.message,
4166
+ children: shown.message
4167
+ }),
4168
+ shown.actions ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4169
+ className: "cc-saveBarActions",
4170
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4171
+ type: "button",
4172
+ className: "cc-saveBarButton cc-saveBarGhost",
4173
+ disabled: !view.visible || !state.dirty || state.saving,
4174
+ onClick: onDiscard,
4175
+ children: t("discard")
4176
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4177
+ type: "button",
4178
+ className: "cc-saveBarButton cc-saveBarPrimary",
4179
+ disabled: !view.visible || !state.dirty || state.invalid || state.saving,
4180
+ onClick: onSave,
4181
+ children: t(state.saving ? "saving" : "save")
4182
+ })]
4183
+ }) : null
4184
+ ]
4185
+ })
4186
+ });
4187
+ }
4188
+ /** The settings page body: accounts, then the staged provider settings. */
3990
4189
  function CommandCodeSettingsPage(props) {
3991
4190
  const { t } = props;
3992
- const footerMarker = (0, react.useRef)(null);
3993
- const [footerFloating, setFooterFloating] = (0, react.useState)(false);
3994
4191
  const state = props.useCommandCodeSettings((snapshot) => snapshot);
3995
4192
  const usage = props.useCommandCodeUsage((snapshot) => snapshot);
3996
4193
  const login = props.useCommandCodeLogin((snapshot) => snapshot);
3997
4194
  const disabled = !state.writable;
3998
- const keyLocked = !state.apiKeyWritable;
3999
4195
  const savedVisible = useSavedFlash(state.savedCount);
4000
4196
  const updateVersion = usePluginUpdate();
4001
- (0, react.useEffect)(() => {
4002
- const marker = footerMarker.current;
4003
- if (!marker || typeof IntersectionObserver === "undefined") return;
4004
- const observer = new IntersectionObserver(([entry]) => {
4005
- if (entry) setFooterFloating(!entry.isIntersecting);
4006
- });
4007
- observer.observe(marker);
4008
- return () => observer.disconnect();
4009
- }, []);
4197
+ const { createAccount, renameAccount, removeAccount, setAccountKey, clearAccountKey, setActiveAccount, setAccountModels, beginLogin, cancelLogin } = props;
4198
+ const actions = (0, react.useMemo)(() => ({
4199
+ create: createAccount,
4200
+ rename: renameAccount,
4201
+ remove: removeAccount,
4202
+ setKey: setAccountKey,
4203
+ clearKey: clearAccountKey,
4204
+ setActive: setActiveAccount,
4205
+ setModels: setAccountModels,
4206
+ beginLogin,
4207
+ cancelLogin
4208
+ }), [
4209
+ createAccount,
4210
+ renameAccount,
4211
+ removeAccount,
4212
+ setAccountKey,
4213
+ clearAccountKey,
4214
+ setActiveAccount,
4215
+ setAccountModels,
4216
+ beginLogin,
4217
+ cancelLogin
4218
+ ]);
4219
+ const bar = saveBarView(state, savedVisible, t);
4220
+ const form = {
4221
+ state,
4222
+ disabled,
4223
+ t,
4224
+ onEdit: props.edit,
4225
+ onReset: props.resetField
4226
+ };
4010
4227
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4011
- className: "cc-section",
4228
+ className: bar.visible ? "cc-section cc-sectionWithBar" : "cc-section",
4012
4229
  "aria-label": t("title"),
4013
4230
  children: [
4014
4231
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
@@ -4024,117 +4241,23 @@ window.__ModuleLoader__.load({
4024
4241
  role: "status",
4025
4242
  children: t("readOnly")
4026
4243
  }) : null,
4027
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
4028
- t,
4029
- usage,
4030
- removingIds: state.accountsRemoving,
4031
- removableIds: state.accounts.map((account) => account.id),
4032
- canManage: state.writable,
4033
- onRefresh: props.refreshUsage,
4034
- onRemoveAccount: props.removeAccount
4035
- }),
4036
4244
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
4037
4245
  t,
4038
4246
  state,
4247
+ usage,
4039
4248
  login,
4040
4249
  disabled,
4041
- onAdd: props.addAccount,
4042
- onRemove: props.removeAccount,
4043
- onLabel: props.editAccountLabel,
4044
- onKey: props.editAccountKey,
4045
- onToggleClear: (id) => props.toggleKeyClear(id),
4046
- onActive: (text) => props.edit("activeAccount", text),
4047
- onActiveReset: () => props.resetField("activeAccount"),
4048
- onBeginLogin: props.beginLogin,
4049
- onCancelLogin: props.cancelLogin
4050
- }),
4051
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RulesCard, {
4052
- t,
4053
- state,
4054
- disabled,
4055
- onAdd: props.addRule,
4056
- onRemove: props.removeRule,
4057
- onModels: props.editRuleModels,
4058
- onAccount: props.editRuleAccount
4250
+ actions,
4251
+ onRefresh: props.refreshUsage
4059
4252
  }),
4060
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsCard, {
4061
- t,
4062
- state,
4063
- disabled,
4253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsCard, {
4254
+ ...form,
4064
4255
  onSelect: props.editVisibleModels,
4065
4256
  onClear: props.clearVisibleModels
4066
4257
  }),
4067
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4068
- className: "cc-card",
4069
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
4070
- label: t("apiKey"),
4071
- hint: keyLocked ? t("apiKeyLocked") : t("apiKeyHint"),
4072
- state: state.apiKey,
4073
- disabled: disabled || keyLocked || login.targetRef === void 0 && (login.phase === "starting" || login.phase === "waiting"),
4074
- configured: state.apiKeyConfigured,
4075
- configuredLabel: t("apiKeySet"),
4076
- unconfiguredLabel: t("apiKeyUnset"),
4077
- clearStaged: state.apiKeyClearStaged,
4078
- showLabel: t("show"),
4079
- hideLabel: t("hide"),
4080
- clearLabel: t("usageKeyClear"),
4081
- clearStagedLabel: t("usageKeyClearStaged"),
4082
- undoClearLabel: t("usageUndoKeyClear"),
4083
- onEdit: (text) => props.edit("apiKey", text),
4084
- onToggleClear: () => props.toggleKeyClear("default")
4085
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginRow, {
4086
- state: login,
4087
- disabled: disabled || keyLocked || state.dirty,
4088
- disabledHint: state.dirty ? t("loginSaveBefore") : void 0,
4089
- t,
4090
- onBegin: props.beginLogin,
4091
- onCancel: props.cancelLogin
4092
- })]
4093
- }),
4094
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, {
4095
- state,
4096
- disabled,
4097
- t,
4098
- onEdit: props.edit,
4099
- onReset: props.resetField
4100
- }),
4101
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4102
- ref: footerMarker,
4103
- className: "cc-footerMarker",
4104
- "aria-hidden": "true"
4105
- }),
4106
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4107
- className: `cc-footer cc-footerSticky${footerFloating ? " cc-footerFloating" : ""}`,
4108
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4109
- className: "cc-footerActions",
4110
- children: [
4111
- state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4112
- className: "cc-failed",
4113
- role: "status",
4114
- children: t("saveFailed")
4115
- }) : null,
4116
- savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4117
- className: "cc-saved",
4118
- role: "status",
4119
- children: t("saved")
4120
- }) : null,
4121
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4122
- variant: "ghost",
4123
- size: "sm",
4124
- disabled: !state.dirty || state.saving,
4125
- onClick: props.discard,
4126
- children: t("discard")
4127
- }),
4128
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4129
- variant: "primary",
4130
- size: "sm",
4131
- disabled: !state.dirty || state.invalid || state.saving,
4132
- onClick: props.save,
4133
- children: t(state.saving ? "saving" : "save")
4134
- })
4135
- ]
4136
- })
4137
- }),
4258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrivacyCard, { ...form }),
4259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegrationsCard, { ...form }),
4260
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, { ...form }),
4138
4261
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4139
4262
  className: "cc-version",
4140
4263
  children: [
@@ -4154,6 +4277,62 @@ window.__ModuleLoader__.load({
4154
4277
  ]
4155
4278
  })] }) : null
4156
4279
  ]
4280
+ }),
4281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SaveBar, {
4282
+ view: bar,
4283
+ state,
4284
+ t,
4285
+ onDiscard: props.discard,
4286
+ onSave: props.save
4287
+ })
4288
+ ]
4289
+ });
4290
+ }
4291
+ //#endregion
4292
+ //#region src/client/login-row.tsx
4293
+ /** The sign-in alternative to pasting a key (settings page + Models card). */
4294
+ function LoginRow({ state, targetRef, disabled, disabledHint, t, onBegin, onCancel }) {
4295
+ const { visible: visibleState, busyElsewhere } = loginStateForTarget(state, targetRef);
4296
+ const busy = visibleState.phase === "starting" || visibleState.phase === "waiting";
4297
+ const hint = loginHint(visibleState, t);
4298
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4299
+ className: "cc-field",
4300
+ children: [
4301
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4302
+ className: "cc-fieldHead",
4303
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4304
+ className: "cc-label",
4305
+ children: t("loginTitle")
4306
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4307
+ className: "cc-badges",
4308
+ children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4309
+ type: "button",
4310
+ className: "cc-reset",
4311
+ onClick: onCancel,
4312
+ children: t("loginCancel")
4313
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4314
+ type: "button",
4315
+ className: "cc-reset",
4316
+ disabled: disabled || busyElsewhere,
4317
+ onClick: onBegin,
4318
+ children: t("loginButton")
4319
+ })
4320
+ })]
4321
+ }),
4322
+ visibleState.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4323
+ className: "cc-hint",
4324
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
4325
+ className: "cc-loginLink",
4326
+ href: visibleState.authUrl,
4327
+ target: "_blank",
4328
+ rel: "noreferrer",
4329
+ children: t("loginOpenLink")
4330
+ })
4331
+ }) : null,
4332
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4333
+ className: hint.className,
4334
+ title: hint.title,
4335
+ children: disabled && !busy && disabledHint !== void 0 ? disabledHint : hint.text
4157
4336
  })
4158
4337
  ]
4159
4338
  });
@@ -4682,16 +4861,13 @@ window.__ModuleLoader__.load({
4682
4861
  * Project the shared usage snapshot into the panel's render tree.
4683
4862
  *
4684
4863
  * Deduplication matches the settings card: hand-edited settings can name one
4685
- * credential twice, and removal staging hides an account before the post-save
4686
- * refresh lands.
4864
+ * credential twice.
4687
4865
  */
4688
4866
  function buildPanelView(input) {
4689
4867
  const { usage } = input;
4690
4868
  const t = input.t ?? panelTextEN;
4691
- const hidden = new Set(input.removingIds ?? []);
4692
4869
  const seen = /* @__PURE__ */ new Set();
4693
4870
  const entries = (usage.report?.accounts ?? []).filter((entry) => {
4694
- if (hidden.has(entry.id)) return false;
4695
4871
  if (seen.has(entry.id)) return false;
4696
4872
  seen.add(entry.id);
4697
4873
  return true;
@@ -4833,12 +5009,9 @@ window.__ModuleLoader__.load({
4833
5009
  */
4834
5010
  /** The panel's view, recomputed from both seats on every notification. */
4835
5011
  function usePanelView(props) {
4836
- const usage = props.useCommandCodeUsage((state) => state);
4837
- const settings = props.useCommandCodeSettings((state) => state);
4838
5012
  return buildPanelView({
4839
- usage,
4840
- apiKeyConfigured: settings.anyAccountConfigured,
4841
- removingIds: settings.accountsRemoving,
5013
+ usage: props.useCommandCodeUsage((state) => state),
5014
+ apiKeyConfigured: props.useCommandCodeSettings((state) => state).anyAccountConfigured,
4842
5015
  t: props.t ?? panelTextEN
4843
5016
  });
4844
5017
  }
@@ -6204,33 +6377,88 @@ window.__ModuleLoader__.load({
6204
6377
  * that silently drops one stylesheet.
6205
6378
  */
6206
6379
  const PAGE_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
6207
- /** The settings-page stylesheet. */
6380
+ /**
6381
+ * The settings-page stylesheet.
6382
+ *
6383
+ * The metrics are the harness's own settings pages', read out of the 0.1.7
6384
+ * bundles so the page sits beside General and Models without looking foreign:
6385
+ * a row is 16px of padding over a 0.5px border-l2 hairline, its title 14/22
6386
+ * label-primary and its description 12/18 label-tertiary, the control on the
6387
+ * right. Selectors and buttons are capsules (36px / 28px compact), inputs are
6388
+ * 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
6389
+ * border-l4 outline with a 16px radius, and a nested panel is a
6390
+ * bg-module-platform fill with a 12px radius. Errors use state-error-primary,
6391
+ * the platform's error colour (label-error is its text-only sibling).
6392
+ */
6208
6393
  const PAGE_CSS = `
6209
- .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
6210
- .cc-title{margin:0;font-size:18px;font-weight:600}
6211
- .cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}
6212
- .cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6213
- .cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}
6394
+ .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex}
6395
+ .cc-title{margin:0;color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px}
6396
+ .cc-intro{margin:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}
6397
+ .cc-readOnly{margin:8px 0 0;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));font-size:12px;line-height:18px}
6398
+ .cc-spacer{flex:1}
6399
+ /* Groups: a heading over hairline-separated rows, no card surface. */
6400
+ .cc-group{flex-direction:column;display:flex;margin-top:28px}
6401
+ .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:28px;padding-bottom:4px}
6402
+ .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
6403
+ .cc-groupDesc{margin:0 0 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
6404
+ .cc-rows>.cc-groupDesc{margin:0;padding:4px 0 0}
6405
+ .cc-disclosure{width:100%;padding:0 0 4px;border:0;background:0 0;font:inherit;text-align:left;cursor:pointer;border-radius:6px}
6406
+ .cc-disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
6407
+ .cc-rows{flex-direction:column;display:flex}
6408
+ .cc-row{align-items:center;gap:8px;display:flex;padding:16px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
6409
+ .cc-rows>.cc-row:last-child{border-bottom:0}
6410
+ .cc-rowNested{padding-left:16px}
6411
+ .cc-rowFlush{padding:0;border-bottom:0}
6412
+ .cc-rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:32px;display:flex}
6413
+ .cc-rowTitleLine{align-items:center;gap:8px;display:flex;min-width:0}
6414
+ .cc-rowTitle{min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}
6415
+ .cc-rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
6416
+ .cc-rowDesc p{margin:0}
6417
+ .cc-rowDesc p+p{margin-top:4px}
6418
+ .cc-rowError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
6419
+ .cc-rowControl{flex:none;align-items:center;justify-content:flex-end;gap:8px;display:inline-flex;max-width:60%}
6420
+ .cc-rowInput{width:200px}
6421
+ .cc-rowInputWide{width:280px}
6422
+ /* The platform's link button: a compact capsule with no fill until hovered. */
6423
+ .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}
6424
+ .cc-linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
6425
+ .cc-linkButton:disabled{cursor:default;opacity:.4}
6426
+ .cc-linkButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6427
+ /* Tags: the platform Tag's capsule (11/17, weight 500). */
6428
+ .cc-badges{align-items:center;gap:8px;display:inline-flex}
6429
+ .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:11px;font-weight:500;line-height:17px}
6430
+ .cc-badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
6431
+ .cc-badgeMuted{border:.5px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-tertiary)}
6432
+ .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))}
6433
+ /* The stacked field (the Models-page provider card and the account forms):
6434
+ * the platform SettingsForm field's metrics. */
6214
6435
  .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
6215
- .cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}
6436
+ .cc-field+.cc-field{border-top:.5px solid var(--dsw-alias-border-l2)}
6216
6437
  .cc-fieldHead{align-items:center;gap:8px;display:flex}
6217
6438
  .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
6218
- .cc-badges{align-items:center;gap:8px;display:inline-flex}
6219
- .cc-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6220
- .cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}
6221
- .cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
6439
+ .cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:18px}
6222
6440
  .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6223
- .cc-reset:disabled{cursor:default;opacity:.5}
6224
- .cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}
6441
+ .cc-reset:disabled{cursor:default;opacity:.4}
6442
+ .cc-input{box-sizing:border-box;height:32px;min-width:0;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
6443
+ .cc-input::placeholder{color:var(--dsw-alias-label-dimmed)}
6225
6444
  .cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
6226
- .cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
6227
- /* The routing-rule model multi-select: a button trigger that opens an
6228
- * anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
6229
- * sits flush with the sibling account select. */
6230
- .cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
6231
- .cc-ruleTrigger:disabled{cursor:default}
6232
- .cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6233
- .cc-ruleCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg)}
6445
+ .cc-input:disabled{opacity:.6;cursor:default}
6446
+ .cc-inputInvalid{border-color:var(--dsw-alias-state-error-primary)}
6447
+ .cc-invalid{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
6448
+ .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
6449
+ /* The model multi-select trigger: the platform's selector capsule (the
6450
+ * language / permission pickers on General). */
6451
+ .cc-modelSelectAnchor{flex-direction:column;align-items:stretch;gap:6px;display:inline-flex;min-width:160px}
6452
+ .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}
6453
+ .cc-selector:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
6454
+ .cc-selector:disabled{cursor:default;opacity:.4}
6455
+ .cc-selector:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6456
+ .cc-selectorText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6457
+ .cc-selectorCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-bottom:3px;transform:rotate(45deg)}
6458
+ /* The search box sits inside the Menu anchor, so typing in it never trips
6459
+ * the Menu's outside-click close. */
6460
+ .cc-modelSearch{width:100%}
6461
+ .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
6234
6462
  .cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
6235
6463
  .cc-checkRow:hover{cursor:pointer}
6236
6464
  /* A hand-drawn box rather than the platform's accent-color checkbox: the
@@ -6246,126 +6474,168 @@ window.__ModuleLoader__.load({
6246
6474
  .cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
6247
6475
  .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)}
6248
6476
  .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6249
- /* The model multi-select search box: stacked under the trigger while the
6250
- * dropdown is open, same input sizing so the pair reads as one control. The
6251
- * box lives inside the Menu anchor (which renders inside the Menu's root
6252
- * span), so focusing/typing it never trips the Menu's outside-click close. */
6253
- .cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
6254
- .cc-modelSearch{width:100%}
6255
- .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
6256
- /* Selects need their own treatment to sit flush with the text inputs:
6257
- * the UA stylesheet renders <select> border-box (34px total vs the inputs'
6258
- * 36px) and forces its own menulist text metrics, so drop the native
6259
- * chrome entirely (appearance:none), restore content-box so the outer box
6260
- * matches the inputs again, and draw the chevron ourselves. Longhand
6261
- * background-* only — the shorthand would reset .cc-input's background. */
6262
- select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box-sizing:content-box;padding-right:32px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center}
6263
- .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
6264
- .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
6265
- /* The Advanced card: its header row is one full-width toggle button; the
6266
- * chevron is two borders on a rotated square (no icon dependency). */
6267
- .cc-advancedHead{align-items:center;gap:8px;display:flex;width:100%;padding:12px 0;background:0 0;border:none;cursor:pointer;font:inherit;text-align:left}
6268
- .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
6269
- .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
6270
- .cc-advancedSpacer{flex:1}
6271
- .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:8px;height:8px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg);transition:transform .15s ease}
6272
- .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
6273
- .cc-advancedBody{flex-direction:column;display:flex}
6274
- .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
6275
- .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6276
- .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
6277
- /* Keep the original footer in flow and inside the settings scrollport. */
6278
- .cc-footerMarker{height:1px;margin:-6px 0 -7px;pointer-events:none}
6279
- .cc-footerSticky{position:sticky;bottom:0;z-index:2;flex-shrink:0;min-height:48px;pointer-events:none}
6280
- .cc-footerActions{align-items:center;gap:8px;display:flex;pointer-events:auto}
6281
- .cc-footerFloating .cc-footerActions{padding:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));box-shadow:0 8px 24px rgba(0,0,0,.12)}
6282
- .cc-accountEditor{border-top:1px solid var(--dsw-alias-border-l2)}
6283
- .cc-accountLoginHint{padding:0 0 12px}
6284
- .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
6285
- .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
6477
+ /* The platform SegmentedControl: a translucent track and one raised pill that
6478
+ * slides under the picked segment. Segments are equal grid tracks, so the
6479
+ * indicator's width and offset follow from the count and the index alone. */
6480
+ .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)}
6481
+ .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}
6482
+ .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}
6483
+ .cc-segment:hover:not(:disabled),.cc-segment[aria-checked=true]{color:var(--dsw-alias-label-primary)}
6484
+ .cc-segment:disabled{cursor:default;opacity:.4}
6485
+ .cc-segment:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
6286
6486
  /* Modelled on the platform's own Switch primitive (ui-primitives
6287
- * Switch.module.css, dsh 0.1.6+): a 36x20 capsule that pads its track by 2px
6288
- * and slides a 16px thumb across the 32px content box, so the control is the
6289
- * same shape, size and motion as every other switch in the app.
6487
+ * Switch.module.css): a 36x20 capsule that pads its track by 2px and slides a
6488
+ * 16px thumb across the 32px content box.
6290
6489
  *
6291
6490
  * Three things are load-bearing. (1) corner-shape:round on BOTH the track and
6292
6491
  * the thumb: the theme sets a global superellipse corner shape on every
6293
6492
  * element, and a superellipse capsule squares its ends off around a round
6294
- * thumb (the primitive's own comment is about exactly this).
6295
- * (2) No literal colours. --dsw-alias-brand-primary INVERTS between the two
6296
- * modes (near-black in light, near-white in dark, on every engine generation
6297
- * this plugin supports), so the old hardcoded white thumb vanished on the
6298
- * checked track in dark mode; the brand foreground is the token the platform
6299
- * paints on that fill, so it contrasts in both modes and in any brand pack.
6300
- * (3) --dsw-alias-border-l3 as the off track: --dsw-alias-border-l2
6301
- * (#ffffff1f in dark) left the unchecked track indistinguishable from the card
6302
- * — the dark-mode half of the same report. */
6493
+ * thumb. (2) No literal colours. --dsw-alias-brand-primary INVERTS between the
6494
+ * two modes (near-black in light, near-white in dark), so a hardcoded white
6495
+ * thumb vanishes on the checked track in dark mode; the brand foreground is the
6496
+ * token the platform paints on that fill. (3) --dsw-alias-border-l3 as the off
6497
+ * track: border-l2 (#ffffff1f in dark) leaves it indistinguishable from the
6498
+ * page. */
6303
6499
  .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}
6304
6500
  .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
6305
- .cc-toggle::after{content:'';display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);transition:transform .12s ease}
6501
+ .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}
6306
6502
  /* 32px content box minus the 16px thumb. */
6307
6503
  .cc-toggle:checked::after{transform:translateX(16px)}
6308
6504
  .cc-toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
6309
6505
  .cc-toggle:disabled{cursor:default;opacity:.5}
6310
- .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
6311
- .cc-usageCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;flex-direction:column;gap:12px;display:flex}
6506
+ .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}
6507
+ .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
6508
+ /* Accounts: one outlined card per account, the Models page's provider card. */
6509
+ .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}
6510
+ .cc-accountList{flex-direction:column;gap:8px;display:flex}
6511
+ .cc-accountList+.cc-addButton,.cc-accountList+.cc-addPanel{margin-top:8px}
6512
+ .cc-accountItem{flex-direction:column;gap:10px;display:flex;padding:12px 14px;border:.5px solid var(--dsw-alias-border-l4);border-radius:16px}
6513
+ .cc-accountItemActive{border-color:var(--dsw-static-neutral-bluish-400,var(--dsw-alias-border-l3))}
6514
+ .cc-accountHead{align-items:center;gap:4px;display:flex;min-height:28px}
6515
+ .cc-accountToggle{align-items:center;gap:8px;display:flex;flex:1;min-width:0;padding:0;background:0 0;border:0;color:inherit;cursor:pointer;font:inherit;text-align:left}
6516
+ .cc-accountToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:4px}
6517
+ .cc-accountName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
6518
+ .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}
6519
+ .cc-iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
6520
+ .cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6521
+ /* Three dots from one element: the dot itself plus two box-shadow copies. */
6522
+ .cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
6523
+ .cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
6524
+ .cc-tabDotOk{background:var(--dsw-alias-state-success-primary)}
6525
+ .cc-tabDotWarn{background:var(--dsw-alias-state-warn-primary,#d97706)}
6526
+ .cc-tabDotError{background:var(--dsw-alias-state-error-primary)}
6527
+ .cc-accountMeters{flex-wrap:wrap;gap:6px 20px;display:flex;padding-left:16px}
6528
+ .cc-miniMeter{align-items:center;gap:8px;display:inline-flex;font-size:12px;line-height:18px}
6529
+ .cc-miniMeterLabel{color:var(--dsw-alias-label-tertiary)}
6530
+ .cc-miniMeterTrack{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;width:64px;height:4px}
6531
+ .cc-miniMeterFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%}
6532
+ .cc-miniMeterValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
6533
+ .cc-accountSetup,.cc-loginStatus{flex-direction:column;gap:8px;display:flex;padding-left:16px}
6534
+ .cc-loginStatus{flex-direction:row;flex-wrap:wrap;align-items:center;gap:4px 12px}
6535
+ .cc-loginStatus>p{margin:0;font-size:12px;line-height:18px}
6536
+ .cc-accountDetails{container-type:inline-size;border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;display:flex;padding-top:12px}
6537
+ .cc-inlineForm,.cc-confirmBar{flex-direction:column;gap:8px;display:flex}
6538
+ .cc-inlineActions{align-items:center;gap:8px;display:flex}
6539
+ .cc-confirmBar{border:.5px solid var(--dsw-alias-state-error-primary);border-radius:12px;padding:12px 14px}
6540
+ .cc-confirmText{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:22px}
6541
+ .cc-dangerButton{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
6542
+ .cc-dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
6543
+ /* Adding an account: the Models page's dashed add button, opening a filled
6544
+ * panel in its place. */
6545
+ .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}
6546
+ .cc-addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
6547
+ .cc-addButton:disabled{cursor:default;opacity:.4}
6548
+ .cc-addButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6549
+ /* A plus from two crossed bars. */
6550
+ .cc-addGlyph{position:relative;width:12px;height:12px}
6551
+ .cc-addGlyph::before,.cc-addGlyph::after{content:'';position:absolute;background:currentColor;border-radius:1px}
6552
+ .cc-addGlyph::before{left:0;right:0;top:5.25px;height:1.5px}
6553
+ .cc-addGlyph::after{top:0;bottom:0;left:5.25px;width:1.5px}
6554
+ .cc-addPanel{flex-direction:column;gap:12px;display:flex;padding:14px 16px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
6555
+ .cc-panelTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
6556
+ /* Account report: stat tiles are filled panels, like the platform's editors. */
6557
+ .cc-accountReport{flex-direction:column;gap:14px;display:flex}
6312
6558
  .cc-usageHead{align-items:center;gap:8px;display:flex}
6313
- .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
6314
- .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6315
- .cc-usagePlan{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}
6316
- .cc-usagePlanStatus{white-space:nowrap;color:var(--dsw-alias-label-error);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6317
- .cc-usageRefresh{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
6318
- .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6319
- .cc-usageRefresh:disabled{cursor:default;opacity:.5}
6320
- .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6321
- .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
6559
+ .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:14px;font-weight:500;line-height:22px}
6560
+ .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
6561
+ .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}
6562
+ .cc-usagePlanStatus{flex:none;margin:0;white-space:nowrap;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
6563
+ .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
6564
+ /* Two fixed grids instead of one auto-fit track: auto-fit wrapped the four
6565
+ * activity tiles 3 + 1 at the details' width. Each group fills its own row
6566
+ * (four activity tiles, three balances) and the activity row folds to 2 × 2
6567
+ * in a narrow details panel. */
6322
6568
  .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
6323
- .cc-usageStat{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}
6324
- .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6325
- .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
6326
- .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6327
- .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
6569
+ .cc-usageStatsActivity{grid-template-columns:repeat(4,minmax(0,1fr))}
6570
+ .cc-usageStatsBalance{grid-template-columns:repeat(3,minmax(0,1fr))}
6571
+ @container (max-width:460px){.cc-usageStatsActivity{grid-template-columns:repeat(2,minmax(0,1fr))}}
6572
+ .cc-usageStat{min-width:0;flex-direction:column;gap:2px;display:flex;padding:10px 12px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
6573
+ .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
6574
+ .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px;font-variant-numeric:tabular-nums}
6575
+ .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6576
+ .cc-usageWindows{flex-direction:column;gap:14px;display:flex}
6328
6577
  .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
6329
6578
  .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
6330
- .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
6331
- .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
6332
- .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
6333
- .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
6579
+ .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;line-height:18px}
6580
+ .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
6581
+ .cc-usageExceeded{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
6582
+ .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;height:4px}
6334
6583
  .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
6335
- .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
6336
- @media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle::after,.cc-usageBarFill{transition:none}}.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6584
+ .cc-usageBarFillWarn{background:var(--dsw-alias-state-error-primary)}
6585
+ .cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
6337
6586
  .cc-usageMeta{align-items:center;gap:8px;display:flex}
6338
- .cc-accountReport{flex-direction:column;gap:12px;display:flex}
6339
- .cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
6340
- .cc-tab{align-items:center;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex;gap:6px}
6341
- .cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
6342
- .cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
6343
- .cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
6344
- .cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
6345
- .cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
6346
6587
  .cc-usageMetaSpacer{flex:1}
6347
- .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6348
- .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
6349
- .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
6350
- .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
6351
- .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
6352
- .cc-usageBlockedDetail{color:var(--dsw-alias-label-secondary);margin:0;font-size:11px;line-height:1.5;word-break:break-word;opacity:.85}
6353
- .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6588
+ .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
6589
+ .cc-usagePartial{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
6590
+ .cc-usageBlocked{border:.5px solid var(--dsw-alias-state-error-primary);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:4px}
6591
+ .cc-usageBlockedTitle{color:var(--dsw-alias-state-error-primary);margin:0;font-size:14px;font-weight:500;line-height:22px}
6592
+ .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
6593
+ .cc-usageBlockedDetail{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px;word-break:break-word}
6594
+ .cc-version{margin:32px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
6595
+ /* The update hint rides the footer version line, quiet until hovered. */
6596
+ .cc-versionLink{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary));text-decoration:none}
6597
+ .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
6354
6598
  /* The Models-page provider panel root: unstyled by design — the row card the
6355
6599
  * Models page owns supplies the surface, the panel only stacks its controls. */
6356
6600
  .cc-providerCard{flex-direction:column;display:flex}
6357
- /* The update hint rides the footer version line: warning-tinted (with a
6358
- * muted fallback for themes without the alias), quiet until hovered. */
6359
- .cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
6360
- .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
6361
- /* The login panel rides the connection card as one more field row; the
6362
- * authorization link is the only branded element on it. */
6363
- .cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
6601
+ .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
6602
+ .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}
6603
+ /* The authorization link is the only link-coloured element on a login row. */
6604
+ .cc-loginLink{color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px}
6364
6605
  .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
6365
6606
  .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
6366
- .cc-loginError{color:var(--dsw-alias-label-error)}
6367
- .cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
6368
- .cc-badgeWarn{background:var(--dsw-alias-state-warning-secondary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary))}
6607
+ .cc-loginError{color:var(--dsw-alias-state-error-primary)}
6608
+ /* The floating save bar: a centered capsule pinned to the bottom of the
6609
+ * settings scrollport. The dock is a zero-height sticky strip at the end of
6610
+ * the page, so the bar overlays the scrolling content instead of taking a row
6611
+ * of its own; while it is shown the section reserves room at its end so the
6612
+ * last line can still scroll clear of it. Hidden, it sinks and fades, and
6613
+ * visibility (switched after the transition) takes its buttons out of the tab
6614
+ * order.
6615
+ *
6616
+ * Its surface is an elevated layer (bg-layer-1 over the settings panel's
6617
+ * layer-2) with the platform's prominent elevation and no stroke. The corners
6618
+ * are concentric: a 44px capsule around 36px capsule buttons inset by 4px, so
6619
+ * the gap around each button is even. */
6620
+ .cc-sectionWithBar{padding-bottom:80px}
6621
+ .cc-saveBarDock{position:sticky;bottom:0;z-index:2;height:0;pointer-events:none}
6622
+ .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
+ .cc-saveBarShown{opacity:1;visibility:visible;transform:translate(-50%,0);pointer-events:auto;transition:opacity .2s ease,transform .24s cubic-bezier(.2,.9,.3,1.1),visibility 0s}
6624
+ .cc-saveBar-error{--cc-saveBar-tone:var(--dsw-alias-state-error-primary)}
6625
+ .cc-saveBar-success{--cc-saveBar-tone:var(--dsw-alias-state-success-primary,#16a34a);padding-right:18px}
6626
+ .cc-saveBarIcon{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:16px;height:16px;color:var(--cc-saveBar-tone)}
6627
+ .cc-saveBarPulse{width:8px;height:8px;border-radius:50%;corner-shape:round;background:var(--cc-saveBar-tone)}
6628
+ .cc-saveBarText{min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
6629
+ .cc-saveBar-error .cc-saveBarText{color:var(--dsw-alias-state-error-primary)}
6630
+ .cc-saveBarActions{flex-shrink:0;align-items:center;gap:4px;display:flex;margin-left:8px}
6631
+ .cc-saveBarButton{box-sizing:border-box;height:36px;padding:0 16px;border:0;border-radius:18px;corner-shape:round;font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer}
6632
+ .cc-saveBarButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
6633
+ .cc-saveBarButton:disabled{cursor:default;opacity:.4}
6634
+ .cc-saveBarGhost{background:0 0;color:var(--dsw-alias-label-primary)}
6635
+ .cc-saveBarGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
6636
+ .cc-saveBarPrimary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
6637
+ .cc-saveBarPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
6638
+ @media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown,.cc-segmentIndicator,.cc-segment{transition:none}}
6369
6639
  `;
6370
6640
  //#endregion
6371
6641
  //#region src/client/panel-styles.ts
@@ -6549,7 +6819,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6549
6819
  const zh = {
6550
6820
  nav: "Command Code",
6551
6821
  title: "Command Code",
6552
- intro: "配置 Command Code Provider 连接。API 密钥仅保存在本机凭据服务中,不会回显;其他字段写入用户设置,下次请求即生效。",
6822
+ intro: "账户操作立即生效;其余设置修改后点击底部「保存」。API 密钥仅保存在本机凭据服务中,不会回显。",
6553
6823
  apiKey: "API 密钥",
6554
6824
  apiKeyHint: "在 commandcode.ai 控制台创建。留空保存不会覆盖已存储的密钥。",
6555
6825
  apiKeySet: "已配置",
@@ -6557,8 +6827,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6557
6827
  apiKeyLocked: "密钥由只读来源提供",
6558
6828
  apiBase: "API 地址",
6559
6829
  apiBaseHint: "默认 https://api.commandcode.ai,一般无需修改。",
6560
- workingDir: "工作目录",
6561
- workingDirHint: "可选。留空时使用占位符显示的进程工作目录;仅在需要固定路径时填写。",
6562
6830
  requestTimeoutMs: "请求超时(毫秒)",
6563
6831
  requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
6564
6832
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
@@ -6566,47 +6834,29 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6566
6834
  transportMaxRetries: "网络失败重试次数",
6567
6835
  transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
6568
6836
  advancedSettings: "高级设置",
6569
- advancedSettingsHint: "API 地址、工作目录、超时、模型过滤、联网搜索与侧边栏额度卡片等不常修改的选项。",
6837
+ advancedSettingsHint: "API 地址和网络参数,一般无需修改。",
6570
6838
  advancedOverriddenOne: "已自定义 1 项",
6571
6839
  advancedOverriddenMany: "已自定义 {count} 项",
6572
6840
  advancedInvalid: "高级设置中有未填好的数字,请展开修正后再保存。",
6573
6841
  filterModelsByPlan: "隐藏套餐外模型",
6574
- filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
6842
+ filterModelsByPlanHint: "只显示套餐内模型;按需余额账户仍显示全部。",
6575
6843
  webSearch: "用 Command Code 承载联网搜索",
6576
- webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key 与地址),并优先于其他搜索后端;关闭则把选择权交还给之前的后端(如 modsearch),而不是强制回退到 DeepSeek 搜索。",
6844
+ webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。",
6577
6845
  showSidebarQuota: "在侧边栏显示额度卡片",
6578
- showSidebarQuotaHint: "开启后,侧边栏底部(Settings 上方)显示 Command Code 套餐与配额卡片,点击可在中间栏打开完整仪表盘。默认关闭:左侧不显示任何额度信息,也不会为其后台刷新用量。保存后立即生效。",
6846
+ showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
6579
6847
  commandGuard: "AI 命令安全预判",
6580
- commandGuardHint: "命令确认弹窗前,先由 typesafe/jev 判断它是否安全:安全就自动放行,其余照常弹窗。默认关闭。",
6581
- commandGuardThreshold: "自动放行阈值(0.5–1)",
6582
- commandGuardThresholdHint: "typesafe/jev 判定\"安全\"的概率达到该值即放行,默认 0.9。",
6583
- commandGuardTimeoutMs: "判定超时(毫秒,200–10000)",
6584
- commandGuardTimeoutMsHint: "typesafe/jev 超过该时间未返回就照常弹窗,默认 1500。",
6848
+ commandGuardHint: "命令确认弹窗前,先由 typesafe/jev 判断它是否安全;沙箱提权还会额外判断范围与必要性。三项都足够确定才自动放行,其余照常弹窗。默认关闭。",
6849
+ commandGuardLevel: "自动放行阈值",
6850
+ commandGuardLevelHint: "阈值越高,需要越确定才放行,弹窗越多。高 = 0.95,中 = 0.9(默认),低 = 0.8。判定超过 3 秒未返回会照常弹窗。",
6851
+ commandGuardLevelHigh: "高",
6852
+ commandGuardLevelMedium: "中",
6853
+ commandGuardLevelLow: "低",
6585
6854
  zdr: "零数据保留(ZDR)",
6586
- zdrHint: "开启后,请求只经由不留存提示词与回复、也不用于训练的上游(等价于 CLI 的 CMD_ZDR=1)。没有可用 ZDR 上游的模型会返回错误,请换用其他模型或关闭此开关。ZDR 容量按上游实价透传,通常更贵;费用读数仍按常规目录价估算。默认关闭。",
6587
- accountsTitle: "多账户轮换",
6588
- accountsHint: "当前账户达到用量限额(429)或密钥失效(401)时,请求自动切换到下一个账户;全部耗尽时会提示最早的重置时间。",
6855
+ zdrHint: "仅使用不留存、不训练的上游;可用模型可能更少且价格通常更高。",
6856
+ accountsTitle: "账户",
6857
+ accountsHint: "账户操作立即生效,无需保存。可以添加多个账户:当前账户达到用量限额或密钥失效时,请求会自动切换到下一个。",
6589
6858
  accountAdd: "添加账户",
6590
- accountRemove: "移除",
6591
- accountLabel: "账户备注名",
6592
- accountKey: "API 密钥",
6593
- accountKeyHint: "该账户的 API 密钥。留空保存不会覆盖已存储的密钥。",
6594
- accountLoginAfterSave: "先点击页面底部的「保存」,再点击该账户的「登录 Command Code」通过网页获取密钥。",
6595
6859
  accountDefault: "默认账户",
6596
- activeAccount: "当前使用账户",
6597
- activeAccountAuto: "自动(第一个可用账户)",
6598
- activeAccountHint: "手动指定优先使用的账户,保存后下次请求即生效;所选账户耗尽时仍会自动切换到其他可用账户。",
6599
- rulesTitle: "按模型切换账户",
6600
- rulesHint: "选择模型并路由到某个账户(可多选)。命中规则的模型且该账户可用时优先使用;账户耗尽或密钥失效时仍自动回落到其他账户。规则按列表顺序匹配,第一条命中生效。",
6601
- rulesEmpty: "尚未配置规则。",
6602
- rulesCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的规则仍会生效。",
6603
- ruleAdd: "添加规则",
6604
- ruleRemove: "移除",
6605
- ruleModel: "模型",
6606
- ruleModelPick: "选择模型…",
6607
- ruleModelCount: "已选 {count} 个模型",
6608
- ruleAccount: "目标账户",
6609
- ruleHint: "从下拉列表勾选要路由的模型(可多选),再选择目标账户。",
6610
6860
  modelSearchPlaceholder: "搜索模型…",
6611
6861
  modelSearchEmpty: "没有匹配的模型。",
6612
6862
  modelStale: "已下架",
@@ -6623,20 +6873,19 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6623
6873
  numberTooSmall: "不能小于 1(毫秒)",
6624
6874
  numberTooLarge: "超出允许上限(2147483647 毫秒)",
6625
6875
  readOnly: "当前配置为只读。",
6626
- unsaved: "未保存",
6627
6876
  save: "保存",
6628
6877
  saving: "保存中",
6629
- saved: "已保存 ✓",
6878
+ saved: "设置已保存",
6630
6879
  saveFailed: "保存失败,请重试。",
6880
+ unsavedChanges: "有未保存的更改",
6881
+ saveInvalid: "有字段填写不正确,修正后才能保存",
6631
6882
  discard: "放弃",
6632
6883
  cancel: "取消",
6633
6884
  show: "显示",
6634
6885
  hide: "隐藏",
6635
- usageTitle: "账户用量",
6636
6886
  usageRefresh: "刷新",
6637
6887
  usageRefreshing: "刷新中…",
6638
6888
  usageLoading: "正在获取账户用量…",
6639
- usageNoKey: "配置 API 密钥后,这里会显示账户的用量与额度状态。",
6640
6889
  usageError: "用量获取失败",
6641
6890
  usageRequests: "请求",
6642
6891
  usageFailed: "失败",
@@ -6650,12 +6899,10 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6650
6899
  usageFree: "赠送",
6651
6900
  usageFiveHour: "5 小时窗口",
6652
6901
  usageWeekly: "每周窗口",
6902
+ usageMonthlyLeft: "月额度剩余",
6653
6903
  usageExceeded: "已超限",
6654
6904
  usageReset: "重置于",
6655
6905
  usagePartial: "部分端点数据不可用",
6656
- usageKeyClear: "清除已存密钥",
6657
- usageKeyClearStaged: "将清除(保存后生效)",
6658
- usageUndoKeyClear: "撤销清除",
6659
6906
  usageKeyInvalid: "API 密钥无效或已过期",
6660
6907
  usageKeyInvalidHint: "服务端拒绝了全部请求(401)。请检查该账户配置的密钥,或到 commandcode.ai 控制台重新生成。",
6661
6908
  usageServiceUnavailable: "Command Code 服务暂时不可用",
@@ -6673,8 +6920,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6673
6920
  updateAvailable: "可更新",
6674
6921
  updateHint: "已发布新版本,点击查看发布说明;更新插件后刷新本页,提示会自动消失。",
6675
6922
  loginTitle: "通过官方登录获取密钥",
6676
- loginHintIdle: "不想手动创建密钥?点击登录后浏览器会打开 commandcode.ai 授权页,完成后密钥自动写入本机凭据服务,下次请求即生效。",
6677
- loginSaveBefore: "请先保存本页更改,再通过网页登录此账户。",
6923
+ loginHintIdle: "也可通过网页授权自动获取并保存密钥。",
6678
6924
  loginButton: "登录 Command Code",
6679
6925
  loginStarting: "正在启动本地回调服务…",
6680
6926
  loginWaiting: "等待在浏览器中完成授权…",
@@ -6692,12 +6938,47 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6692
6938
  cardTitle: "Command Code",
6693
6939
  cardRouteActive: "已启用",
6694
6940
  cardLoadingHint: "正在读取 Command Code 配置…",
6695
- cardRegistrationHint: "此卡片由 Command Code 插件提供,正在等待设置页装配,稍后即可显示完整内容。"
6941
+ cardRegistrationHint: "此卡片由 Command Code 插件提供,正在等待设置页装配,稍后即可显示完整内容。",
6942
+ accountsRotationHint: "账户操作立即生效。当前账户达到用量限额或密钥失效时,请求会按列表顺序自动切换到下一个可用账户。",
6943
+ accountModeAuto: "使用方式:自动切换(按列表顺序)",
6944
+ accountModePinned: "使用方式:固定使用「{name}」,不可用时仍会自动切换",
6945
+ accountStatusPinned: "已固定",
6946
+ accountActions: "账户操作",
6947
+ accountActionPin: "固定使用此账户",
6948
+ accountActionUnpin: "恢复自动切换",
6949
+ accountActionLogin: "网页登录获取新密钥",
6950
+ accountActionKey: "粘贴新密钥",
6951
+ accountActionRename: "重命名",
6952
+ accountActionClearKey: "删除密钥",
6953
+ accountActionRemove: "移除账户",
6954
+ accountRemoveConfirm: "移除「{name}」?它保存的密钥和专用模型设置会一起删除。",
6955
+ accountClearKeyConfirm: "删除默认账户保存的密钥?之后会回退到环境变量或官方 CLI 的登录信息。",
6956
+ accountConfirmRemove: "移除",
6957
+ accountConfirmClear: "删除",
6958
+ accountApply: "确定",
6959
+ accountNoKeyHint: "还没有密钥:通过网页登录自动获取,或粘贴在 commandcode.ai 控制台创建的 API 密钥。",
6960
+ accountAddHint: "网页登录会打开 Command Code 授权页,授权完成后账户自动添加;也可以直接粘贴 API 密钥。",
6961
+ accountAddLogin: "网页登录",
6962
+ accountAddPaste: "粘贴密钥",
6963
+ accountAddConfirm: "添加",
6964
+ accountNamePlaceholder: "备注名(可选,网页登录时默认使用账户名)",
6965
+ accountNameN: "账户 {n}",
6966
+ accountKeyPlaceholder: "粘贴 API 密钥",
6967
+ accountOpFailed: "账户操作未能完成,请重试。",
6968
+ accountModels: "专用模型",
6969
+ accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
6970
+ accountModelOwner: "属于 {name}",
6971
+ modelPick: "选择模型…",
6972
+ modelCount: "已选 {count} 个模型",
6973
+ modelCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的设置仍会生效。",
6974
+ modelsTitle: "模型",
6975
+ privacyTitle: "隐私与安全",
6976
+ integrationsTitle: "集成与显示"
6696
6977
  };
6697
6978
  const en = {
6698
6979
  nav: "Command Code",
6699
6980
  title: "Command Code",
6700
- intro: "Configure the Command Code Provider connection. The API key is stored only in the local credential service and never echoed; other fields are written to user settings and take effect on the next request.",
6981
+ intro: "Account actions apply immediately; other settings are written by Save at the bottom. The API key stays in the local credential service and is never echoed.",
6701
6982
  apiKey: "API key",
6702
6983
  apiKeyHint: "Create one in the commandcode.ai console. Saving with this field blank keeps the stored key.",
6703
6984
  apiKeySet: "Configured",
@@ -6705,8 +6986,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6705
6986
  apiKeyLocked: "Key provided by a read-only source",
6706
6987
  apiBase: "API base URL",
6707
6988
  apiBaseHint: "Defaults to https://api.commandcode.ai; usually leave as-is.",
6708
- workingDir: "Working directory",
6709
- workingDirHint: "Optional. Leave blank to use the process cwd shown as the placeholder; fill in only to pin a specific path.",
6710
6989
  requestTimeoutMs: "Request timeout (ms)",
6711
6990
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
6712
6991
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
@@ -6714,47 +6993,29 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6714
6993
  transportMaxRetries: "Transport retries",
6715
6994
  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.",
6716
6995
  advancedSettings: "Advanced",
6717
- advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, model filtering, web search, and the sidebar quota card.",
6996
+ advancedSettingsHint: "API base URL and network limits; usually leave as-is.",
6718
6997
  advancedOverriddenOne: "1 customized",
6719
6998
  advancedOverriddenMany: "{count} customized",
6720
6999
  advancedInvalid: "A number in Advanced settings is not ready to save; expand to fix it.",
6721
7000
  filterModelsByPlan: "Hide out-of-plan models",
6722
- filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
7001
+ filterModelsByPlanHint: "Show only models in your plan; on-demand credit accounts still see the full catalog.",
6723
7002
  webSearch: "Serve dsh web search with Command Code",
6724
- webSearchHint: "When on, the model-facing web_search tool is backed by Command Code (same API key and base URL as chat), winning over other search backends. Off hands the selection back to the previous backend (e.g. modsearch) instead of forcing the shipped DeepSeek search.",
7003
+ webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend.",
6725
7004
  showSidebarQuota: "Show the quota card in the sidebar",
6726
- showSidebarQuotaHint: "When on, a Command Code plan & quota card sits at the bottom of the sidebar (above Settings) and opens the full dashboard in the centre column. Off by default: nothing is shown on the left, and no background usage poll runs for it. Applies on save.",
7005
+ 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.",
6727
7006
  commandGuard: "AI command guard",
6728
- commandGuardHint: "Before a shell command asks for approval, typesafe/jev judges whether it is safe: safe commands run without a prompt, everything else asks as usual. Off by default.",
6729
- commandGuardThreshold: "Auto-approve threshold (0.5–1)",
6730
- commandGuardThresholdHint: "Approve when typesafe/jev rates the command \"safe\" at least this likely; default 0.9.",
6731
- commandGuardTimeoutMs: "Judgement timeout (ms, 200–10000)",
6732
- commandGuardTimeoutMsHint: "If typesafe/jev does not answer in time, the normal prompt appears; default 1500.",
7007
+ 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.",
7008
+ commandGuardLevel: "Auto-approve threshold",
7009
+ 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.",
7010
+ commandGuardLevelHigh: "High",
7011
+ commandGuardLevelMedium: "Medium",
7012
+ commandGuardLevelLow: "Low",
6733
7013
  zdr: "Zero data retention (ZDR)",
6734
- zdrHint: "Serve requests only through upstreams that keep no prompts or completions and never train on them (the same opt-in as the CLI's CMD_ZDR=1). A few models have no ZDR upstream; requests for those models fail rather than losing ZDR protection. ZDR capacity is billed at the upstream's pass-through rates and usually costs more; the cost readout keeps quoting the ordinary catalog rates. Off by default.",
6735
- accountsTitle: "Account rotation",
6736
- accountsHint: "When the active account hits its usage limit (429) or its key fails (401), requests switch to the next account; when every account is exhausted the error names the earliest window reset.",
7014
+ zdrHint: "Use only upstreams that retain no data and do not train on requests; availability and pricing may differ.",
7015
+ accountsTitle: "Accounts",
7016
+ accountsHint: "Account actions apply immediately, no save needed. Add more accounts and requests switch to the next one when the current account hits its usage limit or its key stops working.",
6737
7017
  accountAdd: "Add account",
6738
- accountRemove: "Remove",
6739
- accountLabel: "Account label",
6740
- accountKey: "API key",
6741
- accountKeyHint: "This account’s API key. Saving with the field blank keeps the stored key.",
6742
- accountLoginAfterSave: "Use Save at the bottom of the page, then sign in to this account in your browser to obtain a key.",
6743
7018
  accountDefault: "Default account",
6744
- activeAccount: "Active account",
6745
- activeAccountAuto: "Auto (first usable account)",
6746
- activeAccountHint: "Pin the preferred account; applies to the next request after saving. If the selected account is exhausted, requests still rotate to another usable account.",
6747
- rulesTitle: "Route models to accounts",
6748
- rulesHint: "Pick models (multi-select) and route them to an account. When the request’s model is in a rule and that account is usable, it serves; an exhausted or invalid routed account falls back to the normal rotation. Rules match in list order — the first hit wins.",
6749
- rulesEmpty: "No rules yet.",
6750
- rulesCatalogFailed: "Could not load the model catalog — selecting models is unavailable; saved rules still apply.",
6751
- ruleAdd: "Add rule",
6752
- ruleRemove: "Remove",
6753
- ruleModel: "Models",
6754
- ruleModelPick: "Select models…",
6755
- ruleModelCount: "{count} model(s) selected",
6756
- ruleAccount: "Target account",
6757
- ruleHint: "Check the models to route from the dropdown (multi-select), then pick the target account.",
6758
7019
  modelSearchPlaceholder: "Search models…",
6759
7020
  modelSearchEmpty: "No matching models.",
6760
7021
  modelStale: "Retired",
@@ -6771,20 +7032,19 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6771
7032
  numberTooSmall: "Must be at least 1 (ms)",
6772
7033
  numberTooLarge: "Above the allowed maximum (2147483647 ms)",
6773
7034
  readOnly: "Settings are read-only.",
6774
- unsaved: "Unsaved",
6775
7035
  save: "Save",
6776
7036
  saving: "Saving",
6777
- saved: "Saved ✓",
7037
+ saved: "Settings saved",
6778
7038
  saveFailed: "Save failed, please retry.",
7039
+ unsavedChanges: "You have unsaved changes",
7040
+ saveInvalid: "Fix the highlighted field before saving",
6779
7041
  discard: "Discard",
6780
7042
  cancel: "Cancel",
6781
7043
  show: "Show",
6782
7044
  hide: "Hide",
6783
- usageTitle: "Account usage",
6784
7045
  usageRefresh: "Refresh",
6785
7046
  usageRefreshing: "Refreshing…",
6786
7047
  usageLoading: "Fetching account usage…",
6787
- usageNoKey: "Configure an API key to see this account’s usage and credit state here.",
6788
7048
  usageError: "Could not fetch usage",
6789
7049
  usageRequests: "Requests",
6790
7050
  usageFailed: "failed",
@@ -6798,12 +7058,10 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6798
7058
  usageFree: "Free",
6799
7059
  usageFiveHour: "5-hour window",
6800
7060
  usageWeekly: "Weekly window",
7061
+ usageMonthlyLeft: "Monthly left",
6801
7062
  usageExceeded: "Exceeded",
6802
7063
  usageReset: "Resets",
6803
7064
  usagePartial: "Some endpoint data unavailable",
6804
- usageKeyClear: "Clear stored key",
6805
- usageKeyClearStaged: "Will be cleared on save",
6806
- usageUndoKeyClear: "Undo clear",
6807
7065
  usageKeyInvalid: "API key invalid or expired",
6808
7066
  usageKeyInvalidHint: "The server rejects every request (401). Check the key configured for this account, or generate a new one in the commandcode.ai console.",
6809
7067
  usageServiceUnavailable: "The Command Code service is temporarily unavailable",
@@ -6821,8 +7079,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6821
7079
  updateAvailable: "update available",
6822
7080
  updateHint: "A newer version has been published; click for release notes. The notice disappears once the plugin is updated.",
6823
7081
  loginTitle: "Sign in to fetch a key",
6824
- loginHintIdle: "Rather not create a key by hand? Sign in and your browser opens the commandcode.ai authorization page; the approved key is stored in the local credential service and applies to the next request.",
6825
- loginSaveBefore: "Save page changes before signing in to this account.",
7082
+ loginHintIdle: "Alternatively, sign in to fetch and store a key automatically.",
6826
7083
  loginButton: "Sign in to Command Code",
6827
7084
  loginStarting: "Starting the local callback server…",
6828
7085
  loginWaiting: "Waiting for authorization in your browser…",
@@ -6840,7 +7097,42 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6840
7097
  cardTitle: "Command Code",
6841
7098
  cardRouteActive: "Active",
6842
7099
  cardLoadingHint: "Loading the Command Code configuration…",
6843
- cardRegistrationHint: "This card is contributed by the Command Code plugin and is waiting for the settings page to finish assembling."
7100
+ cardRegistrationHint: "This card is contributed by the Command Code plugin and is waiting for the settings page to finish assembling.",
7101
+ accountsRotationHint: "Account actions apply immediately. When the current account hits its usage limit or its key stops working, requests switch to the next usable account in list order.",
7102
+ accountModeAuto: "Mode: switch automatically (list order)",
7103
+ accountModePinned: "Mode: pinned to \"{name}\", still switching when it is unavailable",
7104
+ accountStatusPinned: "Pinned",
7105
+ accountActions: "Account actions",
7106
+ accountActionPin: "Pin this account",
7107
+ accountActionUnpin: "Switch automatically",
7108
+ accountActionLogin: "Sign in for a new key",
7109
+ accountActionKey: "Paste a new key",
7110
+ accountActionRename: "Rename",
7111
+ accountActionClearKey: "Delete key",
7112
+ accountActionRemove: "Remove account",
7113
+ accountRemoveConfirm: "Remove \"{name}\"? Its stored key and dedicated models are deleted with it.",
7114
+ accountClearKeyConfirm: "Delete the default account's stored key? The provider then falls back to the environment or the official CLI sign-in.",
7115
+ accountConfirmRemove: "Remove",
7116
+ accountConfirmClear: "Delete",
7117
+ accountApply: "Apply",
7118
+ accountNoKeyHint: "No key yet: sign in through the browser, or paste an API key created in the commandcode.ai console.",
7119
+ accountAddHint: "Sign-in opens the Command Code authorization page and adds the account once you approve; you can also paste an API key.",
7120
+ accountAddLogin: "Sign in",
7121
+ accountAddPaste: "Paste key",
7122
+ accountAddConfirm: "Add",
7123
+ accountNamePlaceholder: "Name (optional; sign-in uses the account name)",
7124
+ accountNameN: "Account {n}",
7125
+ accountKeyPlaceholder: "Paste an API key",
7126
+ accountOpFailed: "The account change did not complete. Try again.",
7127
+ accountModels: "Dedicated models",
7128
+ accountModelsHint: "These models prefer this account and still switch when it is unavailable. A model belongs to one account; selecting it here moves it from any other.",
7129
+ accountModelOwner: "on {name}",
7130
+ modelPick: "Choose models…",
7131
+ modelCount: "{count} models selected",
7132
+ modelCatalogFailed: "The model catalog could not be loaded; saved settings still apply.",
7133
+ modelsTitle: "Models",
7134
+ privacyTitle: "Privacy & security",
7135
+ integrationsTitle: "Integrations & display"
6844
7136
  };
6845
7137
  //#endregion
6846
7138
  //#region src/client/index.ts
@@ -7055,6 +7347,10 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
7055
7347
  lastLoginPhase = phase;
7056
7348
  loginStore.set(loginController.state());
7057
7349
  });
7350
+ const refreshUsageOn = (ok) => {
7351
+ if (ok) usageController.refresh();
7352
+ return ok;
7353
+ };
7058
7354
  const injected = () => ({
7059
7355
  hooks: {
7060
7356
  commandCodeSettings: store,
@@ -7071,15 +7367,16 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
7071
7367
  refreshUsage: () => void usageController.refresh(),
7072
7368
  beginLogin: (targetRef) => void loginController.begin(targetRef),
7073
7369
  cancelLogin: () => void loginController.cancel(),
7074
- addAccount: () => controller.addAccount(),
7075
- removeAccount: (id) => controller.removeAccount(id),
7076
- editAccountLabel: (id, text) => controller.editAccountLabel(id, text),
7077
- editAccountKey: (id, text) => controller.editAccountKey(id, text),
7078
- toggleKeyClear: (id) => controller.toggleKeyClear(id),
7079
- addRule: () => controller.addRule(),
7080
- removeRule: (id) => controller.removeRule(id),
7081
- editRuleModels: (id, ids) => controller.editRuleModels(id, ids),
7082
- editRuleAccount: (id, text) => controller.editRuleAccount(id, text),
7370
+ createAccount: (input) => controller.createAccount(input).then((ref) => {
7371
+ if (ref !== void 0 && input.key) usageController.refresh();
7372
+ return ref;
7373
+ }),
7374
+ renameAccount: (ref, label) => controller.renameAccount(ref, label),
7375
+ removeAccount: (ref) => controller.removeAccount(ref).then(refreshUsageOn),
7376
+ setAccountKey: (target, key) => controller.setAccountKey(target, key).then(refreshUsageOn),
7377
+ clearAccountKey: (target) => controller.clearAccountKey(target).then(refreshUsageOn),
7378
+ setActiveAccount: (id) => controller.setActiveAccount(id).then(refreshUsageOn),
7379
+ setAccountModels: (target, ids) => controller.setAccountModels(target, ids),
7083
7380
  editVisibleModels: (ids) => controller.editVisibleModels(ids),
7084
7381
  clearVisibleModels: () => controller.clearVisibleModels()
7085
7382
  });