@mars-sea/dsh-commandcode-provider 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +8 -4
- package/README.zh-CN.md +8 -4
- package/lib/client.js +519 -144
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +145 -31
- package/lib/index.js +991 -432
- package/lib/index.js.map +1 -1
- package/package.json +8 -1
package/lib/client.js
CHANGED
|
@@ -66,8 +66,8 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
exceededWarning: " ⚠️ 超限!",
|
|
67
67
|
resetSuffix: "重置 {when}",
|
|
68
68
|
partialFailures: "⚠️ 部分端点失败: {list}",
|
|
69
|
-
noData: "
|
|
70
|
-
errorText: "
|
|
69
|
+
noData: "(无数据 — 请检查 API 密钥)",
|
|
70
|
+
errorText: "获取 Command Code 用量失败:{message}",
|
|
71
71
|
imageGate: "当前会话已包含图片,而模型 {model} 不支持图片输入;请选择支持图片的模型,或先移除会话中的图片。"
|
|
72
72
|
},
|
|
73
73
|
en: {
|
|
@@ -343,9 +343,17 @@ window.__ModuleLoader__.load({
|
|
|
343
343
|
addedRules = [];
|
|
344
344
|
/** Staged edits to stored routing rules, by stored row id. */
|
|
345
345
|
ruleDrafts = /* @__PURE__ */ new Map();
|
|
346
|
-
/**
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Stored routing rule rows staged for removal, by stored row id with a
|
|
348
|
+
* content snapshot. The snapshot makes reconcile content-based: stored
|
|
349
|
+
* row ids are positional (`rule-N`) and shift after any write, so an
|
|
350
|
+
* id-only check would misread a landed removal as pending (and a retry
|
|
351
|
+
* would delete the wrong row).
|
|
352
|
+
*/
|
|
353
|
+
removedRuleIds = /* @__PURE__ */ new Map();
|
|
354
|
+
/** Staged visible-model allowlist (undefined = no draft). */
|
|
355
|
+
visibleModelsDraft = void 0;
|
|
356
|
+
/** The catalog the model editors offer (Host-side). */
|
|
349
357
|
catalogModels = [];
|
|
350
358
|
catalogFailed = false;
|
|
351
359
|
saving = false;
|
|
@@ -398,8 +406,8 @@ window.__ModuleLoader__.load({
|
|
|
398
406
|
const snapshot = this.scope.getSnapshot();
|
|
399
407
|
const named = typeof snapshot.value?.apiKeyEnv === "string" && snapshot.value.apiKeyEnv.length > 0 ? snapshot.value.apiKeyEnv : DEFAULT_API_KEY_REF;
|
|
400
408
|
if (named === this.credentialRef) return;
|
|
409
|
+
this.credentialStates.delete(this.credentialRef);
|
|
401
410
|
this.credentialRef = named;
|
|
402
|
-
this.credentialStates.delete(named);
|
|
403
411
|
}
|
|
404
412
|
/** Subscribe to state projections. @returns the disposer. */
|
|
405
413
|
subscribe(listener) {
|
|
@@ -437,9 +445,10 @@ window.__ModuleLoader__.load({
|
|
|
437
445
|
accounts,
|
|
438
446
|
accountsRemoving: [...this.removedRefs],
|
|
439
447
|
rules: this.effectiveRules(),
|
|
448
|
+
visibleModels: this.effectiveVisibleModels(),
|
|
440
449
|
catalogModels: this.catalogModels,
|
|
441
450
|
catalogFailed: this.catalogFailed,
|
|
442
|
-
dirty: plan.length > 0 || this.accountsDirty() || this.rulesDirty(),
|
|
451
|
+
dirty: plan.length > 0 || this.accountsDirty() || this.rulesDirty() || this.visibleModelsDirty(),
|
|
443
452
|
invalid: plan.some((item) => item.run === void 0),
|
|
444
453
|
saving: this.saving,
|
|
445
454
|
failed: this.failed,
|
|
@@ -525,7 +534,16 @@ window.__ModuleLoader__.load({
|
|
|
525
534
|
removeRule(id) {
|
|
526
535
|
const addedIndex = this.addedRules.findIndex((_, index) => `new-${index}` === id);
|
|
527
536
|
if (addedIndex >= 0) this.addedRules.splice(addedIndex, 1);
|
|
528
|
-
else
|
|
537
|
+
else {
|
|
538
|
+
const stored = this.storedRules().find((rule) => rule.id === id);
|
|
539
|
+
this.removedRuleIds.set(id, stored === void 0 ? {
|
|
540
|
+
models: [],
|
|
541
|
+
account: ""
|
|
542
|
+
} : {
|
|
543
|
+
models: [...stored.models],
|
|
544
|
+
account: stored.account
|
|
545
|
+
});
|
|
546
|
+
}
|
|
529
547
|
this.ruleDrafts.delete(id);
|
|
530
548
|
this.failed = false;
|
|
531
549
|
this.publish();
|
|
@@ -598,10 +616,11 @@ window.__ModuleLoader__.load({
|
|
|
598
616
|
}
|
|
599
617
|
/** Discard every staged edit. */
|
|
600
618
|
discard() {
|
|
601
|
-
if (this.staged.size === 0 && !this.accountsStaged() && !this.rulesStaged() && !this.failed) return;
|
|
619
|
+
if (this.staged.size === 0 && !this.accountsStaged() && !this.rulesStaged() && !this.visibleModelsStaged() && !this.failed) return;
|
|
602
620
|
this.staged.clear();
|
|
603
621
|
this.clearAccountStaging();
|
|
604
622
|
this.clearRuleStaging();
|
|
623
|
+
this.clearVisibleModelsStaging();
|
|
605
624
|
this.failed = false;
|
|
606
625
|
this.publish();
|
|
607
626
|
}
|
|
@@ -618,7 +637,8 @@ window.__ModuleLoader__.load({
|
|
|
618
637
|
const plan = this.plan();
|
|
619
638
|
const accountRuns = this.accountPlan();
|
|
620
639
|
const ruleRuns = this.rulesPlan();
|
|
621
|
-
|
|
640
|
+
const visibleRuns = this.visibleModelsPlan();
|
|
641
|
+
if (plan.length === 0 && accountRuns.length === 0 && ruleRuns.length === 0 && visibleRuns.length === 0 || this.saving) return;
|
|
622
642
|
const runs = [];
|
|
623
643
|
for (const item of plan) {
|
|
624
644
|
if (item.run === void 0) return;
|
|
@@ -631,10 +651,19 @@ window.__ModuleLoader__.load({
|
|
|
631
651
|
for (const run of [
|
|
632
652
|
...runs,
|
|
633
653
|
...accountRuns,
|
|
634
|
-
...ruleRuns
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
654
|
+
...ruleRuns,
|
|
655
|
+
...visibleRuns
|
|
656
|
+
]) {
|
|
657
|
+
let ok = false;
|
|
658
|
+
try {
|
|
659
|
+
ok = await run();
|
|
660
|
+
} catch {
|
|
661
|
+
ok = false;
|
|
662
|
+
}
|
|
663
|
+
if (!ok) {
|
|
664
|
+
landed = false;
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
638
667
|
}
|
|
639
668
|
this.saving = false;
|
|
640
669
|
this.failed = !landed;
|
|
@@ -643,9 +672,11 @@ window.__ModuleLoader__.load({
|
|
|
643
672
|
this.staged.clear();
|
|
644
673
|
this.clearAccountStaging();
|
|
645
674
|
this.clearRuleStaging();
|
|
675
|
+
this.clearVisibleModelsStaging();
|
|
646
676
|
} else {
|
|
647
677
|
this.reconcileAccountStaging();
|
|
648
678
|
this.reconcileRuleStaging();
|
|
679
|
+
this.reconcileVisibleModelsStaging();
|
|
649
680
|
}
|
|
650
681
|
this.publish();
|
|
651
682
|
}
|
|
@@ -798,10 +829,10 @@ window.__ModuleLoader__.load({
|
|
|
798
829
|
if (changed) this.publish();
|
|
799
830
|
}
|
|
800
831
|
/**
|
|
801
|
-
* Fetch the model catalog for the
|
|
802
|
-
* Remote. Runs once at construction; call again (e.g. from the
|
|
803
|
-
* once the Remote mount lands) to (re)try — a later success
|
|
804
|
-
* failure flag so the
|
|
832
|
+
* Fetch the model catalog for the settings page's model editors through
|
|
833
|
+
* the Host Remote. Runs once at construction; call again (e.g. from the
|
|
834
|
+
* client entry once the Remote mount lands) to (re)try — a later success
|
|
835
|
+
* clears a prior failure flag so the editors recover without a page reload.
|
|
805
836
|
*/
|
|
806
837
|
refreshCatalog() {
|
|
807
838
|
const models = this.api.models;
|
|
@@ -812,7 +843,18 @@ window.__ModuleLoader__.load({
|
|
|
812
843
|
}
|
|
813
844
|
models().then((response) => {
|
|
814
845
|
if (response.ok && Array.isArray(response.value?.models)) {
|
|
815
|
-
|
|
846
|
+
const shaped = [];
|
|
847
|
+
for (const model of response.value.models) {
|
|
848
|
+
if (typeof model !== "object" || model === null) continue;
|
|
849
|
+
const entry = model;
|
|
850
|
+
if (typeof entry.id !== "string" || typeof entry.name !== "string") continue;
|
|
851
|
+
shaped.push({
|
|
852
|
+
id: entry.id,
|
|
853
|
+
name: entry.name,
|
|
854
|
+
...typeof entry.tier === "string" ? { tier: entry.tier } : {}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
this.catalogModels = shaped;
|
|
816
858
|
this.catalogFailed = false;
|
|
817
859
|
} else this.catalogFailed = true;
|
|
818
860
|
}, () => {
|
|
@@ -978,11 +1020,70 @@ window.__ModuleLoader__.load({
|
|
|
978
1020
|
this.ruleDrafts.clear();
|
|
979
1021
|
this.removedRuleIds.clear();
|
|
980
1022
|
}
|
|
981
|
-
/**
|
|
1023
|
+
/**
|
|
1024
|
+
* Drop rule staging the stored section already reflects (partial-save
|
|
1025
|
+
* retry). Stored row ids are positional (`rule-N`) and shift after any
|
|
1026
|
+
* write, so every check here is content-based, never id-based:
|
|
1027
|
+
* - landed additions (in `addedRules`, already in stored) are dropped, or
|
|
1028
|
+
* a retry would persist them twice;
|
|
1029
|
+
* - staged removals whose snapshot row is gone from stored have landed
|
|
1030
|
+
* (drop them); a removal whose snapshot still matches a stored row is
|
|
1031
|
+
* still pending (keep it).
|
|
1032
|
+
*/
|
|
982
1033
|
reconcileRuleStaging() {
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
for (const id of [...this.
|
|
1034
|
+
const stored = this.storedRules();
|
|
1035
|
+
this.addedRules = this.addedRules.filter((added) => !stored.some((rule) => sameModels(added.models, rule.models) && added.account === rule.account));
|
|
1036
|
+
for (const [id, snapshot] of [...this.removedRuleIds]) if (!stored.some((rule) => sameModels(snapshot.models, rule.models) && snapshot.account === rule.account)) this.removedRuleIds.delete(id);
|
|
1037
|
+
this.ruleDrafts.clear();
|
|
1038
|
+
}
|
|
1039
|
+
/** The stored visible-model allowlist (`visibleModels`); empty = show all. */
|
|
1040
|
+
storedVisibleModels() {
|
|
1041
|
+
const raw = this.scope.getSnapshot().value?.visibleModels;
|
|
1042
|
+
if (!Array.isArray(raw)) return [];
|
|
1043
|
+
return raw.filter((m) => typeof m === "string" && m !== "");
|
|
1044
|
+
}
|
|
1045
|
+
/** Effective visible-model allowlist: staged draft or stored value. */
|
|
1046
|
+
effectiveVisibleModels() {
|
|
1047
|
+
return this.visibleModelsDraft ?? this.storedVisibleModels();
|
|
1048
|
+
}
|
|
1049
|
+
/** Whether the staged visible-model selection differs from stored. */
|
|
1050
|
+
visibleModelsDirty() {
|
|
1051
|
+
return this.visibleModelsDraft !== void 0 && !sameModels(this.visibleModelsDraft, this.storedVisibleModels());
|
|
1052
|
+
}
|
|
1053
|
+
/** Whether any visible-model staging exists. */
|
|
1054
|
+
visibleModelsStaged() {
|
|
1055
|
+
return this.visibleModelsDraft !== void 0;
|
|
1056
|
+
}
|
|
1057
|
+
/** Reset the visible-model staged edit. */
|
|
1058
|
+
clearVisibleModelsStaging() {
|
|
1059
|
+
this.visibleModelsDraft = void 0;
|
|
1060
|
+
}
|
|
1061
|
+
/** Drop visible-model staging the stored section already reflects. */
|
|
1062
|
+
reconcileVisibleModelsStaging() {
|
|
1063
|
+
if (this.visibleModelsDraft !== void 0 && sameModels(this.visibleModelsDraft, this.storedVisibleModels())) this.visibleModelsDraft = void 0;
|
|
1064
|
+
}
|
|
1065
|
+
/** The visible-model writes a save performs (empty when nothing staged). */
|
|
1066
|
+
visibleModelsPlan() {
|
|
1067
|
+
if (!this.visibleModelsDirty()) return [];
|
|
1068
|
+
return [() => this.writeVisibleModels()];
|
|
1069
|
+
}
|
|
1070
|
+
/** Persist the staged visible-model allowlist into the settings section. */
|
|
1071
|
+
async writeVisibleModels() {
|
|
1072
|
+
const list = this.visibleModelsDraft ?? [];
|
|
1073
|
+
await this.scope.set("visibleModels", list);
|
|
1074
|
+
return sameModels(this.storedVisibleModels(), list);
|
|
1075
|
+
}
|
|
1076
|
+
/** Stage the visible-model allowlist (multi-select). */
|
|
1077
|
+
editVisibleModels(models) {
|
|
1078
|
+
this.visibleModelsDraft = [...models];
|
|
1079
|
+
this.failed = false;
|
|
1080
|
+
this.publish();
|
|
1081
|
+
}
|
|
1082
|
+
/** Stage "show all models" (clears the allowlist). */
|
|
1083
|
+
clearVisibleModels() {
|
|
1084
|
+
this.visibleModelsDraft = [];
|
|
1085
|
+
this.failed = false;
|
|
1086
|
+
this.publish();
|
|
986
1087
|
}
|
|
987
1088
|
/** The routing-rule writes a save performs (empty when nothing staged). */
|
|
988
1089
|
rulesPlan() {
|
|
@@ -991,7 +1092,10 @@ window.__ModuleLoader__.load({
|
|
|
991
1092
|
}
|
|
992
1093
|
/** Persist the staged routing rules into the settings section. */
|
|
993
1094
|
async writeRules() {
|
|
994
|
-
const list = [...this.storedRules().filter((rule) =>
|
|
1095
|
+
const list = [...this.storedRules().filter((rule) => {
|
|
1096
|
+
const snapshot = this.removedRuleIds.get(rule.id);
|
|
1097
|
+
return snapshot === void 0 || !sameModels(snapshot.models, rule.models) || snapshot.account !== rule.account;
|
|
1098
|
+
}).map((rule) => {
|
|
995
1099
|
const draft = this.ruleDrafts.get(rule.id);
|
|
996
1100
|
return {
|
|
997
1101
|
models: draft !== void 0 && draft.models.length > 0 ? draft.models : rule.models,
|
|
@@ -1001,9 +1105,16 @@ window.__ModuleLoader__.load({
|
|
|
1001
1105
|
models: rule.models,
|
|
1002
1106
|
account: rule.account
|
|
1003
1107
|
}))].filter((rule) => rule.models.length > 0);
|
|
1004
|
-
|
|
1108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1109
|
+
const deduped = list.filter((rule) => {
|
|
1110
|
+
const key = `${rule.account}\u0001${[...rule.models].sort().join("")}`;
|
|
1111
|
+
if (seen.has(key)) return false;
|
|
1112
|
+
seen.add(key);
|
|
1113
|
+
return true;
|
|
1114
|
+
});
|
|
1115
|
+
await this.scope.set("modelAccountRules", deduped);
|
|
1005
1116
|
const after = this.storedRules();
|
|
1006
|
-
return after.length ===
|
|
1117
|
+
return after.length === deduped.length && deduped.every((item, index) => after[index] !== void 0 && sameModels(after[index].models, item.models) && after[index].account === item.account);
|
|
1007
1118
|
}
|
|
1008
1119
|
publish() {
|
|
1009
1120
|
if (this.disposed) return;
|
|
@@ -1437,7 +1548,7 @@ window.__ModuleLoader__.load({
|
|
|
1437
1548
|
/** The wire namespace all Command Code endpoints share. */
|
|
1438
1549
|
const REMOTE_NAMESPACE = "commandcode";
|
|
1439
1550
|
/**
|
|
1440
|
-
* Build the validator helpers one endpoint uses
|
|
1551
|
+
* Build the validator helpers one endpoint uses. `prefix` names the
|
|
1441
1552
|
* endpoint in the rejection message (e.g. `commandcode/report result:`), so
|
|
1442
1553
|
* each wire file keeps its own diagnostic phrasing while sharing the helper
|
|
1443
1554
|
* bodies.
|
|
@@ -1493,14 +1604,13 @@ window.__ModuleLoader__.load({
|
|
|
1493
1604
|
/** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
|
|
1494
1605
|
const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
1495
1606
|
/**
|
|
1496
|
-
* The shared read/validate helpers
|
|
1497
|
-
* rejection messages name the offending boundary
|
|
1498
|
-
* shares this prefix, matching the historical behavior).
|
|
1607
|
+
* The shared read/validate helpers for the usage report endpoint, prefixed
|
|
1608
|
+
* so rejection messages name the offending boundary.
|
|
1499
1609
|
*/
|
|
1500
|
-
const { reject: reject$1, record, stringField, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
1610
|
+
const { reject: reject$1, record: record$1, stringField: stringField$1, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
1501
1611
|
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
1502
1612
|
function windowLimit(value, field) {
|
|
1503
|
-
const source = record(value, field);
|
|
1613
|
+
const source = record$1(value, field);
|
|
1504
1614
|
return {
|
|
1505
1615
|
used: numberField(source, "used", `${field}.used`),
|
|
1506
1616
|
cap: numberField(source, "cap", `${field}.cap`),
|
|
@@ -1514,7 +1624,7 @@ window.__ModuleLoader__.load({
|
|
|
1514
1624
|
* malformed frame fails the boundary instead of rendering garbage.
|
|
1515
1625
|
*/
|
|
1516
1626
|
function parseUsageReport(value) {
|
|
1517
|
-
const source = record(value, "report");
|
|
1627
|
+
const source = record$1(value, "report");
|
|
1518
1628
|
const failures = source.failures;
|
|
1519
1629
|
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$1("failures");
|
|
1520
1630
|
const report = { failures };
|
|
@@ -1524,15 +1634,15 @@ window.__ModuleLoader__.load({
|
|
|
1524
1634
|
else reject$1("blocked");
|
|
1525
1635
|
}
|
|
1526
1636
|
if (source.account !== void 0) {
|
|
1527
|
-
const account = record(source.account, "account");
|
|
1637
|
+
const account = record$1(source.account, "account");
|
|
1528
1638
|
report.account = {
|
|
1529
|
-
id: stringField(account, "id", "account.id"),
|
|
1530
|
-
name: stringField(account, "name", "account.name"),
|
|
1531
|
-
userName: stringField(account, "userName", "account.userName")
|
|
1639
|
+
id: stringField$1(account, "id", "account.id"),
|
|
1640
|
+
name: stringField$1(account, "name", "account.name"),
|
|
1641
|
+
userName: stringField$1(account, "userName", "account.userName")
|
|
1532
1642
|
};
|
|
1533
1643
|
}
|
|
1534
1644
|
if (source.usage !== void 0) {
|
|
1535
|
-
const usage = record(source.usage, "usage");
|
|
1645
|
+
const usage = record$1(source.usage, "usage");
|
|
1536
1646
|
report.usage = {
|
|
1537
1647
|
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
1538
1648
|
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
@@ -1542,11 +1652,11 @@ window.__ModuleLoader__.load({
|
|
|
1542
1652
|
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
1543
1653
|
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
1544
1654
|
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
1545
|
-
periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
|
|
1655
|
+
periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
|
|
1546
1656
|
};
|
|
1547
1657
|
}
|
|
1548
1658
|
if (source.credits !== void 0) {
|
|
1549
|
-
const credits = record(source.credits, "credits");
|
|
1659
|
+
const credits = record$1(source.credits, "credits");
|
|
1550
1660
|
report.credits = {
|
|
1551
1661
|
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
1552
1662
|
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
@@ -1556,13 +1666,13 @@ window.__ModuleLoader__.load({
|
|
|
1556
1666
|
};
|
|
1557
1667
|
}
|
|
1558
1668
|
if (source.plan !== void 0) {
|
|
1559
|
-
const plan = record(source.plan, "plan");
|
|
1669
|
+
const plan = record$1(source.plan, "plan");
|
|
1560
1670
|
const monthly = plan.monthlyCredits;
|
|
1561
1671
|
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$1("plan.monthlyCredits");
|
|
1562
1672
|
report.plan = {
|
|
1563
|
-
planId: stringField(plan, "planId", "plan.planId"),
|
|
1564
|
-
name: stringField(plan, "name", "plan.name"),
|
|
1565
|
-
status: stringField(plan, "status", "plan.status"),
|
|
1673
|
+
planId: stringField$1(plan, "planId", "plan.planId"),
|
|
1674
|
+
name: stringField$1(plan, "name", "plan.name"),
|
|
1675
|
+
status: stringField$1(plan, "status", "plan.status"),
|
|
1566
1676
|
monthlyCredits: monthly,
|
|
1567
1677
|
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
1568
1678
|
};
|
|
@@ -1571,20 +1681,20 @@ window.__ModuleLoader__.load({
|
|
|
1571
1681
|
}
|
|
1572
1682
|
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
1573
1683
|
function parseAccountUsage(value) {
|
|
1574
|
-
const source = record(value, "account");
|
|
1684
|
+
const source = record$1(value, "account");
|
|
1575
1685
|
return {
|
|
1576
|
-
id: stringField(source, "id", "account.id"),
|
|
1577
|
-
label: stringField(source, "label", "account.label"),
|
|
1686
|
+
id: stringField$1(source, "id", "account.id"),
|
|
1687
|
+
label: stringField$1(source, "label", "account.label"),
|
|
1578
1688
|
configured: booleanField(source, "configured", "account.configured"),
|
|
1579
1689
|
active: booleanField(source, "active", "account.active"),
|
|
1580
|
-
mark: stringField(source, "mark", "account.mark"),
|
|
1690
|
+
mark: stringField$1(source, "mark", "account.mark"),
|
|
1581
1691
|
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
1582
1692
|
report: parseUsageReport(source.report)
|
|
1583
1693
|
};
|
|
1584
1694
|
}
|
|
1585
1695
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
1586
1696
|
function parseAccountsReport(value) {
|
|
1587
|
-
const accounts = record(value, "result").accounts;
|
|
1697
|
+
const accounts = record$1(value, "result").accounts;
|
|
1588
1698
|
if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
|
|
1589
1699
|
return reject$1("accounts");
|
|
1590
1700
|
}
|
|
@@ -1595,19 +1705,27 @@ window.__ModuleLoader__.load({
|
|
|
1595
1705
|
};
|
|
1596
1706
|
/** Canonical `<namespace>/<method>` endpoint of the model-catalog Remote. */
|
|
1597
1707
|
const MODELS_ENDPOINT = "commandcode/models";
|
|
1708
|
+
/**
|
|
1709
|
+
* The shared read/validate helpers for the model-catalog endpoint — a
|
|
1710
|
+
* separate instance so catalog boundary errors name `commandcode/models`,
|
|
1711
|
+
* not the report endpoint.
|
|
1712
|
+
*/
|
|
1713
|
+
const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
|
|
1598
1714
|
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
1599
1715
|
function parseCatalogModel(value) {
|
|
1600
|
-
const source =
|
|
1601
|
-
|
|
1602
|
-
id:
|
|
1603
|
-
name:
|
|
1716
|
+
const source = catalogRecord(value, "model");
|
|
1717
|
+
const model = {
|
|
1718
|
+
id: catalogString(source, "id", "model.id"),
|
|
1719
|
+
name: catalogString(source, "name", "model.name")
|
|
1604
1720
|
};
|
|
1721
|
+
if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
|
|
1722
|
+
return model;
|
|
1605
1723
|
}
|
|
1606
1724
|
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
1607
1725
|
function parseCatalog(value) {
|
|
1608
|
-
const models =
|
|
1726
|
+
const models = catalogRecord(value, "result").models;
|
|
1609
1727
|
if (Array.isArray(models)) return { models: models.map(parseCatalogModel) };
|
|
1610
|
-
|
|
1728
|
+
throw new TypeError("commandcode/models result: invalid models");
|
|
1611
1729
|
}
|
|
1612
1730
|
/** The Client-face contribution for the model-catalog endpoint. */
|
|
1613
1731
|
const MODELS_REMOTE_CONTRIBUTION = {
|
|
@@ -1631,45 +1749,29 @@ window.__ModuleLoader__.load({
|
|
|
1631
1749
|
];
|
|
1632
1750
|
/** The shared read/validate helpers, prefixed with the login endpoint so
|
|
1633
1751
|
* rejection messages name the offending boundary. */
|
|
1634
|
-
|
|
1635
|
-
* function declaration (not the factory's destructured arrow) so TypeScript's
|
|
1636
|
-
* control-flow analysis recognizes it as never-returning and narrows `state`
|
|
1637
|
-
* after the guard below. */
|
|
1638
|
-
function reject(field) {
|
|
1639
|
-
throw new TypeError(`commandcode/login result: invalid ${field}`);
|
|
1640
|
-
}
|
|
1752
|
+
const { reject, record, stringField } = makeBoundaryValidator("commandcode/login result:");
|
|
1641
1753
|
/**
|
|
1642
1754
|
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
1643
1755
|
* Every field is shape-checked so a malformed frame fails the boundary
|
|
1644
1756
|
* instead of leaking into the page.
|
|
1645
1757
|
*/
|
|
1646
1758
|
function parseLoginStatus(value) {
|
|
1647
|
-
|
|
1648
|
-
const source = value;
|
|
1759
|
+
const source = record(value, "status");
|
|
1649
1760
|
const state = source.state;
|
|
1650
|
-
if (state
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
if (
|
|
1654
|
-
status.
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
status.keyName = source.keyName;
|
|
1663
|
-
}
|
|
1664
|
-
if (source.reason !== void 0) {
|
|
1665
|
-
if (!REASONS.includes(source.reason)) reject("reason");
|
|
1666
|
-
status.reason = source.reason;
|
|
1667
|
-
}
|
|
1668
|
-
if (source.message !== void 0) {
|
|
1669
|
-
if (typeof source.message !== "string") reject("message");
|
|
1670
|
-
status.message = source.message;
|
|
1761
|
+
if (state === "idle" || state === "waiting" || state === "success" || state === "failed") {
|
|
1762
|
+
const status = { state };
|
|
1763
|
+
if (source.authUrl !== void 0) status.authUrl = stringField(source, "authUrl", "authUrl");
|
|
1764
|
+
if (source.userName !== void 0) status.userName = stringField(source, "userName", "userName");
|
|
1765
|
+
if (source.keyName !== void 0) status.keyName = stringField(source, "keyName", "keyName");
|
|
1766
|
+
if (source.reason !== void 0) {
|
|
1767
|
+
const reason = source.reason;
|
|
1768
|
+
if (typeof reason === "string" && REASONS.includes(reason)) status.reason = reason;
|
|
1769
|
+
else reject("reason");
|
|
1770
|
+
}
|
|
1771
|
+
if (source.message !== void 0) status.message = stringField(source, "message", "message");
|
|
1772
|
+
return status;
|
|
1671
1773
|
}
|
|
1672
|
-
return
|
|
1774
|
+
return reject("state");
|
|
1673
1775
|
}
|
|
1674
1776
|
/** The strict result codec shared by all three login endpoints. */
|
|
1675
1777
|
const loginStatusSchema = { parse: parseLoginStatus };
|
|
@@ -1735,8 +1837,128 @@ window.__ModuleLoader__.load({
|
|
|
1735
1837
|
});
|
|
1736
1838
|
}
|
|
1737
1839
|
//#endregion
|
|
1840
|
+
//#region src/client/model-select.ts
|
|
1841
|
+
/**
|
|
1842
|
+
* Model-select helpers for the settings page's model editors (browser half).
|
|
1843
|
+
*
|
|
1844
|
+
* The routing-rule editor and the visible-models filter both pick catalog
|
|
1845
|
+
* models through the same checkbox multi-select dropdown (`ModelMultiSelect`
|
|
1846
|
+
* in section.tsx). The dropdown's data shaping — search filtering, stale-id
|
|
1847
|
+
* detection, tier grouping — lives here, React-free, so node tests can drive
|
|
1848
|
+
* it directly.
|
|
1849
|
+
*
|
|
1850
|
+
* Dependency-free by design: the client bundle may only import platform/seed
|
|
1851
|
+
* modules, so the plan snapshot below is a deliberately small vendored copy
|
|
1852
|
+
* (tier key → heading label) rather than an import of src/capabilities.ts.
|
|
1853
|
+
* When upstream adds a plan tier, extend BOTH tables.
|
|
1854
|
+
*
|
|
1855
|
+
* @module dsh-commandcode-provider/model-select
|
|
1856
|
+
*/
|
|
1857
|
+
/**
|
|
1858
|
+
* Minimum plan tier → dropdown section heading. Mirrors the Host-side
|
|
1859
|
+
* `KNOWN_PLANS` values + `PLAN_LABELS` in src/capabilities.ts (kept as a
|
|
1860
|
+
* vendored copy because the client bundle cannot import host modules).
|
|
1861
|
+
* Covers every tier key `KNOWN_PLANS` uses today; an unknown tier key falls
|
|
1862
|
+
* back to the raw key rather than vanishing the row.
|
|
1863
|
+
*/
|
|
1864
|
+
const TIER_HEADINGS = {
|
|
1865
|
+
go: "Go",
|
|
1866
|
+
goat: "GOAT",
|
|
1867
|
+
pro: "Pro",
|
|
1868
|
+
provider: "Provider",
|
|
1869
|
+
max: "Max"
|
|
1870
|
+
};
|
|
1871
|
+
/**
|
|
1872
|
+
* The dropdown section heading for a catalog model id, or undefined for
|
|
1873
|
+
* models outside the known plan tiers (unmapped models and stale ids render
|
|
1874
|
+
* unheaded). `knownPlans` is the Host-side `KNOWN_PLANS` table, threaded in
|
|
1875
|
+
* by the caller so this module stays dependency-free.
|
|
1876
|
+
*/
|
|
1877
|
+
function tierHeadingFor(modelId, knownPlans) {
|
|
1878
|
+
const tier = knownPlans[modelId];
|
|
1879
|
+
if (tier === void 0) return void 0;
|
|
1880
|
+
return TIER_HEADINGS[tier] ?? tier;
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Whether `text` matches `query` as a case-insensitive substring over the
|
|
1884
|
+
* model id AND display name. An empty/blank query matches everything.
|
|
1885
|
+
*/
|
|
1886
|
+
function matchesModelQuery(model, query) {
|
|
1887
|
+
const needle = query.trim().toLowerCase();
|
|
1888
|
+
if (needle === "") return true;
|
|
1889
|
+
return model.id.toLowerCase().includes(needle) || model.name.toLowerCase().includes(needle);
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Build the dropdown options: the catalog (already in picker order) plus
|
|
1893
|
+
* any selected ids the catalog no longer carries, flagged stale so the UI
|
|
1894
|
+
* can mark them — a saved selection never silently loses an entry, and the
|
|
1895
|
+
* user can see which ones went stale upstream.
|
|
1896
|
+
*
|
|
1897
|
+
* When `query` is non-blank, catalog rows are filtered by
|
|
1898
|
+
* {@link matchesModelQuery}; stale rows are kept only while they match too,
|
|
1899
|
+
* so a search for a live model does not surface unrelated stale ids.
|
|
1900
|
+
*/
|
|
1901
|
+
function buildModelSelectOptions(catalog, selected, query = "") {
|
|
1902
|
+
const catalogIds = new Set(catalog.map((model) => model.id));
|
|
1903
|
+
const options = catalog.filter((model) => matchesModelQuery(model, query)).map((model) => ({
|
|
1904
|
+
value: model.id,
|
|
1905
|
+
label: model.name,
|
|
1906
|
+
stale: false
|
|
1907
|
+
}));
|
|
1908
|
+
const seen = new Set(catalogIds);
|
|
1909
|
+
for (const id of selected) {
|
|
1910
|
+
if (id === "" || seen.has(id)) continue;
|
|
1911
|
+
seen.add(id);
|
|
1912
|
+
if (catalogIds.has(id)) continue;
|
|
1913
|
+
if (!matchesModelQuery({
|
|
1914
|
+
id,
|
|
1915
|
+
name: id
|
|
1916
|
+
}, query)) continue;
|
|
1917
|
+
options.push({
|
|
1918
|
+
value: id,
|
|
1919
|
+
label: id,
|
|
1920
|
+
stale: true
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
return options;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Group dropdown options under plan-tier headings (`tierOf` maps a model id
|
|
1927
|
+
* to its tier heading, or undefined for unmapped models — see
|
|
1928
|
+
* {@link tierHeadingFor}). Live rows keep their relative order; stale ids
|
|
1929
|
+
* and unmapped live rows share one trailing unheaded group. Groups merge
|
|
1930
|
+
* repeats, so a catalog interleaving two tiers still renders one section
|
|
1931
|
+
* per tier.
|
|
1932
|
+
*/
|
|
1933
|
+
function groupModelSelectOptions(options, tierOf) {
|
|
1934
|
+
const groups = [];
|
|
1935
|
+
const byHeading = /* @__PURE__ */ new Map();
|
|
1936
|
+
for (const option of options) {
|
|
1937
|
+
const heading = option.stale ? void 0 : tierOf(option.value);
|
|
1938
|
+
let group = byHeading.get(heading);
|
|
1939
|
+
if (group === void 0) {
|
|
1940
|
+
group = {
|
|
1941
|
+
heading,
|
|
1942
|
+
options: []
|
|
1943
|
+
};
|
|
1944
|
+
byHeading.set(heading, group);
|
|
1945
|
+
groups.push(group);
|
|
1946
|
+
}
|
|
1947
|
+
group.options.push(option);
|
|
1948
|
+
}
|
|
1949
|
+
return groups;
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Toggle one model id in a selection: remove it when present, append it
|
|
1953
|
+
* when absent (append keeps catalog order irrelevant — the picker re-sorts
|
|
1954
|
+
* by plan tier on render).
|
|
1955
|
+
*/
|
|
1956
|
+
function toggleModelSelection(selected, modelId) {
|
|
1957
|
+
return selected.includes(modelId) ? selected.filter((value) => value !== modelId) : [...selected, modelId];
|
|
1958
|
+
}
|
|
1959
|
+
//#endregion
|
|
1738
1960
|
//#region package.json
|
|
1739
|
-
var version = "0.10.
|
|
1961
|
+
var version = "0.10.2";
|
|
1740
1962
|
var repository = {
|
|
1741
1963
|
"type": "git",
|
|
1742
1964
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -1759,8 +1981,13 @@ window.__ModuleLoader__.load({
|
|
|
1759
1981
|
/**
|
|
1760
1982
|
* This package's GitHub releases page, derived from the repository field so
|
|
1761
1983
|
* the update hint's link target can never drift from the published home.
|
|
1984
|
+
* Tolerates both repository shapes (`{ url }` and the plain string form).
|
|
1762
1985
|
*/
|
|
1763
|
-
const PLUGIN_RELEASES_URL =
|
|
1986
|
+
const PLUGIN_RELEASES_URL = (() => {
|
|
1987
|
+
const repo = repository;
|
|
1988
|
+
const url = typeof repo === "string" ? repo : repo?.url;
|
|
1989
|
+
return `${typeof url === "string" ? url.replace(/^git\+/, "").replace(/\.git$/, "") : "https://github.com/Mars-Sea/dsh-commandcode-provider"}/releases`;
|
|
1990
|
+
})();
|
|
1764
1991
|
/** Abort a hung registry request rather than keep the footer waiting. */
|
|
1765
1992
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
1766
1993
|
/**
|
|
@@ -1812,7 +2039,10 @@ window.__ModuleLoader__.load({
|
|
|
1812
2039
|
}
|
|
1813
2040
|
/** Split a tolerant version string into numeric core + prerelease ids. */
|
|
1814
2041
|
function splitVersion(value) {
|
|
1815
|
-
const
|
|
2042
|
+
const cleaned = value.trim().replace(/^v/i, "");
|
|
2043
|
+
const dash = cleaned.indexOf("-");
|
|
2044
|
+
const coreText = dash < 0 ? cleaned : cleaned.slice(0, dash);
|
|
2045
|
+
const preText = dash < 0 ? void 0 : cleaned.slice(dash + 1);
|
|
1816
2046
|
return {
|
|
1817
2047
|
core: coreText === "" ? [0] : coreText.split(".").map((part) => {
|
|
1818
2048
|
const parsed = Number.parseInt(part, 10);
|
|
@@ -1951,6 +2181,7 @@ window.__ModuleLoader__.load({
|
|
|
1951
2181
|
className: "cc-reset",
|
|
1952
2182
|
disabled,
|
|
1953
2183
|
onClick: onReset,
|
|
2184
|
+
"aria-label": `${label} — ${t("reset")}`,
|
|
1954
2185
|
children: t("reset")
|
|
1955
2186
|
})]
|
|
1956
2187
|
})]
|
|
@@ -2005,7 +2236,11 @@ window.__ModuleLoader__.load({
|
|
|
2005
2236
|
}),
|
|
2006
2237
|
overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2007
2238
|
className: "cc-badge",
|
|
2008
|
-
children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany"
|
|
2239
|
+
children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany", { count: overridden })
|
|
2240
|
+
}) : null,
|
|
2241
|
+
!expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2242
|
+
className: "cc-badge cc-badgeWarn",
|
|
2243
|
+
children: t("advancedInvalid")
|
|
2009
2244
|
}) : null,
|
|
2010
2245
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-advancedSpacer" }),
|
|
2011
2246
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2108,9 +2343,9 @@ window.__ModuleLoader__.load({
|
|
|
2108
2343
|
className: "cc-field",
|
|
2109
2344
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2110
2345
|
className: "cc-fieldHead",
|
|
2111
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2346
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2112
2347
|
className: "cc-label",
|
|
2113
|
-
|
|
2348
|
+
"aria-hidden": "true",
|
|
2114
2349
|
children: label
|
|
2115
2350
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2116
2351
|
className: "cc-badges",
|
|
@@ -2122,6 +2357,7 @@ window.__ModuleLoader__.load({
|
|
|
2122
2357
|
className: "cc-reset",
|
|
2123
2358
|
disabled,
|
|
2124
2359
|
onClick: onReset,
|
|
2360
|
+
"aria-label": `${label} — ${t("reset")}`,
|
|
2125
2361
|
children: t("reset")
|
|
2126
2362
|
})]
|
|
2127
2363
|
})]
|
|
@@ -2251,6 +2487,7 @@ window.__ModuleLoader__.load({
|
|
|
2251
2487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2252
2488
|
className: "cc-usageBar",
|
|
2253
2489
|
role: "progressbar",
|
|
2490
|
+
"aria-label": label,
|
|
2254
2491
|
"aria-valuemin": 0,
|
|
2255
2492
|
"aria-valuemax": 100,
|
|
2256
2493
|
"aria-valuenow": Math.round(ratio * 100),
|
|
@@ -2456,7 +2693,10 @@ window.__ModuleLoader__.load({
|
|
|
2456
2693
|
/** The status dot on an account tab: cooling/invalid warn, everything else ok. */
|
|
2457
2694
|
function AccountTabDot({ entry }) {
|
|
2458
2695
|
const cls = entry.mark === "invalid-credential" ? "cc-tabDot cc-tabDotError" : entry.mark !== "" || entry.cooldownUntil > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk";
|
|
2459
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2696
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2697
|
+
className: cls,
|
|
2698
|
+
"aria-hidden": "true"
|
|
2699
|
+
});
|
|
2460
2700
|
}
|
|
2461
2701
|
/**
|
|
2462
2702
|
* The account-usage card: the `/commandcode` dashboard's facts rendered as
|
|
@@ -2532,12 +2772,10 @@ window.__ModuleLoader__.load({
|
|
|
2532
2772
|
}) : null,
|
|
2533
2773
|
entries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2534
2774
|
className: "cc-tabs",
|
|
2535
|
-
role: "tablist",
|
|
2536
2775
|
"aria-label": t("accountsTitle"),
|
|
2537
2776
|
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2538
2777
|
type: "button",
|
|
2539
|
-
|
|
2540
|
-
"aria-selected": selected?.id === entry.id,
|
|
2778
|
+
"aria-pressed": selected?.id === entry.id,
|
|
2541
2779
|
className: selected?.id === entry.id ? "cc-tab cc-tabActive" : "cc-tab",
|
|
2542
2780
|
onClick: () => setSelectedId(entry.id),
|
|
2543
2781
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTabDot, { entry }), entry.label]
|
|
@@ -2780,6 +3018,7 @@ window.__ModuleLoader__.load({
|
|
|
2780
3018
|
value: rule.account,
|
|
2781
3019
|
disabled,
|
|
2782
3020
|
onChange: (event) => onAccount(event.target.value),
|
|
3021
|
+
"aria-label": t("ruleAccount"),
|
|
2783
3022
|
children: targets.map((target) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2784
3023
|
value: target.value,
|
|
2785
3024
|
children: target.label
|
|
@@ -2793,59 +3032,91 @@ window.__ModuleLoader__.load({
|
|
|
2793
3032
|
});
|
|
2794
3033
|
}
|
|
2795
3034
|
/**
|
|
2796
|
-
* A checkbox multi-select dropdown for
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
3035
|
+
* A checkbox multi-select dropdown for picking catalog models (the
|
|
3036
|
+
* routing-rule rows and the visible-models filter share it). The trigger
|
|
3037
|
+
* shows the selection count; the Menu lists every catalog model with a
|
|
3038
|
+
* checkbox, toggled by clicking the row. A search box under the trigger
|
|
3039
|
+
* (inside the Menu anchor, so focusing it never trips the outside-click
|
|
3040
|
+
* close) filters the list by id/display-name substring, and items group
|
|
3041
|
+
* under plan-tier headings in picker order. Selected ids the catalog no
|
|
3042
|
+
* longer carries still render — flagged stale — so a saved selection never
|
|
3043
|
+
* silently loses an entry, and the VisibleModelsCard offers a one-click
|
|
3044
|
+
* cleanup.
|
|
2800
3045
|
*/
|
|
2801
3046
|
function ModelMultiSelect({ id, selected, catalog, disabled, t, onSelect }) {
|
|
2802
3047
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3048
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
3049
|
+
(0, react.useEffect)(() => {
|
|
3050
|
+
if (open) setQuery("");
|
|
3051
|
+
}, [open]);
|
|
3052
|
+
const tiers = (0, react.useMemo)(() => Object.fromEntries(catalog.flatMap((model) => model.tier === void 0 ? [] : [[model.id, model.tier]])), [catalog]);
|
|
3053
|
+
const options = buildModelSelectOptions(catalog, selected, query);
|
|
3054
|
+
const items = groupModelSelectOptions(options, (modelId) => tierHeadingFor(modelId, tiers)).flatMap((group) => [...group.heading === void 0 ? [] : [{
|
|
3055
|
+
type: "label",
|
|
3056
|
+
id: `cc-tier-${group.heading}`,
|
|
3057
|
+
text: group.heading
|
|
3058
|
+
}], ...group.options.map((option) => ({
|
|
2812
3059
|
id: option.value,
|
|
2813
3060
|
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2814
3061
|
className: "cc-checkRow",
|
|
2815
|
-
children: [
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3062
|
+
children: [
|
|
3063
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3064
|
+
type: "checkbox",
|
|
3065
|
+
className: "cc-check",
|
|
3066
|
+
checked: selected.includes(option.value),
|
|
3067
|
+
readOnly: true,
|
|
3068
|
+
tabIndex: -1
|
|
3069
|
+
}),
|
|
3070
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3071
|
+
className: "cc-checkName",
|
|
3072
|
+
children: option.label
|
|
3073
|
+
}),
|
|
3074
|
+
option.stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3075
|
+
className: "cc-badge",
|
|
3076
|
+
children: t("modelStale")
|
|
3077
|
+
}) : null
|
|
3078
|
+
]
|
|
2825
3079
|
})
|
|
2826
|
-
}));
|
|
3080
|
+
}))]);
|
|
2827
3081
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2828
3082
|
open,
|
|
2829
3083
|
onClose: () => setOpen(false),
|
|
2830
3084
|
onSelect: (modelId) => {
|
|
2831
|
-
onSelect(
|
|
3085
|
+
onSelect(toggleModelSelection(selected, modelId));
|
|
2832
3086
|
},
|
|
2833
3087
|
selectedIds: selected,
|
|
2834
3088
|
items,
|
|
3089
|
+
footer: options.length === 0 ? [{
|
|
3090
|
+
type: "label",
|
|
3091
|
+
id: "cc-model-search-empty",
|
|
3092
|
+
text: t("modelSearchEmpty")
|
|
3093
|
+
}] : [],
|
|
2835
3094
|
portal: true,
|
|
2836
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
children:
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
"
|
|
2848
|
-
|
|
3095
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3096
|
+
className: "cc-modelSelectAnchor",
|
|
3097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3098
|
+
id,
|
|
3099
|
+
type: "button",
|
|
3100
|
+
className: "cc-input cc-ruleTrigger",
|
|
3101
|
+
disabled: disabled || catalog.length === 0,
|
|
3102
|
+
onClick: () => setOpen((value) => !value),
|
|
3103
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3104
|
+
className: "cc-ruleTriggerText",
|
|
3105
|
+
children: selected.length === 0 ? t("ruleModelPick") : t("ruleModelCount", { count: selected.length })
|
|
3106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3107
|
+
className: "cc-ruleCaret",
|
|
3108
|
+
"aria-hidden": "true"
|
|
3109
|
+
})]
|
|
3110
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3111
|
+
type: "search",
|
|
3112
|
+
className: "cc-input cc-modelSearch",
|
|
3113
|
+
placeholder: t("modelSearchPlaceholder"),
|
|
3114
|
+
"aria-label": t("modelSearchPlaceholder"),
|
|
3115
|
+
value: query,
|
|
3116
|
+
disabled,
|
|
3117
|
+
autoFocus: true,
|
|
3118
|
+
onChange: (event) => setQuery(event.target.value)
|
|
3119
|
+
}) : null]
|
|
2849
3120
|
})
|
|
2850
3121
|
});
|
|
2851
3122
|
}
|
|
@@ -2901,6 +3172,68 @@ window.__ModuleLoader__.load({
|
|
|
2901
3172
|
]
|
|
2902
3173
|
});
|
|
2903
3174
|
}
|
|
3175
|
+
/** The visible-model filter card: an allowlist over the catalog. Empty = show all. */
|
|
3176
|
+
function VisibleModelsCard({ t, state, disabled, onSelect, onClear }) {
|
|
3177
|
+
const count = state.visibleModels.length;
|
|
3178
|
+
const pickT = (key, params) => {
|
|
3179
|
+
if (key === "ruleModelPick") return t("visibleModelsPick");
|
|
3180
|
+
if (key === "ruleModelCount") return t("visibleModelsCount", params);
|
|
3181
|
+
return t(key, params);
|
|
3182
|
+
};
|
|
3183
|
+
const catalogIds = new Set(state.catalogModels.map((model) => model.id));
|
|
3184
|
+
const staleIds = state.visibleModels.filter((id) => !catalogIds.has(id));
|
|
3185
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3186
|
+
className: "cc-card",
|
|
3187
|
+
"aria-label": t("visibleModelsTitle"),
|
|
3188
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3189
|
+
className: "cc-field",
|
|
3190
|
+
children: [
|
|
3191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3192
|
+
className: "cc-fieldHead",
|
|
3193
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
3194
|
+
className: "cc-label",
|
|
3195
|
+
children: t("visibleModelsTitle")
|
|
3196
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3197
|
+
className: "cc-badges",
|
|
3198
|
+
children: [staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3199
|
+
type: "button",
|
|
3200
|
+
className: "cc-reset",
|
|
3201
|
+
disabled,
|
|
3202
|
+
onClick: () => onSelect(state.visibleModels.filter((id) => catalogIds.has(id))),
|
|
3203
|
+
children: t("visibleModelsCleanStale", { count: staleIds.length })
|
|
3204
|
+
}) : null, count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3205
|
+
type: "button",
|
|
3206
|
+
className: "cc-reset",
|
|
3207
|
+
disabled,
|
|
3208
|
+
onClick: onClear,
|
|
3209
|
+
children: t("visibleModelsShowAll")
|
|
3210
|
+
}) : null]
|
|
3211
|
+
})]
|
|
3212
|
+
}),
|
|
3213
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3214
|
+
className: "cc-hint",
|
|
3215
|
+
children: t("visibleModelsHint")
|
|
3216
|
+
}),
|
|
3217
|
+
state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3218
|
+
className: "cc-invalid",
|
|
3219
|
+
children: t("rulesCatalogFailed")
|
|
3220
|
+
}) : null,
|
|
3221
|
+
staleIds.length > 0 && !state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3222
|
+
className: "cc-hint",
|
|
3223
|
+
children: t("visibleModelsStaleHint", { count: staleIds.length })
|
|
3224
|
+
}) : null,
|
|
3225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
|
|
3226
|
+
id: "cc-visible-models",
|
|
3227
|
+
selected: state.visibleModels,
|
|
3228
|
+
catalog: state.catalogModels,
|
|
3229
|
+
disabled,
|
|
3230
|
+
t: pickT,
|
|
3231
|
+
onSelect
|
|
3232
|
+
})
|
|
3233
|
+
]
|
|
3234
|
+
})
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
2904
3237
|
/**
|
|
2905
3238
|
* Show the "Saved ✓" affordance for a short window after each accepted save.
|
|
2906
3239
|
* The controller only counts saves (`savedCount`); the flash timing lives
|
|
@@ -2997,6 +3330,13 @@ window.__ModuleLoader__.load({
|
|
|
2997
3330
|
onModels: props.editRuleModels,
|
|
2998
3331
|
onAccount: props.editRuleAccount
|
|
2999
3332
|
}),
|
|
3333
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsCard, {
|
|
3334
|
+
t,
|
|
3335
|
+
state,
|
|
3336
|
+
disabled,
|
|
3337
|
+
onSelect: props.editVisibleModels,
|
|
3338
|
+
onClear: props.clearVisibleModels
|
|
3339
|
+
}),
|
|
3000
3340
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3001
3341
|
className: "cc-card",
|
|
3002
3342
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
|
|
@@ -3099,10 +3439,14 @@ window.__ModuleLoader__.load({
|
|
|
3099
3439
|
}
|
|
3100
3440
|
/** The closed-panel style: the outlet stays mounted as the detection anchor. */
|
|
3101
3441
|
const HIDDEN_STYLE = { display: "none" };
|
|
3102
|
-
/**
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3442
|
+
/**
|
|
3443
|
+
* Decide the card's posture from one settings snapshot. Pure: the component
|
|
3444
|
+
* subscribes once and passes the snapshot in, so hook order never depends
|
|
3445
|
+
* on the registration→live transition. (The owner facts stay on the
|
|
3446
|
+
* component — only the snapshot decides the posture.)
|
|
3447
|
+
*/
|
|
3448
|
+
function cardMode(snapshot) {
|
|
3449
|
+
if (snapshot === void 0) return { kind: "registration" };
|
|
3106
3450
|
return {
|
|
3107
3451
|
kind: "live",
|
|
3108
3452
|
ready: snapshot.available,
|
|
@@ -3173,8 +3517,8 @@ window.__ModuleLoader__.load({
|
|
|
3173
3517
|
*/
|
|
3174
3518
|
function CommandCodeProviderCard(props) {
|
|
3175
3519
|
const { t } = props;
|
|
3176
|
-
const mode = cardMode(props);
|
|
3177
3520
|
const state = props.useCommandCodeSettings !== void 0 ? props.useCommandCodeSettings((snapshot) => snapshot) : void 0;
|
|
3521
|
+
const mode = cardMode(state);
|
|
3178
3522
|
const login = props.useCommandCodeLogin !== void 0 ? props.useCommandCodeLogin((snapshot) => snapshot) : void 0;
|
|
3179
3523
|
const dirty = state?.dirty ?? false;
|
|
3180
3524
|
const saving = state?.saving ?? false;
|
|
@@ -3313,7 +3657,7 @@ window.__ModuleLoader__.load({
|
|
|
3313
3657
|
filterModelsByPlan: "隐藏套餐外模型",
|
|
3314
3658
|
filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
|
|
3315
3659
|
webSearch: "用 Command Code 承载联网搜索",
|
|
3316
|
-
webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key
|
|
3660
|
+
webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key 与地址),并优先于其他搜索后端;关闭则把选择权交还给之前的后端(如 modsearch),而不是强制回退到 DeepSeek 搜索。",
|
|
3317
3661
|
accountsTitle: "多账户轮换",
|
|
3318
3662
|
accountsHint: "当前账户达到用量限额(429)或密钥失效(401)时,请求自动切换到下一个账户;全部耗尽时会提示最早的重置时间。",
|
|
3319
3663
|
accountAdd: "添加账户",
|
|
@@ -3336,6 +3680,16 @@ window.__ModuleLoader__.load({
|
|
|
3336
3680
|
ruleModelCount: "已选 {count} 个模型",
|
|
3337
3681
|
ruleAccount: "目标账户",
|
|
3338
3682
|
ruleHint: "从下拉列表勾选要路由的模型(可多选),再选择目标账户。",
|
|
3683
|
+
modelSearchPlaceholder: "搜索模型…",
|
|
3684
|
+
modelSearchEmpty: "没有匹配的模型。",
|
|
3685
|
+
modelStale: "已下架",
|
|
3686
|
+
visibleModelsTitle: "显示的模型",
|
|
3687
|
+
visibleModelsHint: "只在模型选择器中显示勾选的模型;不勾选则显示全部。保存后下次打开选择器即生效。",
|
|
3688
|
+
visibleModelsPick: "选择要显示的模型…",
|
|
3689
|
+
visibleModelsCount: "已选 {count} 个模型",
|
|
3690
|
+
visibleModelsShowAll: "显示全部",
|
|
3691
|
+
visibleModelsStaleHint: "有 {count} 个已选模型在目录中找不到了(可能已下架),不影响其他模型;可清理或保留。",
|
|
3692
|
+
visibleModelsCleanStale: "清理失效({count})",
|
|
3339
3693
|
overridden: "已覆盖",
|
|
3340
3694
|
reset: "重置",
|
|
3341
3695
|
invalidNumber: "无效数字",
|
|
@@ -3435,7 +3789,7 @@ window.__ModuleLoader__.load({
|
|
|
3435
3789
|
filterModelsByPlan: "Hide out-of-plan models",
|
|
3436
3790
|
filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
|
|
3437
3791
|
webSearch: "Serve dsh web search with Command Code",
|
|
3438
|
-
webSearchHint: "When on, the model-facing web_search tool is backed by Command Code (same API key and base URL as chat). Off
|
|
3792
|
+
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.",
|
|
3439
3793
|
accountsTitle: "Account rotation",
|
|
3440
3794
|
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.",
|
|
3441
3795
|
accountAdd: "Add account",
|
|
@@ -3458,6 +3812,16 @@ window.__ModuleLoader__.load({
|
|
|
3458
3812
|
ruleModelCount: "{count} model(s) selected",
|
|
3459
3813
|
ruleAccount: "Target account",
|
|
3460
3814
|
ruleHint: "Check the models to route from the dropdown (multi-select), then pick the target account.",
|
|
3815
|
+
modelSearchPlaceholder: "Search models…",
|
|
3816
|
+
modelSearchEmpty: "No matching models.",
|
|
3817
|
+
modelStale: "Retired",
|
|
3818
|
+
visibleModelsTitle: "Visible models",
|
|
3819
|
+
visibleModelsHint: "Show only the checked models in model pickers; unchecked shows all. Applies the next time a picker opens after saving.",
|
|
3820
|
+
visibleModelsPick: "Select models to show…",
|
|
3821
|
+
visibleModelsCount: "{count} model(s) selected",
|
|
3822
|
+
visibleModelsShowAll: "Show all",
|
|
3823
|
+
visibleModelsStaleHint: "{count} selected model(s) are no longer in the catalog (possibly retired); other models are unaffected. Clean them up or keep them.",
|
|
3824
|
+
visibleModelsCleanStale: "Clean stale ({count})",
|
|
3461
3825
|
overridden: "Overridden",
|
|
3462
3826
|
reset: "Reset",
|
|
3463
3827
|
invalidNumber: "Invalid number",
|
|
@@ -3567,6 +3931,13 @@ window.__ModuleLoader__.load({
|
|
|
3567
3931
|
.cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
3568
3932
|
.cc-check:checked::after{content:'';position:absolute;top:2px;left:5px;width:3px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
|
|
3569
3933
|
.cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3934
|
+
/* The model multi-select search box: stacked under the trigger while the
|
|
3935
|
+
* dropdown is open, same input sizing so the pair reads as one control. The
|
|
3936
|
+
* box lives inside the Menu anchor (which renders inside the Menu's root
|
|
3937
|
+
* span), so focusing/typing it never trips the Menu's outside-click close. */
|
|
3938
|
+
.cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
|
|
3939
|
+
.cc-modelSearch{width:100%}
|
|
3940
|
+
.cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
|
|
3570
3941
|
/* Selects need their own treatment to sit flush with the text inputs:
|
|
3571
3942
|
* the UA stylesheet renders <select> border-box (34px total vs the inputs'
|
|
3572
3943
|
* 36px) and forces its own menulist text metrics, so drop the native
|
|
@@ -3621,7 +3992,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3621
3992
|
.cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
|
|
3622
3993
|
.cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
|
|
3623
3994
|
.cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
|
|
3624
|
-
.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
3995
|
+
@media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle,.cc-toggle::after,.cc-usageBarFill{transition:none}}.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
3625
3996
|
.cc-usageMeta{align-items:center;gap:8px;display:flex}
|
|
3626
3997
|
.cc-accountReport{flex-direction:column;gap:12px;display:flex}
|
|
3627
3998
|
.cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
|
|
@@ -3638,6 +4009,9 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3638
4009
|
.cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
3639
4010
|
.cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
|
|
3640
4011
|
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
4012
|
+
/* The Models-page provider panel root: unstyled by design — the row card the
|
|
4013
|
+
* Models page owns supplies the surface, the panel only stacks its controls. */
|
|
4014
|
+
.cc-providerCard{flex-direction:column;display:flex}
|
|
3641
4015
|
/* The update hint rides the footer version line: warning-tinted (with a
|
|
3642
4016
|
* muted fallback for themes without the alias), quiet until hovered. */
|
|
3643
4017
|
.cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
|
|
@@ -3646,7 +4020,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3646
4020
|
* authorization link is the only branded element on it. */
|
|
3647
4021
|
.cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
|
|
3648
4022
|
.cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
|
|
3649
|
-
.cc-loginBusy{color:var(--dsw-alias-label-tertiary)}
|
|
3650
4023
|
.cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
|
|
3651
4024
|
.cc-loginError{color:var(--dsw-alias-label-error)}
|
|
3652
4025
|
.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}
|
|
@@ -3822,7 +4195,9 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3822
4195
|
addRule: () => controller.addRule(),
|
|
3823
4196
|
removeRule: (id) => controller.removeRule(id),
|
|
3824
4197
|
editRuleModels: (id, ids) => controller.editRuleModels(id, ids),
|
|
3825
|
-
editRuleAccount: (id, text) => controller.editRuleAccount(id, text)
|
|
4198
|
+
editRuleAccount: (id, text) => controller.editRuleAccount(id, text),
|
|
4199
|
+
editVisibleModels: (ids) => controller.editVisibleModels(ids),
|
|
4200
|
+
clearVisibleModels: () => controller.clearVisibleModels()
|
|
3826
4201
|
});
|
|
3827
4202
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3828
4203
|
name: "settings.section",
|