@mars-sea/dsh-commandcode-provider 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/lib/client.js +669 -8
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +146 -3
- package/lib/index.js +379 -16
- package/lib/index.js.map +1 -1
- package/package.json +3 -1
package/lib/client.js
CHANGED
|
@@ -5,6 +5,7 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
8
|
+
let react = require("react");
|
|
8
9
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
10
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
11
|
//#region src/client/sessions.ts
|
|
@@ -59,7 +60,7 @@ window.__ModuleLoader__.load({
|
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
/** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */
|
|
62
|
-
function numberField(field) {
|
|
63
|
+
function numberField$1(field) {
|
|
63
64
|
return {
|
|
64
65
|
field,
|
|
65
66
|
format: (value) => typeof value === "number" ? String(value) : "",
|
|
@@ -74,12 +75,37 @@ window.__ModuleLoader__.load({
|
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
|
|
80
|
+
* clears it). The component renders a toggle and only ever stages these two
|
|
81
|
+
* strings; anything else blocks save.
|
|
82
|
+
*/
|
|
83
|
+
function booleanField$1(field) {
|
|
84
|
+
return {
|
|
85
|
+
field,
|
|
86
|
+
format: (value) => typeof value === "boolean" ? String(value) : "",
|
|
87
|
+
parse: (text) => {
|
|
88
|
+
const trimmed = text.trim();
|
|
89
|
+
if (trimmed === "") return { kind: "clear" };
|
|
90
|
+
if (trimmed === "true") return {
|
|
91
|
+
kind: "set",
|
|
92
|
+
value: true
|
|
93
|
+
};
|
|
94
|
+
if (trimmed === "false") return {
|
|
95
|
+
kind: "set",
|
|
96
|
+
value: false
|
|
97
|
+
};
|
|
98
|
+
return { kind: "invalid" };
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
77
102
|
/** The fields this page edits inside the `llm-commandcode` namespace. */
|
|
78
103
|
const SECTION_FIELDS = [
|
|
79
104
|
textField("apiBase"),
|
|
80
105
|
textField("workingDir"),
|
|
81
|
-
numberField("requestTimeoutMs"),
|
|
82
|
-
numberField("streamIdleTimeoutMs")
|
|
106
|
+
numberField$1("requestTimeoutMs"),
|
|
107
|
+
numberField$1("streamIdleTimeoutMs"),
|
|
108
|
+
booleanField$1("filterModelsByPlan")
|
|
83
109
|
];
|
|
84
110
|
/**
|
|
85
111
|
* Controller bridging the `llm-commandcode` scope and the credentials domain
|
|
@@ -179,6 +205,7 @@ window.__ModuleLoader__.load({
|
|
|
179
205
|
defaultWorkingDir: this.defaultWorkingDir,
|
|
180
206
|
requestTimeoutMs: this.field("requestTimeoutMs"),
|
|
181
207
|
streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
|
|
208
|
+
filterModelsByPlan: this.field("filterModelsByPlan"),
|
|
182
209
|
dirty: plan.length > 0,
|
|
183
210
|
invalid: plan.some((item) => item.run === void 0),
|
|
184
211
|
saving: this.saving,
|
|
@@ -361,6 +388,230 @@ window.__ModuleLoader__.load({
|
|
|
361
388
|
}
|
|
362
389
|
};
|
|
363
390
|
//#endregion
|
|
391
|
+
//#region src/client/usage.ts
|
|
392
|
+
const IDLE = {
|
|
393
|
+
status: "idle",
|
|
394
|
+
report: void 0,
|
|
395
|
+
error: void 0,
|
|
396
|
+
fetchedAt: void 0
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Controller bridging the `commandcode/report` Remote onto the card. Public
|
|
400
|
+
* API mirrors {@link CommandCodeSettingsController}: `state()` projections,
|
|
401
|
+
* `subscribe`, and one `refresh()` action.
|
|
402
|
+
*/
|
|
403
|
+
var CommandCodeUsageController = class {
|
|
404
|
+
remote;
|
|
405
|
+
listeners = /* @__PURE__ */ new Set();
|
|
406
|
+
current = IDLE;
|
|
407
|
+
generation = 0;
|
|
408
|
+
inFlight = false;
|
|
409
|
+
disposed = false;
|
|
410
|
+
constructor(remote) {
|
|
411
|
+
this.remote = remote;
|
|
412
|
+
}
|
|
413
|
+
/** Release every subscription. Idempotent; in-flight results are dropped. */
|
|
414
|
+
dispose() {
|
|
415
|
+
this.disposed = true;
|
|
416
|
+
this.generation += 1;
|
|
417
|
+
this.listeners.clear();
|
|
418
|
+
}
|
|
419
|
+
/** Subscribe to state projections. @returns the disposer. */
|
|
420
|
+
subscribe(listener) {
|
|
421
|
+
this.listeners.add(listener);
|
|
422
|
+
return () => this.listeners.delete(listener);
|
|
423
|
+
}
|
|
424
|
+
/** The current card state face. */
|
|
425
|
+
state() {
|
|
426
|
+
return this.current;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Fetch (or refetch) the report. Concurrent refreshes collapse onto one
|
|
430
|
+
* request; a superseded fetch's late result is dropped, never published.
|
|
431
|
+
*/
|
|
432
|
+
async refresh() {
|
|
433
|
+
if (this.disposed || this.inFlight) return;
|
|
434
|
+
const generation = ++this.generation;
|
|
435
|
+
this.inFlight = true;
|
|
436
|
+
this.current = {
|
|
437
|
+
...this.current,
|
|
438
|
+
status: "loading",
|
|
439
|
+
error: void 0
|
|
440
|
+
};
|
|
441
|
+
this.publish();
|
|
442
|
+
try {
|
|
443
|
+
const response = await this.remote.report();
|
|
444
|
+
if (this.disposed || generation !== this.generation) return;
|
|
445
|
+
if (response.ok) this.current = {
|
|
446
|
+
status: "ready",
|
|
447
|
+
report: response.value,
|
|
448
|
+
error: void 0,
|
|
449
|
+
fetchedAt: Date.now()
|
|
450
|
+
};
|
|
451
|
+
else this.current = {
|
|
452
|
+
...this.current,
|
|
453
|
+
status: "error",
|
|
454
|
+
error: response.error.message
|
|
455
|
+
};
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (this.disposed || generation !== this.generation) return;
|
|
458
|
+
this.current = {
|
|
459
|
+
...this.current,
|
|
460
|
+
status: "error",
|
|
461
|
+
error: error instanceof Error ? error.message : String(error)
|
|
462
|
+
};
|
|
463
|
+
} finally {
|
|
464
|
+
if (generation === this.generation) this.inFlight = false;
|
|
465
|
+
}
|
|
466
|
+
this.publish();
|
|
467
|
+
}
|
|
468
|
+
publish() {
|
|
469
|
+
if (this.disposed) return;
|
|
470
|
+
for (const listener of this.listeners) listener();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
/** Format a dollar amount compactly (2 decimals). */
|
|
474
|
+
function formatMoney(value) {
|
|
475
|
+
return `$${value.toFixed(2)}`;
|
|
476
|
+
}
|
|
477
|
+
/** Format a dollar amount precisely (4 decimals) for small totals. */
|
|
478
|
+
function formatMoneyExact(value) {
|
|
479
|
+
return `$${value.toFixed(4)}`;
|
|
480
|
+
}
|
|
481
|
+
/** Format a large token count compactly (1.9M style). */
|
|
482
|
+
function formatTokensCompact(value) {
|
|
483
|
+
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
484
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
485
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
486
|
+
return String(value);
|
|
487
|
+
}
|
|
488
|
+
/** One window's fill ratio in [0, 1]; 0 when uncapped. */
|
|
489
|
+
function windowRatio(used, cap) {
|
|
490
|
+
if (cap <= 0) return 0;
|
|
491
|
+
return Math.max(0, Math.min(1, used / cap));
|
|
492
|
+
}
|
|
493
|
+
/** Format a millis timestamp as a local short date-time; empty when unset. */
|
|
494
|
+
function formatResetAt(ms) {
|
|
495
|
+
if (ms <= 0) return "";
|
|
496
|
+
return new Date(ms).toLocaleString();
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/usage-wire.ts
|
|
500
|
+
/** The npm package identity both contribution registrations claim. */
|
|
501
|
+
const USAGE_REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
|
|
502
|
+
/** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
|
|
503
|
+
const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
504
|
+
/** Reject one boundary value with a field-naming error. */
|
|
505
|
+
function reject(field) {
|
|
506
|
+
throw new TypeError(`commandcode/report result: invalid ${field}`);
|
|
507
|
+
}
|
|
508
|
+
/** Read one required finite number field (`field` is the dotted error label). */
|
|
509
|
+
function numberField(source, key, field) {
|
|
510
|
+
const value = source[key];
|
|
511
|
+
if (typeof value !== "number" || !Number.isFinite(value)) reject(field);
|
|
512
|
+
return value;
|
|
513
|
+
}
|
|
514
|
+
/** Read one required string field (`field` is the dotted error label). */
|
|
515
|
+
function stringField(source, key, field) {
|
|
516
|
+
const value = source[key];
|
|
517
|
+
if (typeof value !== "string") reject(field);
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
/** Read one required boolean field (`field` is the dotted error label). */
|
|
521
|
+
function booleanField(source, key, field) {
|
|
522
|
+
const value = source[key];
|
|
523
|
+
if (typeof value !== "boolean") reject(field);
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
/** Narrow an unknown value to a plain record, or reject. */
|
|
527
|
+
function record(value, field) {
|
|
528
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) reject(field);
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
532
|
+
function windowLimit(value, field) {
|
|
533
|
+
const source = record(value, field);
|
|
534
|
+
return {
|
|
535
|
+
used: numberField(source, "used", `${field}.used`),
|
|
536
|
+
cap: numberField(source, "cap", `${field}.cap`),
|
|
537
|
+
exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
|
|
538
|
+
resetAt: numberField(source, "resetAt", `${field}.resetAt`)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Parse one untrusted boundary value into a {@link CommandCodeUsageReport}.
|
|
543
|
+
* Optional sections stay optional; every present field is shape-checked so a
|
|
544
|
+
* malformed frame fails the boundary instead of rendering garbage.
|
|
545
|
+
*/
|
|
546
|
+
function parseUsageReport(value) {
|
|
547
|
+
const source = record(value, "report");
|
|
548
|
+
const failures = source.failures;
|
|
549
|
+
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject("failures");
|
|
550
|
+
const report = { failures };
|
|
551
|
+
if (source.account !== void 0) {
|
|
552
|
+
const account = record(source.account, "account");
|
|
553
|
+
report.account = {
|
|
554
|
+
id: stringField(account, "id", "account.id"),
|
|
555
|
+
name: stringField(account, "name", "account.name"),
|
|
556
|
+
userName: stringField(account, "userName", "account.userName")
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (source.usage !== void 0) {
|
|
560
|
+
const usage = record(source.usage, "usage");
|
|
561
|
+
report.usage = {
|
|
562
|
+
totalCount: numberField(usage, "totalCount", "usage.totalCount"),
|
|
563
|
+
totalCost: numberField(usage, "totalCost", "usage.totalCost"),
|
|
564
|
+
successRate: numberField(usage, "successRate", "usage.successRate"),
|
|
565
|
+
completedCount: numberField(usage, "completedCount", "usage.completedCount"),
|
|
566
|
+
failedCount: numberField(usage, "failedCount", "usage.failedCount"),
|
|
567
|
+
totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
|
|
568
|
+
totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
|
|
569
|
+
totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
|
|
570
|
+
periodBasis: stringField(usage, "periodBasis", "usage.periodBasis")
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (source.credits !== void 0) {
|
|
574
|
+
const credits = record(source.credits, "credits");
|
|
575
|
+
report.credits = {
|
|
576
|
+
monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
|
|
577
|
+
purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
|
|
578
|
+
freeCredits: numberField(credits, "freeCredits", "credits.freeCredits"),
|
|
579
|
+
fiveHour: windowLimit(credits.fiveHour, "credits.fiveHour"),
|
|
580
|
+
weekly: windowLimit(credits.weekly, "credits.weekly")
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (source.plan !== void 0) {
|
|
584
|
+
const plan = record(source.plan, "plan");
|
|
585
|
+
const monthly = plan.monthlyCredits;
|
|
586
|
+
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject("plan.monthlyCredits");
|
|
587
|
+
report.plan = {
|
|
588
|
+
planId: stringField(plan, "planId", "plan.planId"),
|
|
589
|
+
name: stringField(plan, "name", "plan.name"),
|
|
590
|
+
status: stringField(plan, "status", "plan.status"),
|
|
591
|
+
monthlyCredits: monthly,
|
|
592
|
+
currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
return report;
|
|
596
|
+
}
|
|
597
|
+
/** The Client-face contribution mounted on `ctx.remote`. */
|
|
598
|
+
const USAGE_REMOTE_CONTRIBUTION = {
|
|
599
|
+
package: USAGE_REMOTE_PACKAGE,
|
|
600
|
+
descriptors: [{
|
|
601
|
+
id: `${USAGE_REMOTE_PACKAGE}#${USAGE_REPORT_ENDPOINT}`,
|
|
602
|
+
service: "commandcodeUsage",
|
|
603
|
+
namespace: "commandcode",
|
|
604
|
+
method: "report",
|
|
605
|
+
invocation: { kind: "direct" },
|
|
606
|
+
parameters: [],
|
|
607
|
+
result: {
|
|
608
|
+
mode: "strict",
|
|
609
|
+
typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeUsageReport`,
|
|
610
|
+
schema: { parse: parseUsageReport }
|
|
611
|
+
}
|
|
612
|
+
}]
|
|
613
|
+
};
|
|
614
|
+
//#endregion
|
|
364
615
|
//#region src/client/section.tsx
|
|
365
616
|
/**
|
|
366
617
|
* React component for the "Command Code" settings page (browser half).
|
|
@@ -418,6 +669,51 @@ window.__ModuleLoader__.load({
|
|
|
418
669
|
]
|
|
419
670
|
});
|
|
420
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* One boolean field row rendered as a toggle. The staged text is `'true'` /
|
|
674
|
+
* `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
|
|
675
|
+
* boolean field spec parses back into a real boolean on save.
|
|
676
|
+
*/
|
|
677
|
+
function ToggleField({ id, label, hint, state, disabled, defaultChecked, onEdit, onReset, t }) {
|
|
678
|
+
const checked = state.text === "" ? defaultChecked : state.text === "true";
|
|
679
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
680
|
+
className: "cc-field",
|
|
681
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
682
|
+
className: "cc-fieldHead",
|
|
683
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
684
|
+
className: "cc-label",
|
|
685
|
+
htmlFor: id,
|
|
686
|
+
children: label
|
|
687
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
688
|
+
className: "cc-badges",
|
|
689
|
+
children: [state.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
690
|
+
className: "cc-badge",
|
|
691
|
+
children: t("overridden")
|
|
692
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
693
|
+
type: "button",
|
|
694
|
+
className: "cc-reset",
|
|
695
|
+
disabled,
|
|
696
|
+
onClick: onReset,
|
|
697
|
+
children: t("reset")
|
|
698
|
+
})]
|
|
699
|
+
})]
|
|
700
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
701
|
+
className: "cc-toggleRow",
|
|
702
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
703
|
+
id,
|
|
704
|
+
className: "cc-toggle",
|
|
705
|
+
type: "checkbox",
|
|
706
|
+
role: "switch",
|
|
707
|
+
checked,
|
|
708
|
+
disabled,
|
|
709
|
+
onChange: (event) => onEdit(event.target.checked ? "true" : "false")
|
|
710
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
711
|
+
className: "cc-hint",
|
|
712
|
+
children: hint
|
|
713
|
+
})]
|
|
714
|
+
})]
|
|
715
|
+
});
|
|
716
|
+
}
|
|
421
717
|
/** The API-key control: write-only, reports configured state, never echoes the key. */
|
|
422
718
|
function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, onEdit }) {
|
|
423
719
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -453,10 +749,227 @@ window.__ModuleLoader__.load({
|
|
|
453
749
|
]
|
|
454
750
|
});
|
|
455
751
|
}
|
|
752
|
+
/** One stat tile in the account card's summary grid. */
|
|
753
|
+
function UsageStat({ label, value, sub }) {
|
|
754
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
755
|
+
className: "cc-usageStat",
|
|
756
|
+
children: [
|
|
757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
758
|
+
className: "cc-usageStatLabel",
|
|
759
|
+
children: label
|
|
760
|
+
}),
|
|
761
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
762
|
+
className: "cc-usageStatValue",
|
|
763
|
+
children: value
|
|
764
|
+
}),
|
|
765
|
+
sub !== void 0 && sub !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
766
|
+
className: "cc-usageStatSub",
|
|
767
|
+
children: sub
|
|
768
|
+
}) : null
|
|
769
|
+
]
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
/** One window-limit row: label, used/cap, a fill bar, and the reset time. */
|
|
773
|
+
function UsageWindow({ label, limit: { used, cap, exceeded, resetAt }, t }) {
|
|
774
|
+
const ratio = windowRatio(used, cap);
|
|
775
|
+
const reset = formatResetAt(resetAt);
|
|
776
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
777
|
+
className: "cc-usageWindow",
|
|
778
|
+
children: [
|
|
779
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
780
|
+
className: "cc-usageWindowHead",
|
|
781
|
+
children: [
|
|
782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
783
|
+
className: "cc-usageWindowLabel",
|
|
784
|
+
children: label
|
|
785
|
+
}),
|
|
786
|
+
exceeded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
787
|
+
className: "cc-usageExceeded",
|
|
788
|
+
children: t("usageExceeded")
|
|
789
|
+
}) : null,
|
|
790
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
791
|
+
className: "cc-usageWindowValue",
|
|
792
|
+
children: cap > 0 ? `${formatMoney(used)} / ${formatMoney(cap)}` : formatMoney(used)
|
|
793
|
+
})
|
|
794
|
+
]
|
|
795
|
+
}),
|
|
796
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
797
|
+
className: "cc-usageBar",
|
|
798
|
+
role: "progressbar",
|
|
799
|
+
"aria-valuemin": 0,
|
|
800
|
+
"aria-valuemax": 100,
|
|
801
|
+
"aria-valuenow": Math.round(ratio * 100),
|
|
802
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
803
|
+
className: exceeded ? "cc-usageBarFill cc-usageBarFillWarn" : "cc-usageBarFill",
|
|
804
|
+
style: { width: `${ratio * 100}%` }
|
|
805
|
+
})
|
|
806
|
+
}),
|
|
807
|
+
reset !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
808
|
+
className: "cc-usageWindowReset",
|
|
809
|
+
children: [
|
|
810
|
+
t("usageReset"),
|
|
811
|
+
" ",
|
|
812
|
+
reset
|
|
813
|
+
]
|
|
814
|
+
}) : null
|
|
815
|
+
]
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* The account-usage card: the `/commandcode` dashboard's facts (account,
|
|
820
|
+
* totals, credits, window limits) rendered as a native settings card. Data
|
|
821
|
+
* arrives through the `commandcode/report` Remote; the API key never leaves
|
|
822
|
+
* the Host.
|
|
823
|
+
*/
|
|
824
|
+
function UsageCard({ t, usage, apiKeyConfigured, onRefresh }) {
|
|
825
|
+
(0, react.useEffect)(() => {
|
|
826
|
+
if (apiKeyConfigured && usage.status === "idle") onRefresh();
|
|
827
|
+
}, [
|
|
828
|
+
apiKeyConfigured,
|
|
829
|
+
usage.status,
|
|
830
|
+
onRefresh
|
|
831
|
+
]);
|
|
832
|
+
const loading = usage.status === "loading";
|
|
833
|
+
const report = usage.report;
|
|
834
|
+
const account = report?.account;
|
|
835
|
+
const accountName = account === void 0 ? "" : account.userName || account.name;
|
|
836
|
+
const credits = report?.credits;
|
|
837
|
+
const plan = report?.plan;
|
|
838
|
+
const planName = plan?.name ?? "";
|
|
839
|
+
const planStatus = plan !== void 0 && plan.status !== "" && plan.status !== "active" ? plan.status : "";
|
|
840
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
841
|
+
className: "cc-usageCard",
|
|
842
|
+
"aria-label": t("usageTitle"),
|
|
843
|
+
children: [
|
|
844
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
845
|
+
className: "cc-usageHead",
|
|
846
|
+
children: [
|
|
847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
848
|
+
className: "cc-usageTitle",
|
|
849
|
+
children: t("usageTitle")
|
|
850
|
+
}),
|
|
851
|
+
accountName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
852
|
+
className: "cc-usageAccount",
|
|
853
|
+
children: accountName
|
|
854
|
+
}) : null,
|
|
855
|
+
planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
856
|
+
className: "cc-usagePlan",
|
|
857
|
+
children: planName
|
|
858
|
+
}) : null,
|
|
859
|
+
planStatus !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
860
|
+
className: "cc-usagePlanStatus",
|
|
861
|
+
children: planStatus
|
|
862
|
+
}) : null,
|
|
863
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
864
|
+
type: "button",
|
|
865
|
+
className: "cc-usageRefresh",
|
|
866
|
+
disabled: loading || !apiKeyConfigured,
|
|
867
|
+
onClick: onRefresh,
|
|
868
|
+
children: loading ? t("usageRefreshing") : t("usageRefresh")
|
|
869
|
+
})
|
|
870
|
+
]
|
|
871
|
+
}),
|
|
872
|
+
!apiKeyConfigured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
873
|
+
className: "cc-usageHint",
|
|
874
|
+
children: t("usageNoKey")
|
|
875
|
+
}) : null,
|
|
876
|
+
apiKeyConfigured && report === void 0 && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
877
|
+
className: "cc-usageHint",
|
|
878
|
+
children: t("usageLoading")
|
|
879
|
+
}) : null,
|
|
880
|
+
usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
881
|
+
className: "cc-usageError",
|
|
882
|
+
role: "status",
|
|
883
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
|
|
884
|
+
}) : null,
|
|
885
|
+
report?.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
886
|
+
className: "cc-usageStats",
|
|
887
|
+
children: [
|
|
888
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
889
|
+
label: t("usageRequests"),
|
|
890
|
+
value: String(report.usage.completedCount),
|
|
891
|
+
sub: `${t("usageFailed")} ${report.usage.failedCount}`
|
|
892
|
+
}),
|
|
893
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
894
|
+
label: t("usageSuccessRate"),
|
|
895
|
+
value: `${report.usage.successRate}%`
|
|
896
|
+
}),
|
|
897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
898
|
+
label: t("usageCost"),
|
|
899
|
+
value: formatMoneyExact(report.usage.totalCost),
|
|
900
|
+
sub: `${formatMoney(report.usage.totalCredits)} credits`
|
|
901
|
+
}),
|
|
902
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
903
|
+
label: t("usageTokens"),
|
|
904
|
+
value: formatTokensCompact(report.usage.totalTokensIn + report.usage.totalTokensOut),
|
|
905
|
+
sub: `${formatTokensCompact(report.usage.totalTokensIn)} ${t("usageTokensIn")} / ${formatTokensCompact(report.usage.totalTokensOut)} ${t("usageTokensOut")}`
|
|
906
|
+
})
|
|
907
|
+
]
|
|
908
|
+
}) : null,
|
|
909
|
+
credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
910
|
+
className: "cc-usageStats",
|
|
911
|
+
children: [
|
|
912
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
913
|
+
label: t("usageMonthly"),
|
|
914
|
+
value: formatMoney(credits.monthlyCredits)
|
|
915
|
+
}),
|
|
916
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
917
|
+
label: t("usagePurchased"),
|
|
918
|
+
value: formatMoney(credits.purchasedCredits)
|
|
919
|
+
}),
|
|
920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
921
|
+
label: t("usageFree"),
|
|
922
|
+
value: formatMoney(credits.freeCredits)
|
|
923
|
+
})
|
|
924
|
+
]
|
|
925
|
+
}) : null,
|
|
926
|
+
credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
927
|
+
className: "cc-usageWindows",
|
|
928
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindow, {
|
|
929
|
+
label: t("usageFiveHour"),
|
|
930
|
+
limit: credits.fiveHour,
|
|
931
|
+
t
|
|
932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindow, {
|
|
933
|
+
label: t("usageWeekly"),
|
|
934
|
+
limit: credits.weekly,
|
|
935
|
+
t
|
|
936
|
+
})]
|
|
937
|
+
}) : null,
|
|
938
|
+
report !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
939
|
+
className: "cc-usageMeta",
|
|
940
|
+
children: [
|
|
941
|
+
plan !== void 0 && plan.currentPeriodEnd > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
942
|
+
className: "cc-usageUpdated",
|
|
943
|
+
children: [
|
|
944
|
+
t("usagePeriodEnd"),
|
|
945
|
+
" ",
|
|
946
|
+
new Date(plan.currentPeriodEnd).toLocaleDateString()
|
|
947
|
+
]
|
|
948
|
+
}) : null,
|
|
949
|
+
usage.fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
950
|
+
className: "cc-usageUpdated",
|
|
951
|
+
children: [
|
|
952
|
+
t("usageUpdated"),
|
|
953
|
+
" ",
|
|
954
|
+
new Date(usage.fetchedAt).toLocaleTimeString()
|
|
955
|
+
]
|
|
956
|
+
}) : null,
|
|
957
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
|
|
958
|
+
report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
959
|
+
className: "cc-usagePartial",
|
|
960
|
+
title: report.failures.join("; "),
|
|
961
|
+
children: t("usagePartial")
|
|
962
|
+
}) : null
|
|
963
|
+
]
|
|
964
|
+
}) : null
|
|
965
|
+
]
|
|
966
|
+
});
|
|
967
|
+
}
|
|
456
968
|
/** The settings page body: connection facts for the Command Code provider. */
|
|
457
969
|
function CommandCodeSettingsPage(props) {
|
|
458
970
|
const { t } = props;
|
|
459
971
|
const state = props.useCommandCodeSettings((snapshot) => snapshot);
|
|
972
|
+
const usage = props.useCommandCodeUsage((snapshot) => snapshot);
|
|
460
973
|
const disabled = !state.writable;
|
|
461
974
|
const keyLocked = !state.apiKeyWritable;
|
|
462
975
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
@@ -476,6 +989,12 @@ window.__ModuleLoader__.load({
|
|
|
476
989
|
role: "status",
|
|
477
990
|
children: t("readOnly")
|
|
478
991
|
}) : null,
|
|
992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageCard, {
|
|
993
|
+
t,
|
|
994
|
+
usage,
|
|
995
|
+
apiKeyConfigured: state.apiKeyConfigured,
|
|
996
|
+
onRefresh: props.refreshUsage
|
|
997
|
+
}),
|
|
479
998
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
480
999
|
className: "cc-card",
|
|
481
1000
|
children: [
|
|
@@ -531,6 +1050,17 @@ window.__ModuleLoader__.load({
|
|
|
531
1050
|
onEdit: (text) => props.edit("streamIdleTimeoutMs", text),
|
|
532
1051
|
onReset: () => props.resetField("streamIdleTimeoutMs"),
|
|
533
1052
|
t
|
|
1053
|
+
}),
|
|
1054
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
1055
|
+
id: "cc-filter-models-by-plan",
|
|
1056
|
+
label: t("filterModelsByPlan"),
|
|
1057
|
+
hint: t("filterModelsByPlanHint"),
|
|
1058
|
+
state: state.filterModelsByPlan,
|
|
1059
|
+
disabled,
|
|
1060
|
+
defaultChecked: true,
|
|
1061
|
+
onEdit: (text) => props.edit("filterModelsByPlan", text),
|
|
1062
|
+
onReset: () => props.resetField("filterModelsByPlan"),
|
|
1063
|
+
t
|
|
534
1064
|
})
|
|
535
1065
|
]
|
|
536
1066
|
}),
|
|
@@ -580,6 +1110,8 @@ window.__ModuleLoader__.load({
|
|
|
580
1110
|
requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
|
|
581
1111
|
streamIdleTimeoutMs: "流空闲超时(毫秒)",
|
|
582
1112
|
streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
|
|
1113
|
+
filterModelsByPlan: "隐藏套餐外模型",
|
|
1114
|
+
filterModelsByPlanHint: "开启后,模型选择器只列出当前套餐可用的模型;账户持有按需余额时会显示全部。",
|
|
583
1115
|
overridden: "已覆盖",
|
|
584
1116
|
reset: "重置",
|
|
585
1117
|
invalidNumber: "无效数字",
|
|
@@ -589,7 +1121,30 @@ window.__ModuleLoader__.load({
|
|
|
589
1121
|
saving: "保存中",
|
|
590
1122
|
saveFailed: "保存失败,请重试。",
|
|
591
1123
|
discard: "放弃",
|
|
592
|
-
cancel: "取消"
|
|
1124
|
+
cancel: "取消",
|
|
1125
|
+
usageTitle: "账户用量",
|
|
1126
|
+
usageRefresh: "刷新",
|
|
1127
|
+
usageRefreshing: "刷新中…",
|
|
1128
|
+
usageLoading: "正在获取账户用量…",
|
|
1129
|
+
usageNoKey: "配置 API 密钥后,这里会显示账户的用量与额度状态。",
|
|
1130
|
+
usageError: "用量获取失败",
|
|
1131
|
+
usageRequests: "请求",
|
|
1132
|
+
usageFailed: "失败",
|
|
1133
|
+
usageSuccessRate: "成功率",
|
|
1134
|
+
usageCost: "花费",
|
|
1135
|
+
usageTokens: "Token",
|
|
1136
|
+
usageTokensIn: "入",
|
|
1137
|
+
usageTokensOut: "出",
|
|
1138
|
+
usageMonthly: "月额度",
|
|
1139
|
+
usagePurchased: "已购",
|
|
1140
|
+
usageFree: "赠送",
|
|
1141
|
+
usageFiveHour: "5 小时窗口",
|
|
1142
|
+
usageWeekly: "每周窗口",
|
|
1143
|
+
usageExceeded: "已超限",
|
|
1144
|
+
usageReset: "重置于",
|
|
1145
|
+
usagePartial: "部分端点数据不可用",
|
|
1146
|
+
usageUpdated: "更新于",
|
|
1147
|
+
usagePeriodEnd: "账期截止"
|
|
593
1148
|
};
|
|
594
1149
|
const en = {
|
|
595
1150
|
nav: "Command Code",
|
|
@@ -608,6 +1163,8 @@ window.__ModuleLoader__.load({
|
|
|
608
1163
|
requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
|
|
609
1164
|
streamIdleTimeoutMs: "Stream idle timeout (ms)",
|
|
610
1165
|
streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
|
|
1166
|
+
filterModelsByPlan: "Hide out-of-plan models",
|
|
1167
|
+
filterModelsByPlanHint: "When on, the model picker lists only models your subscription includes; any on-demand credit balance shows the full catalog.",
|
|
611
1168
|
overridden: "Overridden",
|
|
612
1169
|
reset: "Reset",
|
|
613
1170
|
invalidNumber: "Invalid number",
|
|
@@ -617,7 +1174,30 @@ window.__ModuleLoader__.load({
|
|
|
617
1174
|
saving: "Saving",
|
|
618
1175
|
saveFailed: "Save failed, please retry.",
|
|
619
1176
|
discard: "Discard",
|
|
620
|
-
cancel: "Cancel"
|
|
1177
|
+
cancel: "Cancel",
|
|
1178
|
+
usageTitle: "Account usage",
|
|
1179
|
+
usageRefresh: "Refresh",
|
|
1180
|
+
usageRefreshing: "Refreshing…",
|
|
1181
|
+
usageLoading: "Fetching account usage…",
|
|
1182
|
+
usageNoKey: "Configure an API key to see this account’s usage and credit state here.",
|
|
1183
|
+
usageError: "Could not fetch usage",
|
|
1184
|
+
usageRequests: "Requests",
|
|
1185
|
+
usageFailed: "failed",
|
|
1186
|
+
usageSuccessRate: "Success rate",
|
|
1187
|
+
usageCost: "Spend",
|
|
1188
|
+
usageTokens: "Tokens",
|
|
1189
|
+
usageTokensIn: "in",
|
|
1190
|
+
usageTokensOut: "out",
|
|
1191
|
+
usageMonthly: "Monthly",
|
|
1192
|
+
usagePurchased: "Purchased",
|
|
1193
|
+
usageFree: "Free",
|
|
1194
|
+
usageFiveHour: "5-hour window",
|
|
1195
|
+
usageWeekly: "Weekly window",
|
|
1196
|
+
usageExceeded: "Exceeded",
|
|
1197
|
+
usageReset: "Resets",
|
|
1198
|
+
usagePartial: "Some endpoint data unavailable",
|
|
1199
|
+
usageUpdated: "Updated",
|
|
1200
|
+
usagePeriodEnd: "Period ends"
|
|
621
1201
|
};
|
|
622
1202
|
//#endregion
|
|
623
1203
|
//#region src/client/index.ts
|
|
@@ -645,7 +1225,44 @@ window.__ModuleLoader__.load({
|
|
|
645
1225
|
.cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
|
|
646
1226
|
.cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
647
1227
|
.cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
|
|
1228
|
+
.cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
|
|
1229
|
+
.cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
|
|
1230
|
+
.cc-toggle{appearance:none;flex-shrink:0;background:var(--dsw-alias-border-l2);border-radius:999px;width:30px;height:18px;margin:0;cursor:pointer;position:relative;transition:background .15s ease}
|
|
1231
|
+
.cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
|
|
1232
|
+
.cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
|
|
1233
|
+
.cc-toggle:checked::after{left:14px}
|
|
1234
|
+
.cc-toggle:disabled{cursor:default;opacity:.5}
|
|
648
1235
|
.cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
1236
|
+
.cc-usageCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;flex-direction:column;gap:12px;display:flex}
|
|
1237
|
+
.cc-usageHead{align-items:center;gap:8px;display:flex}
|
|
1238
|
+
.cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
1239
|
+
.cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
1240
|
+
.cc-usagePlan{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
|
|
1241
|
+
.cc-usagePlanStatus{white-space:nowrap;color:var(--dsw-alias-label-error);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
1242
|
+
.cc-usageRefresh{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
|
|
1243
|
+
.cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
1244
|
+
.cc-usageRefresh:disabled{cursor:default;opacity:.5}
|
|
1245
|
+
.cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
1246
|
+
.cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
|
|
1247
|
+
.cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
|
|
1248
|
+
.cc-usageStat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex}
|
|
1249
|
+
.cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
|
|
1250
|
+
.cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
|
|
1251
|
+
.cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
|
|
1252
|
+
.cc-usageWindows{flex-direction:column;gap:16px;display:flex}
|
|
1253
|
+
.cc-usageWindow{flex-direction:column;gap:6px;display:flex}
|
|
1254
|
+
.cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
|
|
1255
|
+
.cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
|
|
1256
|
+
.cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
|
|
1257
|
+
.cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
|
|
1258
|
+
.cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
|
|
1259
|
+
.cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
|
|
1260
|
+
.cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
|
|
1261
|
+
.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1262
|
+
.cc-usageMeta{align-items:center;gap:8px;display:flex}
|
|
1263
|
+
.cc-usageMetaSpacer{flex:1}
|
|
1264
|
+
.cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1265
|
+
.cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
|
|
649
1266
|
`;
|
|
650
1267
|
/** Inject the page stylesheet once (idempotent per tag). */
|
|
651
1268
|
function injectPageCss() {
|
|
@@ -678,12 +1295,56 @@ window.__ModuleLoader__.load({
|
|
|
678
1295
|
ctx.effect(() => () => controller.dispose(), "dsh-commandcode-provider: settings controller");
|
|
679
1296
|
const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(controller.state());
|
|
680
1297
|
controller.subscribe(() => store.set(controller.state()));
|
|
1298
|
+
let usageNamespace;
|
|
1299
|
+
let usageMountError;
|
|
1300
|
+
ctx.effect(() => {
|
|
1301
|
+
let cancelled = false;
|
|
1302
|
+
let unmount;
|
|
1303
|
+
ctx.remote.$mount(USAGE_REMOTE_CONTRIBUTION).then((dispose) => {
|
|
1304
|
+
if (cancelled) {
|
|
1305
|
+
dispose();
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
unmount = dispose;
|
|
1309
|
+
ctx.inject(["remote.commandcode"], (namespaceCtx) => {
|
|
1310
|
+
usageNamespace = namespaceCtx.remote.commandcode;
|
|
1311
|
+
namespaceCtx.effect(() => () => {
|
|
1312
|
+
usageNamespace = void 0;
|
|
1313
|
+
}, "dsh-commandcode-provider: usage namespace");
|
|
1314
|
+
});
|
|
1315
|
+
}, (error) => {
|
|
1316
|
+
usageMountError = error instanceof Error ? error.message : String(error);
|
|
1317
|
+
});
|
|
1318
|
+
return () => {
|
|
1319
|
+
cancelled = true;
|
|
1320
|
+
usageNamespace = void 0;
|
|
1321
|
+
if (unmount !== void 0) unmount();
|
|
1322
|
+
};
|
|
1323
|
+
}, "dsh-commandcode-provider: usage remote");
|
|
1324
|
+
const usageController = new CommandCodeUsageController({ report: async () => {
|
|
1325
|
+
const namespace = usageNamespace;
|
|
1326
|
+
if (namespace === void 0) return {
|
|
1327
|
+
ok: false,
|
|
1328
|
+
error: { message: usageMountError ?? "commandcode/report remote is not mounted" }
|
|
1329
|
+
};
|
|
1330
|
+
return namespace.report();
|
|
1331
|
+
} });
|
|
1332
|
+
ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
|
|
1333
|
+
const usageStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(usageController.state());
|
|
1334
|
+
usageController.subscribe(() => usageStore.set(usageController.state()));
|
|
681
1335
|
const injected = () => ({
|
|
682
|
-
hooks: {
|
|
1336
|
+
hooks: {
|
|
1337
|
+
commandCodeSettings: store,
|
|
1338
|
+
commandCodeUsage: usageStore
|
|
1339
|
+
},
|
|
683
1340
|
edit: (field, text) => controller.edit(field, text),
|
|
684
1341
|
resetField: (field) => controller.resetField(field),
|
|
685
|
-
save: () => void controller.save()
|
|
686
|
-
|
|
1342
|
+
save: () => void controller.save().then(() => {
|
|
1343
|
+
const settled = controller.state();
|
|
1344
|
+
if (!settled.failed && settled.apiKeyConfigured) usageController.refresh();
|
|
1345
|
+
}),
|
|
1346
|
+
discard: () => controller.discard(),
|
|
1347
|
+
refreshUsage: () => void usageController.refresh()
|
|
687
1348
|
});
|
|
688
1349
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
689
1350
|
name: "settings.section",
|