@mars-sea/dsh-commandcode-provider 0.10.1 → 0.10.3
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 +41 -1
- package/README.md +7 -3
- package/README.zh-CN.md +7 -3
- package/lib/client.js +530 -145
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +165 -51
- package/lib/index.js +848 -604
- package/lib/index.js.map +1 -1
- package/package.json +9 -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;
|
|
@@ -1149,6 +1260,16 @@ window.__ModuleLoader__.load({
|
|
|
1149
1260
|
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
1150
1261
|
return String(value);
|
|
1151
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Format a success-rate percentage (already in percent units, e.g. 99.96):
|
|
1265
|
+
* at most two decimals, trailing zeros trimmed — `100` stays `100`, not
|
|
1266
|
+
* `100.00`, and the raw upstream float `99.965552876334` becomes `99.97`.
|
|
1267
|
+
* The `%` suffix is appended by the caller (the card and the dashboard line
|
|
1268
|
+
* both compose it).
|
|
1269
|
+
*/
|
|
1270
|
+
function formatSuccessRate(value) {
|
|
1271
|
+
return String(Number(value.toFixed(2)));
|
|
1272
|
+
}
|
|
1152
1273
|
/** One window's fill ratio in [0, 1]; 0 when uncapped. */
|
|
1153
1274
|
function windowRatio(used, cap) {
|
|
1154
1275
|
if (cap <= 0) return 0;
|
|
@@ -1437,7 +1558,7 @@ window.__ModuleLoader__.load({
|
|
|
1437
1558
|
/** The wire namespace all Command Code endpoints share. */
|
|
1438
1559
|
const REMOTE_NAMESPACE = "commandcode";
|
|
1439
1560
|
/**
|
|
1440
|
-
* Build the validator helpers one endpoint uses
|
|
1561
|
+
* Build the validator helpers one endpoint uses. `prefix` names the
|
|
1441
1562
|
* endpoint in the rejection message (e.g. `commandcode/report result:`), so
|
|
1442
1563
|
* each wire file keeps its own diagnostic phrasing while sharing the helper
|
|
1443
1564
|
* bodies.
|
|
@@ -1493,14 +1614,13 @@ window.__ModuleLoader__.load({
|
|
|
1493
1614
|
/** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
|
|
1494
1615
|
const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
1495
1616
|
/**
|
|
1496
|
-
* The shared read/validate helpers
|
|
1497
|
-
* rejection messages name the offending boundary
|
|
1498
|
-
* shares this prefix, matching the historical behavior).
|
|
1617
|
+
* The shared read/validate helpers for the usage report endpoint, prefixed
|
|
1618
|
+
* so rejection messages name the offending boundary.
|
|
1499
1619
|
*/
|
|
1500
|
-
const { reject: reject$1, record, stringField, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
1620
|
+
const { reject: reject$1, record: record$1, stringField: stringField$1, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
|
|
1501
1621
|
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
1502
1622
|
function windowLimit(value, field) {
|
|
1503
|
-
const source = record(value, field);
|
|
1623
|
+
const source = record$1(value, field);
|
|
1504
1624
|
return {
|
|
1505
1625
|
used: numberField(source, "used", `${field}.used`),
|
|
1506
1626
|
cap: numberField(source, "cap", `${field}.cap`),
|
|
@@ -1514,7 +1634,7 @@ window.__ModuleLoader__.load({
|
|
|
1514
1634
|
* malformed frame fails the boundary instead of rendering garbage.
|
|
1515
1635
|
*/
|
|
1516
1636
|
function parseUsageReport(value) {
|
|
1517
|
-
const source = record(value, "report");
|
|
1637
|
+
const source = record$1(value, "report");
|
|
1518
1638
|
const failures = source.failures;
|
|
1519
1639
|
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$1("failures");
|
|
1520
1640
|
const report = { failures };
|
|
@@ -1524,15 +1644,15 @@ window.__ModuleLoader__.load({
|
|
|
1524
1644
|
else reject$1("blocked");
|
|
1525
1645
|
}
|
|
1526
1646
|
if (source.account !== void 0) {
|
|
1527
|
-
const account = record(source.account, "account");
|
|
1647
|
+
const account = record$1(source.account, "account");
|
|
1528
1648
|
report.account = {
|
|
1529
|
-
id: stringField(account, "id", "account.id"),
|
|
1530
|
-
name: stringField(account, "name", "account.name"),
|
|
1531
|
-
userName: stringField(account, "userName", "account.userName")
|
|
1649
|
+
id: stringField$1(account, "id", "account.id"),
|
|
1650
|
+
name: stringField$1(account, "name", "account.name"),
|
|
1651
|
+
userName: stringField$1(account, "userName", "account.userName")
|
|
1532
1652
|
};
|
|
1533
1653
|
}
|
|
1534
1654
|
if (source.usage !== void 0) {
|
|
1535
|
-
const usage = record(source.usage, "usage");
|
|
1655
|
+
const usage = record$1(source.usage, "usage");
|
|
1536
1656
|
report.usage = {
|
|
1537
1657
|
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
1538
1658
|
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
@@ -1542,11 +1662,11 @@ window.__ModuleLoader__.load({
|
|
|
1542
1662
|
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
1543
1663
|
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
1544
1664
|
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
1545
|
-
periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
|
|
1665
|
+
periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
|
|
1546
1666
|
};
|
|
1547
1667
|
}
|
|
1548
1668
|
if (source.credits !== void 0) {
|
|
1549
|
-
const credits = record(source.credits, "credits");
|
|
1669
|
+
const credits = record$1(source.credits, "credits");
|
|
1550
1670
|
report.credits = {
|
|
1551
1671
|
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
1552
1672
|
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
@@ -1556,13 +1676,13 @@ window.__ModuleLoader__.load({
|
|
|
1556
1676
|
};
|
|
1557
1677
|
}
|
|
1558
1678
|
if (source.plan !== void 0) {
|
|
1559
|
-
const plan = record(source.plan, "plan");
|
|
1679
|
+
const plan = record$1(source.plan, "plan");
|
|
1560
1680
|
const monthly = plan.monthlyCredits;
|
|
1561
1681
|
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$1("plan.monthlyCredits");
|
|
1562
1682
|
report.plan = {
|
|
1563
|
-
planId: stringField(plan, "planId", "plan.planId"),
|
|
1564
|
-
name: stringField(plan, "name", "plan.name"),
|
|
1565
|
-
status: stringField(plan, "status", "plan.status"),
|
|
1683
|
+
planId: stringField$1(plan, "planId", "plan.planId"),
|
|
1684
|
+
name: stringField$1(plan, "name", "plan.name"),
|
|
1685
|
+
status: stringField$1(plan, "status", "plan.status"),
|
|
1566
1686
|
monthlyCredits: monthly,
|
|
1567
1687
|
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
1568
1688
|
};
|
|
@@ -1571,20 +1691,20 @@ window.__ModuleLoader__.load({
|
|
|
1571
1691
|
}
|
|
1572
1692
|
/** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
|
|
1573
1693
|
function parseAccountUsage(value) {
|
|
1574
|
-
const source = record(value, "account");
|
|
1694
|
+
const source = record$1(value, "account");
|
|
1575
1695
|
return {
|
|
1576
|
-
id: stringField(source, "id", "account.id"),
|
|
1577
|
-
label: stringField(source, "label", "account.label"),
|
|
1696
|
+
id: stringField$1(source, "id", "account.id"),
|
|
1697
|
+
label: stringField$1(source, "label", "account.label"),
|
|
1578
1698
|
configured: booleanField(source, "configured", "account.configured"),
|
|
1579
1699
|
active: booleanField(source, "active", "account.active"),
|
|
1580
|
-
mark: stringField(source, "mark", "account.mark"),
|
|
1700
|
+
mark: stringField$1(source, "mark", "account.mark"),
|
|
1581
1701
|
cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
|
|
1582
1702
|
report: parseUsageReport(source.report)
|
|
1583
1703
|
};
|
|
1584
1704
|
}
|
|
1585
1705
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
1586
1706
|
function parseAccountsReport(value) {
|
|
1587
|
-
const accounts = record(value, "result").accounts;
|
|
1707
|
+
const accounts = record$1(value, "result").accounts;
|
|
1588
1708
|
if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
|
|
1589
1709
|
return reject$1("accounts");
|
|
1590
1710
|
}
|
|
@@ -1595,19 +1715,27 @@ window.__ModuleLoader__.load({
|
|
|
1595
1715
|
};
|
|
1596
1716
|
/** Canonical `<namespace>/<method>` endpoint of the model-catalog Remote. */
|
|
1597
1717
|
const MODELS_ENDPOINT = "commandcode/models";
|
|
1718
|
+
/**
|
|
1719
|
+
* The shared read/validate helpers for the model-catalog endpoint — a
|
|
1720
|
+
* separate instance so catalog boundary errors name `commandcode/models`,
|
|
1721
|
+
* not the report endpoint.
|
|
1722
|
+
*/
|
|
1723
|
+
const { record: catalogRecord, stringField: catalogString } = makeBoundaryValidator("commandcode/models result:");
|
|
1598
1724
|
/** Parse one untrusted boundary value into a {@link CommandCodeCatalogModel}. */
|
|
1599
1725
|
function parseCatalogModel(value) {
|
|
1600
|
-
const source =
|
|
1601
|
-
|
|
1602
|
-
id:
|
|
1603
|
-
name:
|
|
1726
|
+
const source = catalogRecord(value, "model");
|
|
1727
|
+
const model = {
|
|
1728
|
+
id: catalogString(source, "id", "model.id"),
|
|
1729
|
+
name: catalogString(source, "name", "model.name")
|
|
1604
1730
|
};
|
|
1731
|
+
if (source.tier !== void 0) model.tier = catalogString(source, "tier", "model.tier");
|
|
1732
|
+
return model;
|
|
1605
1733
|
}
|
|
1606
1734
|
/** Parse the wire result into a {@link CommandCodeCatalog}. */
|
|
1607
1735
|
function parseCatalog(value) {
|
|
1608
|
-
const models =
|
|
1736
|
+
const models = catalogRecord(value, "result").models;
|
|
1609
1737
|
if (Array.isArray(models)) return { models: models.map(parseCatalogModel) };
|
|
1610
|
-
|
|
1738
|
+
throw new TypeError("commandcode/models result: invalid models");
|
|
1611
1739
|
}
|
|
1612
1740
|
/** The Client-face contribution for the model-catalog endpoint. */
|
|
1613
1741
|
const MODELS_REMOTE_CONTRIBUTION = {
|
|
@@ -1631,45 +1759,29 @@ window.__ModuleLoader__.load({
|
|
|
1631
1759
|
];
|
|
1632
1760
|
/** The shared read/validate helpers, prefixed with the login endpoint so
|
|
1633
1761
|
* 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
|
-
}
|
|
1762
|
+
const { reject, record, stringField } = makeBoundaryValidator("commandcode/login result:");
|
|
1641
1763
|
/**
|
|
1642
1764
|
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
1643
1765
|
* Every field is shape-checked so a malformed frame fails the boundary
|
|
1644
1766
|
* instead of leaking into the page.
|
|
1645
1767
|
*/
|
|
1646
1768
|
function parseLoginStatus(value) {
|
|
1647
|
-
|
|
1648
|
-
const source = value;
|
|
1769
|
+
const source = record(value, "status");
|
|
1649
1770
|
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;
|
|
1771
|
+
if (state === "idle" || state === "waiting" || state === "success" || state === "failed") {
|
|
1772
|
+
const status = { state };
|
|
1773
|
+
if (source.authUrl !== void 0) status.authUrl = stringField(source, "authUrl", "authUrl");
|
|
1774
|
+
if (source.userName !== void 0) status.userName = stringField(source, "userName", "userName");
|
|
1775
|
+
if (source.keyName !== void 0) status.keyName = stringField(source, "keyName", "keyName");
|
|
1776
|
+
if (source.reason !== void 0) {
|
|
1777
|
+
const reason = source.reason;
|
|
1778
|
+
if (typeof reason === "string" && REASONS.includes(reason)) status.reason = reason;
|
|
1779
|
+
else reject("reason");
|
|
1780
|
+
}
|
|
1781
|
+
if (source.message !== void 0) status.message = stringField(source, "message", "message");
|
|
1782
|
+
return status;
|
|
1671
1783
|
}
|
|
1672
|
-
return
|
|
1784
|
+
return reject("state");
|
|
1673
1785
|
}
|
|
1674
1786
|
/** The strict result codec shared by all three login endpoints. */
|
|
1675
1787
|
const loginStatusSchema = { parse: parseLoginStatus };
|
|
@@ -1735,8 +1847,128 @@ window.__ModuleLoader__.load({
|
|
|
1735
1847
|
});
|
|
1736
1848
|
}
|
|
1737
1849
|
//#endregion
|
|
1850
|
+
//#region src/client/model-select.ts
|
|
1851
|
+
/**
|
|
1852
|
+
* Model-select helpers for the settings page's model editors (browser half).
|
|
1853
|
+
*
|
|
1854
|
+
* The routing-rule editor and the visible-models filter both pick catalog
|
|
1855
|
+
* models through the same checkbox multi-select dropdown (`ModelMultiSelect`
|
|
1856
|
+
* in section.tsx). The dropdown's data shaping — search filtering, stale-id
|
|
1857
|
+
* detection, tier grouping — lives here, React-free, so node tests can drive
|
|
1858
|
+
* it directly.
|
|
1859
|
+
*
|
|
1860
|
+
* Dependency-free by design: the client bundle may only import platform/seed
|
|
1861
|
+
* modules, so the plan snapshot below is a deliberately small vendored copy
|
|
1862
|
+
* (tier key → heading label) rather than an import of src/capabilities.ts.
|
|
1863
|
+
* When upstream adds a plan tier, extend BOTH tables.
|
|
1864
|
+
*
|
|
1865
|
+
* @module dsh-commandcode-provider/model-select
|
|
1866
|
+
*/
|
|
1867
|
+
/**
|
|
1868
|
+
* Minimum plan tier → dropdown section heading. Mirrors the Host-side
|
|
1869
|
+
* `KNOWN_PLANS` values + `PLAN_LABELS` in src/capabilities.ts (kept as a
|
|
1870
|
+
* vendored copy because the client bundle cannot import host modules).
|
|
1871
|
+
* Covers every tier key `KNOWN_PLANS` uses today; an unknown tier key falls
|
|
1872
|
+
* back to the raw key rather than vanishing the row.
|
|
1873
|
+
*/
|
|
1874
|
+
const TIER_HEADINGS = {
|
|
1875
|
+
go: "Go",
|
|
1876
|
+
goat: "GOAT",
|
|
1877
|
+
pro: "Pro",
|
|
1878
|
+
provider: "Provider",
|
|
1879
|
+
max: "Max"
|
|
1880
|
+
};
|
|
1881
|
+
/**
|
|
1882
|
+
* The dropdown section heading for a catalog model id, or undefined for
|
|
1883
|
+
* models outside the known plan tiers (unmapped models and stale ids render
|
|
1884
|
+
* unheaded). `knownPlans` is the Host-side `KNOWN_PLANS` table, threaded in
|
|
1885
|
+
* by the caller so this module stays dependency-free.
|
|
1886
|
+
*/
|
|
1887
|
+
function tierHeadingFor(modelId, knownPlans) {
|
|
1888
|
+
const tier = knownPlans[modelId];
|
|
1889
|
+
if (tier === void 0) return void 0;
|
|
1890
|
+
return TIER_HEADINGS[tier] ?? tier;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Whether `text` matches `query` as a case-insensitive substring over the
|
|
1894
|
+
* model id AND display name. An empty/blank query matches everything.
|
|
1895
|
+
*/
|
|
1896
|
+
function matchesModelQuery(model, query) {
|
|
1897
|
+
const needle = query.trim().toLowerCase();
|
|
1898
|
+
if (needle === "") return true;
|
|
1899
|
+
return model.id.toLowerCase().includes(needle) || model.name.toLowerCase().includes(needle);
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Build the dropdown options: the catalog (already in picker order) plus
|
|
1903
|
+
* any selected ids the catalog no longer carries, flagged stale so the UI
|
|
1904
|
+
* can mark them — a saved selection never silently loses an entry, and the
|
|
1905
|
+
* user can see which ones went stale upstream.
|
|
1906
|
+
*
|
|
1907
|
+
* When `query` is non-blank, catalog rows are filtered by
|
|
1908
|
+
* {@link matchesModelQuery}; stale rows are kept only while they match too,
|
|
1909
|
+
* so a search for a live model does not surface unrelated stale ids.
|
|
1910
|
+
*/
|
|
1911
|
+
function buildModelSelectOptions(catalog, selected, query = "") {
|
|
1912
|
+
const catalogIds = new Set(catalog.map((model) => model.id));
|
|
1913
|
+
const options = catalog.filter((model) => matchesModelQuery(model, query)).map((model) => ({
|
|
1914
|
+
value: model.id,
|
|
1915
|
+
label: model.name,
|
|
1916
|
+
stale: false
|
|
1917
|
+
}));
|
|
1918
|
+
const seen = new Set(catalogIds);
|
|
1919
|
+
for (const id of selected) {
|
|
1920
|
+
if (id === "" || seen.has(id)) continue;
|
|
1921
|
+
seen.add(id);
|
|
1922
|
+
if (catalogIds.has(id)) continue;
|
|
1923
|
+
if (!matchesModelQuery({
|
|
1924
|
+
id,
|
|
1925
|
+
name: id
|
|
1926
|
+
}, query)) continue;
|
|
1927
|
+
options.push({
|
|
1928
|
+
value: id,
|
|
1929
|
+
label: id,
|
|
1930
|
+
stale: true
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
return options;
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Group dropdown options under plan-tier headings (`tierOf` maps a model id
|
|
1937
|
+
* to its tier heading, or undefined for unmapped models — see
|
|
1938
|
+
* {@link tierHeadingFor}). Live rows keep their relative order; stale ids
|
|
1939
|
+
* and unmapped live rows share one trailing unheaded group. Groups merge
|
|
1940
|
+
* repeats, so a catalog interleaving two tiers still renders one section
|
|
1941
|
+
* per tier.
|
|
1942
|
+
*/
|
|
1943
|
+
function groupModelSelectOptions(options, tierOf) {
|
|
1944
|
+
const groups = [];
|
|
1945
|
+
const byHeading = /* @__PURE__ */ new Map();
|
|
1946
|
+
for (const option of options) {
|
|
1947
|
+
const heading = option.stale ? void 0 : tierOf(option.value);
|
|
1948
|
+
let group = byHeading.get(heading);
|
|
1949
|
+
if (group === void 0) {
|
|
1950
|
+
group = {
|
|
1951
|
+
heading,
|
|
1952
|
+
options: []
|
|
1953
|
+
};
|
|
1954
|
+
byHeading.set(heading, group);
|
|
1955
|
+
groups.push(group);
|
|
1956
|
+
}
|
|
1957
|
+
group.options.push(option);
|
|
1958
|
+
}
|
|
1959
|
+
return groups;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Toggle one model id in a selection: remove it when present, append it
|
|
1963
|
+
* when absent (append keeps catalog order irrelevant — the picker re-sorts
|
|
1964
|
+
* by plan tier on render).
|
|
1965
|
+
*/
|
|
1966
|
+
function toggleModelSelection(selected, modelId) {
|
|
1967
|
+
return selected.includes(modelId) ? selected.filter((value) => value !== modelId) : [...selected, modelId];
|
|
1968
|
+
}
|
|
1969
|
+
//#endregion
|
|
1738
1970
|
//#region package.json
|
|
1739
|
-
var version = "0.10.
|
|
1971
|
+
var version = "0.10.3";
|
|
1740
1972
|
var repository = {
|
|
1741
1973
|
"type": "git",
|
|
1742
1974
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -1759,8 +1991,13 @@ window.__ModuleLoader__.load({
|
|
|
1759
1991
|
/**
|
|
1760
1992
|
* This package's GitHub releases page, derived from the repository field so
|
|
1761
1993
|
* the update hint's link target can never drift from the published home.
|
|
1994
|
+
* Tolerates both repository shapes (`{ url }` and the plain string form).
|
|
1762
1995
|
*/
|
|
1763
|
-
const PLUGIN_RELEASES_URL =
|
|
1996
|
+
const PLUGIN_RELEASES_URL = (() => {
|
|
1997
|
+
const repo = repository;
|
|
1998
|
+
const url = typeof repo === "string" ? repo : repo?.url;
|
|
1999
|
+
return `${typeof url === "string" ? url.replace(/^git\+/, "").replace(/\.git$/, "") : "https://github.com/Mars-Sea/dsh-commandcode-provider"}/releases`;
|
|
2000
|
+
})();
|
|
1764
2001
|
/** Abort a hung registry request rather than keep the footer waiting. */
|
|
1765
2002
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
1766
2003
|
/**
|
|
@@ -1812,7 +2049,10 @@ window.__ModuleLoader__.load({
|
|
|
1812
2049
|
}
|
|
1813
2050
|
/** Split a tolerant version string into numeric core + prerelease ids. */
|
|
1814
2051
|
function splitVersion(value) {
|
|
1815
|
-
const
|
|
2052
|
+
const cleaned = value.trim().replace(/^v/i, "");
|
|
2053
|
+
const dash = cleaned.indexOf("-");
|
|
2054
|
+
const coreText = dash < 0 ? cleaned : cleaned.slice(0, dash);
|
|
2055
|
+
const preText = dash < 0 ? void 0 : cleaned.slice(dash + 1);
|
|
1816
2056
|
return {
|
|
1817
2057
|
core: coreText === "" ? [0] : coreText.split(".").map((part) => {
|
|
1818
2058
|
const parsed = Number.parseInt(part, 10);
|
|
@@ -1951,6 +2191,7 @@ window.__ModuleLoader__.load({
|
|
|
1951
2191
|
className: "cc-reset",
|
|
1952
2192
|
disabled,
|
|
1953
2193
|
onClick: onReset,
|
|
2194
|
+
"aria-label": `${label} — ${t("reset")}`,
|
|
1954
2195
|
children: t("reset")
|
|
1955
2196
|
})]
|
|
1956
2197
|
})]
|
|
@@ -2005,7 +2246,11 @@ window.__ModuleLoader__.load({
|
|
|
2005
2246
|
}),
|
|
2006
2247
|
overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2007
2248
|
className: "cc-badge",
|
|
2008
|
-
children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany"
|
|
2249
|
+
children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany", { count: overridden })
|
|
2250
|
+
}) : null,
|
|
2251
|
+
!expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2252
|
+
className: "cc-badge cc-badgeWarn",
|
|
2253
|
+
children: t("advancedInvalid")
|
|
2009
2254
|
}) : null,
|
|
2010
2255
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-advancedSpacer" }),
|
|
2011
2256
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2108,9 +2353,9 @@ window.__ModuleLoader__.load({
|
|
|
2108
2353
|
className: "cc-field",
|
|
2109
2354
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2110
2355
|
className: "cc-fieldHead",
|
|
2111
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2356
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2112
2357
|
className: "cc-label",
|
|
2113
|
-
|
|
2358
|
+
"aria-hidden": "true",
|
|
2114
2359
|
children: label
|
|
2115
2360
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2116
2361
|
className: "cc-badges",
|
|
@@ -2122,6 +2367,7 @@ window.__ModuleLoader__.load({
|
|
|
2122
2367
|
className: "cc-reset",
|
|
2123
2368
|
disabled,
|
|
2124
2369
|
onClick: onReset,
|
|
2370
|
+
"aria-label": `${label} — ${t("reset")}`,
|
|
2125
2371
|
children: t("reset")
|
|
2126
2372
|
})]
|
|
2127
2373
|
})]
|
|
@@ -2251,6 +2497,7 @@ window.__ModuleLoader__.load({
|
|
|
2251
2497
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2252
2498
|
className: "cc-usageBar",
|
|
2253
2499
|
role: "progressbar",
|
|
2500
|
+
"aria-label": label,
|
|
2254
2501
|
"aria-valuemin": 0,
|
|
2255
2502
|
"aria-valuemax": 100,
|
|
2256
2503
|
"aria-valuenow": Math.round(ratio * 100),
|
|
@@ -2368,7 +2615,7 @@ window.__ModuleLoader__.load({
|
|
|
2368
2615
|
}),
|
|
2369
2616
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
2370
2617
|
label: t("usageSuccessRate"),
|
|
2371
|
-
value: `${report.usage.successRate}%`
|
|
2618
|
+
value: `${formatSuccessRate(report.usage.successRate)}%`
|
|
2372
2619
|
}),
|
|
2373
2620
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
2374
2621
|
label: t("usageCost"),
|
|
@@ -2456,7 +2703,10 @@ window.__ModuleLoader__.load({
|
|
|
2456
2703
|
/** The status dot on an account tab: cooling/invalid warn, everything else ok. */
|
|
2457
2704
|
function AccountTabDot({ entry }) {
|
|
2458
2705
|
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", {
|
|
2706
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2707
|
+
className: cls,
|
|
2708
|
+
"aria-hidden": "true"
|
|
2709
|
+
});
|
|
2460
2710
|
}
|
|
2461
2711
|
/**
|
|
2462
2712
|
* The account-usage card: the `/commandcode` dashboard's facts rendered as
|
|
@@ -2532,12 +2782,10 @@ window.__ModuleLoader__.load({
|
|
|
2532
2782
|
}) : null,
|
|
2533
2783
|
entries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2534
2784
|
className: "cc-tabs",
|
|
2535
|
-
role: "tablist",
|
|
2536
2785
|
"aria-label": t("accountsTitle"),
|
|
2537
2786
|
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2538
2787
|
type: "button",
|
|
2539
|
-
|
|
2540
|
-
"aria-selected": selected?.id === entry.id,
|
|
2788
|
+
"aria-pressed": selected?.id === entry.id,
|
|
2541
2789
|
className: selected?.id === entry.id ? "cc-tab cc-tabActive" : "cc-tab",
|
|
2542
2790
|
onClick: () => setSelectedId(entry.id),
|
|
2543
2791
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTabDot, { entry }), entry.label]
|
|
@@ -2780,6 +3028,7 @@ window.__ModuleLoader__.load({
|
|
|
2780
3028
|
value: rule.account,
|
|
2781
3029
|
disabled,
|
|
2782
3030
|
onChange: (event) => onAccount(event.target.value),
|
|
3031
|
+
"aria-label": t("ruleAccount"),
|
|
2783
3032
|
children: targets.map((target) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2784
3033
|
value: target.value,
|
|
2785
3034
|
children: target.label
|
|
@@ -2793,59 +3042,91 @@ window.__ModuleLoader__.load({
|
|
|
2793
3042
|
});
|
|
2794
3043
|
}
|
|
2795
3044
|
/**
|
|
2796
|
-
* A checkbox multi-select dropdown for
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
3045
|
+
* A checkbox multi-select dropdown for picking catalog models (the
|
|
3046
|
+
* routing-rule rows and the visible-models filter share it). The trigger
|
|
3047
|
+
* shows the selection count; the Menu lists every catalog model with a
|
|
3048
|
+
* checkbox, toggled by clicking the row. A search box under the trigger
|
|
3049
|
+
* (inside the Menu anchor, so focusing it never trips the outside-click
|
|
3050
|
+
* close) filters the list by id/display-name substring, and items group
|
|
3051
|
+
* under plan-tier headings in picker order. Selected ids the catalog no
|
|
3052
|
+
* longer carries still render — flagged stale — so a saved selection never
|
|
3053
|
+
* silently loses an entry, and the VisibleModelsCard offers a one-click
|
|
3054
|
+
* cleanup.
|
|
2800
3055
|
*/
|
|
2801
3056
|
function ModelMultiSelect({ id, selected, catalog, disabled, t, onSelect }) {
|
|
2802
3057
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3058
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
3059
|
+
(0, react.useEffect)(() => {
|
|
3060
|
+
if (open) setQuery("");
|
|
3061
|
+
}, [open]);
|
|
3062
|
+
const tiers = (0, react.useMemo)(() => Object.fromEntries(catalog.flatMap((model) => model.tier === void 0 ? [] : [[model.id, model.tier]])), [catalog]);
|
|
3063
|
+
const options = buildModelSelectOptions(catalog, selected, query);
|
|
3064
|
+
const items = groupModelSelectOptions(options, (modelId) => tierHeadingFor(modelId, tiers)).flatMap((group) => [...group.heading === void 0 ? [] : [{
|
|
3065
|
+
type: "label",
|
|
3066
|
+
id: `cc-tier-${group.heading}`,
|
|
3067
|
+
text: group.heading
|
|
3068
|
+
}], ...group.options.map((option) => ({
|
|
2812
3069
|
id: option.value,
|
|
2813
3070
|
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2814
3071
|
className: "cc-checkRow",
|
|
2815
|
-
children: [
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3072
|
+
children: [
|
|
3073
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3074
|
+
type: "checkbox",
|
|
3075
|
+
className: "cc-check",
|
|
3076
|
+
checked: selected.includes(option.value),
|
|
3077
|
+
readOnly: true,
|
|
3078
|
+
tabIndex: -1
|
|
3079
|
+
}),
|
|
3080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3081
|
+
className: "cc-checkName",
|
|
3082
|
+
children: option.label
|
|
3083
|
+
}),
|
|
3084
|
+
option.stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3085
|
+
className: "cc-badge",
|
|
3086
|
+
children: t("modelStale")
|
|
3087
|
+
}) : null
|
|
3088
|
+
]
|
|
2825
3089
|
})
|
|
2826
|
-
}));
|
|
3090
|
+
}))]);
|
|
2827
3091
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
2828
3092
|
open,
|
|
2829
3093
|
onClose: () => setOpen(false),
|
|
2830
3094
|
onSelect: (modelId) => {
|
|
2831
|
-
onSelect(
|
|
3095
|
+
onSelect(toggleModelSelection(selected, modelId));
|
|
2832
3096
|
},
|
|
2833
3097
|
selectedIds: selected,
|
|
2834
3098
|
items,
|
|
3099
|
+
footer: options.length === 0 ? [{
|
|
3100
|
+
type: "label",
|
|
3101
|
+
id: "cc-model-search-empty",
|
|
3102
|
+
text: t("modelSearchEmpty")
|
|
3103
|
+
}] : [],
|
|
2835
3104
|
portal: true,
|
|
2836
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
children:
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
"
|
|
2848
|
-
|
|
3105
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3106
|
+
className: "cc-modelSelectAnchor",
|
|
3107
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3108
|
+
id,
|
|
3109
|
+
type: "button",
|
|
3110
|
+
className: "cc-input cc-ruleTrigger",
|
|
3111
|
+
disabled: disabled || catalog.length === 0,
|
|
3112
|
+
onClick: () => setOpen((value) => !value),
|
|
3113
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3114
|
+
className: "cc-ruleTriggerText",
|
|
3115
|
+
children: selected.length === 0 ? t("ruleModelPick") : t("ruleModelCount", { count: selected.length })
|
|
3116
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3117
|
+
className: "cc-ruleCaret",
|
|
3118
|
+
"aria-hidden": "true"
|
|
3119
|
+
})]
|
|
3120
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3121
|
+
type: "search",
|
|
3122
|
+
className: "cc-input cc-modelSearch",
|
|
3123
|
+
placeholder: t("modelSearchPlaceholder"),
|
|
3124
|
+
"aria-label": t("modelSearchPlaceholder"),
|
|
3125
|
+
value: query,
|
|
3126
|
+
disabled,
|
|
3127
|
+
autoFocus: true,
|
|
3128
|
+
onChange: (event) => setQuery(event.target.value)
|
|
3129
|
+
}) : null]
|
|
2849
3130
|
})
|
|
2850
3131
|
});
|
|
2851
3132
|
}
|
|
@@ -2901,6 +3182,68 @@ window.__ModuleLoader__.load({
|
|
|
2901
3182
|
]
|
|
2902
3183
|
});
|
|
2903
3184
|
}
|
|
3185
|
+
/** The visible-model filter card: an allowlist over the catalog. Empty = show all. */
|
|
3186
|
+
function VisibleModelsCard({ t, state, disabled, onSelect, onClear }) {
|
|
3187
|
+
const count = state.visibleModels.length;
|
|
3188
|
+
const pickT = (key, params) => {
|
|
3189
|
+
if (key === "ruleModelPick") return t("visibleModelsPick");
|
|
3190
|
+
if (key === "ruleModelCount") return t("visibleModelsCount", params);
|
|
3191
|
+
return t(key, params);
|
|
3192
|
+
};
|
|
3193
|
+
const catalogIds = new Set(state.catalogModels.map((model) => model.id));
|
|
3194
|
+
const staleIds = state.visibleModels.filter((id) => !catalogIds.has(id));
|
|
3195
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3196
|
+
className: "cc-card",
|
|
3197
|
+
"aria-label": t("visibleModelsTitle"),
|
|
3198
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3199
|
+
className: "cc-field",
|
|
3200
|
+
children: [
|
|
3201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3202
|
+
className: "cc-fieldHead",
|
|
3203
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
3204
|
+
className: "cc-label",
|
|
3205
|
+
children: t("visibleModelsTitle")
|
|
3206
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3207
|
+
className: "cc-badges",
|
|
3208
|
+
children: [staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3209
|
+
type: "button",
|
|
3210
|
+
className: "cc-reset",
|
|
3211
|
+
disabled,
|
|
3212
|
+
onClick: () => onSelect(state.visibleModels.filter((id) => catalogIds.has(id))),
|
|
3213
|
+
children: t("visibleModelsCleanStale", { count: staleIds.length })
|
|
3214
|
+
}) : null, count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3215
|
+
type: "button",
|
|
3216
|
+
className: "cc-reset",
|
|
3217
|
+
disabled,
|
|
3218
|
+
onClick: onClear,
|
|
3219
|
+
children: t("visibleModelsShowAll")
|
|
3220
|
+
}) : null]
|
|
3221
|
+
})]
|
|
3222
|
+
}),
|
|
3223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3224
|
+
className: "cc-hint",
|
|
3225
|
+
children: t("visibleModelsHint")
|
|
3226
|
+
}),
|
|
3227
|
+
state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3228
|
+
className: "cc-invalid",
|
|
3229
|
+
children: t("rulesCatalogFailed")
|
|
3230
|
+
}) : null,
|
|
3231
|
+
staleIds.length > 0 && !state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3232
|
+
className: "cc-hint",
|
|
3233
|
+
children: t("visibleModelsStaleHint", { count: staleIds.length })
|
|
3234
|
+
}) : null,
|
|
3235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelMultiSelect, {
|
|
3236
|
+
id: "cc-visible-models",
|
|
3237
|
+
selected: state.visibleModels,
|
|
3238
|
+
catalog: state.catalogModels,
|
|
3239
|
+
disabled,
|
|
3240
|
+
t: pickT,
|
|
3241
|
+
onSelect
|
|
3242
|
+
})
|
|
3243
|
+
]
|
|
3244
|
+
})
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
2904
3247
|
/**
|
|
2905
3248
|
* Show the "Saved ✓" affordance for a short window after each accepted save.
|
|
2906
3249
|
* The controller only counts saves (`savedCount`); the flash timing lives
|
|
@@ -2997,6 +3340,13 @@ window.__ModuleLoader__.load({
|
|
|
2997
3340
|
onModels: props.editRuleModels,
|
|
2998
3341
|
onAccount: props.editRuleAccount
|
|
2999
3342
|
}),
|
|
3343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsCard, {
|
|
3344
|
+
t,
|
|
3345
|
+
state,
|
|
3346
|
+
disabled,
|
|
3347
|
+
onSelect: props.editVisibleModels,
|
|
3348
|
+
onClear: props.clearVisibleModels
|
|
3349
|
+
}),
|
|
3000
3350
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3001
3351
|
className: "cc-card",
|
|
3002
3352
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretKeyField, {
|
|
@@ -3099,10 +3449,14 @@ window.__ModuleLoader__.load({
|
|
|
3099
3449
|
}
|
|
3100
3450
|
/** The closed-panel style: the outlet stays mounted as the detection anchor. */
|
|
3101
3451
|
const HIDDEN_STYLE = { display: "none" };
|
|
3102
|
-
/**
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3452
|
+
/**
|
|
3453
|
+
* Decide the card's posture from one settings snapshot. Pure: the component
|
|
3454
|
+
* subscribes once and passes the snapshot in, so hook order never depends
|
|
3455
|
+
* on the registration→live transition. (The owner facts stay on the
|
|
3456
|
+
* component — only the snapshot decides the posture.)
|
|
3457
|
+
*/
|
|
3458
|
+
function cardMode(snapshot) {
|
|
3459
|
+
if (snapshot === void 0) return { kind: "registration" };
|
|
3106
3460
|
return {
|
|
3107
3461
|
kind: "live",
|
|
3108
3462
|
ready: snapshot.available,
|
|
@@ -3173,8 +3527,8 @@ window.__ModuleLoader__.load({
|
|
|
3173
3527
|
*/
|
|
3174
3528
|
function CommandCodeProviderCard(props) {
|
|
3175
3529
|
const { t } = props;
|
|
3176
|
-
const mode = cardMode(props);
|
|
3177
3530
|
const state = props.useCommandCodeSettings !== void 0 ? props.useCommandCodeSettings((snapshot) => snapshot) : void 0;
|
|
3531
|
+
const mode = cardMode(state);
|
|
3178
3532
|
const login = props.useCommandCodeLogin !== void 0 ? props.useCommandCodeLogin((snapshot) => snapshot) : void 0;
|
|
3179
3533
|
const dirty = state?.dirty ?? false;
|
|
3180
3534
|
const saving = state?.saving ?? false;
|
|
@@ -3313,7 +3667,7 @@ window.__ModuleLoader__.load({
|
|
|
3313
3667
|
filterModelsByPlan: "隐藏套餐外模型",
|
|
3314
3668
|
filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
|
|
3315
3669
|
webSearch: "用 Command Code 承载联网搜索",
|
|
3316
|
-
webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key
|
|
3670
|
+
webSearchHint: "开启后,dsh 的 web_search 工具由 Command Code 承担(复用同一个 API key 与地址),并优先于其他搜索后端;关闭则把选择权交还给之前的后端(如 modsearch),而不是强制回退到 DeepSeek 搜索。",
|
|
3317
3671
|
accountsTitle: "多账户轮换",
|
|
3318
3672
|
accountsHint: "当前账户达到用量限额(429)或密钥失效(401)时,请求自动切换到下一个账户;全部耗尽时会提示最早的重置时间。",
|
|
3319
3673
|
accountAdd: "添加账户",
|
|
@@ -3336,6 +3690,16 @@ window.__ModuleLoader__.load({
|
|
|
3336
3690
|
ruleModelCount: "已选 {count} 个模型",
|
|
3337
3691
|
ruleAccount: "目标账户",
|
|
3338
3692
|
ruleHint: "从下拉列表勾选要路由的模型(可多选),再选择目标账户。",
|
|
3693
|
+
modelSearchPlaceholder: "搜索模型…",
|
|
3694
|
+
modelSearchEmpty: "没有匹配的模型。",
|
|
3695
|
+
modelStale: "已下架",
|
|
3696
|
+
visibleModelsTitle: "模型白名单",
|
|
3697
|
+
visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。保存后,下次打开模型选择器生效。",
|
|
3698
|
+
visibleModelsPick: "选择要保留的模型…",
|
|
3699
|
+
visibleModelsCount: "已选 {count} 个模型",
|
|
3700
|
+
visibleModelsShowAll: "显示全部",
|
|
3701
|
+
visibleModelsStaleHint: "有 {count} 个已选模型在目录中找不到了(可能已下架),不影响其他模型;可清理或保留。",
|
|
3702
|
+
visibleModelsCleanStale: "清理失效({count})",
|
|
3339
3703
|
overridden: "已覆盖",
|
|
3340
3704
|
reset: "重置",
|
|
3341
3705
|
invalidNumber: "无效数字",
|
|
@@ -3435,7 +3799,7 @@ window.__ModuleLoader__.load({
|
|
|
3435
3799
|
filterModelsByPlan: "Hide out-of-plan models",
|
|
3436
3800
|
filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
|
|
3437
3801
|
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
|
|
3802
|
+
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
3803
|
accountsTitle: "Account rotation",
|
|
3440
3804
|
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
3805
|
accountAdd: "Add account",
|
|
@@ -3458,6 +3822,16 @@ window.__ModuleLoader__.load({
|
|
|
3458
3822
|
ruleModelCount: "{count} model(s) selected",
|
|
3459
3823
|
ruleAccount: "Target account",
|
|
3460
3824
|
ruleHint: "Check the models to route from the dropdown (multi-select), then pick the target account.",
|
|
3825
|
+
modelSearchPlaceholder: "Search models…",
|
|
3826
|
+
modelSearchEmpty: "No matching models.",
|
|
3827
|
+
modelStale: "Retired",
|
|
3828
|
+
visibleModelsTitle: "Model allowlist",
|
|
3829
|
+
visibleModelsHint: "Check the models you want to keep, and model pickers will list only those. If nothing is checked, every model is shown. After saving, the change applies the next time you open a model picker.",
|
|
3830
|
+
visibleModelsPick: "Select models to keep…",
|
|
3831
|
+
visibleModelsCount: "{count} model(s) selected",
|
|
3832
|
+
visibleModelsShowAll: "Show all",
|
|
3833
|
+
visibleModelsStaleHint: "{count} selected model(s) are no longer in the catalog (possibly retired); other models are unaffected. Clean them up or keep them.",
|
|
3834
|
+
visibleModelsCleanStale: "Clean stale ({count})",
|
|
3461
3835
|
overridden: "Overridden",
|
|
3462
3836
|
reset: "Reset",
|
|
3463
3837
|
invalidNumber: "Invalid number",
|
|
@@ -3567,6 +3941,13 @@ window.__ModuleLoader__.load({
|
|
|
3567
3941
|
.cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
3568
3942
|
.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
3943
|
.cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3944
|
+
/* The model multi-select search box: stacked under the trigger while the
|
|
3945
|
+
* dropdown is open, same input sizing so the pair reads as one control. The
|
|
3946
|
+
* box lives inside the Menu anchor (which renders inside the Menu's root
|
|
3947
|
+
* span), so focusing/typing it never trips the Menu's outside-click close. */
|
|
3948
|
+
.cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
|
|
3949
|
+
.cc-modelSearch{width:100%}
|
|
3950
|
+
.cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
|
|
3570
3951
|
/* Selects need their own treatment to sit flush with the text inputs:
|
|
3571
3952
|
* the UA stylesheet renders <select> border-box (34px total vs the inputs'
|
|
3572
3953
|
* 36px) and forces its own menulist text metrics, so drop the native
|
|
@@ -3621,7 +4002,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3621
4002
|
.cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
|
|
3622
4003
|
.cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
|
|
3623
4004
|
.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}
|
|
4005
|
+
@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
4006
|
.cc-usageMeta{align-items:center;gap:8px;display:flex}
|
|
3626
4007
|
.cc-accountReport{flex-direction:column;gap:12px;display:flex}
|
|
3627
4008
|
.cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
|
|
@@ -3638,6 +4019,9 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3638
4019
|
.cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
3639
4020
|
.cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
|
|
3640
4021
|
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
4022
|
+
/* The Models-page provider panel root: unstyled by design — the row card the
|
|
4023
|
+
* Models page owns supplies the surface, the panel only stacks its controls. */
|
|
4024
|
+
.cc-providerCard{flex-direction:column;display:flex}
|
|
3641
4025
|
/* The update hint rides the footer version line: warning-tinted (with a
|
|
3642
4026
|
* muted fallback for themes without the alias), quiet until hovered. */
|
|
3643
4027
|
.cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
|
|
@@ -3646,7 +4030,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3646
4030
|
* authorization link is the only branded element on it. */
|
|
3647
4031
|
.cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
|
|
3648
4032
|
.cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
|
|
3649
|
-
.cc-loginBusy{color:var(--dsw-alias-label-tertiary)}
|
|
3650
4033
|
.cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
|
|
3651
4034
|
.cc-loginError{color:var(--dsw-alias-label-error)}
|
|
3652
4035
|
.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 +4205,9 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
3822
4205
|
addRule: () => controller.addRule(),
|
|
3823
4206
|
removeRule: (id) => controller.removeRule(id),
|
|
3824
4207
|
editRuleModels: (id, ids) => controller.editRuleModels(id, ids),
|
|
3825
|
-
editRuleAccount: (id, text) => controller.editRuleAccount(id, text)
|
|
4208
|
+
editRuleAccount: (id, text) => controller.editRuleAccount(id, text),
|
|
4209
|
+
editVisibleModels: (ids) => controller.editVisibleModels(ids),
|
|
4210
|
+
clearVisibleModels: () => controller.clearVisibleModels()
|
|
3826
4211
|
});
|
|
3827
4212
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3828
4213
|
name: "settings.section",
|