dsh-connect-workbuddy 5.6.3 → 5.8.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 +74 -0
- package/README.en.md +4 -1
- package/README.md +4 -1
- package/docs/WINDOWS.md +6 -0
- package/lib/bin.js +58 -3
- package/lib/client.js +800 -142
- package/lib/{host-heartbeat-r_icwyw3.js → host-heartbeat-BiVFMXGE.js} +626 -27
- package/lib/index.d.ts +297 -4
- package/lib/index.js +110 -26
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -773,6 +773,120 @@ window.__ModuleLoader__.load({
|
|
|
773
773
|
throw hostError instanceof Error ? hostError : new WorkBuddySettingsWriteError("cliBridge", "neither the Host save endpoint nor the settings scope persisted the value");
|
|
774
774
|
}
|
|
775
775
|
/**
|
|
776
|
+
* Read the CodeBuddy-CLI route's PINNED model out of a settings snapshot.
|
|
777
|
+
*
|
|
778
|
+
* `''` is a real answer here, not a missing one: it means "nothing is pinned,
|
|
779
|
+
* the CLI runs on its own default". A non-string is a settings-file typo, and
|
|
780
|
+
* it reads as `''` for the same reason `cliBridgeEnabledOf` reads consent only
|
|
781
|
+
* out of an explicit `true` — garbage must never masquerade as a pinned model.
|
|
782
|
+
*/
|
|
783
|
+
function cliBridgeModelOf(configured) {
|
|
784
|
+
const bridge = configured?.cliBridge;
|
|
785
|
+
const model = typeof bridge === "object" && bridge !== null ? bridge.model : void 0;
|
|
786
|
+
return typeof model === "string" ? model : "";
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Read the CodeBuddy-CLI route's PINNED reasoning effort out of a settings
|
|
790
|
+
* snapshot, with the same `''`-means-CLI-default rule as
|
|
791
|
+
* {@link cliBridgeModelOf}.
|
|
792
|
+
*/
|
|
793
|
+
function cliBridgeEffortOf(configured) {
|
|
794
|
+
const bridge = configured?.cliBridge;
|
|
795
|
+
const effort = typeof bridge === "object" && bridge !== null ? bridge.effort : void 0;
|
|
796
|
+
return typeof effort === "string" ? effort : "";
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Write ONE key of the GLOBAL CodeBuddy-CLI block, then confirm it landed.
|
|
800
|
+
*
|
|
801
|
+
* Same two-writer order as {@link writeCliBridgeEnabled}: the plugin's Host
|
|
802
|
+
* endpoint first (its merge is authoritative, so the tuned caps sitting next
|
|
803
|
+
* to the key survive a one-key patch), the settings scope second as fallback
|
|
804
|
+
* plus verification.
|
|
805
|
+
*
|
|
806
|
+
* The Host-side merge is one level deep, so a one-key patch can never drop the
|
|
807
|
+
* block's sibling keys. The empty string must still be a delivered VALUE, not
|
|
808
|
+
* "absent": it is the picker's "back to the CLI's own default" answer, and
|
|
809
|
+
* posting `{ model: '' }` is what makes that answer COMMITTED rather than
|
|
810
|
+
* merely forgotten.
|
|
811
|
+
*
|
|
812
|
+
* @param scope - the bound settings scope for this plugin's namespace.
|
|
813
|
+
* @param patch - the one key to write, e.g. `{ model: 'glm-5.3' }`.
|
|
814
|
+
* @param verify - confirms the patch landed on the committed value.
|
|
815
|
+
* @throws {WorkBuddySettingsWriteError} when neither writer persists the value.
|
|
816
|
+
*/
|
|
817
|
+
async function writeCliBridgePatch(scope, patch, verify) {
|
|
818
|
+
let hostError;
|
|
819
|
+
try {
|
|
820
|
+
const authoritative = await saveViaHostEndpoint("cliBridge", void 0, patch);
|
|
821
|
+
if (authoritative !== void 0) {
|
|
822
|
+
try {
|
|
823
|
+
await scope.set("cliBridge", authoritative);
|
|
824
|
+
} catch {}
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
} catch (error) {
|
|
828
|
+
hostError = error;
|
|
829
|
+
}
|
|
830
|
+
const stored = unwrapVolatileDeep(scope.getSnapshot().value?.cliBridge);
|
|
831
|
+
const next = {
|
|
832
|
+
...typeof stored === "object" && stored !== null ? stored : {},
|
|
833
|
+
...patch
|
|
834
|
+
};
|
|
835
|
+
let delivered = false;
|
|
836
|
+
try {
|
|
837
|
+
delivered = await scope.set("cliBridge", next) !== false;
|
|
838
|
+
} catch {
|
|
839
|
+
delivered = false;
|
|
840
|
+
}
|
|
841
|
+
if (delivered && verify(scope.getSnapshot().value)) return;
|
|
842
|
+
throw hostError instanceof Error ? hostError : new WorkBuddySettingsWriteError("cliBridge", "neither the Host save endpoint nor the settings scope persisted the value");
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Pin the model the local CodeBuddy CLI runs on — or `''` for "keep the CLI's
|
|
846
|
+
* own default" — through {@link writeCliBridgePatch}.
|
|
847
|
+
*
|
|
848
|
+
* The picker offers the active tab's live catalog, but any string is a legal
|
|
849
|
+
* write: a model the card's catalog does not list may still be valid for the
|
|
850
|
+
* CLI, and hand-configured values must not be refused by this writer.
|
|
851
|
+
*/
|
|
852
|
+
async function writeCliBridgeModel(scope, model) {
|
|
853
|
+
await writeCliBridgePatch(scope, { model }, (configured) => cliBridgeModelOf(configured) === model);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Pin the reasoning effort the local CodeBuddy CLI runs on — or `''` for
|
|
857
|
+
* "keep the CLI's own default" — through {@link writeCliBridgePatch}.
|
|
858
|
+
*/
|
|
859
|
+
async function writeCliBridgeEffort(scope, effort) {
|
|
860
|
+
await writeCliBridgePatch(scope, { effort }, (configured) => cliBridgeEffortOf(configured) === effort);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Read the credential-region order the CodeBuddy-CLI route walks out of a
|
|
864
|
+
* settings snapshot.
|
|
865
|
+
*
|
|
866
|
+
* `''` means "nothing is pinned": the route then falls back to the CLI default
|
|
867
|
+
* order (CN first, `cn,global`), so the picker must show that default rather
|
|
868
|
+
* than an empty answer. A non-string reads as `''` — the same garbage rule as
|
|
869
|
+
* {@link cliBridgeModelOf}.
|
|
870
|
+
*/
|
|
871
|
+
function cliBridgeRegionOrderOf(configured) {
|
|
872
|
+
const bridge = configured?.cliBridge;
|
|
873
|
+
const regionOrder = typeof bridge === "object" && bridge !== null ? bridge.regionOrder : void 0;
|
|
874
|
+
return typeof regionOrder === "string" ? regionOrder : "";
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Pin the credential-region order the local CodeBuddy CLI walks — a
|
|
878
|
+
* comma-separated region list such as `'cn,global'` — through
|
|
879
|
+
* {@link writeCliBridgePatch}.
|
|
880
|
+
*
|
|
881
|
+
* Any string is a legal write: the order is matched against the regions the
|
|
882
|
+
* Host actually built, and an unknown region name simply never matches, so a
|
|
883
|
+
* hand-configured typo degrades to the CLI default order instead of breaking
|
|
884
|
+
* the route.
|
|
885
|
+
*/
|
|
886
|
+
async function writeCliBridgeRegionOrder(scope, regionOrder) {
|
|
887
|
+
await writeCliBridgePatch(scope, { regionOrder }, (configured) => cliBridgeRegionOrderOf(configured) === regionOrder);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
776
890
|
* Write one volatile field, then confirm the value actually landed.
|
|
777
891
|
*
|
|
778
892
|
* ORDER MATTERS, and getting it wrong DELETES DATA. The two writers do not have
|
|
@@ -1089,6 +1203,7 @@ window.__ModuleLoader__.load({
|
|
|
1089
1203
|
case "model-busy": return t("row.poolExcludedModelBusy");
|
|
1090
1204
|
case "out-of-credit": return t("row.poolExcludedOutOfCredit");
|
|
1091
1205
|
case "credential-rejected": return t("row.poolExcludedRejected");
|
|
1206
|
+
case "account-restricted": return t("row.poolExcludedAccountRestricted");
|
|
1092
1207
|
case "not-found": return t("row.probeNotFound");
|
|
1093
1208
|
case "unavailable": return t("row.probeUnavailable");
|
|
1094
1209
|
case "failed": return t("row.probeFailed");
|
|
@@ -1160,6 +1275,7 @@ window.__ModuleLoader__.load({
|
|
|
1160
1275
|
switch (probe.outcome) {
|
|
1161
1276
|
case "ok": return;
|
|
1162
1277
|
case "credential-rejected": return "credential-rejected";
|
|
1278
|
+
case "account-restricted": return "account-restricted";
|
|
1163
1279
|
case "not-found": return "not-found";
|
|
1164
1280
|
case "unavailable": return retryDue(probe, nowMs) ? void 0 : "unavailable";
|
|
1165
1281
|
case "failed": return retryDue(probe, nowMs) ? void 0 : "failed";
|
|
@@ -1328,6 +1444,7 @@ window.__ModuleLoader__.load({
|
|
|
1328
1444
|
case "model-busy": return t("row.poolExcludedModelBusy");
|
|
1329
1445
|
case "out-of-credit": return t("row.poolExcludedOutOfCredit");
|
|
1330
1446
|
case "credential-rejected": return t("row.probeCredentialRejected");
|
|
1447
|
+
case "account-restricted": return t("row.probeAccountRestricted");
|
|
1331
1448
|
case "channel-rejected": return t("row.probeChannelRejected");
|
|
1332
1449
|
case "not-found": return t("row.probeNotFound");
|
|
1333
1450
|
case "unavailable": return t("row.probeUnavailable");
|
|
@@ -1427,6 +1544,7 @@ window.__ModuleLoader__.load({
|
|
|
1427
1544
|
case "model-busy": return t("row.poolExcludedModelBusy");
|
|
1428
1545
|
case "out-of-credit": return t("row.poolExcludedOutOfCredit");
|
|
1429
1546
|
case "credential-rejected": return t("row.poolExcludedRejected");
|
|
1547
|
+
case "account-restricted": return t("row.poolExcludedAccountRestricted");
|
|
1430
1548
|
case "not-found": return t("row.probeNotFound");
|
|
1431
1549
|
case "unavailable": return t("row.probeUnavailable");
|
|
1432
1550
|
case "failed": return t("row.probeFailed");
|
|
@@ -1519,6 +1637,7 @@ window.__ModuleLoader__.load({
|
|
|
1519
1637
|
"out-of-credit",
|
|
1520
1638
|
"credential-rejected",
|
|
1521
1639
|
"policy-rejected",
|
|
1640
|
+
"account-restricted",
|
|
1522
1641
|
"channel-rejected",
|
|
1523
1642
|
"unavailable",
|
|
1524
1643
|
"not-found",
|
|
@@ -1764,7 +1883,8 @@ window.__ModuleLoader__.load({
|
|
|
1764
1883
|
const SELF_EXPLANATORY = /* @__PURE__ */ new Set([
|
|
1765
1884
|
"rate-limited",
|
|
1766
1885
|
"model-busy",
|
|
1767
|
-
"out-of-credit"
|
|
1886
|
+
"out-of-credit",
|
|
1887
|
+
"account-restricted"
|
|
1768
1888
|
]);
|
|
1769
1889
|
/**
|
|
1770
1890
|
* Longest inline reason kept.
|
|
@@ -2097,6 +2217,7 @@ window.__ModuleLoader__.load({
|
|
|
2097
2217
|
if (running !== void 0) return running;
|
|
2098
2218
|
return sorted.find((promo) => promo.badge.display !== "activeOnly" && promoWithinAbsoluteWindow(promo, nowMs) && promo.schedule?.dailyUnusable !== true);
|
|
2099
2219
|
}
|
|
2220
|
+
"WorkBuddy/5.8.3 WorkBuddy/5.8.3 CLI/2.161.0".match(/WorkBuddy\/([^\s]+)/u)?.[1];
|
|
2100
2221
|
//#endregion
|
|
2101
2222
|
//#region src/probe.ts
|
|
2102
2223
|
/**
|
|
@@ -2410,6 +2531,7 @@ window.__ModuleLoader__.load({
|
|
|
2410
2531
|
const [draft, setDraft] = (0, react.useState)(null);
|
|
2411
2532
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
2412
2533
|
const [saveError, setSaveError] = (0, react.useState)(void 0);
|
|
2534
|
+
const [staleNotice, setStaleNotice] = (0, react.useState)(void 0);
|
|
2413
2535
|
const [busy, setBusy] = (0, react.useState)(void 0);
|
|
2414
2536
|
const [actionError, setActionError] = (0, react.useState)(void 0);
|
|
2415
2537
|
const [log, setLog] = (0, react.useState)(() => logStore.get(region) ?? []);
|
|
@@ -2559,28 +2681,25 @@ window.__ModuleLoader__.load({
|
|
|
2559
2681
|
setDraft(null);
|
|
2560
2682
|
setDraftRegion(void 0);
|
|
2561
2683
|
setSaveError(void 0);
|
|
2684
|
+
setStaleNotice(void 0);
|
|
2562
2685
|
}, []);
|
|
2563
2686
|
/** Save the pool preferences through the plugin's VERIFIED write path. */
|
|
2564
2687
|
const save = (0, react.useCallback)(async () => {
|
|
2565
2688
|
if (settingsScope === void 0 || !dirty || draft === null) return;
|
|
2566
2689
|
setSaving(true);
|
|
2567
2690
|
setSaveError(void 0);
|
|
2568
|
-
|
|
2691
|
+
setStaleNotice(void 0);
|
|
2569
2692
|
try {
|
|
2570
2693
|
await writePoolPreferences(settingsScope, region, draft);
|
|
2571
|
-
if (
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
}
|
|
2575
|
-
committed = true;
|
|
2694
|
+
if (mounted.current) setSaving(false);
|
|
2695
|
+
discard();
|
|
2696
|
+
if (await onSaved?.() === false && mounted.current) setStaleNotice(t("row.poolSavedStaleRefresh"));
|
|
2576
2697
|
} catch (error) {
|
|
2577
2698
|
if (mounted.current) {
|
|
2578
2699
|
const reason = error instanceof Error ? error.message : t("row.requestFailed");
|
|
2579
2700
|
setSaveError(isFileContentionWriteError(error) ? `${reason}${t("row.saveContentionHint")}` : reason);
|
|
2701
|
+
setSaving(false);
|
|
2580
2702
|
}
|
|
2581
|
-
} finally {
|
|
2582
|
-
if (committed) discard();
|
|
2583
|
-
if (mounted.current) setSaving(false);
|
|
2584
2703
|
}
|
|
2585
2704
|
}, [
|
|
2586
2705
|
dirty,
|
|
@@ -3152,6 +3271,7 @@ window.__ModuleLoader__.load({
|
|
|
3152
3271
|
saving,
|
|
3153
3272
|
siblingBusy: siblingBusy === true,
|
|
3154
3273
|
saveError,
|
|
3274
|
+
staleNotice,
|
|
3155
3275
|
settingsScope,
|
|
3156
3276
|
onEdit: editDraft,
|
|
3157
3277
|
onSave: () => {
|
|
@@ -3224,7 +3344,7 @@ window.__ModuleLoader__.load({
|
|
|
3224
3344
|
"aria-label": t("row.poolMoveDownAria", { accountName: name }),
|
|
3225
3345
|
disabled: moveDownDisabled,
|
|
3226
3346
|
onClick: onMoveDown
|
|
3227
|
-
}, "↓")) : null) : null, account.current ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-current-tag" }, t("row.poolCurrentBadge")) : null, checked ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-not-member" }, t("row.poolNotMember")), excluded === void 0 ? probe === void 0 ? null : (0, react.createElement)("span", null, t("row.poolTestedAt", { at: formatShortClock(probe.atMs) })) : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-excluded" }, excluded))), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-credits" }, account.credits === void 0 ? (0, react.createElement)("span", null, "—") : (0, react.createElement)("b", null, formatNumber$1(account.credits)), account.expiringSoon !== void 0 && account.expiringSoon > 0 ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon" }, t("row.poolCreditSoon", { count: formatNumber$1(account.expiringSoon) })) : account.nearestExpiryMs === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon-plain" }, t("row.poolCreditNearest", { at: formatShortClock(account.nearestExpiryMs) })), account.benefits === void 0 || account.benefits.length === 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-benefits" }, account.benefits.map((benefit, index) => (0, react.createElement)("span", {
|
|
3347
|
+
}, "↓")) : null) : null, account.current ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-current-tag" }, t("row.poolCurrentBadge")) : null, checked ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-not-member" }, t("row.poolNotMember")), excluded === void 0 ? probe === void 0 ? null : (0, react.createElement)("span", null, t("row.poolTestedAt", { at: formatShortClock(probe.atMs) })) : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-excluded" }, excluded))), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-credits" }, account.credits === void 0 ? (0, react.createElement)("span", null, "—") : (0, react.createElement)("b", null, formatNumber$1(account.credits)), account.expiringSoon !== void 0 && account.expiringSoon > 0 ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon" }, t("row.poolCreditSoon", { count: formatNumber$1(account.expiringSoon) })) : account.nearestExpiryMs === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-soon-plain" }, t("row.poolCreditNearest", { at: formatShortClock(account.nearestExpiryMs) })), account.enterprise === true || account.benefits === void 0 || account.benefits.length === 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-benefits" }, account.benefits.map((benefit, index) => (0, react.createElement)("span", {
|
|
3228
3348
|
key: benefit.kind,
|
|
3229
3349
|
className: benefit.urgent ? "dsm-workbuddy-pool-benefit dsm-workbuddy-pool-benefit-urgent" : "dsm-workbuddy-pool-benefit"
|
|
3230
3350
|
}, index === 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-sep" }, "·"), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-benefit-tag" }, benefit.urgent ? benefit.daysLeft === void 0 || benefit.daysLeft === 0 ? t("row.poolBenefitDueToday", {
|
|
@@ -3250,7 +3370,7 @@ window.__ModuleLoader__.load({
|
|
|
3250
3370
|
}, (0, react.createElement)("span", null, claimBusy && rowBusyAction === "claim" ? t("row.poolBenefitClaiming") : t("row.poolBenefitClaim"))))))), (0, react.createElement)("span", {
|
|
3251
3371
|
className: "dsm-workbuddy-pool-probe",
|
|
3252
3372
|
...probe?.message === void 0 ? {} : { title: probe.message }
|
|
3253
|
-
}, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-main" }, probeLine), probeLabel === void 0 && age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-prov" }, probeLabel === void 0 ? null : (0, react.createElement)("span", { className: probeLabel.className }, probeLabel.text), probeLabel === void 0 || age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-sep" }, "·"), age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-age" }, age), probe !== void 0 && probe.modelId !== poolModelId ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-model" }, probe.modelId === void 0 ? t("row.poolProbeModelUnknown") : t("row.poolProbeModel", { model: probe.modelId })) : null)), checkinSupported ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checkin" }, account.checkedInToday === void 0 ? (0, react.createElement)("span", {
|
|
3373
|
+
}, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-main" }, probeLine), probeLabel === void 0 && age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-probe-prov" }, probeLabel === void 0 ? null : (0, react.createElement)("span", { className: probeLabel.className }, probeLabel.text), probeLabel === void 0 || age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-sep" }, "·"), age === void 0 ? null : (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-age" }, age), probe !== void 0 && probe.modelId !== poolModelId ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-prov-model" }, probe.modelId === void 0 ? t("row.poolProbeModelUnknown") : t("row.poolProbeModel", { model: probe.modelId })) : null)), checkinSupported ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checkin" }, account.enterprise === true ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-unknown" }, t("row.poolCheckinEnterprise")) : account.checkedInToday === void 0 ? (0, react.createElement)("span", {
|
|
3254
3374
|
className: "dsm-workbuddy-pool-unknown",
|
|
3255
3375
|
title: t("row.poolNeverTested")
|
|
3256
3376
|
}, t("row.poolCheckinUnknown")) : account.checkedInToday ? (0, react.createElement)("span", { className: "dsm-workbuddy-pool-checked" }, t("row.poolCheckedIn")) : t("row.poolNotCheckedIn")) : null, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-row-actions" }, (0, react.createElement)("button", {
|
|
@@ -3270,7 +3390,7 @@ window.__ModuleLoader__.load({
|
|
|
3270
3390
|
}
|
|
3271
3391
|
/** The preference editor plus its save/discard actions. */
|
|
3272
3392
|
function renderSettings(input) {
|
|
3273
|
-
const { t, pool, active, dirty, saving, siblingBusy, saveError, settingsScope, onEdit, onSave, onDiscard, targetProvenance, accountTools, orderRules } = input;
|
|
3393
|
+
const { t, pool, active, dirty, saving, siblingBusy, saveError, staleNotice, settingsScope, onEdit, onSave, onDiscard, targetProvenance, accountTools, orderRules } = input;
|
|
3274
3394
|
const canEdit = settingsScope !== void 0;
|
|
3275
3395
|
return (0, react.createElement)("div", { className: "dsm-workbuddy-pool-settings-wrap" }, accountTools === void 0 ? null : (0, react.createElement)("div", { className: "dsm-workbuddy-pool-tools" }, accountTools), (0, react.createElement)("div", { className: "dsm-workbuddy-pool-card-head" }, (0, react.createElement)("h4", { className: "dsm-workbuddy-pool-card-title" }, t("row.poolSettingsTitle"))), orderRules, (0, react.createElement)("p", { className: "dsm-workbuddy-pool-note" }, t("row.poolSettingsHint")), (0, react.createElement)("div", { className: "dsm-workbuddy-pool-settings" }, (0, react.createElement)("div", { className: "dsm-workbuddy-pool-set" }, (0, react.createElement)("span", { className: "dsm-workbuddy-pool-set-copy" }, (0, react.createElement)("b", null, t("row.poolTargetLabel")), (0, react.createElement)("span", null, targetProvenance === null ? null : `${targetProvenance} `, t("row.poolTargetHint"))), (0, react.createElement)("span", { className: "dsm-workbuddy-pool-set-ctl" }, (0, react.createElement)("select", {
|
|
3276
3396
|
className: "dsm-workbuddy-pool-select",
|
|
@@ -3325,7 +3445,10 @@ window.__ModuleLoader__.load({
|
|
|
3325
3445
|
}, saving ? t("row.poolSaving") : t("row.poolSaved")))), saveError === void 0 ? null : (0, react.createElement)("p", {
|
|
3326
3446
|
className: "dsm-workbuddy-pool-error",
|
|
3327
3447
|
role: "alert"
|
|
3328
|
-
}, t("row.poolSaveFailed", { message: saveError })))
|
|
3448
|
+
}, t("row.poolSaveFailed", { message: saveError })), staleNotice === void 0 ? null : (0, react.createElement)("p", {
|
|
3449
|
+
className: "dsm-workbuddy-pool-warn",
|
|
3450
|
+
role: "status"
|
|
3451
|
+
}, staleNotice));
|
|
3329
3452
|
}
|
|
3330
3453
|
/** The activity log. */
|
|
3331
3454
|
function renderLog(input) {
|
|
@@ -4770,6 +4893,17 @@ window.__ModuleLoader__.load({
|
|
|
4770
4893
|
.dsm-workbuddy-credit-position:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
4771
4894
|
.dsm-workbuddy-credit-position:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
4772
4895
|
.dsm-workbuddy-credit-position:disabled{opacity:.5;cursor:default}
|
|
4896
|
+
/* The quick-adjust pins of the CLI route (model, effort): two native selects
|
|
4897
|
+
side by side, the same hairline-bordered rule as the seat picker — content-
|
|
4898
|
+
sized, no chevron build, no own fill. The row rides the switch row's
|
|
4899
|
+
dsm-workbuddy-pref-indent indent, because it answers a question the switch
|
|
4900
|
+
above it poses. */
|
|
4901
|
+
.dsm-workbuddy-cli-bridge-pickers{flex-wrap:wrap}
|
|
4902
|
+
.dsm-workbuddy-cli-bridge-pick{display:flex;align-items:center;gap:6px;min-width:0}
|
|
4903
|
+
.dsm-workbuddy-cli-bridge-select{flex:none;max-width:none;margin-top:0;padding:3px 8px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:7px;background:var(--dsw-alias-bg-layer-3,#2a2c33);color:var(--dsw-alias-label-primary,#e6e6e6);font:inherit;font-size:12px;line-height:16px;cursor:pointer}
|
|
4904
|
+
.dsm-workbuddy-cli-bridge-select:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
4905
|
+
.dsm-workbuddy-cli-bridge-select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
4906
|
+
.dsm-workbuddy-cli-bridge-select:disabled{opacity:.5;cursor:default}
|
|
4773
4907
|
/* A card whose provider is switched OFF dims its LABEL AND DOT. The checkbox
|
|
4774
4908
|
* beside them must stay at full strength, because that checkbox is the only
|
|
4775
4909
|
* way to switch the region back on — dimming it would grey out the control
|
|
@@ -4875,6 +5009,11 @@ window.__ModuleLoader__.load({
|
|
|
4875
5009
|
label to push the row's own controls out of view. */
|
|
4876
5010
|
.dsm-workbuddy-model-label-promo{max-width:6em;overflow:hidden}
|
|
4877
5011
|
.dsm-workbuddy-model-label-promo-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
5012
|
+
/* The "本机首次见到" marker: the SAME quiet ink as the composer pill's
|
|
5013
|
+
"· N 新模型" segment (label-dimmed), but no chip border and one step smaller,
|
|
5014
|
+
so it reads as a side note beside the promotion chip rather than a second
|
|
5015
|
+
badge competing with the model name. */
|
|
5016
|
+
.dsm-workbuddy-model-label-new{flex:none;display:inline-flex;align-items:center;padding:0 5px;border-radius:999px;font-size:10px;line-height:16px;font-weight:600;white-space:nowrap;color:var(--dsw-alias-label-dimmed,rgba(127,127,127,.6))}
|
|
4878
5017
|
.dsm-workbuddy-busy{flex:none;display:inline-flex;align-items:center;block-size:12px;color:var(--dsm-workbuddy-busy-color);vertical-align:-2px}
|
|
4879
5018
|
.dsm-workbuddy-busy svg{display:block;block-size:12px;inline-size:auto}
|
|
4880
5019
|
.dsm-workbuddy-model-promo{flex:none;display:inline-flex;align-items:center;padding:0 7px;border-radius:999px;font-size:10px;line-height:16px;font-weight:600;white-space:nowrap;color:var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe));background:rgba(255,255,255,.08);background:color-mix(in srgb,var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe)) 16%,transparent);border:1px solid transparent;border-color:color-mix(in srgb,var(--dsm-workbuddy-promo-color,var(--dsw-alias-brand-primary,#5686fe)) 40%,transparent)}
|
|
@@ -5412,6 +5551,21 @@ window.__ModuleLoader__.load({
|
|
|
5412
5551
|
}
|
|
5413
5552
|
.dsm-workbuddy-composer-credits-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
5414
5553
|
.dsm-workbuddy-composer-credits-trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
5554
|
+
/* The new-model marker ("· 2 新模型") is its OWN button, a sibling of the credit
|
|
5555
|
+
trigger inside the same pill — not text bolted into it, so the row is not one
|
|
5556
|
+
giant click target and a screen reader can take a user straight to "which
|
|
5557
|
+
models". MUTED on purpose, and muted FURTHER than the figure: the credits are
|
|
5558
|
+
what this pill exists to show, and the marker only qualifies them, so
|
|
5559
|
+
"2 新模型" must never out-shout "143 积分". label-dimmed sits a step below the
|
|
5560
|
+
trigger's own label-tertiary for exactly that reason. */
|
|
5561
|
+
.dsm-workbuddy-composer-credits-new{
|
|
5562
|
+
appearance:none;display:inline-flex;align-items:center;align-self:stretch;
|
|
5563
|
+
padding:2px 6px;border:0;border-radius:6px;background:transparent;
|
|
5564
|
+
font-size:12px;line-height:18px;color:var(--dsw-alias-label-dimmed,rgba(127,127,127,.6));
|
|
5565
|
+
white-space:nowrap;cursor:pointer;font-variant-numeric:tabular-nums
|
|
5566
|
+
}
|
|
5567
|
+
.dsm-workbuddy-composer-credits-new:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
5568
|
+
.dsm-workbuddy-composer-credits-new:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
5415
5569
|
/* The panel is portaled to document.body and positioned from the trigger, so it
|
|
5416
5570
|
MUST be fixed — otherwise the left/top it measures are ignored and it lands in
|
|
5417
5571
|
document flow. All material comes from shell tokens so it follows the theme. */
|
|
@@ -5542,6 +5696,31 @@ window.__ModuleLoader__.load({
|
|
|
5542
5696
|
.dsm-workbuddy-composer-panel-dot-ok{background:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
5543
5697
|
.dsm-workbuddy-composer-panel-dot-error{background:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
5544
5698
|
.dsm-workbuddy-composer-panel-dot-unknown{background:var(--dsw-alias-label-dimmed,rgba(127,127,127,.5))}
|
|
5699
|
+
/* The new-model readout BELOW the foot hairline: the label ("本机首次见到的模型")
|
|
5700
|
+
in the footnote tone and the names in the body tone, so it reads as a note and
|
|
5701
|
+
never as a fourth column of the table above. Sits AFTER the foot because the
|
|
5702
|
+
foot's hairline is the one separator between the table and this block — two
|
|
5703
|
+
hairlines back to back would look like a rendering fault.
|
|
5704
|
+
The names WRAP rather than ellipsise (overflow-wrap:anywhere): Chinese has no
|
|
5705
|
+
spaces to break at, and a hard cut carries no signal that more exists. */
|
|
5706
|
+
.dsm-workbuddy-composer-panel-new{margin-top:9px}
|
|
5707
|
+
.dsm-workbuddy-composer-panel-new-row{margin:0;font-size:11px;line-height:16px}
|
|
5708
|
+
/* The row is FOCUSED (not just scrolled) when the "· N 新模型" button opened the
|
|
5709
|
+
panel, so a screen reader lands on it; the component keeps it out of the tab
|
|
5710
|
+
order with tabIndex={-1}, hence outline:none here. The highlight is a
|
|
5711
|
+
barely-there tint: the row has to be findable, not loud — this is a footnote,
|
|
5712
|
+
not a notification, and the credits table above is the thing to read first. */
|
|
5713
|
+
.dsm-workbuddy-composer-panel-new-row-revealed{outline:none;border-radius:4px;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14))}
|
|
5714
|
+
.dsm-workbuddy-composer-panel-new-label{color:var(--dsw-alias-label-tertiary,GrayText)}
|
|
5715
|
+
.dsm-workbuddy-composer-panel-new-list{color:var(--dsw-alias-label-secondary,#444);overflow-wrap:anywhere}
|
|
5716
|
+
/* One class for BOTH footnotes: the definition of "new", and the version note
|
|
5717
|
+
when the on-disk history was written by a newer build. They are the same
|
|
5718
|
+
KIND of sentence — "what a word means here" — and two classes would let the
|
|
5719
|
+
two drift apart in size or tone. */
|
|
5720
|
+
.dsm-workbuddy-composer-panel-note{
|
|
5721
|
+
margin:5px 0 0;font-size:11px;line-height:16px;
|
|
5722
|
+
color:var(--dsw-alias-label-tertiary,GrayText);overflow-wrap:anywhere;
|
|
5723
|
+
}
|
|
5545
5724
|
.dsm-workbuddy-composer-panel-foot{
|
|
5546
5725
|
display:flex;justify-content:space-between;gap:8px;
|
|
5547
5726
|
margin-top:9px;padding-top:7px;
|
|
@@ -5729,6 +5908,37 @@ window.__ModuleLoader__.load({
|
|
|
5729
5908
|
*/
|
|
5730
5909
|
const POLL_INTERVAL_MS = 6e4;
|
|
5731
5910
|
/**
|
|
5911
|
+
* The reasoning tiers the local CodeBuddy CLI accepts for its `--effort` flag.
|
|
5912
|
+
*
|
|
5913
|
+
* Kept in sync with `EFFORT_LEVELS` in `src/cli-bridge/tool.ts` by hand: that
|
|
5914
|
+
* module imports the node runtime, so the client bundle cannot share the
|
|
5915
|
+
* constant — a drifted list here would offer a tier the CLI rejects.
|
|
5916
|
+
*/
|
|
5917
|
+
const CLI_BRIDGE_EFFORT_LEVELS = [
|
|
5918
|
+
"minimal",
|
|
5919
|
+
"low",
|
|
5920
|
+
"medium",
|
|
5921
|
+
"high",
|
|
5922
|
+
"xhigh",
|
|
5923
|
+
"max"
|
|
5924
|
+
];
|
|
5925
|
+
/**
|
|
5926
|
+
* The credential-region orders the quick-adjust picker offers for the CLI
|
|
5927
|
+
* route's `regionOrder`.
|
|
5928
|
+
*
|
|
5929
|
+
* The Host builds exactly two regions — `cn` and `global` (see
|
|
5930
|
+
* `REGION_KEYS` in `src/index.ts`), so every meaningful order of that pair
|
|
5931
|
+
* fits in these four presets: a preference, a reversed preference, or a
|
|
5932
|
+
* single-region lock. `''` (uncommitted) renders as the CLI's own default
|
|
5933
|
+
* order, CN first.
|
|
5934
|
+
*/
|
|
5935
|
+
const CLI_BRIDGE_REGION_ORDERS = [
|
|
5936
|
+
"cn,global",
|
|
5937
|
+
"global,cn",
|
|
5938
|
+
"cn",
|
|
5939
|
+
"global"
|
|
5940
|
+
];
|
|
5941
|
+
/**
|
|
5732
5942
|
* Tooltip for one model's image checkbox. The box is pre-checked only for
|
|
5733
5943
|
* models the vendored vendor table documents as natively multimodal, so the
|
|
5734
5944
|
* two other outcomes need to say WHY they are off — otherwise a documented
|
|
@@ -5972,6 +6182,10 @@ window.__ModuleLoader__.load({
|
|
|
5972
6182
|
text: t("row.probeCredentialRejected"),
|
|
5973
6183
|
tone: "bad"
|
|
5974
6184
|
};
|
|
6185
|
+
case "account-restricted": return {
|
|
6186
|
+
text: t("row.probeAccountRestricted"),
|
|
6187
|
+
tone: "bad"
|
|
6188
|
+
};
|
|
5975
6189
|
case "policy-rejected": return {
|
|
5976
6190
|
text: t("row.probePolicyRejected"),
|
|
5977
6191
|
tone: "bad"
|
|
@@ -6065,6 +6279,22 @@ window.__ModuleLoader__.load({
|
|
|
6065
6279
|
const [cliBridgeOn, setCliBridgeOn] = (0, react.useState)(void 0);
|
|
6066
6280
|
const [cliBridgeBusy, setCliBridgeBusy] = (0, react.useState)(false);
|
|
6067
6281
|
/**
|
|
6282
|
+
* The quick-adjust pins of the CLI route (model, effort, region order) made
|
|
6283
|
+
* by THIS card instance.
|
|
6284
|
+
*
|
|
6285
|
+
* Each is `undefined` until the user changes its picker here: the value
|
|
6286
|
+
* then renders from the Host's answer (the authoritative mirror) and falls
|
|
6287
|
+
* back to the settings snapshot only when the Host cannot say — the same
|
|
6288
|
+
* rule as `cliBridgeOn`. A failed write reverts to `undefined`, so the
|
|
6289
|
+
* picker shows the COMMITTED value again instead of one that never landed.
|
|
6290
|
+
*/
|
|
6291
|
+
const [cliBridgeModel, setCliBridgeModel] = (0, react.useState)(void 0);
|
|
6292
|
+
const [cliBridgeModelBusy, setCliBridgeModelBusy] = (0, react.useState)(false);
|
|
6293
|
+
const [cliBridgeEffort, setCliBridgeEffort] = (0, react.useState)(void 0);
|
|
6294
|
+
const [cliBridgeEffortBusy, setCliBridgeEffortBusy] = (0, react.useState)(false);
|
|
6295
|
+
const [cliBridgeRegionOrder, setCliBridgeRegionOrder] = (0, react.useState)(void 0);
|
|
6296
|
+
const [cliBridgeRegionOrderBusy, setCliBridgeRegionOrderBusy] = (0, react.useState)(false);
|
|
6297
|
+
/**
|
|
6068
6298
|
* Probe results, keyed by region then model id.
|
|
6069
6299
|
*
|
|
6070
6300
|
* Kept per region for the same reason drafts are: the two tabs are separate
|
|
@@ -6525,6 +6755,99 @@ window.__ModuleLoader__.load({
|
|
|
6525
6755
|
if (mounted.current) setCliBridgeBusy(false);
|
|
6526
6756
|
}
|
|
6527
6757
|
};
|
|
6758
|
+
/**
|
|
6759
|
+
* The pinned model / effort of the CLI route, rendered the same way the
|
|
6760
|
+
* switch is: this card's own successful write first, then the Host's answer
|
|
6761
|
+
* (the authoritative mirror), then the settings snapshot. `''` is a real
|
|
6762
|
+
* value on every level: "nothing is pinned, the CLI runs on its own
|
|
6763
|
+
* default".
|
|
6764
|
+
*/
|
|
6765
|
+
const cliBridgeModelValue = () => {
|
|
6766
|
+
if (cliBridgeModel !== void 0) return cliBridgeModel;
|
|
6767
|
+
const fromHost = statusByRegion[activeRegion]?.cliBridgeModel;
|
|
6768
|
+
if (typeof fromHost === "string") return fromHost;
|
|
6769
|
+
return cliBridgeModelOf(settingsScope?.getSnapshot().value);
|
|
6770
|
+
};
|
|
6771
|
+
const cliBridgeEffortValue = () => {
|
|
6772
|
+
if (cliBridgeEffort !== void 0) return cliBridgeEffort;
|
|
6773
|
+
const fromHost = statusByRegion[activeRegion]?.cliBridgeEffort;
|
|
6774
|
+
if (typeof fromHost === "string") return fromHost;
|
|
6775
|
+
return cliBridgeEffortOf(settingsScope?.getSnapshot().value);
|
|
6776
|
+
};
|
|
6777
|
+
/**
|
|
6778
|
+
* Pin the model the local CLI runs on (or `''` for "back to the CLI's
|
|
6779
|
+
* default").
|
|
6780
|
+
*
|
|
6781
|
+
* The write is the same two-writer order as the switch; on a refusal the
|
|
6782
|
+
* optimistic value is REVERTED so the picker renders the committed value
|
|
6783
|
+
* again, and the error surfaces through the card's shared error line.
|
|
6784
|
+
*/
|
|
6785
|
+
const pickCliBridgeModel = async (model) => {
|
|
6786
|
+
if (settingsScope === void 0) return;
|
|
6787
|
+
setCliBridgeModelBusy(true);
|
|
6788
|
+
try {
|
|
6789
|
+
await writeCliBridgeModel(settingsScope, model);
|
|
6790
|
+
if (mounted.current) setCliBridgeModel(model);
|
|
6791
|
+
} catch (error) {
|
|
6792
|
+
if (mounted.current) {
|
|
6793
|
+
setCliBridgeModel(void 0);
|
|
6794
|
+
setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
6795
|
+
}
|
|
6796
|
+
} finally {
|
|
6797
|
+
if (mounted.current) setCliBridgeModelBusy(false);
|
|
6798
|
+
}
|
|
6799
|
+
};
|
|
6800
|
+
/**
|
|
6801
|
+
* Pin the reasoning effort the local CLI runs on, same write-and-revert
|
|
6802
|
+
* rule as {@link pickCliBridgeModel}.
|
|
6803
|
+
*/
|
|
6804
|
+
const pickCliBridgeEffort = async (effort) => {
|
|
6805
|
+
if (settingsScope === void 0) return;
|
|
6806
|
+
setCliBridgeEffortBusy(true);
|
|
6807
|
+
try {
|
|
6808
|
+
await writeCliBridgeEffort(settingsScope, effort);
|
|
6809
|
+
if (mounted.current) setCliBridgeEffort(effort);
|
|
6810
|
+
} catch (error) {
|
|
6811
|
+
if (mounted.current) {
|
|
6812
|
+
setCliBridgeEffort(void 0);
|
|
6813
|
+
setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
6814
|
+
}
|
|
6815
|
+
} finally {
|
|
6816
|
+
if (mounted.current) setCliBridgeEffortBusy(false);
|
|
6817
|
+
}
|
|
6818
|
+
};
|
|
6819
|
+
/**
|
|
6820
|
+
* The credential-region order of the CLI route, rendered with the same
|
|
6821
|
+
* write-first rule. Unlike the model and effort pins, `''` is NOT a
|
|
6822
|
+
* meaningful committed value here: it means "nothing pinned", and the route
|
|
6823
|
+
* then runs on the CLI's own default order — CN first. The picker therefore
|
|
6824
|
+
* shows that default instead of an empty answer.
|
|
6825
|
+
*/
|
|
6826
|
+
const cliBridgeRegionOrderValue = () => {
|
|
6827
|
+
if (cliBridgeRegionOrder !== void 0) return cliBridgeRegionOrder;
|
|
6828
|
+
const fromHost = statusByRegion[activeRegion]?.cliBridgeRegionOrder;
|
|
6829
|
+
const committed = typeof fromHost === "string" && fromHost !== "" ? fromHost : cliBridgeRegionOrderOf(settingsScope?.getSnapshot().value);
|
|
6830
|
+
return committed !== "" ? committed : "cn,global";
|
|
6831
|
+
};
|
|
6832
|
+
/**
|
|
6833
|
+
* Pin the credential-region order the local CLI walks, same
|
|
6834
|
+
* write-and-revert rule as {@link pickCliBridgeModel}.
|
|
6835
|
+
*/
|
|
6836
|
+
const pickCliBridgeRegionOrder = async (regionOrder) => {
|
|
6837
|
+
if (settingsScope === void 0) return;
|
|
6838
|
+
setCliBridgeRegionOrderBusy(true);
|
|
6839
|
+
try {
|
|
6840
|
+
await writeCliBridgeRegionOrder(settingsScope, regionOrder);
|
|
6841
|
+
if (mounted.current) setCliBridgeRegionOrder(regionOrder);
|
|
6842
|
+
} catch (error) {
|
|
6843
|
+
if (mounted.current) {
|
|
6844
|
+
setCliBridgeRegionOrder(void 0);
|
|
6845
|
+
setAccountError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
6846
|
+
}
|
|
6847
|
+
} finally {
|
|
6848
|
+
if (mounted.current) setCliBridgeRegionOrderBusy(false);
|
|
6849
|
+
}
|
|
6850
|
+
};
|
|
6528
6851
|
const toggleImageTool = async (item, enabled) => {
|
|
6529
6852
|
if (settingsScope === void 0) return;
|
|
6530
6853
|
setTogglingCredits(item);
|
|
@@ -6921,8 +7244,10 @@ window.__ModuleLoader__.load({
|
|
|
6921
7244
|
offModelIds: [...activeOffIds],
|
|
6922
7245
|
contextBudgets: activeContextBudgets
|
|
6923
7246
|
});
|
|
7247
|
+
if (mounted.current) setSaving(false);
|
|
6924
7248
|
discardModels();
|
|
6925
|
-
|
|
7249
|
+
refreshUsage(activeRegion);
|
|
7250
|
+
return;
|
|
6926
7251
|
} catch (error) {
|
|
6927
7252
|
if (mounted.current) {
|
|
6928
7253
|
const reason = error instanceof Error ? error.message : t("row.requestFailed");
|
|
@@ -7083,10 +7408,131 @@ window.__ModuleLoader__.load({
|
|
|
7083
7408
|
})]
|
|
7084
7409
|
})]
|
|
7085
7410
|
});
|
|
7411
|
+
/**
|
|
7412
|
+
* The two quick-adjust pins of the CLI route, rendered under its switch the
|
|
7413
|
+
* way the seat picker sits under its switch: indented, and only while the
|
|
7414
|
+
* switch is ON — the pickers configure the route the switch turned on, and
|
|
7415
|
+
* they disappear together with it.
|
|
7416
|
+
*
|
|
7417
|
+
* The model options are the active tab's LIVE catalog (`visibleModels`), so
|
|
7418
|
+
* they follow the upstream instead of a hardcoded list; a pinned value the
|
|
7419
|
+
* catalog does not list is still offered as its own option rather than
|
|
7420
|
+
* vanishing from the select. The effort options are the six CLI tiers,
|
|
7421
|
+
* narrowed to the pinned model's `reasoning.supportedEfforts` when that
|
|
7422
|
+
* model is in the catalog and names tiers the CLI knows.
|
|
7423
|
+
*/
|
|
7424
|
+
const cliBridgeModelPinned = cliBridgeModelValue();
|
|
7425
|
+
const cliBridgeModelOptions = visibleModels.map((model) => ({
|
|
7426
|
+
value: model.id,
|
|
7427
|
+
label: model.name !== model.id ? model.name : model.id
|
|
7428
|
+
}));
|
|
7429
|
+
if (cliBridgeModelPinned !== "" && !cliBridgeModelOptions.some((option) => option.value === cliBridgeModelPinned)) cliBridgeModelOptions.push({
|
|
7430
|
+
value: cliBridgeModelPinned,
|
|
7431
|
+
label: cliBridgeModelPinned
|
|
7432
|
+
});
|
|
7433
|
+
const cliBridgeEffortPinned = cliBridgeEffortValue();
|
|
7434
|
+
const pinnedCliBridgeTiers = visibleModels.find((model) => model.id === cliBridgeModelPinned)?.reasoning?.supportedEfforts;
|
|
7435
|
+
const cliBridgeEffortLevels = (() => {
|
|
7436
|
+
if (pinnedCliBridgeTiers === void 0 || pinnedCliBridgeTiers.length === 0) return [...CLI_BRIDGE_EFFORT_LEVELS];
|
|
7437
|
+
const known = CLI_BRIDGE_EFFORT_LEVELS.filter((level) => pinnedCliBridgeTiers.includes(level));
|
|
7438
|
+
return known.length > 0 ? known : [...CLI_BRIDGE_EFFORT_LEVELS];
|
|
7439
|
+
})();
|
|
7440
|
+
if (cliBridgeEffortPinned !== "" && !cliBridgeEffortLevels.includes(cliBridgeEffortPinned)) cliBridgeEffortLevels.push(cliBridgeEffortPinned);
|
|
7441
|
+
const cliBridgeRegionOrderPinned = cliBridgeRegionOrderValue();
|
|
7442
|
+
const cliBridgeRegionOrderOptions = CLI_BRIDGE_REGION_ORDERS.map((order) => ({
|
|
7443
|
+
value: order,
|
|
7444
|
+
label: order === "cn,global" ? t("row.cliBridgeRegionOrderCnFirst") : order === "global,cn" ? t("row.cliBridgeRegionOrderGlobalFirst") : order === "cn" ? t("row.cliBridgeRegionOrderCnOnly") : t("row.cliBridgeRegionOrderGlobalOnly")
|
|
7445
|
+
}));
|
|
7446
|
+
if (cliBridgeRegionOrderOptions.every((option) => option.value !== cliBridgeRegionOrderPinned)) cliBridgeRegionOrderOptions.push({
|
|
7447
|
+
value: cliBridgeRegionOrderPinned,
|
|
7448
|
+
label: cliBridgeRegionOrderPinned
|
|
7449
|
+
});
|
|
7450
|
+
const cliBridgePickers = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7451
|
+
className: "dsm-workbuddy-pref dsm-workbuddy-pref-indent",
|
|
7452
|
+
title: t("row.cliBridgePickersHint"),
|
|
7453
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7454
|
+
className: "dsm-workbuddy-pref-copy",
|
|
7455
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7456
|
+
className: "dsm-workbuddy-pref-lead dsm-workbuddy-cli-bridge-pickers",
|
|
7457
|
+
children: [
|
|
7458
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
7459
|
+
className: "dsm-workbuddy-cli-bridge-pick",
|
|
7460
|
+
title: t("row.cliBridgeModelHint"),
|
|
7461
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7462
|
+
className: "dsm-workbuddy-pref-label",
|
|
7463
|
+
children: t("row.cliBridgeModelLabel")
|
|
7464
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
7465
|
+
className: "dsm-workbuddy-cli-bridge-select",
|
|
7466
|
+
value: cliBridgeModelPinned,
|
|
7467
|
+
disabled: !canWrite || saving || poolBusy || cliBridgeModelBusy || cliBridgeEffortBusy || cliBridgeRegionOrderBusy,
|
|
7468
|
+
"aria-label": t("row.cliBridgeModelAria"),
|
|
7469
|
+
onChange: (event) => {
|
|
7470
|
+
pickCliBridgeModel(event.currentTarget.value);
|
|
7471
|
+
},
|
|
7472
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7473
|
+
value: "",
|
|
7474
|
+
children: t("row.cliBridgeModelDefault")
|
|
7475
|
+
}), cliBridgeModelOptions.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7476
|
+
value: option.value,
|
|
7477
|
+
children: option.label
|
|
7478
|
+
}, option.value))]
|
|
7479
|
+
})]
|
|
7480
|
+
}),
|
|
7481
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
7482
|
+
className: "dsm-workbuddy-cli-bridge-pick",
|
|
7483
|
+
title: t("row.cliBridgeEffortHint"),
|
|
7484
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7485
|
+
className: "dsm-workbuddy-pref-label",
|
|
7486
|
+
children: t("row.cliBridgeEffortLabel")
|
|
7487
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
7488
|
+
className: "dsm-workbuddy-cli-bridge-select",
|
|
7489
|
+
value: cliBridgeEffortPinned,
|
|
7490
|
+
disabled: !canWrite || saving || poolBusy || cliBridgeModelBusy || cliBridgeEffortBusy || cliBridgeRegionOrderBusy,
|
|
7491
|
+
"aria-label": t("row.cliBridgeEffortAria"),
|
|
7492
|
+
onChange: (event) => {
|
|
7493
|
+
pickCliBridgeEffort(event.currentTarget.value);
|
|
7494
|
+
},
|
|
7495
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7496
|
+
value: "",
|
|
7497
|
+
children: t("row.cliBridgeEffortDefault")
|
|
7498
|
+
}), cliBridgeEffortLevels.map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7499
|
+
value: level,
|
|
7500
|
+
children: level
|
|
7501
|
+
}, level))]
|
|
7502
|
+
})]
|
|
7503
|
+
}),
|
|
7504
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
7505
|
+
className: "dsm-workbuddy-cli-bridge-pick",
|
|
7506
|
+
title: t("row.cliBridgeRegionOrderHint"),
|
|
7507
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7508
|
+
className: "dsm-workbuddy-pref-label",
|
|
7509
|
+
children: t("row.cliBridgeRegionOrderLabel")
|
|
7510
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
7511
|
+
className: "dsm-workbuddy-cli-bridge-select",
|
|
7512
|
+
value: cliBridgeRegionOrderPinned,
|
|
7513
|
+
disabled: !canWrite || saving || poolBusy || cliBridgeModelBusy || cliBridgeEffortBusy || cliBridgeRegionOrderBusy,
|
|
7514
|
+
"aria-label": t("row.cliBridgeRegionOrderAria"),
|
|
7515
|
+
onChange: (event) => {
|
|
7516
|
+
pickCliBridgeRegionOrder(event.currentTarget.value);
|
|
7517
|
+
},
|
|
7518
|
+
children: cliBridgeRegionOrderOptions.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7519
|
+
value: option.value,
|
|
7520
|
+
children: option.label
|
|
7521
|
+
}, option.value))
|
|
7522
|
+
})]
|
|
7523
|
+
})
|
|
7524
|
+
]
|
|
7525
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7526
|
+
className: "dsm-workbuddy-pref-hint",
|
|
7527
|
+
children: t("row.cliBridgePickersHint")
|
|
7528
|
+
})]
|
|
7529
|
+
})
|
|
7530
|
+
});
|
|
7086
7531
|
const creditPreferences = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7087
7532
|
className: "dsm-workbuddy-prefs",
|
|
7088
7533
|
children: [
|
|
7089
7534
|
cliBridgePreference,
|
|
7535
|
+
cliBridgeSwitchedOn() ? cliBridgePickers : null,
|
|
7090
7536
|
imageToolPreference,
|
|
7091
7537
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
7092
7538
|
className: "dsm-workbuddy-pref",
|
|
@@ -7903,6 +8349,42 @@ window.__ModuleLoader__.load({
|
|
|
7903
8349
|
]);
|
|
7904
8350
|
}
|
|
7905
8351
|
//#endregion
|
|
8352
|
+
//#region src/client/new-model-ids.ts
|
|
8353
|
+
/**
|
|
8354
|
+
* The ids a usage document reports as first seen by THIS machine, normalised.
|
|
8355
|
+
*
|
|
8356
|
+
* One filter, two surfaces. Both the composer pill's "· N 新模型" segment and the
|
|
8357
|
+
* host model picker's per-row marker have to agree about which models are new,
|
|
8358
|
+
* so they must not each invent their own reading of the list — a difference
|
|
8359
|
+
* between the two would show a pill that counts two and a picker that marks
|
|
8360
|
+
* three. They therefore share this single normaliser, and both read the SAME
|
|
8361
|
+
* usage document the Host already serves.
|
|
8362
|
+
*
|
|
8363
|
+
* Three filters, none of them optional:
|
|
8364
|
+
* 1. Not an array answers `[]` rather than throwing. The Host's answer is the
|
|
8365
|
+
* source of truth, so a missing field is "nothing to show", never an error.
|
|
8366
|
+
* 2. A non-string, or an id that is only whitespace, is dropped.
|
|
8367
|
+
* 3. Duplicates are dropped. The Host already dedupes, but the client must not
|
|
8368
|
+
* trust a peer: a duplicated id would turn "2 新模型" into two rows naming
|
|
8369
|
+
* the same model and stop the count meaning anything.
|
|
8370
|
+
*
|
|
8371
|
+
* @param raw - The `newModelIds` field of a usage document.
|
|
8372
|
+
* @returns The ids, in the order the Host sent them.
|
|
8373
|
+
*/
|
|
8374
|
+
function normalizeNewModelIds(raw) {
|
|
8375
|
+
if (!Array.isArray(raw)) return [];
|
|
8376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8377
|
+
const ids = [];
|
|
8378
|
+
for (const value of raw) {
|
|
8379
|
+
if (typeof value !== "string") continue;
|
|
8380
|
+
const id = value.trim();
|
|
8381
|
+
if (id === "" || seen.has(id)) continue;
|
|
8382
|
+
seen.add(id);
|
|
8383
|
+
ids.push(id);
|
|
8384
|
+
}
|
|
8385
|
+
return ids;
|
|
8386
|
+
}
|
|
8387
|
+
//#endregion
|
|
7906
8388
|
//#region src/client/ComposerCredits.tsx
|
|
7907
8389
|
/**
|
|
7908
8390
|
* Credit readout for the composer tool row (`conversation.input.left`).
|
|
@@ -8002,6 +8484,34 @@ window.__ModuleLoader__.load({
|
|
|
8002
8484
|
return usage.credits?.total;
|
|
8003
8485
|
}
|
|
8004
8486
|
/**
|
|
8487
|
+
* The "· N 新模型" ids out of one usage document, in first-sighted order.
|
|
8488
|
+
*
|
|
8489
|
+
* The filters live in `normalizeNewModelIds`, shared with the host picker's row
|
|
8490
|
+
* marker, so the two surfaces cannot disagree about which models are new.
|
|
8491
|
+
*
|
|
8492
|
+
* Three filters, none of them optional:
|
|
8493
|
+
* 1. Not a signed-in document answers `[]` rather than throwing — a Host that
|
|
8494
|
+
* predates the feature, or a degraded document, must cost the pill its
|
|
8495
|
+
* credits readout, and it must not cost it a crash.
|
|
8496
|
+
* 2. A non-string, or an id that is only whitespace, is dropped.
|
|
8497
|
+
* 3. Duplicates are dropped. The Host already dedupes, but the client must not
|
|
8498
|
+
* trust a peer: a duplicated id would turn "2 新模型" into two rows naming
|
|
8499
|
+
* the same model and stop the count meaning anything.
|
|
8500
|
+
*/
|
|
8501
|
+
function newModelIdsOf(usage) {
|
|
8502
|
+
if (usage === void 0 || usage.status !== "signed-in") return [];
|
|
8503
|
+
return normalizeNewModelIds(usage.newModelIds);
|
|
8504
|
+
}
|
|
8505
|
+
/**
|
|
8506
|
+
* The display name for a first-seen id, or the id itself when the catalog does
|
|
8507
|
+
* not list it — a model that left the roster can still be inside the new
|
|
8508
|
+
* window, and showing its id is a lie-free answer to "which model".
|
|
8509
|
+
*/
|
|
8510
|
+
function modelNameOf(models, id) {
|
|
8511
|
+
const found = models.find((model) => model.id === id);
|
|
8512
|
+
return found === void 0 || found.name.trim() === "" ? id : found.name;
|
|
8513
|
+
}
|
|
8514
|
+
/**
|
|
8005
8515
|
* The Host's per-region readout switch off a usage document, when it carries one.
|
|
8006
8516
|
*
|
|
8007
8517
|
* The `error` variant has no data at all, so it can only ever answer `undefined`
|
|
@@ -8102,6 +8612,36 @@ window.__ModuleLoader__.load({
|
|
|
8102
8612
|
const mounted = (0, react.useRef)(true);
|
|
8103
8613
|
const rootRef = (0, react.useRef)(null);
|
|
8104
8614
|
const panelRef = (0, react.useRef)(null);
|
|
8615
|
+
/** Whether the panel was opened BY the "· N 新模型" button (so it answers the
|
|
8616
|
+
* question the reader just asked, instead of just opening on the table). */
|
|
8617
|
+
const [revealed, setRevealed] = (0, react.useState)(false);
|
|
8618
|
+
const newRowRef = (0, react.useRef)(null);
|
|
8619
|
+
/**
|
|
8620
|
+
* The "· N 新模型" button is an affordance for the reader who wants to know
|
|
8621
|
+
* WHICH models: it opens the panel and brings the new-model row into view.
|
|
8622
|
+
*
|
|
8623
|
+
* The Host owns the model picker and offers this plugin no API to open it or
|
|
8624
|
+
* steer its selection, so the honest answer is the panel's own row — the one
|
|
8625
|
+
* surface in the pill that names them. The rows themselves are marked "新" in
|
|
8626
|
+
* the picker by `WorkBuddyModelLabel`, reading the same ids.
|
|
8627
|
+
*
|
|
8628
|
+
* The row is focused (not just scrolled) because that is what a screen reader
|
|
8629
|
+
* needs to keep its place; it is pulled out of the tab order so Tab does not
|
|
8630
|
+
* stop on it.
|
|
8631
|
+
*/
|
|
8632
|
+
(0, react.useEffect)(() => {
|
|
8633
|
+
if (!open) {
|
|
8634
|
+
if (revealed) setRevealed(false);
|
|
8635
|
+
return;
|
|
8636
|
+
}
|
|
8637
|
+
if (!revealed) return;
|
|
8638
|
+
const row = newRowRef.current;
|
|
8639
|
+
if (row === null) return;
|
|
8640
|
+
try {
|
|
8641
|
+
row.scrollIntoView({ block: "nearest" });
|
|
8642
|
+
} catch {}
|
|
8643
|
+
row.focus();
|
|
8644
|
+
}, [open, revealed]);
|
|
8105
8645
|
(0, react.useEffect)(() => {
|
|
8106
8646
|
mounted.current = true;
|
|
8107
8647
|
return () => {
|
|
@@ -8383,6 +8923,9 @@ window.__ModuleLoader__.load({
|
|
|
8383
8923
|
const signedOut = usage !== void 0 && usage.status === "signed-out";
|
|
8384
8924
|
const valueText = total === void 0 ? "—" : formatCredits(total);
|
|
8385
8925
|
const label = t(activeRegion === "cn" ? "composer.pointsCn" : "composer.pointsGlobal");
|
|
8926
|
+
const newModelNames = newModelIdsOf(usage).map((id) => modelNameOf(signedIn?.models ?? [], id));
|
|
8927
|
+
const newModelTooltip = newModelNames.length === 0 ? void 0 : `${t("composer.newModelsTitle")}${newModelNames.slice(0, 3).join(t("composer.newModelsSep"))}${newModelNames.length > 3 ? "…" : ""}`;
|
|
8928
|
+
const rosterSeenNote = signedIn?.rosterSeenVersionNote;
|
|
8386
8929
|
/**
|
|
8387
8930
|
* The table's rows: every account in this region, with its balance.
|
|
8388
8931
|
*
|
|
@@ -8410,135 +8953,177 @@ window.__ModuleLoader__.load({
|
|
|
8410
8953
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8411
8954
|
ref: rootRef,
|
|
8412
8955
|
className: "dsm-workbuddy-composer-credits",
|
|
8413
|
-
children: [
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
}
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8956
|
+
children: [
|
|
8957
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8958
|
+
type: "button",
|
|
8959
|
+
className: "dsm-workbuddy-composer-credits-trigger",
|
|
8960
|
+
"aria-haspopup": "dialog",
|
|
8961
|
+
"aria-expanded": open,
|
|
8962
|
+
onClick: () => {
|
|
8963
|
+
setOpen(!open);
|
|
8964
|
+
},
|
|
8965
|
+
children: [
|
|
8966
|
+
label,
|
|
8967
|
+
" · ",
|
|
8968
|
+
valueText
|
|
8969
|
+
]
|
|
8970
|
+
}),
|
|
8971
|
+
newModelNames.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8972
|
+
type: "button",
|
|
8973
|
+
className: "dsm-workbuddy-composer-credits-new",
|
|
8974
|
+
"aria-haspopup": "dialog",
|
|
8975
|
+
"aria-expanded": open,
|
|
8976
|
+
title: newModelTooltip,
|
|
8977
|
+
onClick: () => {
|
|
8978
|
+
setRevealed(true);
|
|
8979
|
+
setOpen(true);
|
|
8980
|
+
},
|
|
8981
|
+
children: [
|
|
8982
|
+
"· ",
|
|
8983
|
+
newModelNames.length,
|
|
8984
|
+
" ",
|
|
8985
|
+
t("composer.newModels")
|
|
8986
|
+
]
|
|
8987
|
+
}),
|
|
8988
|
+
open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8989
|
+
ref: panelRef,
|
|
8990
|
+
className: "dsm-workbuddy-composer-panel",
|
|
8991
|
+
style: position ?? {
|
|
8992
|
+
visibility: "hidden",
|
|
8993
|
+
left: 0,
|
|
8994
|
+
top: 0
|
|
8995
|
+
},
|
|
8996
|
+
role: "dialog",
|
|
8997
|
+
"aria-label": t("composer.panelTitle"),
|
|
8998
|
+
children: [
|
|
8999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9000
|
+
className: "dsm-workbuddy-composer-panel-head",
|
|
9001
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9002
|
+
className: "dsm-workbuddy-composer-panel-title",
|
|
9003
|
+
children: t(activeRegion === "cn" ? "composer.panelTitleCn" : "composer.panelTitleGlobal")
|
|
9004
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9005
|
+
type: "button",
|
|
9006
|
+
className: "dsm-workbuddy-composer-panel-refresh",
|
|
9007
|
+
disabled: busy || testing || switchingId !== void 0,
|
|
9008
|
+
title: t("composer.testAllHint"),
|
|
9009
|
+
onClick: () => {
|
|
9010
|
+
testEveryAccount();
|
|
9011
|
+
},
|
|
9012
|
+
children: testing ? t("composer.testing") : t("composer.testAll")
|
|
9013
|
+
})]
|
|
9014
|
+
}),
|
|
9015
|
+
signedOut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9016
|
+
className: "dsm-workbuddy-composer-panel-empty",
|
|
9017
|
+
children: t("composer.signedOut")
|
|
9018
|
+
}) : rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9019
|
+
className: "dsm-workbuddy-composer-panel-empty",
|
|
9020
|
+
children: t("composer.noAccounts")
|
|
9021
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
9022
|
+
className: "dsm-workbuddy-composer-panel-table",
|
|
9023
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
9024
|
+
scope: "col",
|
|
9025
|
+
children: t("composer.account")
|
|
9026
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
9027
|
+
scope: "col",
|
|
9028
|
+
className: "dsm-workbuddy-composer-panel-num",
|
|
9029
|
+
children: t("composer.credits")
|
|
9030
|
+
})] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: orderedRows.map((row) => {
|
|
9031
|
+
const current = row.id === selectedId;
|
|
9032
|
+
const poolRow = poolById.get(row.id);
|
|
9033
|
+
const live = poolRow === void 0 ? current : poolRow.current;
|
|
9034
|
+
const availability = poolRow ?? {
|
|
9035
|
+
excludedBy: row.excludedBy,
|
|
9036
|
+
probe: void 0
|
|
9037
|
+
};
|
|
9038
|
+
const usable = stateTone(availability) === "ok";
|
|
9039
|
+
const currentMark = live ? t("composer.current") : void 0;
|
|
9040
|
+
const reasonMark = usable ? void 0 : availabilityMark(t, availability);
|
|
9041
|
+
const spoken = detailLine(live ? t("composer.current") : void 0, usable ? void 0 : availabilityLine(t, availability));
|
|
9042
|
+
const whoLabel = spoken === void 0 ? row.accountName : `${row.accountName} — ${spoken}`;
|
|
9043
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
9044
|
+
className: live ? "dsm-workbuddy-composer-panel-row-current" : void 0,
|
|
9045
|
+
title: spoken,
|
|
9046
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
9047
|
+
scope: "row",
|
|
9048
|
+
"aria-label": whoLabel,
|
|
9049
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
9050
|
+
type: "button",
|
|
9051
|
+
className: "dsm-workbuddy-composer-panel-switch",
|
|
9052
|
+
disabled: !canSwitch || switchingId !== void 0 || live,
|
|
9053
|
+
"aria-label": t("composer.switchTo", { account: row.accountName }),
|
|
9054
|
+
onClick: () => {
|
|
9055
|
+
switchTo(row.id);
|
|
9056
|
+
},
|
|
8500
9057
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8501
|
-
className:
|
|
8502
|
-
|
|
8503
|
-
}),
|
|
8504
|
-
className: "dsm-workbuddy-composer-panel-
|
|
8505
|
-
children: [
|
|
8506
|
-
className: "dsm-workbuddy-composer-panel-
|
|
8507
|
-
children:
|
|
8508
|
-
}), reasonMark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.
|
|
8509
|
-
className: "dsm-workbuddy-composer-panel-mark
|
|
8510
|
-
children:
|
|
9058
|
+
className: dotClass(availability),
|
|
9059
|
+
"aria-hidden": "true"
|
|
9060
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
9061
|
+
className: "dsm-workbuddy-composer-panel-who",
|
|
9062
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9063
|
+
className: "dsm-workbuddy-composer-panel-name",
|
|
9064
|
+
children: row.accountName
|
|
9065
|
+
}), currentMark === void 0 && reasonMark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
9066
|
+
className: "dsm-workbuddy-composer-panel-mark",
|
|
9067
|
+
children: [currentMark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9068
|
+
className: "dsm-workbuddy-composer-panel-mark-current",
|
|
9069
|
+
children: currentMark
|
|
9070
|
+
}), reasonMark === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9071
|
+
className: "dsm-workbuddy-composer-panel-mark-reason",
|
|
9072
|
+
children: reasonMark
|
|
9073
|
+
})]
|
|
8511
9074
|
})]
|
|
8512
9075
|
})]
|
|
8513
|
-
})
|
|
8514
|
-
})
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
9076
|
+
})
|
|
9077
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
9078
|
+
className: "dsm-workbuddy-composer-panel-num",
|
|
9079
|
+
children: row.credits === void 0 ? "—" : formatCredits(row.credits)
|
|
9080
|
+
})]
|
|
9081
|
+
}, row.id);
|
|
9082
|
+
}) })]
|
|
9083
|
+
}),
|
|
9084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9085
|
+
className: "dsm-workbuddy-composer-panel-foot",
|
|
9086
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
9087
|
+
t("composer.lastRefresh"),
|
|
9088
|
+
" ",
|
|
9089
|
+
lastRefresh === void 0 ? "—" : formatClock(lastRefresh)
|
|
9090
|
+
] })
|
|
9091
|
+
}),
|
|
9092
|
+
newModelNames.length === 0 && rosterSeenNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9093
|
+
className: "dsm-workbuddy-composer-panel-new",
|
|
9094
|
+
children: [newModelNames.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
9095
|
+
ref: newRowRef,
|
|
9096
|
+
tabIndex: -1,
|
|
9097
|
+
"aria-label": `${t("composer.newModelsTitle")}${newModelNames.join(t("composer.newModelsSep"))}`,
|
|
9098
|
+
className: revealed ? "dsm-workbuddy-composer-panel-new-row dsm-workbuddy-composer-panel-new-row-revealed" : "dsm-workbuddy-composer-panel-new-row",
|
|
9099
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9100
|
+
className: "dsm-workbuddy-composer-panel-new-label",
|
|
9101
|
+
children: t("composer.newModelsRow")
|
|
9102
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9103
|
+
className: "dsm-workbuddy-composer-panel-new-list",
|
|
9104
|
+
children: newModelNames.join(t("composer.newModelsSep"))
|
|
8518
9105
|
})]
|
|
8519
|
-
},
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
]
|
|
8541
|
-
}), document.body)]
|
|
9106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9107
|
+
className: "dsm-workbuddy-composer-panel-note",
|
|
9108
|
+
children: t("composer.newModelsNote")
|
|
9109
|
+
})] }), rosterSeenNote === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9110
|
+
className: "dsm-workbuddy-composer-panel-note",
|
|
9111
|
+
children: rosterSeenNote
|
|
9112
|
+
})]
|
|
9113
|
+
}),
|
|
9114
|
+
failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9115
|
+
className: "dsm-workbuddy-composer-panel-error",
|
|
9116
|
+
role: "status",
|
|
9117
|
+
children: t("composer.refreshFailed")
|
|
9118
|
+
}) : null,
|
|
9119
|
+
testError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
9120
|
+
className: "dsm-workbuddy-composer-panel-error",
|
|
9121
|
+
role: "status",
|
|
9122
|
+
children: testError
|
|
9123
|
+
})
|
|
9124
|
+
]
|
|
9125
|
+
}), document.body)
|
|
9126
|
+
]
|
|
8542
9127
|
});
|
|
8543
9128
|
}
|
|
8544
9129
|
//#endregion
|
|
@@ -8740,7 +9325,8 @@ window.__ModuleLoader__.load({
|
|
|
8740
9325
|
const snapshot = {
|
|
8741
9326
|
levels: body.busyLevels ?? {},
|
|
8742
9327
|
promos,
|
|
8743
|
-
rates
|
|
9328
|
+
rates,
|
|
9329
|
+
newModelIds: normalizeNewModelIds(body.newModelIds)
|
|
8744
9330
|
};
|
|
8745
9331
|
caches.set(region, {
|
|
8746
9332
|
snapshot,
|
|
@@ -8807,6 +9393,29 @@ window.__ModuleLoader__.load({
|
|
|
8807
9393
|
});
|
|
8808
9394
|
}
|
|
8809
9395
|
/**
|
|
9396
|
+
* The "本机首次见到" marker, as the picker draws it.
|
|
9397
|
+
*
|
|
9398
|
+
* WHY IT IS HERE AT ALL. The pill's "· N 新模型" answers "how many" and its
|
|
9399
|
+
* panel answers "which" — but only once the reader opens it. The picker is
|
|
9400
|
+
* where the choice is actually made, and this is the SAME set marked on the
|
|
9401
|
+
* row, so a newly seen model is visible the moment the picker opens, with no
|
|
9402
|
+
* extra step. It reads the pill's normalised list (`normalizeNewModelIds`), not
|
|
9403
|
+
* a second reading of the same field, so the two surfaces cannot disagree.
|
|
9404
|
+
*
|
|
9405
|
+
* MUTED, and muted further than the promotion chip: a promotion is a fact about
|
|
9406
|
+
* price, "新" only says when this machine first recorded the id. The pill's ink
|
|
9407
|
+
* (label-dimmed) is reused here for the same reason it is used there — the
|
|
9408
|
+
* marker must not out-shout the model name or the chip beside it.
|
|
9409
|
+
*/
|
|
9410
|
+
function NewModelMark() {
|
|
9411
|
+
const language = uiLanguage();
|
|
9412
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9413
|
+
className: "dsm-workbuddy-model-label-new",
|
|
9414
|
+
title: language === "zh" ? "本机首次见到的模型" : "First seen by this machine",
|
|
9415
|
+
children: language === "zh" ? "新" : "NEW"
|
|
9416
|
+
});
|
|
9417
|
+
}
|
|
9418
|
+
/**
|
|
8810
9419
|
* The label for one model row in the host's picker.
|
|
8811
9420
|
*
|
|
8812
9421
|
* @param props - The host's model identity and the placement of this label.
|
|
@@ -8856,7 +9465,8 @@ window.__ModuleLoader__.load({
|
|
|
8856
9465
|
promo,
|
|
8857
9466
|
compact
|
|
8858
9467
|
}),
|
|
8859
|
-
level === void 0 ? null : busyIcon(level, "zh")
|
|
9468
|
+
level === void 0 ? null : busyIcon(level, "zh"),
|
|
9469
|
+
snapshot?.newModelIds.includes(modelId) ?? false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NewModelMark, {}) : null
|
|
8860
9470
|
]
|
|
8861
9471
|
});
|
|
8862
9472
|
}
|
|
@@ -9443,6 +10053,8 @@ window.__ModuleLoader__.load({
|
|
|
9443
10053
|
"row.poolCheckedIn": "Checked in",
|
|
9444
10054
|
"row.poolNotCheckedIn": "Not checked in",
|
|
9445
10055
|
"row.poolCheckinUnknown": "—",
|
|
10056
|
+
"row.probeAccountRestricted": "Account restricted upstream",
|
|
10057
|
+
"row.poolCheckinEnterprise": "N/A (enterprise)",
|
|
9446
10058
|
"row.poolBenefitClaimable": "{title}: {count} credits",
|
|
9447
10059
|
"row.poolBenefitUrgent": "{title}: {count} credits · {days}d left",
|
|
9448
10060
|
"row.poolBenefitDueToday": "{title}: {count} credits · last day",
|
|
@@ -9458,6 +10070,7 @@ window.__ModuleLoader__.load({
|
|
|
9458
10070
|
"row.poolExcludedModelBusy": "Model busy",
|
|
9459
10071
|
"row.poolExcludedOutOfCredit": "Out of credits",
|
|
9460
10072
|
"row.poolExcludedRejected": "Rejected",
|
|
10073
|
+
"row.poolExcludedAccountRestricted": "Account restricted upstream",
|
|
9461
10074
|
"row.poolExcludedUnusable": "Unusable",
|
|
9462
10075
|
"row.poolRetryAt": "Back after {at}",
|
|
9463
10076
|
"row.poolRetryUnknown": "The upstream gave no time for when this frees up",
|
|
@@ -9563,6 +10176,22 @@ window.__ModuleLoader__.load({
|
|
|
9563
10176
|
"row.poolRowRemoveDesktopOnly": "{accountName}: hidden from the list — this account comes from the desktop app's own sign-in, which is left untouched. 「Detect accounts again」 re-adds it.",
|
|
9564
10177
|
"row.cliBridgeLabel": "Let the local CodeBuddy CLI do the work",
|
|
9565
10178
|
"row.cliBridgeHint": "Adds the codebuddy_ask tool and the /codebuddy command. Each call spends CodeBuddy quota. injectResult only affects /codebuddy — its result is injected back into the session, costing one DSH turn when the session is idle; tool calls never read it. Rate-limited: 6 turns per session per hour, 20 per process per hour, one turn at a time. Off by default.",
|
|
10179
|
+
"row.cliBridgeModelLabel": "CLI model",
|
|
10180
|
+
"row.cliBridgeModelDefault": "CLI default",
|
|
10181
|
+
"row.cliBridgeModelAria": "Model the local CodeBuddy CLI runs on",
|
|
10182
|
+
"row.cliBridgeModelHint": "Written to cliBridge.model and passed to the CLI as --model. \"CLI default\" leaves the model to the CLI itself. The list is the catalog this tab synced from the upstream.",
|
|
10183
|
+
"row.cliBridgeEffortLabel": "CLI effort",
|
|
10184
|
+
"row.cliBridgeEffortDefault": "CLI default",
|
|
10185
|
+
"row.cliBridgeEffortAria": "Reasoning effort the local CodeBuddy CLI runs on",
|
|
10186
|
+
"row.cliBridgeEffortHint": "Written to cliBridge.effort and passed to the CLI as --effort. \"CLI default\" leaves the tier to the CLI itself. When the picked model lists supported tiers, only those are offered.",
|
|
10187
|
+
"row.cliBridgeRegionOrderLabel": "Region order",
|
|
10188
|
+
"row.cliBridgeRegionOrderAria": "Order in which the local CodeBuddy CLI walks the credential regions",
|
|
10189
|
+
"row.cliBridgeRegionOrderHint": "Written to cliBridge.regionOrder: the order the CLI tries the WorkBuddy credentials in, until one logs in. The region actually used is reported per turn as credential_region. Applies from the next call.",
|
|
10190
|
+
"row.cliBridgeRegionOrderCnFirst": "CN first",
|
|
10191
|
+
"row.cliBridgeRegionOrderGlobalFirst": "Global first",
|
|
10192
|
+
"row.cliBridgeRegionOrderCnOnly": "CN only",
|
|
10193
|
+
"row.cliBridgeRegionOrderGlobalOnly": "Global only",
|
|
10194
|
+
"row.cliBridgePickersHint": "All three pickers write the global cliBridge block; \"CLI default\" on model and effort is an explicit empty value, not \"unset\" — the CLI then uses its own defaults; the region picker writes cliBridge.regionOrder.",
|
|
9566
10195
|
"row.imageToolLabel": "Let the model draw images",
|
|
9567
10196
|
"row.imageToolHint": "Adds the workbuddy_draw_image tool, which the MODEL may call on its own — about 5.71 credits per image, with no per-call confirmation. Off means the tool is not registered at all.",
|
|
9568
10197
|
"row.showCreditsCn": "Show credits in the composer toolbar",
|
|
@@ -9596,6 +10225,11 @@ window.__ModuleLoader__.load({
|
|
|
9596
10225
|
"composer.refreshFailed": "Could not refresh credits",
|
|
9597
10226
|
"composer.lastRefresh": "Last refreshed",
|
|
9598
10227
|
"composer.panelTitle": "WorkBuddy credits",
|
|
10228
|
+
"composer.newModels": "new models",
|
|
10229
|
+
"composer.newModelsRow": "Models first seen by this machine",
|
|
10230
|
+
"composer.newModelsTitle": "Models first seen by this machine: ",
|
|
10231
|
+
"composer.newModelsSep": ", ",
|
|
10232
|
+
"composer.newModelsNote": "\"New\" means this machine recorded the model id for the first time; it says nothing about when the upstream listed it. Only models first seen within the last 7 days are shown.",
|
|
9599
10233
|
"sidebar.creditsCn": "WB CN credits",
|
|
9600
10234
|
"sidebar.creditsGlobal": "WB AI credits",
|
|
9601
10235
|
"sidebar.refresh": "Refresh",
|
|
@@ -9822,6 +10456,8 @@ window.__ModuleLoader__.load({
|
|
|
9822
10456
|
"row.poolCheckedIn": "已签到",
|
|
9823
10457
|
"row.poolNotCheckedIn": "未签到",
|
|
9824
10458
|
"row.poolCheckinUnknown": "—",
|
|
10459
|
+
"row.probeAccountRestricted": "账号被上游限制使用",
|
|
10460
|
+
"row.poolCheckinEnterprise": "不适用(企业版)",
|
|
9825
10461
|
"row.poolBenefitClaimable": "{title}:{count} 积分",
|
|
9826
10462
|
"row.poolBenefitUrgent": "{title}:{count} 积分 · 剩 {days} 天",
|
|
9827
10463
|
"row.poolBenefitDueToday": "{title}:{count} 积分 · 今天截止",
|
|
@@ -9837,6 +10473,7 @@ window.__ModuleLoader__.load({
|
|
|
9837
10473
|
"row.poolExcludedModelBusy": "模型繁忙",
|
|
9838
10474
|
"row.poolExcludedOutOfCredit": "积分耗尽",
|
|
9839
10475
|
"row.poolExcludedRejected": "被拒绝",
|
|
10476
|
+
"row.poolExcludedAccountRestricted": "账号被上游限制使用",
|
|
9840
10477
|
"row.poolExcludedUnusable": "不可用",
|
|
9841
10478
|
"row.poolRetryAt": "{at} 之后可再用",
|
|
9842
10479
|
"row.poolRetryUnknown": "上游未给出何时恢复",
|
|
@@ -9942,6 +10579,22 @@ window.__ModuleLoader__.load({
|
|
|
9942
10579
|
"row.poolRowRemoveDesktopOnly": "{accountName}:已从列表中隐藏——这个账号来自桌面 App 自己的登录,该登录未被改动。点「重新检测账号」会重新加入。",
|
|
9943
10580
|
"row.cliBridgeLabel": "允许调用本机 CodeBuddy CLI 干活",
|
|
9944
10581
|
"row.cliBridgeHint": "开启后多出 codebuddy_ask 工具与 /codebuddy 指令;每次调用花 CodeBuddy 配额。injectResult 只影响 /codebuddy:其结果回注会话,会话空闲时多花一次 DSH 回合;工具调用不读它。限流:每会话每小时 6 回合、每进程每小时 20 回合、同时只跑一个回合。默认关闭。",
|
|
10582
|
+
"row.cliBridgeModelLabel": "CLI 模型",
|
|
10583
|
+
"row.cliBridgeModelDefault": "CLI 默认",
|
|
10584
|
+
"row.cliBridgeModelAria": "本地 CodeBuddy CLI 运行所用的模型",
|
|
10585
|
+
"row.cliBridgeModelHint": "写入 cliBridge.model,CLI 以 --model 接收;\"CLI 默认\"表示沿用 CLI 自身默认。列表来自当前页签同步到的目录。",
|
|
10586
|
+
"row.cliBridgeEffortLabel": "CLI 档位",
|
|
10587
|
+
"row.cliBridgeEffortDefault": "CLI 默认",
|
|
10588
|
+
"row.cliBridgeEffortAria": "本地 CodeBuddy CLI 的推理档位",
|
|
10589
|
+
"row.cliBridgeEffortHint": "写入 cliBridge.effort,CLI 以 --effort 接收;\"CLI 默认\"表示沿用 CLI 自身默认。所选模型列出支持档位时只给出这些档位。",
|
|
10590
|
+
"row.cliBridgeRegionOrderLabel": "区域顺序",
|
|
10591
|
+
"row.cliBridgeRegionOrderAria": "本地 CodeBuddy CLI 遍历凭据区域的顺序",
|
|
10592
|
+
"row.cliBridgeRegionOrderHint": "写入 cliBridge.regionOrder:CLI 按此顺序尝试 WorkBuddy 凭据,直到登录成功;实际使用的区域在工具输出里以 credential_region 报告。自下次调用起生效。",
|
|
10593
|
+
"row.cliBridgeRegionOrderCnFirst": "国内优先",
|
|
10594
|
+
"row.cliBridgeRegionOrderGlobalFirst": "国际优先",
|
|
10595
|
+
"row.cliBridgeRegionOrderCnOnly": "仅国内",
|
|
10596
|
+
"row.cliBridgeRegionOrderGlobalOnly": "仅国际",
|
|
10597
|
+
"row.cliBridgePickersHint": "三个选择都写入全局 cliBridge 块;模型/档位的\"CLI 默认\"是显式的空值而非未设置,届时 CLI 使用自己的默认;区域选择写入 cliBridge.regionOrder。",
|
|
9945
10598
|
"row.imageToolLabel": "允许模型自主生图",
|
|
9946
10599
|
"row.imageToolHint": "开启后模型可以自己调用 workbuddy_draw_image 画图——约 5.71 积分/张,没有逐次确认。关闭时该工具根本不会注册。",
|
|
9947
10600
|
"row.showCreditsCn": "在输入框工具栏显示积分读数",
|
|
@@ -9975,6 +10628,11 @@ window.__ModuleLoader__.load({
|
|
|
9975
10628
|
"composer.refreshFailed": "积分刷新失败",
|
|
9976
10629
|
"composer.lastRefresh": "最近刷新",
|
|
9977
10630
|
"composer.panelTitle": "WorkBuddy 积分",
|
|
10631
|
+
"composer.newModels": "新模型",
|
|
10632
|
+
"composer.newModelsRow": "本机首次见到的模型",
|
|
10633
|
+
"composer.newModelsTitle": "本机首次见到的模型:",
|
|
10634
|
+
"composer.newModelsSep": "、",
|
|
10635
|
+
"composer.newModelsNote": "「新」指本机首次记到它,与上游何时上架无关。只列最近 7 天内首见的。",
|
|
9978
10636
|
"sidebar.creditsCn": "WB CN 积分",
|
|
9979
10637
|
"sidebar.creditsGlobal": "WB AI 积分",
|
|
9980
10638
|
"sidebar.refresh": "刷新",
|