@mars-sea/dsh-commandcode-provider 0.13.3 → 0.14.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 +64 -0
- package/README.md +43 -12
- package/README.zh-CN.md +34 -8
- package/lib/client.js +827 -112
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +217 -79
- package/lib/index.js +802 -209
- package/lib/index.js.map +1 -1
- package/package.json +42 -43
package/lib/client.js
CHANGED
|
@@ -36,13 +36,15 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
-
//#region src/
|
|
39
|
+
//#region src/readable-error.ts
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Readable error text for the settings page AND the Host-side failure paths.
|
|
42
42
|
*
|
|
43
|
-
* @module dsh-commandcode-provider/
|
|
43
|
+
* @module dsh-commandcode-provider/readable-error
|
|
44
44
|
*/
|
|
45
45
|
/**
|
|
46
|
+
* Turn a caught value into readable text.
|
|
47
|
+
*
|
|
46
48
|
* `error instanceof Error ? error.message : String(error)` degrades to the
|
|
47
49
|
* literal string `[object Object]` for a plain object with no `Error`
|
|
48
50
|
* prototype — which is exactly what a Remote call across the webview/host
|
|
@@ -52,6 +54,11 @@ window.__ModuleLoader__.load({
|
|
|
52
54
|
* off the plain object first recovers the original text in that case; only a
|
|
53
55
|
* value with neither an `Error` prototype nor a usable `message` field falls
|
|
54
56
|
* through to a JSON dump.
|
|
57
|
+
*
|
|
58
|
+
* Shared by both halves on purpose. The browser client needs it for rejections
|
|
59
|
+
* that crossed the RPC boundary, and the Host needs it for values that reached
|
|
60
|
+
* it from a transport, a JSON body, or a user-thrown non-Error — the same
|
|
61
|
+
* degradation, one owner.
|
|
55
62
|
*/
|
|
56
63
|
function readableErrorText(error) {
|
|
57
64
|
if (error instanceof Error) return error.message;
|
|
@@ -482,6 +489,88 @@ window.__ModuleLoader__.load({
|
|
|
482
489
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
483
490
|
return Object.fromEntries(Object.entries(raw).filter(([id, value]) => id !== "" && typeof value === "boolean"));
|
|
484
491
|
}
|
|
492
|
+
/** 自定义上下文长度接受的上界(token);覆盖任何已知模型窗口仍留有余量。 */
|
|
493
|
+
const CONTEXT_WINDOW_MAX = 1e7;
|
|
494
|
+
/**
|
|
495
|
+
* 判定一个值能否作为自定义上下文长度:边界内的安全整数,符合宿主模型元数据约束。
|
|
496
|
+
*
|
|
497
|
+
* 程序化构造的连接可能绕过 schemastery 归一化,所以 `resolveAdapterOptions`
|
|
498
|
+
* 也要重新判定——越界或非数值直接丢弃,回落到目录公布值,而不是让请求带着
|
|
499
|
+
* 一个网关会拒绝的窗口上线。
|
|
500
|
+
*/
|
|
501
|
+
function isValidContextWindow(value) {
|
|
502
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 1e7;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 只接收合法条目的按模型覆盖表;空表、非对象与非法数值一律视为未设置。
|
|
506
|
+
* 配置校验会拒绝非整数或越界值;本函数额外保护绕过配置校验的程序化调用,
|
|
507
|
+
* 丢弃坏条目,不把非法窗口交给宿主。返回新对象;未设置时为 `undefined`。
|
|
508
|
+
*/
|
|
509
|
+
function readContextWindowOverrides(raw) {
|
|
510
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
511
|
+
const entries = Object.entries(raw).filter((entry) => entry[0] !== "" && isValidContextWindow(entry[1]));
|
|
512
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/output-budget.ts
|
|
516
|
+
/**
|
|
517
|
+
* Output-budget bounds shared by the Host, the browser settings page and the
|
|
518
|
+
* adapter (issue #74).
|
|
519
|
+
*
|
|
520
|
+
* The module stays pure (no node imports) so the client bundle can inline it,
|
|
521
|
+
* exactly like `context-window.ts`. The report behind issue #74 asked for a
|
|
522
|
+
* knob that stops the adapter from writing `max_tokens` at all; that is the
|
|
523
|
+
* `0` sentinel here rather than a separate `null` spelling, because the value
|
|
524
|
+
* travels through one numeric settings field on both surfaces.
|
|
525
|
+
*/
|
|
526
|
+
/**
|
|
527
|
+
* The output budget this bundle asks for, and the ceiling it will never exceed.
|
|
528
|
+
*
|
|
529
|
+
* 131 072, chosen against a measured provider limit rather than a round guess.
|
|
530
|
+
* Probing `/alpha/generate` directly on 2026-09-28: 64 000 → 200, 131 072 → 200,
|
|
531
|
+
* 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server
|
|
532
|
+
* therefore caps `max_tokens` at 200 000, and it does so identically for a
|
|
533
|
+
* 262 144-window model (`stealth/pixel-canary`, retired by Command Code on
|
|
534
|
+
* 2026-10-01 and named here only as the probe's subject) and a 1 000 000-window
|
|
535
|
+
* one (`stealth/space-bunny-alpha`) — the cap is global, not per model. 131 072
|
|
536
|
+
* keeps a 35 % margin under it while covering the real output ceiling of the
|
|
537
|
+
* models that have one (Claude 64 K, Gemini 64 K, several at 128 K).
|
|
538
|
+
*
|
|
539
|
+
* The old 64 000 was never a provider requirement: it cost nothing in latency
|
|
540
|
+
* (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but
|
|
541
|
+
* it did cap what a model could ever say in one turn.
|
|
542
|
+
*
|
|
543
|
+
* No client-side clamp is applied on top of this: an earlier version pre-shrank
|
|
544
|
+
* `max_tokens` from an estimate of the prompt already sent (`requestContextBudget()`,
|
|
545
|
+
* issue #67), but that estimate over-corrected on ordinary long conversations —
|
|
546
|
+
* exactly the "output gets cut short as the conversation grows" symptom issue #74
|
|
547
|
+
* reported — while a live A/B probe (2026-09-30, `stealth/space-bunny-alpha`,
|
|
548
|
+
* ~19.6% over its 1,000,000-token window with no client-side shrink) showed the
|
|
549
|
+
* endpoint answers a genuine overflow with an unambiguous `400 … exceeds the
|
|
550
|
+
* model's context window`, which `isContextOverflowDetail()` classifies as
|
|
551
|
+
* `CONTEXT_WINDOW_EXCEEDED` independently of this constant.
|
|
552
|
+
*
|
|
553
|
+
* A user-set positive budget can only TIGHTEN this, never raise it — a figure
|
|
554
|
+
* above the cap would be refused by the gateway anyway, so the settings field
|
|
555
|
+
* refuses it at the input instead of spending a round trip to learn that.
|
|
556
|
+
*
|
|
557
|
+
* Lives here rather than in `adapter.ts` so the browser settings page can bound
|
|
558
|
+
* its input by the same figure the Host enforces; `adapter.ts` re-exports it
|
|
559
|
+
* under its historical name.
|
|
560
|
+
*/
|
|
561
|
+
const MAX_OUTPUT_TOKEN_LIMIT = 131072;
|
|
562
|
+
/**
|
|
563
|
+
* Whether a value can serve as a user output budget: the omit sentinel, or a
|
|
564
|
+
* whole token count inside the gateway's own ceiling.
|
|
565
|
+
*
|
|
566
|
+
* Programmatically built connections can bypass schemastery normalization, so
|
|
567
|
+
* the resolve step re-judges every value here and drops an out-of-range one
|
|
568
|
+
* back to "unset" instead of letting the request carry a budget the gateway
|
|
569
|
+
* refuses.
|
|
570
|
+
*/
|
|
571
|
+
function isValidMaxOutputTokens(value) {
|
|
572
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 131072;
|
|
573
|
+
}
|
|
485
574
|
//#endregion
|
|
486
575
|
//#region src/client/settings-write.ts
|
|
487
576
|
/** 设置写入过程:同一控制器的意图按顺序执行,配置确认与凭据确认分别保留。
|
|
@@ -579,6 +668,11 @@ window.__ModuleLoader__.load({
|
|
|
579
668
|
value: Object.fromEntries([...keys].map((id) => [id, list.length === 0 || list.includes(id)]))
|
|
580
669
|
});
|
|
581
670
|
}
|
|
671
|
+
if (owned.kind === "save" && owned.contextWindowOverrides !== void 0) ops.push({
|
|
672
|
+
op: "set",
|
|
673
|
+
path: ["contextWindowOverrides"],
|
|
674
|
+
value: owned.contextWindowOverrides
|
|
675
|
+
});
|
|
582
676
|
const baseline = new Map(ops.map((op) => [op.path[0], structuredClone(fieldBaseline(snapshot, op.path[0]))]));
|
|
583
677
|
const defaultRef = owned.kind === "save" && owned.key !== void 0 || owned.kind === "key" && owned.target === "default" ? this.defaultRef() : void 0;
|
|
584
678
|
return new Promise((resolve) => {
|
|
@@ -929,6 +1023,59 @@ window.__ModuleLoader__.load({
|
|
|
929
1023
|
};
|
|
930
1024
|
}
|
|
931
1025
|
/**
|
|
1026
|
+
* The compact token-count shorthand (`32K`, `1M`) in BINARY units, the way
|
|
1027
|
+
* context windows are quoted: 32K → 32 768 (32 × 1024), 128K → 131 072,
|
|
1028
|
+
* 1M → 1 048 576 (1024 × 1024), so the common power-of-two windows land on
|
|
1029
|
+
* their exact values instead of 1000-based rounding. A leading sign is not part
|
|
1030
|
+
* of the grammar, so this path can only produce values ≥ 0.
|
|
1031
|
+
*/
|
|
1032
|
+
const COMPACT_TOKENS = /^(\d+(?:\.\d+)?)\s*([kKmM])$/;
|
|
1033
|
+
/**
|
|
1034
|
+
* A whole-token count field with optional compact shorthand, shared by the
|
|
1035
|
+
* context-length and output-budget inputs (issues #86, #74).
|
|
1036
|
+
*
|
|
1037
|
+
* Plain text goes through the {@link numberField} parse plus the caller's
|
|
1038
|
+
* whole-token validator; shorthand converts to whole tokens first
|
|
1039
|
+
* (`Math.round` keeps fractional shorthand like `1.5K` on whole tokens) and is
|
|
1040
|
+
* then bounds-checked against the SAME `bounds`, so an overshoot names
|
|
1041
|
+
* `tooLarge` here instead of reaching the Host schema, which would reject it at
|
|
1042
|
+
* save time with only a generic failure. Empty clears in both paths.
|
|
1043
|
+
*
|
|
1044
|
+
* `isValid` is what tells the two apart: context lengths start at 1, while the
|
|
1045
|
+
* output budget's lower bound IS its omit sentinel (`0` means "send no
|
|
1046
|
+
* `max_tokens`"), so no single numeric range can serve both.
|
|
1047
|
+
*/
|
|
1048
|
+
function tokenCountField(field, bounds, isValid) {
|
|
1049
|
+
const plain = numberField$1(field, bounds);
|
|
1050
|
+
return {
|
|
1051
|
+
field,
|
|
1052
|
+
format: plain.format,
|
|
1053
|
+
parse: (text) => {
|
|
1054
|
+
const compact = COMPACT_TOKENS.exec(text.trim());
|
|
1055
|
+
if (compact === null) {
|
|
1056
|
+
const parsed = plain.parse(text);
|
|
1057
|
+
return parsed.kind === "set" && !isValid(parsed.value) ? {
|
|
1058
|
+
kind: "invalid",
|
|
1059
|
+
reason: "format"
|
|
1060
|
+
} : parsed;
|
|
1061
|
+
}
|
|
1062
|
+
const value = Math.round(Number(compact[1]) * (compact[2].toLowerCase() === "k" ? 1024 : 1048576));
|
|
1063
|
+
if (value < bounds.min) return {
|
|
1064
|
+
kind: "invalid",
|
|
1065
|
+
reason: "tooSmall"
|
|
1066
|
+
};
|
|
1067
|
+
if (value > bounds.max) return {
|
|
1068
|
+
kind: "invalid",
|
|
1069
|
+
reason: "tooLarge"
|
|
1070
|
+
};
|
|
1071
|
+
return {
|
|
1072
|
+
kind: "set",
|
|
1073
|
+
value
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
932
1079
|
* A boolean field, staged as the strings `'true'`/`'false'` (an empty draft
|
|
933
1080
|
* clears it). The component renders a toggle and only ever stages these two
|
|
934
1081
|
* strings; anything else blocks save.
|
|
@@ -956,6 +1103,23 @@ window.__ModuleLoader__.load({
|
|
|
956
1103
|
};
|
|
957
1104
|
}
|
|
958
1105
|
/**
|
|
1106
|
+
* The single-turn output budget (issue #74).
|
|
1107
|
+
*
|
|
1108
|
+
* The range INCLUDES `0`, and `0` is not a budget: it is the sentinel asking
|
|
1109
|
+
* the adapter to send no `max_tokens` field at all, leaving the per-turn output
|
|
1110
|
+
* size to the platform. The upper bound is the gateway's own cap
|
|
1111
|
+
* ({@link MAX_OUTPUT_TOKEN_LIMIT}) rather than an arbitrary one — a larger
|
|
1112
|
+
* figure would simply be refused, so the input is where that is worth saying. A
|
|
1113
|
+
* blank draft clears the setting back to the derived default, which is the
|
|
1114
|
+
* behaviour every existing profile has.
|
|
1115
|
+
*/
|
|
1116
|
+
function maxOutputTokensField(field) {
|
|
1117
|
+
return tokenCountField(field, {
|
|
1118
|
+
min: 0,
|
|
1119
|
+
max: MAX_OUTPUT_TOKEN_LIMIT
|
|
1120
|
+
}, isValidMaxOutputTokens);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
959
1123
|
* 秒制输入保留毫秒精度,兼容已有 1500ms、1ms 等配置。
|
|
960
1124
|
*
|
|
961
1125
|
* Milliseconds are the wrong unit to show: the defaults are 300000 and the
|
|
@@ -1007,6 +1171,11 @@ window.__ModuleLoader__.load({
|
|
|
1007
1171
|
min: 0,
|
|
1008
1172
|
max: 50
|
|
1009
1173
|
}),
|
|
1174
|
+
tokenCountField("contextWindow", {
|
|
1175
|
+
min: 1,
|
|
1176
|
+
max: CONTEXT_WINDOW_MAX
|
|
1177
|
+
}, isValidContextWindow),
|
|
1178
|
+
maxOutputTokensField("maxOutputTokens"),
|
|
1010
1179
|
booleanField$1("offloadSeenImagesForCache"),
|
|
1011
1180
|
booleanField$1("filterModelsByPlan"),
|
|
1012
1181
|
booleanField$1("webSearch"),
|
|
@@ -1034,6 +1203,8 @@ window.__ModuleLoader__.load({
|
|
|
1034
1203
|
accountResult;
|
|
1035
1204
|
/** Staged visible-model allowlist (undefined = no draft). */
|
|
1036
1205
|
visibleModelsDraft = void 0;
|
|
1206
|
+
/** Staged per-model context-length overrides as raw text (undefined = no draft, issue #86). */
|
|
1207
|
+
contextWindowOverridesDraft = void 0;
|
|
1037
1208
|
catalogModels = [];
|
|
1038
1209
|
catalogFailed = false;
|
|
1039
1210
|
saving = false;
|
|
@@ -1061,6 +1232,7 @@ window.__ModuleLoader__.load({
|
|
|
1061
1232
|
this.writer.dispose();
|
|
1062
1233
|
this.staged.clear();
|
|
1063
1234
|
this.visibleModelsDraft = void 0;
|
|
1235
|
+
this.contextWindowOverridesDraft = void 0;
|
|
1064
1236
|
for (const dispose of this.disposers) dispose();
|
|
1065
1237
|
this.disposers.length = 0;
|
|
1066
1238
|
this.listeners.clear();
|
|
@@ -1122,10 +1294,13 @@ window.__ModuleLoader__.load({
|
|
|
1122
1294
|
accountEnrollmentRefs: this.enrollmentRefs(false),
|
|
1123
1295
|
visibleModels: this.effectiveVisibleModels(),
|
|
1124
1296
|
visibleModelsAll: this.visibleModelsDraft === void 0 ? this.storedShowsAll() : this.visibleModelsDraft.length === 0,
|
|
1297
|
+
contextWindow: this.field("contextWindow"),
|
|
1298
|
+
maxOutputTokens: this.field("maxOutputTokens"),
|
|
1299
|
+
contextWindowOverrides: this.effectiveContextWindowOverrides(),
|
|
1125
1300
|
catalogModels: this.catalogModels,
|
|
1126
1301
|
catalogFailed: this.catalogFailed,
|
|
1127
|
-
dirty: plan.length > 0 || this.visibleModelsDirty(),
|
|
1128
|
-
invalid: plan.some((item) => item.change === void 0),
|
|
1302
|
+
dirty: plan.length > 0 || this.visibleModelsDirty() || this.contextWindowOverridesDirty(),
|
|
1303
|
+
invalid: plan.some((item) => item.change === void 0) || this.effectiveContextWindowOverrides().some((row) => row.invalid),
|
|
1129
1304
|
saving: this.saving,
|
|
1130
1305
|
failed: this.failed,
|
|
1131
1306
|
savedCount: this.savedCount,
|
|
@@ -1173,9 +1348,10 @@ window.__ModuleLoader__.load({
|
|
|
1173
1348
|
}
|
|
1174
1349
|
/** Discard every staged edit. */
|
|
1175
1350
|
discard() {
|
|
1176
|
-
if (this.staged.size === 0 && this.visibleModelsDraft === void 0 && !this.failed) return;
|
|
1351
|
+
if (this.staged.size === 0 && this.visibleModelsDraft === void 0 && this.contextWindowOverridesDraft === void 0 && !this.failed) return;
|
|
1177
1352
|
this.staged.clear();
|
|
1178
1353
|
this.visibleModelsDraft = void 0;
|
|
1354
|
+
this.contextWindowOverridesDraft = void 0;
|
|
1179
1355
|
this.failed = false;
|
|
1180
1356
|
this.saveResult = void 0;
|
|
1181
1357
|
this.publish();
|
|
@@ -1202,9 +1378,12 @@ window.__ModuleLoader__.load({
|
|
|
1202
1378
|
if (this.disposed || this.saving) return;
|
|
1203
1379
|
const submitted = new Map(this.staged);
|
|
1204
1380
|
const submittedVisible = this.visibleModelsDraft;
|
|
1381
|
+
const submittedContextOverrides = this.contextWindowOverridesDraft;
|
|
1205
1382
|
const plan = this.plan();
|
|
1206
1383
|
const visibleDirty = this.visibleModelsDirty();
|
|
1207
|
-
|
|
1384
|
+
const contextOverrides = this.parsedContextWindowOverrides();
|
|
1385
|
+
const contextOverridesDirty = this.contextWindowOverridesDirty();
|
|
1386
|
+
if (plan.length === 0 && !visibleDirty && !contextOverridesDirty || plan.some((item) => item.change === void 0) || contextOverrides.invalid) return;
|
|
1208
1387
|
const ops = plan.flatMap((item) => typeof item.change === "object" ? [item.change] : []);
|
|
1209
1388
|
const key = plan.find((item) => item.field === "apiKey")?.change;
|
|
1210
1389
|
this.saving = true;
|
|
@@ -1214,6 +1393,7 @@ window.__ModuleLoader__.load({
|
|
|
1214
1393
|
kind: "save",
|
|
1215
1394
|
ops,
|
|
1216
1395
|
...visibleDirty ? { visibleModels: submittedVisible ?? [] } : {},
|
|
1396
|
+
...contextOverridesDirty ? { contextWindowOverrides: contextOverrides.values } : {},
|
|
1217
1397
|
...typeof key === "string" ? { key } : {}
|
|
1218
1398
|
});
|
|
1219
1399
|
this.publish();
|
|
@@ -1225,6 +1405,7 @@ window.__ModuleLoader__.load({
|
|
|
1225
1405
|
if (!this.failed) this.savedCount += 1;
|
|
1226
1406
|
for (const [field, draft] of submitted) if ((field === "apiKey" ? outcome.credential === "confirmed" && outcome.issue === void 0 : outcome.config === "confirmed" || writeCompleted(outcome)) && this.staged.get(field) === draft) this.staged.delete(field);
|
|
1227
1407
|
if ((outcome.config === "confirmed" || writeCompleted(outcome)) && this.visibleModelsDraft === submittedVisible) this.visibleModelsDraft = void 0;
|
|
1408
|
+
if ((outcome.config === "confirmed" || writeCompleted(outcome)) && this.contextWindowOverridesDraft === submittedContextOverrides) this.contextWindowOverridesDraft = void 0;
|
|
1228
1409
|
this.publish();
|
|
1229
1410
|
return outcome;
|
|
1230
1411
|
}
|
|
@@ -1242,6 +1423,63 @@ window.__ModuleLoader__.load({
|
|
|
1242
1423
|
this.saveResult = void 0;
|
|
1243
1424
|
this.publish();
|
|
1244
1425
|
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Stage the whole per-model context-length map (issue #86): one entry per
|
|
1428
|
+
* row, the value the row's input shows verbatim (blank = drop the override
|
|
1429
|
+
* on save). The component rebuilds the map from its projected rows, so a
|
|
1430
|
+
* model reselect moves the value to its new key in the same edit.
|
|
1431
|
+
*/
|
|
1432
|
+
editContextWindowOverrides(next) {
|
|
1433
|
+
this.contextWindowOverridesDraft = { ...next };
|
|
1434
|
+
this.failed = false;
|
|
1435
|
+
this.saveResult = void 0;
|
|
1436
|
+
this.publish();
|
|
1437
|
+
}
|
|
1438
|
+
/** The stored per-model overrides, already cleaned by the shared validity rule. */
|
|
1439
|
+
storedContextWindowOverrides() {
|
|
1440
|
+
return { ...readContextWindowOverrides(this.sectionValue("contextWindowOverrides")) };
|
|
1441
|
+
}
|
|
1442
|
+
/** Effective rows: the staged draft verbatim, or the stored values formatted. */
|
|
1443
|
+
effectiveContextWindowOverrides() {
|
|
1444
|
+
const draft = this.contextWindowOverridesDraft;
|
|
1445
|
+
const source = draft !== void 0 ? draft : Object.fromEntries(Object.entries(this.storedContextWindowOverrides()).map(([model, value]) => [model, String(value)]));
|
|
1446
|
+
return Object.entries(source).filter(([model]) => model !== "").map(([model, text]) => {
|
|
1447
|
+
const parsed = this.spec("contextWindow").parse(text);
|
|
1448
|
+
return {
|
|
1449
|
+
model,
|
|
1450
|
+
text,
|
|
1451
|
+
invalid: parsed.kind === "invalid",
|
|
1452
|
+
invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
|
|
1453
|
+
};
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* The draft parsed for the write: blank rows and unparseable rows drop out
|
|
1458
|
+
* (an invalid row blocks save on its own), everything else is the value the
|
|
1459
|
+
* writer puts at `contextWindowOverrides`.
|
|
1460
|
+
*/
|
|
1461
|
+
parsedContextWindowOverrides() {
|
|
1462
|
+
const values = {};
|
|
1463
|
+
let invalid = false;
|
|
1464
|
+
for (const row of this.effectiveContextWindowOverrides()) {
|
|
1465
|
+
if (row.invalid) {
|
|
1466
|
+
invalid = true;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
const parsed = this.spec("contextWindow").parse(row.text);
|
|
1470
|
+
if (parsed.kind === "set" && typeof parsed.value === "number") values[row.model] = parsed.value;
|
|
1471
|
+
}
|
|
1472
|
+
return {
|
|
1473
|
+
values,
|
|
1474
|
+
invalid
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
contextWindowOverridesDirty() {
|
|
1478
|
+
if (this.contextWindowOverridesDraft === void 0) return false;
|
|
1479
|
+
const values = this.parsedContextWindowOverrides().values;
|
|
1480
|
+
const stored = this.storedContextWindowOverrides();
|
|
1481
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(stored)])].some((model) => values[model] !== stored[model]);
|
|
1482
|
+
}
|
|
1245
1483
|
renameAccount(ref, label) {
|
|
1246
1484
|
return this.runAccountOp("rename", {
|
|
1247
1485
|
kind: "rename",
|
|
@@ -1519,12 +1757,51 @@ window.__ModuleLoader__.load({
|
|
|
1519
1757
|
function formatMoneyExact(value) {
|
|
1520
1758
|
return `$${value.toFixed(4)}`;
|
|
1521
1759
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1760
|
+
/**
|
|
1761
|
+
* 紧凑数字:K/M/B 三级,一切档位决策的唯一实现。
|
|
1762
|
+
*
|
|
1763
|
+
* 档位必须**回头判断**:先除以档位再四舍五入会算出 `999_999 → "1000.0K"`、
|
|
1764
|
+
* `999_999_999 → "1000.0M"` 这类既超长、又落在并不存在的档位上的结果,
|
|
1765
|
+
* 因此每个档位算完后若已经够到下一档,就改用下一档重算一次。
|
|
1766
|
+
*
|
|
1767
|
+
* @param value - 非负计数或词元数。
|
|
1768
|
+
* @param keepZeroDecimal - 整数值是否保留一位小数。词元显示沿用 `3.0B` 的旧样式;
|
|
1769
|
+
* 计数显示去掉多余的 `.0`,见 {@link formatCompactCount}。
|
|
1770
|
+
* @returns 紧凑文本;不足 1000 时原样返回该整数。
|
|
1771
|
+
*/
|
|
1772
|
+
function compact(value, keepZeroDecimal) {
|
|
1773
|
+
const rounded = (scaled) => Math.round(scaled * 10) / 10;
|
|
1774
|
+
const text = (scaled) => {
|
|
1775
|
+
const fixed = rounded(scaled);
|
|
1776
|
+
return keepZeroDecimal ? fixed.toFixed(1) : String(fixed);
|
|
1777
|
+
};
|
|
1778
|
+
if (value >= 1e9) return `${text(value / 1e9)}B`;
|
|
1779
|
+
if (value >= 1e6) return rounded(value / 1e6) >= 1e3 ? `${text(value / 1e9)}B` : `${text(value / 1e6)}M`;
|
|
1780
|
+
if (value >= 1e3) return rounded(value / 1e3) >= 1e3 ? `${text(value / 1e6)}M` : `${text(value / 1e3)}K`;
|
|
1526
1781
|
return String(value);
|
|
1527
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* 紧凑计数:请求数、成功/失败数这类会被窄卡片截断的整数。
|
|
1785
|
+
* `1234` → `1.2K`、`12000` → `12K`、`1500000` → `1.5M`,整数不带多余的 `.0`。
|
|
1786
|
+
*/
|
|
1787
|
+
function formatCompactCount(value) {
|
|
1788
|
+
return compact(value, false);
|
|
1789
|
+
}
|
|
1790
|
+
/** 紧凑词元数:与 {@link formatCompactCount} 共用档位,但整数值保留一位小数(`3.0B`)。 */
|
|
1791
|
+
function formatTokensCompact(value) {
|
|
1792
|
+
return compact(value, true);
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* 精确值悬停提示:只在紧凑显示确实丢了信息时才返回文本。
|
|
1796
|
+
* 未换算时悬停弹出的文字与卡片上写的一模一样,没有意义,因此返回 `undefined` 让调用方
|
|
1797
|
+
* 整个省略 `title`,而不是挂一个内容重复的提示。
|
|
1798
|
+
*
|
|
1799
|
+
* @param parts - 标签与精确数值,按卡片上的显示顺序。
|
|
1800
|
+
* @returns 形如 `请求数 1234567 · 成功 1234567 · 失败 123` 的提示,或 `undefined`。
|
|
1801
|
+
*/
|
|
1802
|
+
function compactCountTitle(parts) {
|
|
1803
|
+
return parts.some(([, value]) => formatCompactCount(value) !== String(value)) ? parts.map(([label, value]) => `${label} ${value}`).join(" · ") : void 0;
|
|
1804
|
+
}
|
|
1528
1805
|
/** The input is already in percent units; callers add the percent sign. */
|
|
1529
1806
|
function formatSuccessRate(value) {
|
|
1530
1807
|
return String(Number(value.toFixed(2)));
|
|
@@ -2436,6 +2713,30 @@ window.__ModuleLoader__.load({
|
|
|
2436
2713
|
};
|
|
2437
2714
|
//#endregion
|
|
2438
2715
|
//#region src/enrollment-wire.ts
|
|
2716
|
+
const ENROLLMENT_MESSAGE_KEYS = [
|
|
2717
|
+
"enrollmentCreating",
|
|
2718
|
+
"enrollmentWaiting",
|
|
2719
|
+
"enrollmentWriting",
|
|
2720
|
+
"enrollmentNaming",
|
|
2721
|
+
"enrollmentNameFailed",
|
|
2722
|
+
"enrollmentFinished",
|
|
2723
|
+
"enrollmentCancelling",
|
|
2724
|
+
"enrollmentCancelled",
|
|
2725
|
+
"enrollmentCancelledCleaned",
|
|
2726
|
+
"enrollmentFailed",
|
|
2727
|
+
"enrollmentFailedCleaned",
|
|
2728
|
+
"enrollmentCleanupPending",
|
|
2729
|
+
"enrollmentCleanupFailed",
|
|
2730
|
+
"enrollmentRecordNaming",
|
|
2731
|
+
"enrollmentRecordCleanup",
|
|
2732
|
+
"enrollmentBeginFailed",
|
|
2733
|
+
"enrollmentCancelUnconfirmed",
|
|
2734
|
+
"enrollmentNameRequestFailed",
|
|
2735
|
+
"enrollmentCleanupRequestFailed",
|
|
2736
|
+
"enrollmentDisconnected",
|
|
2737
|
+
"enrollmentHostUnavailable",
|
|
2738
|
+
"enrollmentStatusUnavailable"
|
|
2739
|
+
];
|
|
2439
2740
|
const guard = makeBoundaryValidator("commandcode/enrollment");
|
|
2440
2741
|
const phases = [
|
|
2441
2742
|
"creating",
|
|
@@ -2452,11 +2753,13 @@ window.__ModuleLoader__.load({
|
|
|
2452
2753
|
const source = guard.record(value, "state");
|
|
2453
2754
|
const phase = source.phase;
|
|
2454
2755
|
if (!phases.includes(phase)) return guard.reject("phase");
|
|
2756
|
+
const messageKey = source.messageKey;
|
|
2757
|
+
if (!ENROLLMENT_MESSAGE_KEYS.includes(messageKey)) return guard.reject("messageKey");
|
|
2455
2758
|
const state = {
|
|
2456
2759
|
id: guard.stringField(source, "id", "id"),
|
|
2457
2760
|
ref: guard.stringField(source, "ref", "ref"),
|
|
2458
2761
|
phase,
|
|
2459
|
-
|
|
2762
|
+
messageKey
|
|
2460
2763
|
};
|
|
2461
2764
|
for (const field of ["authUrl", "suggestedName"]) if (source[field] !== void 0) state[field] = guard.stringField(source, field, field);
|
|
2462
2765
|
return state;
|
|
@@ -2611,7 +2914,7 @@ window.__ModuleLoader__.load({
|
|
|
2611
2914
|
id,
|
|
2612
2915
|
ref: "",
|
|
2613
2916
|
phase: "creating",
|
|
2614
|
-
|
|
2917
|
+
messageKey: "enrollmentCreating"
|
|
2615
2918
|
}
|
|
2616
2919
|
});
|
|
2617
2920
|
try {
|
|
@@ -2632,7 +2935,7 @@ window.__ModuleLoader__.load({
|
|
|
2632
2935
|
this.receive(id, result);
|
|
2633
2936
|
} catch {
|
|
2634
2937
|
if (this.id === id) this.cancel();
|
|
2635
|
-
this.failure("
|
|
2938
|
+
this.failure("enrollmentBeginFailed");
|
|
2636
2939
|
}
|
|
2637
2940
|
}
|
|
2638
2941
|
/** 页面可立即离开;宿主仍等待写入和补偿收尾。 */
|
|
@@ -2651,7 +2954,7 @@ window.__ModuleLoader__.load({
|
|
|
2651
2954
|
})).then(() => {
|
|
2652
2955
|
this.changed();
|
|
2653
2956
|
this.refresh();
|
|
2654
|
-
}, () => this.failure("
|
|
2957
|
+
}, () => this.failure("enrollmentCancelUnconfirmed"));
|
|
2655
2958
|
}
|
|
2656
2959
|
async name(name) {
|
|
2657
2960
|
const id = this.id;
|
|
@@ -2663,7 +2966,7 @@ window.__ModuleLoader__.load({
|
|
|
2663
2966
|
...name ? { name } : {}
|
|
2664
2967
|
}));
|
|
2665
2968
|
} catch {
|
|
2666
|
-
this.failure("
|
|
2969
|
+
this.failure("enrollmentNameRequestFailed");
|
|
2667
2970
|
}
|
|
2668
2971
|
}
|
|
2669
2972
|
async recover(record, name) {
|
|
@@ -2673,7 +2976,7 @@ window.__ModuleLoader__.load({
|
|
|
2673
2976
|
pageId: this.pageId,
|
|
2674
2977
|
...name ? { name } : {}
|
|
2675
2978
|
});
|
|
2676
|
-
if (!result.ok) this.failure("
|
|
2979
|
+
if (!result.ok) this.failure("enrollmentCleanupPending");
|
|
2677
2980
|
else if (!this.disposed && this.store.getSnapshot().active?.id === record.id) this.store.set({
|
|
2678
2981
|
...this.store.getSnapshot(),
|
|
2679
2982
|
active: result.value,
|
|
@@ -2682,7 +2985,7 @@ window.__ModuleLoader__.load({
|
|
|
2682
2985
|
this.changed();
|
|
2683
2986
|
await this.refresh();
|
|
2684
2987
|
} catch {
|
|
2685
|
-
this.failure("
|
|
2988
|
+
this.failure("enrollmentCleanupRequestFailed");
|
|
2686
2989
|
}
|
|
2687
2990
|
}
|
|
2688
2991
|
dispose() {
|
|
@@ -2707,7 +3010,7 @@ window.__ModuleLoader__.load({
|
|
|
2707
3010
|
this.watch = void 0;
|
|
2708
3011
|
this.ready = void 0;
|
|
2709
3012
|
this.cancel();
|
|
2710
|
-
this.failure("
|
|
3013
|
+
this.failure("enrollmentDisconnected");
|
|
2711
3014
|
}
|
|
2712
3015
|
}
|
|
2713
3016
|
})();
|
|
@@ -2724,7 +3027,7 @@ window.__ModuleLoader__.load({
|
|
|
2724
3027
|
receive(id, result) {
|
|
2725
3028
|
if (this.disposed || this.id !== id) return;
|
|
2726
3029
|
if (!result.ok) {
|
|
2727
|
-
this.failure("
|
|
3030
|
+
this.failure("enrollmentHostUnavailable");
|
|
2728
3031
|
return;
|
|
2729
3032
|
}
|
|
2730
3033
|
const active = result.value;
|
|
@@ -2756,16 +3059,17 @@ window.__ModuleLoader__.load({
|
|
|
2756
3059
|
pageId: this.pageId
|
|
2757
3060
|
}));
|
|
2758
3061
|
} catch {
|
|
2759
|
-
this.failure("
|
|
3062
|
+
this.failure("enrollmentStatusUnavailable");
|
|
2760
3063
|
if (!this.disposed && this.id === id) this.timer = setTimeout(() => {
|
|
2761
3064
|
this.poll(id);
|
|
2762
3065
|
}, this.pollMs);
|
|
2763
3066
|
}
|
|
2764
3067
|
}
|
|
2765
|
-
|
|
3068
|
+
/** 记下要显示的语义键;文案在渲染处按界面语言取,本控制器不接触成品文本。 */
|
|
3069
|
+
failure(key) {
|
|
2766
3070
|
if (!this.disposed) this.store.set({
|
|
2767
3071
|
...this.store.getSnapshot(),
|
|
2768
|
-
error:
|
|
3072
|
+
error: key
|
|
2769
3073
|
});
|
|
2770
3074
|
}
|
|
2771
3075
|
stopTimer() {
|
|
@@ -2803,7 +3107,7 @@ window.__ModuleLoader__.load({
|
|
|
2803
3107
|
filterModelsByPlan: "隐藏套餐外模型",
|
|
2804
3108
|
filterModelsByPlanHint: "只显示套餐内模型;按需余额账户仍显示全部。",
|
|
2805
3109
|
webSearch: "用 Command Code 承载联网搜索",
|
|
2806
|
-
webSearchHint: "使用同一密钥承载 dsh 的 web_search
|
|
3110
|
+
webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。若「网页搜索」设置里已显式选择其他后端,本开关不生效。",
|
|
2807
3111
|
showSidebarQuota: "在侧边栏显示额度卡片",
|
|
2808
3112
|
showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
|
|
2809
3113
|
zdr: "零数据保留(ZDR)",
|
|
@@ -2824,6 +3128,23 @@ window.__ModuleLoader__.load({
|
|
|
2824
3128
|
visibleModelsShowAll: "显示全部",
|
|
2825
3129
|
visibleModelsStaleHint: "有 {count} 个已选模型在目录中找不到了(可能已下架),不影响其他模型;可清理或保留。",
|
|
2826
3130
|
visibleModelsCleanStale: "清理失效({count})",
|
|
3131
|
+
contextWindow: "自定义上下文长度",
|
|
3132
|
+
contextWindowHint: "覆盖模型目录公布的上下文窗口(token):上下文计量、自动压缩时机与模型选择器的上下文标注都按此值计算,对未单独设置的模型生效。留空则跟随目录;支持 32K、1M 这样的简写(二进制换算:32K = 32 768,1M = 1 048 576)。",
|
|
3133
|
+
contextOverridesTitle: "按模型覆盖",
|
|
3134
|
+
contextOverridesHint: "为单个模型单独设置上下文长度,优先于上面的全局值;模型下拉只列出模型白名单中的可见模型(显示全部时即整个目录)。",
|
|
3135
|
+
contextOverrideAdd: "添加模型",
|
|
3136
|
+
contextOverrideRemove: "移除该模型的上下文覆盖",
|
|
3137
|
+
contextOverrideModel: "模型",
|
|
3138
|
+
contextOverrideValue: "上下文长度",
|
|
3139
|
+
contextOverrideNoCatalog: "模型目录尚未加载,暂时无法添加按模型覆盖。",
|
|
3140
|
+
contextWindowTooSmall: "上下文长度不能小于 1",
|
|
3141
|
+
contextWindowTooLarge: "上下文长度不能大于 10,000,000",
|
|
3142
|
+
contextWindowInvalid: "请输入有效的整数 token 数",
|
|
3143
|
+
maxOutputTokens: "单轮输出上限",
|
|
3144
|
+
maxOutputTokensHint: "每次请求发送的输出预算(token)。留空=按模型自身输出上限推导(默认,行为不变);填正整数=与推导值取较小者,永远不会超过模型上限或网关上限 131072。填 `0`=请求中不发送该字段,把单轮输出规模交回平台自行限制——这是「长回答被硬切」的解法,但仅 CLI 与 OpenAI 两条传输支持;Claude 走的 Messages 端点必填该字段,因此那里仍按推导值发送,填 0 对它无效。会话标题这类由宿主明确限定预算的辅助调用也不受此设置影响。支持 64K 这样的简写。",
|
|
3145
|
+
maxOutputTokensTooSmall: "输出上限不能小于 0(0 表示不发送该字段)",
|
|
3146
|
+
maxOutputTokensTooLarge: "输出上限不能大于 131072(网关上限)",
|
|
3147
|
+
maxOutputTokensInvalid: "请输入 0 或有效的整数 token 数",
|
|
2827
3148
|
overridden: "已覆盖",
|
|
2828
3149
|
reset: "重置",
|
|
2829
3150
|
invalidNumber: "无效数字",
|
|
@@ -2850,6 +3171,7 @@ window.__ModuleLoader__.load({
|
|
|
2850
3171
|
writeRefreshFailed: "密钥状态刷新失败;已获确认的写入仍有效。",
|
|
2851
3172
|
writeRefreshRetry: "仅刷新密钥状态",
|
|
2852
3173
|
writeStateUnknown: "密钥状态尚未确认",
|
|
3174
|
+
writeCredentialRefNote: "(原引用:{ref})",
|
|
2853
3175
|
unsavedChanges: "有未保存的更改",
|
|
2854
3176
|
saveInvalid: "有字段填写不正确,修正后才能保存",
|
|
2855
3177
|
discard: "放弃",
|
|
@@ -2937,21 +3259,50 @@ window.__ModuleLoader__.load({
|
|
|
2937
3259
|
accountAddLogin: "网页登录",
|
|
2938
3260
|
accountAddPaste: "粘贴密钥",
|
|
2939
3261
|
accountAddConfirm: "添加",
|
|
3262
|
+
accountAddCancel: "取消开通",
|
|
3263
|
+
accountAddLinkHint: "若登录页面未自动打开,请点击下方链接。",
|
|
3264
|
+
accountAddOpenLink: "打开浏览器登录",
|
|
2940
3265
|
accountNamePlaceholder: "备注名(可选,网页登录时默认使用账户名)",
|
|
2941
3266
|
accountNameN: "账户 {n}",
|
|
3267
|
+
accountNameSaveRetry: "重试保存名称",
|
|
3268
|
+
accountNameAcceptExisting: "接受已有名称",
|
|
2942
3269
|
accountKeyPlaceholder: "粘贴 API 密钥",
|
|
2943
3270
|
accountOpFailed: "账户操作未能完成,请重试。",
|
|
2944
3271
|
accountCleanupPending: "账户已移除,但有密钥尚未清理。可重试,不影响其他账户。",
|
|
2945
3272
|
accountCleanupRetry: "重试清理密钥",
|
|
3273
|
+
accountEnrollmentCleanupRetry: "重试清理",
|
|
3274
|
+
accountRefSeparator: ":",
|
|
2946
3275
|
accountModels: "专用模型",
|
|
2947
3276
|
accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
|
|
2948
3277
|
accountModelOwner: "属于 {name}",
|
|
2949
3278
|
modelPick: "选择模型…",
|
|
2950
3279
|
modelCount: "已选 {count} 个模型",
|
|
2951
3280
|
modelCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的设置仍会生效。",
|
|
2952
|
-
modelsTitle: "
|
|
3281
|
+
modelsTitle: "模型与上下文",
|
|
2953
3282
|
privacyTitle: "隐私与安全",
|
|
2954
|
-
integrationsTitle: "集成与显示"
|
|
3283
|
+
integrationsTitle: "集成与显示",
|
|
3284
|
+
enrollmentCreating: "正在创建账号",
|
|
3285
|
+
enrollmentWaiting: "等待浏览器登录",
|
|
3286
|
+
enrollmentWriting: "正在保存凭据",
|
|
3287
|
+
enrollmentNaming: "账号已登录,正在保存名称",
|
|
3288
|
+
enrollmentNameFailed: "账号已登录,名称保存失败,请重试或接受已有名称",
|
|
3289
|
+
enrollmentFinished: "账号开通完成",
|
|
3290
|
+
enrollmentCancelling: "正在等待写入结束并清理",
|
|
3291
|
+
enrollmentCancelled: "已取消",
|
|
3292
|
+
enrollmentCancelledCleaned: "已取消并清理",
|
|
3293
|
+
enrollmentFailed: "账号开通失败,未创建账号",
|
|
3294
|
+
enrollmentFailedCleaned: "开通失败,已清理临时账号",
|
|
3295
|
+
enrollmentCleanupPending: "收尾未完成,请重试",
|
|
3296
|
+
enrollmentCleanupFailed: "清理未完成,请重试",
|
|
3297
|
+
enrollmentRecordNaming: "已登录账号等待确认名称",
|
|
3298
|
+
enrollmentRecordCleanup: "账号开通收尾未完成,请重试",
|
|
3299
|
+
enrollmentBeginFailed: "账号开通请求失败,请检查连接后重试",
|
|
3300
|
+
enrollmentCancelUnconfirmed: "取消请求未确认,重新连接后请检查未完成记录",
|
|
3301
|
+
enrollmentNameRequestFailed: "名称保存失败,请重试或接受已有名称",
|
|
3302
|
+
enrollmentCleanupRequestFailed: "收尾请求失败,请检查连接后重试",
|
|
3303
|
+
enrollmentDisconnected: "开通连接已断开,宿主正在收尾;重连后请检查未完成记录",
|
|
3304
|
+
enrollmentHostUnavailable: "宿主无法完成账号开通,请检查连接与写入权限",
|
|
3305
|
+
enrollmentStatusUnavailable: "开通状态暂不可用,正在等待重新连接"
|
|
2955
3306
|
};
|
|
2956
3307
|
const en = {
|
|
2957
3308
|
openAnalytics: "View model usage, cost and performance",
|
|
@@ -2981,7 +3332,7 @@ window.__ModuleLoader__.load({
|
|
|
2981
3332
|
filterModelsByPlan: "Hide out-of-plan models",
|
|
2982
3333
|
filterModelsByPlanHint: "Show only models in your plan; on-demand credit accounts still see the full catalog.",
|
|
2983
3334
|
webSearch: "Serve dsh web search with Command Code",
|
|
2984
|
-
webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend.",
|
|
3335
|
+
webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend. When the Web search settings already pin another backend, this switch has no effect.",
|
|
2985
3336
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
2986
3337
|
showSidebarQuotaHint: "Show a plan and quota card at the bottom of the sidebar that opens the full dashboard. Off by default; while off, no background usage refresh runs for it.",
|
|
2987
3338
|
zdr: "Zero data retention (ZDR)",
|
|
@@ -3002,6 +3353,23 @@ window.__ModuleLoader__.load({
|
|
|
3002
3353
|
visibleModelsShowAll: "Show all",
|
|
3003
3354
|
visibleModelsStaleHint: "{count} selected model(s) are no longer in the catalog (possibly retired); other models are unaffected. Clean them up or keep them.",
|
|
3004
3355
|
visibleModelsCleanStale: "Clean stale ({count})",
|
|
3356
|
+
contextWindow: "Custom context length",
|
|
3357
|
+
contextWindowHint: "Overrides the context window published by the model catalog (in tokens): the context meter, auto-compaction timing and the model-picker annotation all follow it, for every model without a per-model override below. Empty follows the catalog; shorthand like 32K or 1M is accepted (binary units: 32K is 32,768, 1M is 1,048,576).",
|
|
3358
|
+
contextOverridesTitle: "Per-model overrides",
|
|
3359
|
+
contextOverridesHint: "Set a context length for one model; it wins over the global value above. The picker lists only the models kept by the visible-models allowlist (the whole catalog while showing all).",
|
|
3360
|
+
contextOverrideAdd: "Add model",
|
|
3361
|
+
contextOverrideRemove: "Remove this model’s context override",
|
|
3362
|
+
contextOverrideModel: "Model",
|
|
3363
|
+
contextOverrideValue: "Context length",
|
|
3364
|
+
contextOverrideNoCatalog: "The model catalog has not loaded yet; per-model overrides cannot be added right now.",
|
|
3365
|
+
contextWindowTooSmall: "Context length must be at least 1",
|
|
3366
|
+
contextWindowTooLarge: "Context length must not exceed 10,000,000",
|
|
3367
|
+
contextWindowInvalid: "Enter a valid whole-token count",
|
|
3368
|
+
maxOutputTokens: "Output limit per turn",
|
|
3369
|
+
maxOutputTokensHint: "The output budget sent with each request, in tokens. Empty derives it from the model’s own ceiling (the default; nothing changes). A positive number is taken as a cap alongside that derived value, so it can never exceed the model ceiling or the gateway’s 131,072. `0` sends no such field at all and leaves the per-turn output size to the platform — the fix for answers being cut short — but only the CLI and OpenAI transports support it: the Messages endpoint Claude uses requires the field, so Claude keeps its derived budget and `0` has no effect there. Auxiliary calls whose budget the host fixes itself, such as the session title, are unaffected too. Shorthand like 64K is accepted.",
|
|
3370
|
+
maxOutputTokensTooSmall: "Output limit must be at least 0 (0 omits the field)",
|
|
3371
|
+
maxOutputTokensTooLarge: "Output limit must not exceed 131,072 (the gateway cap)",
|
|
3372
|
+
maxOutputTokensInvalid: "Enter 0 or a valid whole-token count",
|
|
3005
3373
|
overridden: "Overridden",
|
|
3006
3374
|
reset: "Reset",
|
|
3007
3375
|
invalidNumber: "Invalid number",
|
|
@@ -3015,19 +3383,20 @@ window.__ModuleLoader__.load({
|
|
|
3015
3383
|
saving: "Saving",
|
|
3016
3384
|
saved: "Settings saved",
|
|
3017
3385
|
saveFailed: "Save failed, please retry.",
|
|
3018
|
-
writeConflict: "
|
|
3019
|
-
writeConfigUnconfirmed: "
|
|
3020
|
-
writeConfigSaved: "
|
|
3021
|
-
writeKeyNotIssued: "
|
|
3022
|
-
writeKeyUnconfirmed: "
|
|
3023
|
-
writeKeyConfirmed: "
|
|
3024
|
-
writeTargetBefore: "
|
|
3025
|
-
writeTargetAfter: "
|
|
3026
|
-
writeClosed: "
|
|
3027
|
-
writeCleanupPending: "
|
|
3028
|
-
writeRefreshFailed: "
|
|
3029
|
-
writeRefreshRetry: "
|
|
3030
|
-
writeStateUnknown: "
|
|
3386
|
+
writeConflict: "Pending fields were modified by another operation; the configuration was not written. Please review and save again.",
|
|
3387
|
+
writeConfigUnconfirmed: "The configuration submission was not confirmed; the key was not written. Please check the host state and retry.",
|
|
3388
|
+
writeConfigSaved: "Configuration saved.",
|
|
3389
|
+
writeKeyNotIssued: "The key operation has not been issued; please review and retry.",
|
|
3390
|
+
writeKeyUnconfirmed: "The key operation was not confirmed; please review and retry manually.",
|
|
3391
|
+
writeKeyConfirmed: "The key operation on the original reference has been confirmed.",
|
|
3392
|
+
writeTargetBefore: "The default key reference has changed; this key operation was not issued. Please check the current target.",
|
|
3393
|
+
writeTargetAfter: "The key operation on the original reference was issued, then the default reference changed. Please check both references.",
|
|
3394
|
+
writeClosed: "The client has been destroyed; unissued operations were stopped. Confirmed changes are kept.",
|
|
3395
|
+
writeCleanupPending: "The account change was confirmed, but cleanup records are still pending. Please retry cleanup.",
|
|
3396
|
+
writeRefreshFailed: "Key status refresh failed; confirmed writes remain valid.",
|
|
3397
|
+
writeRefreshRetry: "Refresh key status only",
|
|
3398
|
+
writeStateUnknown: "Key status not yet confirmed",
|
|
3399
|
+
writeCredentialRefNote: "(original ref: {ref})",
|
|
3031
3400
|
unsavedChanges: "You have unsaved changes",
|
|
3032
3401
|
saveInvalid: "Fix the highlighted field before saving",
|
|
3033
3402
|
discard: "Discard",
|
|
@@ -3044,7 +3413,7 @@ window.__ModuleLoader__.load({
|
|
|
3044
3413
|
usageCost: "Spend",
|
|
3045
3414
|
usageCompleted: "Completed",
|
|
3046
3415
|
usageBillingPeriod: "Official billing-period usage",
|
|
3047
|
-
usagePeriodUnknown: "
|
|
3416
|
+
usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
|
|
3048
3417
|
usageTokens: "Tokens",
|
|
3049
3418
|
usageTokensIn: "in",
|
|
3050
3419
|
usageTokensOut: "out",
|
|
@@ -3115,21 +3484,50 @@ window.__ModuleLoader__.load({
|
|
|
3115
3484
|
accountAddLogin: "Sign in",
|
|
3116
3485
|
accountAddPaste: "Paste key",
|
|
3117
3486
|
accountAddConfirm: "Add",
|
|
3487
|
+
accountAddCancel: "Cancel setup",
|
|
3488
|
+
accountAddLinkHint: "If the sign-in page did not open automatically, use the link below.",
|
|
3489
|
+
accountAddOpenLink: "Open the sign-in page",
|
|
3118
3490
|
accountNamePlaceholder: "Name (optional; sign-in uses the account name)",
|
|
3119
3491
|
accountNameN: "Account {n}",
|
|
3492
|
+
accountNameSaveRetry: "Retry saving the name",
|
|
3493
|
+
accountNameAcceptExisting: "Accept the existing name",
|
|
3120
3494
|
accountKeyPlaceholder: "Paste an API key",
|
|
3121
3495
|
accountOpFailed: "The account change did not complete. Try again.",
|
|
3122
3496
|
accountCleanupPending: "The account was removed, but credential cleanup is still pending. Retry without affecting other accounts.",
|
|
3123
3497
|
accountCleanupRetry: "Retry credential cleanup",
|
|
3498
|
+
accountEnrollmentCleanupRetry: "Retry cleanup",
|
|
3499
|
+
accountRefSeparator: ": ",
|
|
3124
3500
|
accountModels: "Dedicated models",
|
|
3125
3501
|
accountModelsHint: "These models prefer this account and still switch when it is unavailable. A model belongs to one account; selecting it here moves it from any other.",
|
|
3126
3502
|
accountModelOwner: "on {name}",
|
|
3127
3503
|
modelPick: "Choose models…",
|
|
3128
3504
|
modelCount: "{count} models selected",
|
|
3129
3505
|
modelCatalogFailed: "The model catalog could not be loaded; saved settings still apply.",
|
|
3130
|
-
modelsTitle: "Models",
|
|
3506
|
+
modelsTitle: "Models & context",
|
|
3131
3507
|
privacyTitle: "Privacy & security",
|
|
3132
|
-
integrationsTitle: "Integrations & display"
|
|
3508
|
+
integrationsTitle: "Integrations & display",
|
|
3509
|
+
enrollmentCreating: "Creating the account",
|
|
3510
|
+
enrollmentWaiting: "Waiting for the browser sign-in",
|
|
3511
|
+
enrollmentWriting: "Saving the credential",
|
|
3512
|
+
enrollmentNaming: "Signed in. Saving the name",
|
|
3513
|
+
enrollmentNameFailed: "Signed in, but saving the name failed. Retry, or accept the existing name.",
|
|
3514
|
+
enrollmentFinished: "Account setup complete",
|
|
3515
|
+
enrollmentCancelling: "Waiting for the write to finish, then cleaning up",
|
|
3516
|
+
enrollmentCancelled: "Cancelled",
|
|
3517
|
+
enrollmentCancelledCleaned: "Cancelled and cleaned up",
|
|
3518
|
+
enrollmentFailed: "Account setup failed. No account was created.",
|
|
3519
|
+
enrollmentFailedCleaned: "Setup failed; the temporary account was cleaned up.",
|
|
3520
|
+
enrollmentCleanupPending: "Cleanup is not finished. Please retry.",
|
|
3521
|
+
enrollmentCleanupFailed: "Cleanup failed. Please retry.",
|
|
3522
|
+
enrollmentRecordNaming: "Signed-in account waiting for its name to be confirmed",
|
|
3523
|
+
enrollmentRecordCleanup: "Account setup was not finished. Please retry.",
|
|
3524
|
+
enrollmentBeginFailed: "The account setup request failed. Check the connection and retry.",
|
|
3525
|
+
enrollmentCancelUnconfirmed: "The cancel request was not confirmed. After reconnecting, check for unfinished records.",
|
|
3526
|
+
enrollmentNameRequestFailed: "Saving the name failed. Retry, or accept the existing name.",
|
|
3527
|
+
enrollmentCleanupRequestFailed: "The cleanup request failed. Check the connection and retry.",
|
|
3528
|
+
enrollmentDisconnected: "The setup connection dropped; the host is finishing up. After reconnecting, check for unfinished records.",
|
|
3529
|
+
enrollmentHostUnavailable: "The host could not complete the account setup. Check the connection and write permissions.",
|
|
3530
|
+
enrollmentStatusUnavailable: "Setup status is unavailable; waiting to reconnect."
|
|
3133
3531
|
};
|
|
3134
3532
|
/** 同一确认结果在设置页和模型卡使用相同措辞,不从 configured 推断写入结果。 */
|
|
3135
3533
|
function settingsWriteNotice(result, t) {
|
|
@@ -3145,7 +3543,7 @@ window.__ModuleLoader__.load({
|
|
|
3145
3543
|
const messages = key ? [t(key)] : [];
|
|
3146
3544
|
if (result.issue === "target-changed" && result.credentialRef) {
|
|
3147
3545
|
messages.push(t(result.credential === "confirmed" ? "writeKeyConfirmed" : "writeKeyUnconfirmed"));
|
|
3148
|
-
messages.push(
|
|
3546
|
+
messages.push(t("writeCredentialRefNote").replace("{ref}", result.credentialRef));
|
|
3149
3547
|
}
|
|
3150
3548
|
if (key && result.config === "confirmed" && result.issue !== "cleanup-pending") messages.unshift(t("writeConfigSaved"));
|
|
3151
3549
|
if (result.refreshFailed) messages.push(t("writeRefreshFailed"));
|
|
@@ -3270,6 +3668,19 @@ window.__ModuleLoader__.load({
|
|
|
3270
3668
|
return selected.includes(modelId) ? selected.filter((value) => value !== modelId) : [...selected, modelId];
|
|
3271
3669
|
}
|
|
3272
3670
|
/**
|
|
3671
|
+
* The catalog rows a picker may offer under the visible-models allowlist, in
|
|
3672
|
+
* catalog order: the whole catalog while "show all" is active (the empty
|
|
3673
|
+
* allowlist means exactly that), otherwise only the allowlisted ids. The
|
|
3674
|
+
* per-model context override editor uses this so its dropdown agrees with the
|
|
3675
|
+
* whitelist on the same card stack — a staged whitelist edit narrows it
|
|
3676
|
+
* immediately, since both read the projected page state.
|
|
3677
|
+
*/
|
|
3678
|
+
function allowlistedModels(catalog, allowlist, showAll) {
|
|
3679
|
+
if (showAll) return [...catalog];
|
|
3680
|
+
const keep = new Set(allowlist);
|
|
3681
|
+
return catalog.filter((model) => keep.has(model.id));
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3273
3684
|
* Whether the catalog is trustworthy enough to call an unlisted selection
|
|
3274
3685
|
* "retired". FALSE while the first fetch is in flight, after a failure, and
|
|
3275
3686
|
* for a loaded-but-empty catalog — an empty list makes every selected id look
|
|
@@ -3291,7 +3702,7 @@ window.__ModuleLoader__.load({
|
|
|
3291
3702
|
}
|
|
3292
3703
|
//#endregion
|
|
3293
3704
|
//#region package.json
|
|
3294
|
-
var version = "0.
|
|
3705
|
+
var version = "0.14.0";
|
|
3295
3706
|
var repository = {
|
|
3296
3707
|
"type": "git",
|
|
3297
3708
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -3474,11 +3885,12 @@ window.__ModuleLoader__.load({
|
|
|
3474
3885
|
*
|
|
3475
3886
|
* Layout, top to bottom: Accounts (one card per account, status, quota,
|
|
3476
3887
|
* actions and dedicated models — every operation commits immediately), Models
|
|
3477
|
-
* (plan filter + visible-model allowlist
|
|
3478
|
-
*
|
|
3479
|
-
*
|
|
3480
|
-
*
|
|
3481
|
-
*
|
|
3888
|
+
* & context (plan filter + visible-model allowlist, then the context-length
|
|
3889
|
+
* rows: the global custom window and the per-model overrides, issue #86),
|
|
3890
|
+
* Privacy & security (ZDR), Integrations & display (web search, sidebar quota
|
|
3891
|
+
* card), and a collapsed Advanced card (API base + network limits). Everything
|
|
3892
|
+
* below Accounts is a flat group of rows (title/description left, control
|
|
3893
|
+
* right) inside the staged form the floating save bar writes.
|
|
3482
3894
|
*
|
|
3483
3895
|
* Styles are injected once by the client entry (see src/client/index.ts) and
|
|
3484
3896
|
* class-prefixed `cc-` to stay local.
|
|
@@ -3549,7 +3961,7 @@ window.__ModuleLoader__.load({
|
|
|
3549
3961
|
}) : null;
|
|
3550
3962
|
}
|
|
3551
3963
|
/** One text/number field row. */
|
|
3552
|
-
function Field({ id, label, hint, state, disabled, numeric, decimal, wide, placeholder, onEdit, onReset, t }) {
|
|
3964
|
+
function Field({ id, label, hint, state, disabled, numeric, decimal, context, output, wide, placeholder, onEdit, onReset, t }) {
|
|
3553
3965
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
3554
3966
|
title: label,
|
|
3555
3967
|
titleFor: id,
|
|
@@ -3558,7 +3970,7 @@ window.__ModuleLoader__.load({
|
|
|
3558
3970
|
t
|
|
3559
3971
|
}),
|
|
3560
3972
|
description: hint,
|
|
3561
|
-
error: state.invalid ? invalidCopy(state.invalidReason, t, decimal) : void 0,
|
|
3973
|
+
error: state.invalid ? invalidCopy(state.invalidReason, t, output === true ? "output" : context === true ? "context" : decimal === true ? "duration" : "number") : void 0,
|
|
3562
3974
|
control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
|
|
3563
3975
|
label,
|
|
3564
3976
|
state,
|
|
@@ -3579,12 +3991,22 @@ window.__ModuleLoader__.load({
|
|
|
3579
3991
|
});
|
|
3580
3992
|
}
|
|
3581
3993
|
/** The per-field error copy for a staged draft's failure reason. */
|
|
3582
|
-
function invalidCopy(reason, t,
|
|
3583
|
-
if (duration) {
|
|
3994
|
+
function invalidCopy(reason, t, mode = "number") {
|
|
3995
|
+
if (mode === "duration") {
|
|
3584
3996
|
if (reason === "tooSmall") return t("durationTooSmall");
|
|
3585
3997
|
if (reason === "tooLarge") return t("durationTooLarge");
|
|
3586
3998
|
return t("durationInvalid");
|
|
3587
3999
|
}
|
|
4000
|
+
if (mode === "context") {
|
|
4001
|
+
if (reason === "tooSmall") return t("contextWindowTooSmall");
|
|
4002
|
+
if (reason === "tooLarge") return t("contextWindowTooLarge");
|
|
4003
|
+
return t("contextWindowInvalid");
|
|
4004
|
+
}
|
|
4005
|
+
if (mode === "output") {
|
|
4006
|
+
if (reason === "tooSmall") return t("maxOutputTokensTooSmall");
|
|
4007
|
+
if (reason === "tooLarge") return t("maxOutputTokensTooLarge");
|
|
4008
|
+
return t("maxOutputTokensInvalid");
|
|
4009
|
+
}
|
|
3588
4010
|
if (reason === "tooSmall") return t("numberTooSmall");
|
|
3589
4011
|
if (reason === "tooLarge") return t("numberTooLarge");
|
|
3590
4012
|
return t("invalidNumber");
|
|
@@ -3622,9 +4044,10 @@ window.__ModuleLoader__.load({
|
|
|
3622
4044
|
});
|
|
3623
4045
|
}
|
|
3624
4046
|
/** One stat tile in the account card's summary grid. */
|
|
3625
|
-
function UsageStat({ label, value, sub }) {
|
|
4047
|
+
function UsageStat({ label, value, sub, title }) {
|
|
3626
4048
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3627
4049
|
className: "cc-usageStat",
|
|
4050
|
+
title,
|
|
3628
4051
|
children: [
|
|
3629
4052
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3630
4053
|
className: "cc-usageStatLabel",
|
|
@@ -3757,8 +4180,13 @@ window.__ModuleLoader__.load({
|
|
|
3757
4180
|
children: [
|
|
3758
4181
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
3759
4182
|
label: t("usageRequests"),
|
|
3760
|
-
value:
|
|
3761
|
-
sub: `${t("usageCompleted")} ${report.usage.completedCount} / ${t("usageFailed")} ${report.usage.failedCount}
|
|
4183
|
+
value: formatCompactCount(report.usage.totalCount),
|
|
4184
|
+
sub: `${t("usageCompleted")} ${formatCompactCount(report.usage.completedCount)} / ${t("usageFailed")} ${formatCompactCount(report.usage.failedCount)}`,
|
|
4185
|
+
title: compactCountTitle([
|
|
4186
|
+
[t("usageRequests"), report.usage.totalCount],
|
|
4187
|
+
[t("usageCompleted"), report.usage.completedCount],
|
|
4188
|
+
[t("usageFailed"), report.usage.failedCount]
|
|
4189
|
+
])
|
|
3762
4190
|
}),
|
|
3763
4191
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
|
|
3764
4192
|
label: t("usageSuccessRate"),
|
|
@@ -4025,31 +4453,201 @@ window.__ModuleLoader__.load({
|
|
|
4025
4453
|
});
|
|
4026
4454
|
}
|
|
4027
4455
|
/**
|
|
4028
|
-
* Models: the plan filter and the allowlist sit together because both
|
|
4029
|
-
* what the model picker lists (the allowlist applies after the plan
|
|
4456
|
+
* Models & context: the plan filter and the allowlist sit together because both
|
|
4457
|
+
* decide what the model picker lists (the allowlist applies after the plan
|
|
4458
|
+
* filter), and the context-length fields follow them because they are the same
|
|
4459
|
+
* subject seen from the other side — the window each listed model advertises.
|
|
4460
|
+
* Those fields carry no heading or divider of their own: they are rows of this
|
|
4461
|
+
* group, not a section inside it.
|
|
4030
4462
|
*/
|
|
4031
|
-
function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear }) {
|
|
4463
|
+
function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear, onOverrides }) {
|
|
4032
4464
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
|
|
4033
4465
|
title: t("modelsTitle"),
|
|
4034
4466
|
divided: true,
|
|
4035
|
-
children: [
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4467
|
+
children: [
|
|
4468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
|
|
4469
|
+
id: "cc-filter-models-by-plan",
|
|
4470
|
+
label: t("filterModelsByPlan"),
|
|
4471
|
+
hint: t("filterModelsByPlanHint"),
|
|
4472
|
+
state: state.filterModelsByPlan,
|
|
4473
|
+
disabled,
|
|
4474
|
+
defaultChecked: true,
|
|
4475
|
+
onEdit: (text) => onEdit("filterModelsByPlan", text),
|
|
4476
|
+
onReset: () => onReset("filterModelsByPlan"),
|
|
4477
|
+
t
|
|
4478
|
+
}),
|
|
4479
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VisibleModelsRow, {
|
|
4480
|
+
t,
|
|
4481
|
+
state,
|
|
4482
|
+
disabled,
|
|
4483
|
+
onSelect,
|
|
4484
|
+
onClear
|
|
4485
|
+
}),
|
|
4486
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextCard, {
|
|
4487
|
+
state,
|
|
4488
|
+
disabled,
|
|
4489
|
+
t,
|
|
4490
|
+
onEdit,
|
|
4491
|
+
onReset,
|
|
4492
|
+
onOverrides
|
|
4493
|
+
})
|
|
4494
|
+
]
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
/**
|
|
4498
|
+
* Context length (issue #86): the global custom window plus one row per model
|
|
4499
|
+
* override, both staged into the same save as the other form fields. Rendered
|
|
4500
|
+
* as plain rows of the Models & context group — no heading, no divider of its
|
|
4501
|
+
* own: the setting belongs to the models that group already governs, and on the
|
|
4502
|
+
* real page a section inside the group read as visual noise. The advertised
|
|
4503
|
+
* window is what the harness meters context against and when it compacts, so a
|
|
4504
|
+
* lowered value compacts earlier and a raised one follows the model's real
|
|
4505
|
+
* capacity when the catalog under-reports it. Precedence: a per-model row, then
|
|
4506
|
+
* the global value, then the catalog.
|
|
4507
|
+
*
|
|
4508
|
+
* The rows live as raw text drafts: blank drops the override on save, an
|
|
4509
|
+
* unparseable one blocks the save with its own bounds copy, and the value
|
|
4510
|
+
* accepts the compact shorthand the picker prints (`256K`, `1M`). The model
|
|
4511
|
+
* select offers only allowlisted models the list does not already carry —
|
|
4512
|
+
* one override per model, matching what the Models card lets through — and
|
|
4513
|
+
* keeps a stored id the catalog no longer carries (or the whitelist just
|
|
4514
|
+
* dropped) as itself so an existing override never silently disappears.
|
|
4515
|
+
*/
|
|
4516
|
+
function ContextCard({ state, disabled, t, onEdit, onReset, onOverrides }) {
|
|
4517
|
+
const rows = state.contextWindowOverrides;
|
|
4518
|
+
const used = new Set(rows.map((row) => row.model));
|
|
4519
|
+
const available = allowlistedModels(state.catalogModels, state.visibleModels, state.visibleModelsAll).filter((model) => !used.has(model.id));
|
|
4520
|
+
const asMap = () => Object.fromEntries(rows.map((row) => [row.model, row.text]));
|
|
4521
|
+
const setText = (model, text) => onOverrides({
|
|
4522
|
+
...asMap(),
|
|
4523
|
+
[model]: text
|
|
4524
|
+
});
|
|
4525
|
+
const setModel = (from, to) => {
|
|
4526
|
+
if (to === "" || to === from || used.has(to)) return;
|
|
4527
|
+
const next = {};
|
|
4528
|
+
for (const row of rows) next[row.model === from ? to : row.model] = row.text;
|
|
4529
|
+
onOverrides(next);
|
|
4530
|
+
};
|
|
4531
|
+
const removeRow = (model) => {
|
|
4532
|
+
const next = asMap();
|
|
4533
|
+
delete next[model];
|
|
4534
|
+
onOverrides(next);
|
|
4535
|
+
};
|
|
4536
|
+
const addRow = () => {
|
|
4537
|
+
const model = available[0];
|
|
4538
|
+
if (model === void 0) return;
|
|
4539
|
+
onOverrides({
|
|
4540
|
+
...asMap(),
|
|
4541
|
+
[model.id]: ""
|
|
4542
|
+
});
|
|
4543
|
+
};
|
|
4544
|
+
const labelOf = (model) => {
|
|
4545
|
+
const entry = state.catalogModels.find((candidate) => candidate.id === model);
|
|
4546
|
+
return entry === void 0 ? `${model} · ${t("modelStale")}` : `${entry.name} (${model})`;
|
|
4547
|
+
};
|
|
4548
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
4550
|
+
id: "cc-context-window",
|
|
4551
|
+
label: t("contextWindow"),
|
|
4552
|
+
hint: t("contextWindowHint"),
|
|
4553
|
+
state: state.contextWindow,
|
|
4040
4554
|
disabled,
|
|
4041
|
-
|
|
4042
|
-
onEdit: (text) => onEdit("
|
|
4043
|
-
onReset: () => onReset("
|
|
4555
|
+
context: true,
|
|
4556
|
+
onEdit: (text) => onEdit("contextWindow", text),
|
|
4557
|
+
onReset: () => onReset("contextWindow"),
|
|
4044
4558
|
t
|
|
4045
|
-
}),
|
|
4046
|
-
|
|
4047
|
-
|
|
4559
|
+
}),
|
|
4560
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
4561
|
+
id: "cc-max-output-tokens",
|
|
4562
|
+
label: t("maxOutputTokens"),
|
|
4563
|
+
hint: t("maxOutputTokensHint"),
|
|
4564
|
+
state: state.maxOutputTokens,
|
|
4048
4565
|
disabled,
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4566
|
+
numeric: true,
|
|
4567
|
+
output: true,
|
|
4568
|
+
onEdit: (text) => onEdit("maxOutputTokens", text),
|
|
4569
|
+
onReset: () => onReset("maxOutputTokens"),
|
|
4570
|
+
t
|
|
4571
|
+
}),
|
|
4572
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
|
|
4573
|
+
title: t("contextOverridesTitle"),
|
|
4574
|
+
description: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("contextOverridesHint") }),
|
|
4575
|
+
control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4576
|
+
type: "button",
|
|
4577
|
+
className: "cc-linkButton",
|
|
4578
|
+
disabled: disabled || available.length === 0,
|
|
4579
|
+
onClick: addRow,
|
|
4580
|
+
children: t("contextOverrideAdd")
|
|
4581
|
+
})
|
|
4582
|
+
}),
|
|
4583
|
+
state.catalogFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4584
|
+
className: "cc-groupDesc",
|
|
4585
|
+
children: t("contextOverrideNoCatalog")
|
|
4586
|
+
}) : null,
|
|
4587
|
+
rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4588
|
+
className: "cc-contextHead",
|
|
4589
|
+
children: [
|
|
4590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4591
|
+
className: "cc-contextHeadModel",
|
|
4592
|
+
children: t("contextOverrideModel")
|
|
4593
|
+
}),
|
|
4594
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4595
|
+
className: "cc-contextHeadValue",
|
|
4596
|
+
children: t("contextOverrideValue")
|
|
4597
|
+
}),
|
|
4598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4599
|
+
className: "cc-contextHeadGap",
|
|
4600
|
+
"aria-hidden": "true"
|
|
4601
|
+
})
|
|
4602
|
+
]
|
|
4603
|
+
}) : null,
|
|
4604
|
+
rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4605
|
+
className: "cc-contextItem",
|
|
4606
|
+
children: [
|
|
4607
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4608
|
+
className: "cc-contextSelect",
|
|
4609
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
4610
|
+
className: "cc-input cc-contextModel",
|
|
4611
|
+
value: row.model,
|
|
4612
|
+
"aria-label": t("contextOverrideModel"),
|
|
4613
|
+
disabled,
|
|
4614
|
+
onChange: (event) => setModel(row.model, event.target.value),
|
|
4615
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4616
|
+
value: row.model,
|
|
4617
|
+
children: labelOf(row.model)
|
|
4618
|
+
}), available.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4619
|
+
value: model.id,
|
|
4620
|
+
children: labelOf(model.id)
|
|
4621
|
+
}, model.id))]
|
|
4622
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4623
|
+
className: "cc-contextCaret",
|
|
4624
|
+
"aria-hidden": "true"
|
|
4625
|
+
})]
|
|
4626
|
+
}),
|
|
4627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4628
|
+
className: `cc-input cc-contextValue${row.invalid ? " cc-inputInvalid" : ""}`,
|
|
4629
|
+
type: "text",
|
|
4630
|
+
"aria-label": t("contextOverrideValue"),
|
|
4631
|
+
"aria-invalid": row.invalid || void 0,
|
|
4632
|
+
value: row.text,
|
|
4633
|
+
disabled,
|
|
4634
|
+
onChange: (event) => setText(row.model, event.target.value)
|
|
4635
|
+
}),
|
|
4636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4637
|
+
type: "button",
|
|
4638
|
+
className: "cc-iconButton",
|
|
4639
|
+
"aria-label": t("contextOverrideRemove"),
|
|
4640
|
+
disabled,
|
|
4641
|
+
onClick: () => removeRow(row.model),
|
|
4642
|
+
children: "×"
|
|
4643
|
+
}),
|
|
4644
|
+
row.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4645
|
+
className: "cc-rowError cc-contextError",
|
|
4646
|
+
children: invalidCopy(row.invalidReason, t, "context")
|
|
4647
|
+
}) : null
|
|
4648
|
+
]
|
|
4649
|
+
}, row.model))
|
|
4650
|
+
] });
|
|
4053
4651
|
}
|
|
4054
4652
|
/**
|
|
4055
4653
|
* Privacy & security: the opt-in that changes where data goes. ZDR restricts
|
|
@@ -4786,23 +5384,23 @@ window.__ModuleLoader__.load({
|
|
|
4786
5384
|
active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4787
5385
|
className: "cc-hint",
|
|
4788
5386
|
role: "status",
|
|
4789
|
-
children: active.
|
|
5387
|
+
children: t(active.messageKey)
|
|
4790
5388
|
}) : null,
|
|
4791
5389
|
active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4792
5390
|
className: "cc-hint",
|
|
4793
|
-
children: "
|
|
5391
|
+
children: t("accountAddLinkHint")
|
|
4794
5392
|
}) : null,
|
|
4795
5393
|
active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
4796
5394
|
className: "cc-linkButton",
|
|
4797
5395
|
href: active.authUrl,
|
|
4798
5396
|
target: "_blank",
|
|
4799
5397
|
rel: "noreferrer",
|
|
4800
|
-
children: "
|
|
5398
|
+
children: t("accountAddOpenLink")
|
|
4801
5399
|
}) : null,
|
|
4802
5400
|
snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4803
5401
|
className: "cc-loginError",
|
|
4804
5402
|
role: "status",
|
|
4805
|
-
children: snapshot.error
|
|
5403
|
+
children: t(snapshot.error)
|
|
4806
5404
|
}) : null,
|
|
4807
5405
|
active?.phase === "naming" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4808
5406
|
className: "cc-inlineActions",
|
|
@@ -4812,14 +5410,14 @@ window.__ModuleLoader__.load({
|
|
|
4812
5410
|
size: "sm",
|
|
4813
5411
|
disabled,
|
|
4814
5412
|
onClick: () => void enrollment.name(active.suggestedName || label || fallbackName),
|
|
4815
|
-
children: "
|
|
5413
|
+
children: t("accountNameSaveRetry")
|
|
4816
5414
|
}),
|
|
4817
5415
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4818
5416
|
variant: "ghost",
|
|
4819
5417
|
size: "sm",
|
|
4820
5418
|
disabled,
|
|
4821
5419
|
onClick: () => void enrollment.name(),
|
|
4822
|
-
children: "
|
|
5420
|
+
children: t("accountNameAcceptExisting")
|
|
4823
5421
|
}),
|
|
4824
5422
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4825
5423
|
variant: "ghost",
|
|
@@ -4870,7 +5468,7 @@ window.__ModuleLoader__.load({
|
|
|
4870
5468
|
variant: "ghost",
|
|
4871
5469
|
size: "sm",
|
|
4872
5470
|
onClick: close,
|
|
4873
|
-
children: "
|
|
5471
|
+
children: t("accountAddCancel")
|
|
4874
5472
|
}) : null
|
|
4875
5473
|
]
|
|
4876
5474
|
});
|
|
@@ -4957,7 +5555,7 @@ window.__ModuleLoader__.load({
|
|
|
4957
5555
|
!adding && snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4958
5556
|
className: "cc-rowError",
|
|
4959
5557
|
role: "status",
|
|
4960
|
-
children: snapshot.error
|
|
5558
|
+
children: t(snapshot.error)
|
|
4961
5559
|
}) : null,
|
|
4962
5560
|
state.pendingCredentialCleanup.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4963
5561
|
className: "cc-rowError",
|
|
@@ -4978,34 +5576,34 @@ window.__ModuleLoader__.load({
|
|
|
4978
5576
|
children: [
|
|
4979
5577
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
4980
5578
|
record.ref,
|
|
4981
|
-
"
|
|
4982
|
-
record.
|
|
5579
|
+
t("accountRefSeparator"),
|
|
5580
|
+
t(record.messageKey)
|
|
4983
5581
|
] }),
|
|
4984
5582
|
record.phase === "naming" && record.suggestedName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4985
5583
|
variant: "ghost",
|
|
4986
5584
|
size: "sm",
|
|
4987
5585
|
disabled,
|
|
4988
5586
|
onClick: () => void enrollment.recover(record, record.suggestedName),
|
|
4989
|
-
children: "
|
|
5587
|
+
children: t("accountNameSaveRetry")
|
|
4990
5588
|
}) : null,
|
|
4991
5589
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
4992
5590
|
variant: "ghost",
|
|
4993
5591
|
size: "sm",
|
|
4994
5592
|
disabled,
|
|
4995
5593
|
onClick: () => void enrollment.recover(record),
|
|
4996
|
-
children: record.phase === "naming" ? "
|
|
5594
|
+
children: record.phase === "naming" ? t("accountNameAcceptExisting") : t("accountEnrollmentCleanupRetry")
|
|
4997
5595
|
})
|
|
4998
5596
|
]
|
|
4999
5597
|
}, record.id)),
|
|
5000
5598
|
snapshot.active?.phase === "cleanup-needed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5001
5599
|
className: "cc-rowError",
|
|
5002
5600
|
role: "status",
|
|
5003
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: snapshot.active.
|
|
5601
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.active.messageKey) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5004
5602
|
variant: "ghost",
|
|
5005
5603
|
size: "sm",
|
|
5006
5604
|
disabled,
|
|
5007
5605
|
onClick: () => void enrollment.recover(snapshot.active),
|
|
5008
|
-
children: "
|
|
5606
|
+
children: t("accountEnrollmentCleanupRetry")
|
|
5009
5607
|
})]
|
|
5010
5608
|
}) : null,
|
|
5011
5609
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -5279,7 +5877,8 @@ window.__ModuleLoader__.load({
|
|
|
5279
5877
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsCard, {
|
|
5280
5878
|
...form,
|
|
5281
5879
|
onSelect: props.editVisibleModels,
|
|
5282
|
-
onClear: props.clearVisibleModels
|
|
5880
|
+
onClear: props.clearVisibleModels,
|
|
5881
|
+
onOverrides: props.editContextWindowOverrides
|
|
5283
5882
|
}),
|
|
5284
5883
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrivacyCard, { ...form }),
|
|
5285
5884
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegrationsCard, { ...form }),
|
|
@@ -5608,11 +6207,22 @@ window.__ModuleLoader__.load({
|
|
|
5608
6207
|
analysisNoShare: "无占比",
|
|
5609
6208
|
analysisShareHint: "占全部已知成本小计",
|
|
5610
6209
|
analysisSimulationHeading: "相同工作量价格试算",
|
|
5611
|
-
analysisSimulationHint: "
|
|
6210
|
+
analysisSimulationHint: "差额以所选历史全部请求的当前价估算为基准;负数表示成本更低,仅比较完整覆盖的相同工作量。",
|
|
5612
6211
|
analysisSimulationCost: "试算成本",
|
|
5613
6212
|
analysisCoverage: "计价覆盖",
|
|
5614
6213
|
analysisUsd: "美元",
|
|
5615
6214
|
analysisTokenUnit: "词元",
|
|
6215
|
+
analysisScenarioMonthly: "折算 30 天",
|
|
6216
|
+
analysisDifference: "较历史基准",
|
|
6217
|
+
analysisBaseline: "历史成本基准",
|
|
6218
|
+
analysisZeroBaseline: "基准为零,不计算比例",
|
|
6219
|
+
analysisNeedRange: "请选择日期范围",
|
|
6220
|
+
analysisShortRange: "观察范围不足一天",
|
|
6221
|
+
analysisIncomplete: "历史读取不完整",
|
|
6222
|
+
analysisNoRequests: "没有请求记录",
|
|
6223
|
+
analysisScenarioExcluded: "存在超上下文请求",
|
|
6224
|
+
analysisScenarioUnpriced: "存在未知用量或价格",
|
|
6225
|
+
analysisBaselineUnpriced: "历史基准未全部计价",
|
|
5616
6226
|
analysisCost: "已知成本 · 美元",
|
|
5617
6227
|
analysisRequests: "模型请求",
|
|
5618
6228
|
analysisSessionCount: "会话数",
|
|
@@ -5650,6 +6260,7 @@ window.__ModuleLoader__.load({
|
|
|
5650
6260
|
analysisAssumption: "沿用原请求的词元与时段,对比不同模型的价格。换模型后的生成长度、缓存命中、重试和质量可能变化;不会调用模型或自动推荐。",
|
|
5651
6261
|
analysisContext: "上下文词元",
|
|
5652
6262
|
analysisCapabilities: "模型能力",
|
|
6263
|
+
analysisInputType: "输入类型",
|
|
5653
6264
|
analysisEfforts: "推理力度",
|
|
5654
6265
|
analysisExcluded: "超出上下文",
|
|
5655
6266
|
analysisUnknownPrice: "未知用量或价格",
|
|
@@ -5701,11 +6312,22 @@ window.__ModuleLoader__.load({
|
|
|
5701
6312
|
analysisNoShare: ANALYTICS_COPY_ZH.analysisNoShare,
|
|
5702
6313
|
analysisShareHint: "Share of the known cost subtotal",
|
|
5703
6314
|
analysisSimulationHeading: "Price simulation for the same workload",
|
|
5704
|
-
analysisSimulationHint: "
|
|
6315
|
+
analysisSimulationHint: "Differences use all selected recorded requests repriced at current rates as the baseline. Negative means lower cost; only complete coverage is compared.",
|
|
5705
6316
|
analysisSimulationCost: "Simulated cost",
|
|
5706
6317
|
analysisCoverage: "Priced coverage",
|
|
5707
6318
|
analysisUsd: "USD",
|
|
5708
6319
|
analysisTokenUnit: "tokens",
|
|
6320
|
+
analysisScenarioMonthly: "30-day estimate",
|
|
6321
|
+
analysisDifference: "vs. recorded baseline",
|
|
6322
|
+
analysisBaseline: "Recorded cost baseline",
|
|
6323
|
+
analysisZeroBaseline: ANALYTICS_COPY_ZH.analysisZeroBaseline,
|
|
6324
|
+
analysisNeedRange: ANALYTICS_COPY_ZH.analysisNeedRange,
|
|
6325
|
+
analysisShortRange: ANALYTICS_COPY_ZH.analysisShortRange,
|
|
6326
|
+
analysisIncomplete: ANALYTICS_COPY_ZH.analysisIncomplete,
|
|
6327
|
+
analysisNoRequests: ANALYTICS_COPY_ZH.analysisNoRequests,
|
|
6328
|
+
analysisScenarioExcluded: ANALYTICS_COPY_ZH.analysisScenarioExcluded,
|
|
6329
|
+
analysisScenarioUnpriced: ANALYTICS_COPY_ZH.analysisScenarioUnpriced,
|
|
6330
|
+
analysisBaselineUnpriced: ANALYTICS_COPY_ZH.analysisBaselineUnpriced,
|
|
5709
6331
|
analysisCost: "Known cost · USD",
|
|
5710
6332
|
analysisRequests: "Model requests",
|
|
5711
6333
|
analysisSessionCount: "Sessions",
|
|
@@ -5743,6 +6365,7 @@ window.__ModuleLoader__.load({
|
|
|
5743
6365
|
analysisAssumption: "Compares model prices using original tokens and request times. Output length, cache hits, retries and quality may change with another model. Makes no model calls or automatic recommendations.",
|
|
5744
6366
|
analysisContext: "Context tokens",
|
|
5745
6367
|
analysisCapabilities: "Model capabilities",
|
|
6368
|
+
analysisInputType: "Input type",
|
|
5746
6369
|
analysisEfforts: "Reasoning efforts",
|
|
5747
6370
|
analysisExcluded: "Over context limit",
|
|
5748
6371
|
analysisUnknownPrice: "Unknown usage or price",
|
|
@@ -5855,7 +6478,7 @@ window.__ModuleLoader__.load({
|
|
|
5855
6478
|
usage: "Usage",
|
|
5856
6479
|
completed: "completed",
|
|
5857
6480
|
usageBillingPeriod: "Official billing-period usage",
|
|
5858
|
-
usagePeriodUnknown: "
|
|
6481
|
+
usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
|
|
5859
6482
|
monthly: "Monthly",
|
|
5860
6483
|
monthlyLimit: "Monthly limit",
|
|
5861
6484
|
monthlyUsed: "Monthly used",
|
|
@@ -6010,8 +6633,13 @@ window.__ModuleLoader__.load({
|
|
|
6010
6633
|
if (usage !== void 0) {
|
|
6011
6634
|
stats.push({
|
|
6012
6635
|
label: "requests",
|
|
6013
|
-
value:
|
|
6014
|
-
sub: `${usage.completedCount} ${t("completed")} / ${usage.failedCount} ${t("failed")}
|
|
6636
|
+
value: formatCompactCount(usage.totalCount),
|
|
6637
|
+
sub: `${formatCompactCount(usage.completedCount)} ${t("completed")} / ${formatCompactCount(usage.failedCount)} ${t("failed")}`,
|
|
6638
|
+
title: compactCountTitle([
|
|
6639
|
+
[t("requests"), usage.totalCount],
|
|
6640
|
+
[t("completed"), usage.completedCount],
|
|
6641
|
+
[t("failed"), usage.failedCount]
|
|
6642
|
+
])
|
|
6015
6643
|
});
|
|
6016
6644
|
stats.push({
|
|
6017
6645
|
label: "successRate",
|
|
@@ -6337,6 +6965,44 @@ window.__ModuleLoader__.load({
|
|
|
6337
6965
|
}]
|
|
6338
6966
|
};
|
|
6339
6967
|
//#endregion
|
|
6968
|
+
//#region src/client/analytics-comparison.ts
|
|
6969
|
+
function coverageReason(report, scenario) {
|
|
6970
|
+
if (report.status !== "ready") return "unavailable";
|
|
6971
|
+
if (report.truncated || report.unreadableSessions > 0 || report.skippedSessions > 0) return "partial";
|
|
6972
|
+
if (report.totals.requests === 0) return "empty";
|
|
6973
|
+
if (scenario.excludedRequests > 0) return "excluded";
|
|
6974
|
+
if (scenario.unknownRequests > 0 || scenario.pricedRequests !== report.totals.requests) return "coverage";
|
|
6975
|
+
}
|
|
6976
|
+
function compareScenario(report, scenario) {
|
|
6977
|
+
const reason = coverageReason(report, scenario);
|
|
6978
|
+
let monthly;
|
|
6979
|
+
if (reason) monthly = {
|
|
6980
|
+
value: null,
|
|
6981
|
+
reason
|
|
6982
|
+
};
|
|
6983
|
+
else if (report.query.mode !== "range") monthly = {
|
|
6984
|
+
value: null,
|
|
6985
|
+
reason: "recent"
|
|
6986
|
+
};
|
|
6987
|
+
else {
|
|
6988
|
+
const elapsed = Math.min(report.query.to, report.generatedAt) - report.query.from;
|
|
6989
|
+
monthly = elapsed >= 864e5 ? { value: scenario.cost * (2592e6 / elapsed) } : {
|
|
6990
|
+
value: null,
|
|
6991
|
+
reason: "short-range"
|
|
6992
|
+
};
|
|
6993
|
+
}
|
|
6994
|
+
const comparisonReason = reason ?? (report.totals.pricedRequests !== report.totals.requests ? "baseline" : void 0);
|
|
6995
|
+
const difference = comparisonReason ? {
|
|
6996
|
+
value: null,
|
|
6997
|
+
reason: comparisonReason
|
|
6998
|
+
} : { value: scenario.cost - report.totals.cost };
|
|
6999
|
+
return {
|
|
7000
|
+
monthly,
|
|
7001
|
+
difference,
|
|
7002
|
+
differencePercent: difference.value !== null && report.totals.cost > 0 ? difference.value / report.totals.cost * 100 : null
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
7005
|
+
//#endregion
|
|
6340
7006
|
//#region src/client/analytics-view.tsx
|
|
6341
7007
|
/** 只展示数字快照;筛选提交后才查询,页签、展开与模型筛选不触发历史读取。 */
|
|
6342
7008
|
function localDate(time) {
|
|
@@ -6587,7 +7253,19 @@ window.__ModuleLoader__.load({
|
|
|
6587
7253
|
})]
|
|
6588
7254
|
});
|
|
6589
7255
|
}
|
|
6590
|
-
|
|
7256
|
+
const estimateReasonKeys = {
|
|
7257
|
+
unavailable: "analysisUnavailable",
|
|
7258
|
+
empty: "analysisNoRequests",
|
|
7259
|
+
partial: "analysisIncomplete",
|
|
7260
|
+
excluded: "analysisScenarioExcluded",
|
|
7261
|
+
coverage: "analysisScenarioUnpriced",
|
|
7262
|
+
baseline: "analysisBaselineUnpriced",
|
|
7263
|
+
recent: "analysisNeedRange",
|
|
7264
|
+
"short-range": "analysisShortRange"
|
|
7265
|
+
};
|
|
7266
|
+
const signedMoney = (n) => `${n > 0 ? "+" : n < 0 ? "-" : ""}${money(Math.abs(n))}`;
|
|
7267
|
+
function ScenarioRow({ m, report, text }) {
|
|
7268
|
+
const { monthly, difference, differencePercent } = compareScenario(report, m);
|
|
6591
7269
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
6592
7270
|
className: "ccp-analysisModelRow",
|
|
6593
7271
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
@@ -6606,17 +7284,17 @@ window.__ModuleLoader__.load({
|
|
|
6606
7284
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
6607
7285
|
label: text("analysisSimulationCost"),
|
|
6608
7286
|
value: m.pricedRequests ? money(m.cost) : text("analysisMissing"),
|
|
6609
|
-
sub: text("
|
|
7287
|
+
sub: `${text("analysisCoverage")} ${m.pricedRequests}/${report.totals.requests}`
|
|
6610
7288
|
}),
|
|
6611
7289
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
6612
|
-
label: text("
|
|
6613
|
-
value:
|
|
6614
|
-
sub:
|
|
7290
|
+
label: text("analysisScenarioMonthly"),
|
|
7291
|
+
value: monthly.value === null ? text("analysisMissing") : money(monthly.value),
|
|
7292
|
+
sub: monthly.value === null ? text(estimateReasonKeys[monthly.reason]) : text("analysisUsd")
|
|
6615
7293
|
}),
|
|
6616
7294
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
|
|
6617
|
-
label: text("
|
|
6618
|
-
value:
|
|
6619
|
-
sub: text(
|
|
7295
|
+
label: text("analysisDifference"),
|
|
7296
|
+
value: difference.value === null ? text("analysisMissing") : signedMoney(difference.value),
|
|
7297
|
+
sub: difference.value === null ? text(estimateReasonKeys[difference.reason]) : differencePercent === null ? text("analysisZeroBaseline") : `${differencePercent > 0 ? "+" : ""}${differencePercent.toFixed(1)}%`
|
|
6620
7298
|
}),
|
|
6621
7299
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {})
|
|
6622
7300
|
]
|
|
@@ -6633,7 +7311,11 @@ window.__ModuleLoader__.load({
|
|
|
6633
7311
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailGroup, {
|
|
6634
7312
|
title: "analysisCapabilities",
|
|
6635
7313
|
text,
|
|
6636
|
-
values: [
|
|
7314
|
+
values: [
|
|
7315
|
+
["analysisContext", m.contextWindow === null ? text("analysisMissing") : `${digits(m.contextWindow)} ${text("analysisTokenUnit")}`],
|
|
7316
|
+
["analysisInputType", text(m.images ? "analysisImages" : "analysisTextOnly")],
|
|
7317
|
+
["analysisEfforts", m.efforts.length ? m.efforts.map((e) => effortName(e, text)).join(", ") : text("analysisNoEfforts")]
|
|
7318
|
+
]
|
|
6637
7319
|
})]
|
|
6638
7320
|
})]
|
|
6639
7321
|
});
|
|
@@ -6935,6 +7617,16 @@ window.__ModuleLoader__.load({
|
|
|
6935
7617
|
className: "ccp-analysisAssumption",
|
|
6936
7618
|
children: text("analysisAssumption")
|
|
6937
7619
|
}),
|
|
7620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7621
|
+
className: "ccp-analysisNote ccp-analysisBaseline",
|
|
7622
|
+
children: [
|
|
7623
|
+
text("analysisBaseline"),
|
|
7624
|
+
": ",
|
|
7625
|
+
m.pricedRequests === m.requests && m.requests > 0 && !report.truncated && !report.unreadableSessions && !report.skippedSessions ? money(m.cost) : text("analysisMissing"),
|
|
7626
|
+
" · ",
|
|
7627
|
+
text("analysisMonthlyHint")
|
|
7628
|
+
]
|
|
7629
|
+
}),
|
|
6938
7630
|
report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6939
7631
|
className: "ccp-analysisMessage ccp-analysisWarning",
|
|
6940
7632
|
role: "status",
|
|
@@ -6976,12 +7668,13 @@ window.__ModuleLoader__.load({
|
|
|
6976
7668
|
children: [
|
|
6977
7669
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisModel") }),
|
|
6978
7670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisSimulationCost") }),
|
|
6979
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("
|
|
6980
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("
|
|
7671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisScenarioMonthly") }),
|
|
7672
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text("analysisDifference") }),
|
|
6981
7673
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})
|
|
6982
7674
|
]
|
|
6983
7675
|
}), scenarios.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScenarioRow, {
|
|
6984
7676
|
m: model,
|
|
7677
|
+
report,
|
|
6985
7678
|
text
|
|
6986
7679
|
}, model.id))]
|
|
6987
7680
|
}) : !report.catalogUnavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -7125,9 +7818,10 @@ window.__ModuleLoader__.load({
|
|
|
7125
7818
|
});
|
|
7126
7819
|
}
|
|
7127
7820
|
/** A labelled figure. */
|
|
7128
|
-
function Tile({ label, value, sub }) {
|
|
7821
|
+
function Tile({ label, value, sub, title }) {
|
|
7129
7822
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7130
7823
|
className: "ccp-tile",
|
|
7824
|
+
title,
|
|
7131
7825
|
children: [
|
|
7132
7826
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7133
7827
|
className: "ccp-tileLabel",
|
|
@@ -7299,7 +7993,8 @@ window.__ModuleLoader__.load({
|
|
|
7299
7993
|
children: account.stats.map((stat) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tile, {
|
|
7300
7994
|
label: text(stat.label),
|
|
7301
7995
|
value: stat.value,
|
|
7302
|
-
sub: stat.sub
|
|
7996
|
+
sub: stat.sub,
|
|
7997
|
+
title: stat.title
|
|
7303
7998
|
}, stat.label))
|
|
7304
7999
|
})
|
|
7305
8000
|
]
|
|
@@ -8430,7 +9125,8 @@ window.__ModuleLoader__.load({
|
|
|
8430
9125
|
* 12/18 label-tertiary, the control on the right. The hairline rhythm is
|
|
8431
9126
|
* section-level: a 0.5px border-l2 rule separates the GROUPS (see
|
|
8432
9127
|
* `cc-groupDivided`), never the rows inside one, so a group's rows read as one
|
|
8433
|
-
* list. Selectors and buttons are
|
|
9128
|
+
* list. Selectors and buttons are
|
|
9129
|
+
* capsules (36px / 28px compact), inputs are
|
|
8434
9130
|
* 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
|
|
8435
9131
|
* border-l4 outline with a 16px radius, and a nested panel is a
|
|
8436
9132
|
* bg-module-platform fill with a 12px radius. Errors use state-error-primary.
|
|
@@ -8537,6 +9233,25 @@ window.__ModuleLoader__.load({
|
|
|
8537
9233
|
.cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
8538
9234
|
.cc-iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
8539
9235
|
.cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
|
|
9236
|
+
/* Per-model context-length override rows (issue #86): the error wraps onto
|
|
9237
|
+
its own line beneath the select/input/remove trio. The three cells form a
|
|
9238
|
+
fixed column contract — flexible model, 112px value, 28px remove — and the
|
|
9239
|
+
cc-contextHead labels repeat the first two (plus the remove cell's width
|
|
9240
|
+
as a spacer) so they sit exactly over their columns. */
|
|
9241
|
+
.cc-contextItem{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:8px 0}
|
|
9242
|
+
.cc-contextHead{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
9243
|
+
.cc-contextHeadModel{flex:1;min-width:200px}
|
|
9244
|
+
.cc-contextHeadValue{flex:none;width:112px}
|
|
9245
|
+
.cc-contextHeadGap{flex:none;width:28px}
|
|
9246
|
+
.cc-contextSelect{position:relative;display:block;flex:1;min-width:200px}
|
|
9247
|
+
/* appearance:none swaps the platform arrow for the cc-contextCaret, shaped
|
|
9248
|
+
like cc-selectorCaret so every dropdown in the plugin opens with the same
|
|
9249
|
+
hand-drawn chevron. */
|
|
9250
|
+
.cc-contextModel{box-sizing:border-box;appearance:none;-webkit-appearance:none;width:100%;padding-inline-end:26px;text-overflow:ellipsis;cursor:pointer}
|
|
9251
|
+
.cc-contextCaret{position:absolute;inset-inline-end:12px;top:50%;width:6px;height:6px;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);transform:translateY(-50%) rotate(45deg);pointer-events:none}
|
|
9252
|
+
.cc-contextSelect:has(.cc-contextModel:disabled) .cc-contextCaret{opacity:.6}
|
|
9253
|
+
.cc-contextValue{flex:none;width:112px}
|
|
9254
|
+
.cc-contextError{flex-basis:100%}
|
|
8540
9255
|
/* Three dots from one element: the dot plus two box-shadow copies. */
|
|
8541
9256
|
.cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
|
|
8542
9257
|
.cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
|
|
@@ -8848,7 +9563,6 @@ window.__ModuleLoader__.load({
|
|
|
8848
9563
|
.ccp-analysisTableHead{padding-top:11px;padding-bottom:11px;background:var(--dsw-alias-interactive-bg-hover);border-bottom:.5px solid var(--dsw-alias-border-l2);font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
8849
9564
|
.ccp-analysisTableHead > span:not(:first-child){text-align:right}
|
|
8850
9565
|
.ccp-analysisScenarioHead,.ccp-analysisScenarioSummary{grid-template-columns:minmax(190px,2fr) minmax(118px,1.1fr) minmax(155px,1.35fr) minmax(142px,1.15fr) 16px}
|
|
8851
|
-
.ccp-analysisScenarioHead > span:nth-child(3),.ccp-analysisScenarioHead > span:nth-child(4){text-align:left}
|
|
8852
9566
|
.ccp-analysisModelSummary,.ccp-analysisScenarioSummary{cursor:pointer;list-style:none;min-height:60px}
|
|
8853
9567
|
.ccp-analysisModelSummary::-webkit-details-marker,.ccp-analysisScenarioSummary::-webkit-details-marker,.ccp-analysisDisclosure::-webkit-details-marker{display:none}
|
|
8854
9568
|
.ccp-analysisModelSummary:hover,.ccp-analysisScenarioSummary:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -8856,7 +9570,6 @@ window.__ModuleLoader__.load({
|
|
|
8856
9570
|
.ccp-analysisModelName{font-size:14px;line-height:22px;font-weight:500;overflow-wrap:anywhere}
|
|
8857
9571
|
.ccp-analysisModelCount{margin-top:3px}
|
|
8858
9572
|
.ccp-analysisModelSummary > .ccp-analysisMetric,.ccp-analysisScenarioSummary > .ccp-analysisMetric{align-items:flex-end}
|
|
8859
|
-
.ccp-analysisScenarioSummary > .ccp-analysisMetric:nth-child(3),.ccp-analysisScenarioSummary > .ccp-analysisMetric:nth-child(4){align-items:flex-start}
|
|
8860
9573
|
/* 宽表使用集中表头,但保留每个指标的无障碍标签;窄面板恢复可见标签。 */
|
|
8861
9574
|
.ccp-analysisModelSummary .ccp-analysisMetricLabel,.ccp-analysisScenarioSummary .ccp-analysisMetricLabel,.ccp-analysisSearchLabel{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
8862
9575
|
.ccp-analysisShare{display:flex;align-items:center;gap:8px;width:100%;max-width:148px;margin-top:3px}
|
|
@@ -8880,6 +9593,7 @@ window.__ModuleLoader__.load({
|
|
|
8880
9593
|
.ccp-analysisMonthlyValue{font-size:14px;line-height:22px;font-weight:500;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}
|
|
8881
9594
|
.ccp-analysisMonthly .ccp-analysisNote{width:100%;margin:0}
|
|
8882
9595
|
.ccp-analysisAssumption{font-size:14px;line-height:22px;color:var(--dsw-alias-label-tertiary);margin:0 0 18px;max-width:76ch}
|
|
9596
|
+
.ccp-analysisBaseline{margin:0 0 16px}
|
|
8883
9597
|
.ccp-analysisSearch{flex:0 1 270px}
|
|
8884
9598
|
.ccp-analysisSearchShell{position:relative;display:block}
|
|
8885
9599
|
.ccp-analysisSearchShell > .ccp-analysisIcon{position:absolute;top:10px;left:11px;color:var(--dsw-alias-label-tertiary);pointer-events:none}
|
|
@@ -9265,7 +9979,8 @@ window.__ModuleLoader__.load({
|
|
|
9265
9979
|
setActiveAccount: (id) => controller.setActiveAccount(id).then(refreshUsageOn),
|
|
9266
9980
|
setAccountModels: (target, ids) => controller.setAccountModels(target, ids),
|
|
9267
9981
|
editVisibleModels: (ids) => controller.editVisibleModels(ids),
|
|
9268
|
-
clearVisibleModels: () => controller.clearVisibleModels()
|
|
9982
|
+
clearVisibleModels: () => controller.clearVisibleModels(),
|
|
9983
|
+
editContextWindowOverrides: (next) => controller.editContextWindowOverrides(next)
|
|
9269
9984
|
});
|
|
9270
9985
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
9271
9986
|
name: "settings.section",
|