dsh-xdpool 2.5.0 → 2.6.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 +158 -0
- package/README.en.md +18 -4
- package/README.md +19 -5
- package/assets/community-qr-qq.jpg +0 -0
- package/assets/community-qr-wechat.jpg +0 -0
- package/cordis.patch.yml +17 -41
- package/lib/bin.js +604 -87
- package/lib/client.js +1227 -891
- package/lib/index.d.ts +430 -143
- package/lib/index.js +2388 -1361
- package/package.json +1 -4
- package/vendor/channel-pack/pack.js +222 -16
package/lib/client.js
CHANGED
|
@@ -45,6 +45,34 @@ window.__ModuleLoader__.load({
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
//#endregion
|
|
48
|
+
//#region src/client/distribution.ts
|
|
49
|
+
/** Display order: the default first, then the rest by how often they are wanted. */
|
|
50
|
+
const DIST_OPTIONS = [
|
|
51
|
+
"sticky",
|
|
52
|
+
"priority",
|
|
53
|
+
"balanced",
|
|
54
|
+
"round-robin",
|
|
55
|
+
"expiry"
|
|
56
|
+
];
|
|
57
|
+
/** Localized name of one distribution mode. */
|
|
58
|
+
function distLabel(option, t) {
|
|
59
|
+
const at = t;
|
|
60
|
+
if (option === "sticky") return at?.("row.distSticky") ?? "Per conversation";
|
|
61
|
+
if (option === "priority") return at?.("row.distPriority") ?? "Priority";
|
|
62
|
+
if (option === "balanced") return at?.("row.distBalanced") ?? "Balanced";
|
|
63
|
+
if (option === "expiry") return at?.("row.distExpiry") ?? "Expiring first";
|
|
64
|
+
return at?.("row.distRoundRobin") ?? "Round-robin";
|
|
65
|
+
}
|
|
66
|
+
/** One-line explanation of what a mode does to caching and spend. */
|
|
67
|
+
function distHint(option, t) {
|
|
68
|
+
const at = t;
|
|
69
|
+
if (option === "sticky") return at?.("row.distStickyHint") ?? "";
|
|
70
|
+
if (option === "priority") return at?.("row.distPriorityHint") ?? "";
|
|
71
|
+
if (option === "balanced") return at?.("row.distBalancedHint") ?? "";
|
|
72
|
+
if (option === "expiry") return at?.("row.distExpiryHint") ?? "";
|
|
73
|
+
return at?.("row.distRoundRobinHint") ?? "";
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
48
76
|
//#region src/status-paths.ts
|
|
49
77
|
/**
|
|
50
78
|
* Node-free constants and types shared by the Host and browser halves of the
|
|
@@ -200,22 +228,6 @@ window.__ModuleLoader__.load({
|
|
|
200
228
|
*/
|
|
201
229
|
const POOL_RELAY_PATH = "/plugins/dsh-xdpool/relay";
|
|
202
230
|
/**
|
|
203
|
-
* What the locally running billion-context proxy is doing, read-only.
|
|
204
|
-
*
|
|
205
|
-
* Its own route because the answer comes from ANOTHER plugin: billion-context
|
|
206
|
-
* runs its own loopback listener and publishes its own JSON, so this route only
|
|
207
|
-
* finds that listener and relays a filtered subset. Nothing here is folded into
|
|
208
|
-
* {@link POOL_STATUS_PATH}: the pool card polls that every 30 seconds, and a
|
|
209
|
-
* third-party proxy's session table is not something every pool poll should
|
|
210
|
-
* carry.
|
|
211
|
-
*
|
|
212
|
-
* The word "read-only" is load-bearing on both ends. This plugin sends GETs
|
|
213
|
-
* only — it never configures, restarts or writes to the proxy — and the payload
|
|
214
|
-
* it relays deliberately omits every field that could carry conversation
|
|
215
|
-
* material.
|
|
216
|
-
*/
|
|
217
|
-
const POOL_BILLION_PATH = "/plugins/dsh-xdpool/billion";
|
|
218
|
-
/**
|
|
219
231
|
* The pack's own management route, which the card posts to directly.
|
|
220
232
|
*
|
|
221
233
|
* Not a re-export of anything: the vendored bundle registers this path itself
|
|
@@ -243,6 +255,13 @@ window.__ModuleLoader__.load({
|
|
|
243
255
|
* instead of a second one that could quietly drift.
|
|
244
256
|
*/
|
|
245
257
|
const POOL_USAGE_PATH = "/plugins/dsh-xdpool/usage";
|
|
258
|
+
Object.freeze([
|
|
259
|
+
"priority",
|
|
260
|
+
"round-robin",
|
|
261
|
+
"balanced",
|
|
262
|
+
"sticky",
|
|
263
|
+
"expiry"
|
|
264
|
+
]);
|
|
246
265
|
/**
|
|
247
266
|
* The schedule every automation job falls back to.
|
|
248
267
|
*
|
|
@@ -509,6 +528,14 @@ window.__ModuleLoader__.load({
|
|
|
509
528
|
.dsm-xdpool-models-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
|
|
510
529
|
.dsm-xdpool-models-summary{margin:2px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
511
530
|
.dsm-xdpool-model-list{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;overflow:hidden}
|
|
531
|
+
/* The expander under a collapsed model list. A full-width quiet strip rather
|
|
532
|
+
than a floating link: it belongs to the list above it, and a centred pill
|
|
533
|
+
would read as a separate action. */
|
|
534
|
+
.dsm-xdpool-model-more{margin-top:8px;width:100%;appearance:none;padding:8px 12px;border-radius:10px;border:1px dashed color-mix(in srgb, var(--dsw-alias-border-l2,#36373b) 92%, transparent);background:transparent;color:var(--dsw-alias-label-secondary,#c6c9d0);font:inherit;font-size:12px;line-height:17px;font-weight:600;cursor:pointer;transition:color .18s ease, border-color .18s ease, background .18s ease}
|
|
535
|
+
.dsm-xdpool-model-more:hover{color:var(--dsw-alias-state-business-primary,#5686fe);border-color:color-mix(in srgb, var(--dsw-alias-state-business-primary,#5686fe) 48%, transparent);background:color-mix(in srgb, var(--dsw-alias-state-business-primary,#5686fe) 7%, transparent)}
|
|
536
|
+
/* "N saved models not offered right now": the one line that stops a roster
|
|
537
|
+
change from reading as lost settings. Amber, not red — nothing is broken. */
|
|
538
|
+
.dsm-xdpool-region-pane-warn{font-size:11px;line-height:15px;color:color-mix(in srgb, #f0a441 78%, var(--dsw-alias-label-secondary,#c6c9d0));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:210px;cursor:help}
|
|
512
539
|
.dsm-xdpool-model{display:grid;grid-template-columns:minmax(0,1fr);gap:7px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2,#232529);transition:opacity .16s}
|
|
513
540
|
.dsm-xdpool-model+.dsm-xdpool-model{border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
|
|
514
541
|
.dsm-xdpool-model-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
|
|
@@ -564,6 +591,17 @@ window.__ModuleLoader__.load({
|
|
|
564
591
|
/* Automation: run-now button and the per-job progress line. */
|
|
565
592
|
.dsm-xdpool-auto-run{flex:none}
|
|
566
593
|
.dsm-xdpool-auto-job-detail{grid-column:1/-1;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:16px}
|
|
594
|
+
/* Per-item counts, grouped by status. Deliberately quieter than the detail
|
|
595
|
+
line above: the sentences are the narrative, these are the tally. Colour
|
|
596
|
+
carries the meaning so four chips stay scannable — grey for a non-event, amber
|
|
597
|
+
for "not yet" (the only one that implies something may still happen), red only
|
|
598
|
+
for a real failure. */
|
|
599
|
+
.dsm-xdpool-auto-job-items{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
|
|
600
|
+
.dsm-xdpool-auto-job-item{appearance:none;padding:1px 7px;border-radius:999px;border:1px solid transparent;font-size:10px;line-height:15px;font-weight:600;letter-spacing:.01em;cursor:default;background:color-mix(in srgb, var(--dsw-alias-label-tertiary,#9aa0a8) 12%, transparent);color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
601
|
+
.dsm-xdpool-auto-job-item-claimed{background:color-mix(in srgb, #3ec98a 16%, transparent);border-color:color-mix(in srgb, #3ec98a 38%, transparent);color:color-mix(in srgb, #3ec98a 86%, var(--dsw-alias-label-primary,#e9ebef))}
|
|
602
|
+
.dsm-xdpool-auto-job-item-already{background:color-mix(in srgb, var(--dsw-alias-label-tertiary,#9aa0a8) 10%, transparent);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
603
|
+
.dsm-xdpool-auto-job-item-skipped{background:color-mix(in srgb, #f0a441 14%, transparent);border-color:color-mix(in srgb, #f0a441 34%, transparent);color:color-mix(in srgb, #f0a441 82%, var(--dsw-alias-label-primary,#e9ebef))}
|
|
604
|
+
.dsm-xdpool-auto-job-item-failed{background:color-mix(in srgb, #ef5f5f 16%, transparent);border-color:color-mix(in srgb, #ef5f5f 40%, transparent);color:color-mix(in srgb, #ef5f5f 86%, var(--dsw-alias-label-primary,#e9ebef))}
|
|
567
605
|
/* Credit packages ride on the figures line above the check-in row. */
|
|
568
606
|
.dsm-xdpool-panel-packages{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
|
|
569
607
|
/* Check-in: the action and its state on ONE line. It was a centred column under
|
|
@@ -1150,6 +1188,38 @@ window.__ModuleLoader__.load({
|
|
|
1150
1188
|
.dsm-xdpool-star svg{fill:currentColor;transition:transform .5s cubic-bezier(.34,1.56,.64,1)}
|
|
1151
1189
|
.dsm-xdpool-star:hover svg{transform:rotate(72deg) scale(1.12)}
|
|
1152
1190
|
.dsm-xdpool-star:active svg{transform:rotate(72deg) scale(.94)}
|
|
1191
|
+
/* The group button, next to the star, sharing the brand row.
|
|
1192
|
+
The nav is a grid of 1fr auto; a second control in the same cell would
|
|
1193
|
+
STACK on top of the star rather than sit beside it (grid items in one cell
|
|
1194
|
+
overlap). So the track list grows to three: brand | star | group, and both
|
|
1195
|
+
buttons are pinned to row 1 on their own columns. Auto-placement alone puts
|
|
1196
|
+
them under the tab strip, because the strip comes first in the DOM. */
|
|
1197
|
+
.dsm-xdpool-nav{grid-template-columns:1fr auto auto}
|
|
1198
|
+
.dsm-xdpool-star{grid-column:2;grid-row:1}
|
|
1199
|
+
.dsm-xdpool-group{grid-column:3;grid-row:1;margin-left:6px;cursor:pointer;font:inherit;font-size:12.5px;line-height:17px;font-weight:650;color:color-mix(in srgb, var(--dsw-alias-state-business-primary,#5686fe) 82%, var(--dsw-alias-label-secondary,#c6c9d0));border-color:color-mix(in srgb, var(--dsw-alias-border-l2,#ffffff1f) 88%, transparent);background:linear-gradient(140deg, color-mix(in srgb, var(--dsw-alias-bg-layer-3,#23252b) 72%, transparent), transparent 70%)}
|
|
1200
|
+
.dsm-xdpool-group:hover{transform:translateY(-1px);color:var(--dsw-alias-state-business-primary,#5686fe);box-shadow:0 0 0 4px color-mix(in srgb, var(--dsw-alias-state-business-primary,#5686fe) 9%, transparent), 0 8px 22px rgb(0 0 0 / 22%)}
|
|
1201
|
+
/* The pulse is on the icon only, and slow: this button is a door, not a call to
|
|
1202
|
+
action, and a card that nags is a card people mute. */
|
|
1203
|
+
.dsm-xdpool-group:hover svg{transform:scale(1.1)}
|
|
1204
|
+
.dsm-xdpool-group svg{fill:currentColor;transition:transform .28s cubic-bezier(.34,1.56,.64,1)}
|
|
1205
|
+
/* The invite dialog. Positioned against the shell (which is the containing
|
|
1206
|
+
block) so it covers the card and nothing of the host dialog around it. */
|
|
1207
|
+
.dsm-xdpool-groupmask{position:absolute;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;padding:16px;border-radius:16px;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#191a1e) 72%, transparent)}
|
|
1208
|
+
.dsm-xdpool-groupcard{display:flex;flex-direction:column;align-items:center;gap:10px;max-width:520px;padding:16px 18px 18px;border-radius:16px;border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2,#ffffff1f) 86%, transparent);background:var(--dsw-alias-bg-layer-2,#1c1e22);box-shadow:0 18px 48px rgb(0 0 0 / 34%), inset 0 1px 0 rgb(255 255 255 / 7%)}
|
|
1209
|
+
/* The two codes sit side by side and WRAP to a column before either shrinks:
|
|
1210
|
+
a QR squeezed narrower than its module grid is a QR that stops scanning, so
|
|
1211
|
+
the layout gives up the row rather than the size. */
|
|
1212
|
+
.dsm-xdpool-groupcodes{display:flex;flex-wrap:wrap;justify-content:center;gap:14px;width:100%}
|
|
1213
|
+
.dsm-xdpool-groupcode{display:flex;flex-direction:column;align-items:center;gap:6px;flex:0 0 auto}
|
|
1214
|
+
.dsm-xdpool-groupcode-label{font-size:12px;line-height:16px;font-weight:650;color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
1215
|
+
.dsm-xdpool-groupcode-step{max-width:220px;font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary,#8a90a0);text-align:center}
|
|
1216
|
+
.dsm-xdpool-groupcardhead{display:flex;align-items:center;gap:10px;width:100%;font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
1217
|
+
.dsm-xdpool-groupclose{margin-left:auto;appearance:none;border:none;background:transparent;padding:0 4px;font:inherit;font-size:18px;line-height:18px;color:var(--dsw-alias-label-tertiary,#8a90a0);cursor:pointer;border-radius:8px}
|
|
1218
|
+
.dsm-xdpool-groupclose:hover{color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
1219
|
+
/* A white plate under the code, always: a QR inverted onto a dark panel scans
|
|
1220
|
+
on some phones and not others, and there is no way to tell which from here. */
|
|
1221
|
+
.dsm-xdpool-groupqr{display:block;width:220px;height:220px;border-radius:12px;background:#fff;padding:6px;object-fit:contain}
|
|
1222
|
+
.dsm-xdpool-groupnote{width:100%;margin:0;font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary,#8a90a0);text-align:center}
|
|
1153
1223
|
.dsm-xdpool-navtabs{grid-column:1/-1;position:relative;display:flex;flex-wrap:wrap;gap:2px;padding:3px;border-radius:13px;border:1px solid color-mix(in srgb, var(--dsw-alias-border-l1,#ffffff14) 80%, transparent);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#191a1e) 62%, transparent);box-shadow:inset 0 1px 2px rgb(0 0 0 / 4%)}
|
|
1154
1224
|
/* A raised surface, not a tinted wash: the glider has to read as "this one is
|
|
1155
1225
|
sitting on top of the strip" from across the panel, and a wash alone is
|
|
@@ -1197,29 +1267,6 @@ window.__ModuleLoader__.load({
|
|
|
1197
1267
|
/* Long model ids and account emails get clipped rather than wrapped: a wrapped
|
|
1198
1268
|
number column breaks the alignment that makes it scannable. */
|
|
1199
1269
|
|
|
1200
|
-
`.trim();
|
|
1201
|
-
const BILLION_CARD_CSS = `
|
|
1202
|
-
|
|
1203
|
-
.dsm-xdpool-steps{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
1204
|
-
.dsm-xdpool-steps code{font-family:var(--dsw-alias-font-mono,ui-monospace,Menlo,Consolas,monospace);font-size:11px;background:color-mix(in srgb, var(--dsw-alias-bg-layer-3,#26282e) 80%, transparent);border-radius:6px;padding:2px 6px}
|
|
1205
|
-
/* A session with requests still in flight. The dot is the only motion on this
|
|
1206
|
-
page; nothing animates while idle, so a still dot means still idle. */
|
|
1207
|
-
.dsm-xdpool-live{display:inline-block;width:6px;height:6px;border-radius:50%;margin-left:6px;vertical-align:middle;background:var(--dsw-alias-state-success,#22c55e)}
|
|
1208
|
-
/* Section header that carries its own controls: title on the left, actions on
|
|
1209
|
-
the right, both on one baseline. */
|
|
1210
|
-
.dsm-xdpool-sechead{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
|
|
1211
|
-
.dsm-xdpool-sechead .dsm-xdpool-sectitle{margin:0}
|
|
1212
|
-
.dsm-xdpool-secbtns{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
1213
|
-
/* A control that reads as a link-shaped button: it only navigates or reveals,
|
|
1214
|
-
it never commits state, so it stays quiet until hovered. */
|
|
1215
|
-
.dsm-xdpool-secghost{appearance:none;display:inline-flex;align-items:center;font:inherit;font-size:11.5px;font-weight:560;line-height:17px;padding:3px 10px;border-radius:8px;border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2,#36373b) 85%, transparent);background:color-mix(in srgb, var(--dsw-alias-bg-layer-3,#2a2c33) 70%, transparent);color:var(--dsw-alias-label-secondary,#c6c9d0);text-decoration:none;cursor:pointer;white-space:nowrap;transition:border-color .16s ease,color .16s ease,background .16s ease,transform .18s var(--ease-instrument)}
|
|
1216
|
-
.dsm-xdpool-secghost:hover{border-color:color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 66%, var(--dsw-alias-border-l2,#36373b));color:var(--dsw-alias-label-primary,#e8e8ea);transform:translateY(-1px)}
|
|
1217
|
-
.dsm-xdpool-secghost:focus-visible{outline:2px solid color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 78%, transparent);outline-offset:2px;box-shadow:0 0 0 4px color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 13%, transparent)}
|
|
1218
|
-
/* The session id itself. A deep link into another plugin's UI, so it has to
|
|
1219
|
-
read as a link without pretending to be the pool's own navigation. */
|
|
1220
|
-
.dsm-xdpool-sesslink{color:color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 88%, var(--dsw-alias-label-primary,#e8e8ea));text-decoration:none;border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 30%, transparent);transition:color .16s ease,border-color .16s ease}
|
|
1221
|
-
.dsm-xdpool-sesslink:hover{color:var(--dsw-alias-brand-primary,#5686fe);border-bottom-color:color-mix(in srgb, var(--dsw-alias-brand-primary,#5686fe) 70%, transparent)}
|
|
1222
|
-
|
|
1223
1270
|
`.trim();
|
|
1224
1271
|
const ROUTING_CARD_CSS = `
|
|
1225
1272
|
|
|
@@ -1239,6 +1286,22 @@ window.__ModuleLoader__.load({
|
|
|
1239
1286
|
/* Members render as an ordered, numbered list: order IS the semantics of a
|
|
1240
1287
|
chain, so a two-column tag cloud would throw away the thing that matters. */
|
|
1241
1288
|
.dsm-xdpool-rt-members{display:flex;flex-direction:column;gap:5px;margin:0;padding:0;list-style:none}
|
|
1289
|
+
/* Collapsed chain: one capsule per platform, numbered by priority.
|
|
1290
|
+
A wrapping row, not a list — these are labels to scan, not rows to operate, and
|
|
1291
|
+
the count after the dot is what makes "tries TRAE first, one model there" legible
|
|
1292
|
+
without opening the editor. */
|
|
1293
|
+
.dsm-xdpool-rt-lanes{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}
|
|
1294
|
+
.dsm-xdpool-rt-lane{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#36373b);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#191a1e) 62%, transparent);font-size:12px;line-height:18px}
|
|
1295
|
+
.dsm-xdpool-rt-lanename{color:var(--dsw-alias-label-primary,#e9ebef);font-weight:560;max-width:210px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1296
|
+
.dsm-xdpool-rt-lanecount{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-variant-numeric:tabular-nums}
|
|
1297
|
+
/* Editor: one box per platform, visually nested so "these models belong to that
|
|
1298
|
+
platform" is spatial rather than something the reader has to infer from ids. */
|
|
1299
|
+
.dsm-xdpool-rt-laneboxes{display:flex;flex-direction:column;gap:10px}
|
|
1300
|
+
.dsm-xdpool-rt-lanebox{display:flex;flex-direction:column;gap:8px;padding:11px 12px;border-radius:12px;border:1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary,#5686fe) 26%, var(--dsw-alias-border-l2,#36373b));background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#191a1e) 58%, transparent)}
|
|
1301
|
+
.dsm-xdpool-rt-lanebox-head{display:flex;align-items:center;gap:8px;min-width:0}
|
|
1302
|
+
.dsm-xdpool-rt-lanebox-name{font-size:13px;line-height:19px;font-weight:640;color:var(--dsw-alias-label-primary,#e9ebef);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1303
|
+
.dsm-xdpool-rt-laneacts{display:flex;flex-wrap:wrap;gap:6px}
|
|
1304
|
+
.dsm-xdpool-rt-laneacts .dsm-xdpool-rt-drop{min-width:0}
|
|
1242
1305
|
.dsm-xdpool-rt-member{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:9px;border:1px solid var(--dsw-alias-border-l2,#36373b);background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#191a1e) 72%, transparent)}
|
|
1243
1306
|
.dsm-xdpool-rt-ord{flex:none;width:19px;height:19px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:10px;line-height:19px;color:var(--dsw-alias-label-tertiary,#8a90a0);border:1px solid var(--dsw-alias-border-l2,#36373b);font-variant-numeric:tabular-nums}
|
|
1244
1307
|
.dsm-xdpool-rt-mid{flex:1 1 auto;min-width:0;font-family:var(--dsw-alias-font-mono,ui-monospace,Menlo,Consolas,monospace);font-size:11px;line-height:17px;color:var(--dsw-alias-label-primary,#e8e8ea);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -1408,9 +1471,8 @@ window.__ModuleLoader__.load({
|
|
|
1408
1471
|
.dsm-btn-primary:hover:not(:disabled){opacity:.9}
|
|
1409
1472
|
/* The borderless third of the button family: links out to a vendor's console
|
|
1410
1473
|
and the destructive "forget this key" action. It had no rule at all, so the
|
|
1411
|
-
aggregate row's three links
|
|
1412
|
-
|
|
1413
|
-
looking like the primary one. */
|
|
1474
|
+
aggregate row's three links rendered as bare browser buttons — which is how a
|
|
1475
|
+
secondary action ends up looking like the primary one. */
|
|
1414
1476
|
.dsm-btn-ghost{border-color:transparent;background:transparent;color:var(--dsw-alias-label-tertiary,#8a90a0)}
|
|
1415
1477
|
.dsm-btn-ghost:hover:not(:disabled){border-color:var(--dsw-alias-border-l2,#36373b);color:var(--dsw-alias-label-primary,#e8e8ea);background:rgba(255,255,255,.04);text-decoration:none}
|
|
1416
1478
|
.dsm-xdpool-panel-empty{color:var(--dsw-alias-label-tertiary,#999);font-size:14px;line-height:20px}
|
|
@@ -1443,6 +1505,14 @@ window.__ModuleLoader__.load({
|
|
|
1443
1505
|
.dsm-xdpool-table td{padding:7px 10px;border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1,#ffffff14) 62%, transparent);color:var(--dsw-alias-label-secondary,#c6c9d0);font-variant-numeric:tabular-nums}
|
|
1444
1506
|
.dsm-xdpool-table tbody tr:last-child td{border-bottom:none}
|
|
1445
1507
|
.dsm-xdpool-table tbody tr[data-ok="false"] td{color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
1508
|
+
/* Numeric columns, header AND cells together.
|
|
1509
|
+
The bare .dsm-xdpool-num rule could never win: .dsm-xdpool-table th is (0,1,1)
|
|
1510
|
+
and .dsm-xdpool-num is (0,1,0), so every numeric HEADER stayed left-aligned
|
|
1511
|
+
while its cells went right — the column read as shifted against its own title.
|
|
1512
|
+
Both selectors below out-specify the th rule, so the pair moves together and
|
|
1513
|
+
the table lines up. */
|
|
1514
|
+
.dsm-xdpool-table th.dsm-xdpool-num,
|
|
1515
|
+
.dsm-xdpool-table td.dsm-xdpool-num,
|
|
1446
1516
|
.dsm-xdpool-num{text-align:right;font-variant-numeric:tabular-nums}
|
|
1447
1517
|
.dsm-xdpool-ellipsis{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1448
1518
|
|
|
@@ -1743,6 +1813,12 @@ window.__ModuleLoader__.load({
|
|
|
1743
1813
|
*/
|
|
1744
1814
|
const POOL_CARD_AVATAR = "data:image/jpeg;base64,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";
|
|
1745
1815
|
//#endregion
|
|
1816
|
+
//#region src/client/community-qr.ts
|
|
1817
|
+
/** WeChat bot's contact code. Scanning adds the bot; send `XD` to be pulled in. */
|
|
1818
|
+
const COMMUNITY_QR_WECHAT = "data:image/png;base64,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";
|
|
1819
|
+
/** QQ group join code. Scanning is joining — no message to send. */
|
|
1820
|
+
const COMMUNITY_QR_QQ = "data:image/png;base64,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";
|
|
1821
|
+
//#endregion
|
|
1746
1822
|
//#region src/client/shell.tsx
|
|
1747
1823
|
/**
|
|
1748
1824
|
* The card shell: brand bar, page tabs, per-page hero.
|
|
@@ -1769,7 +1845,8 @@ window.__ModuleLoader__.load({
|
|
|
1769
1845
|
* highlight on the correct button at the correct x, in the wrong row.
|
|
1770
1846
|
*
|
|
1771
1847
|
* 3. **Nothing here is a gate.** The star button opens the repository in a new
|
|
1772
|
-
* tab and nothing else
|
|
1848
|
+
* tab and nothing else, and the group button beside it opens a dialog with
|
|
1849
|
+
* the WeChat invite code. A settings card that can refuse to render is a card
|
|
1773
1850
|
* that, on a machine without outbound network, looks exactly like a broken
|
|
1774
1851
|
* plugin.
|
|
1775
1852
|
*
|
|
@@ -1863,6 +1940,26 @@ window.__ModuleLoader__.load({
|
|
|
1863
1940
|
function PoolShell({ t, version, avatar, tabs, active, onSelect, hero, children }) {
|
|
1864
1941
|
/** Measured box of the active tab's button, in tab-strip coordinates. */
|
|
1865
1942
|
const [glider, setGlider] = (0, react.useState)(void 0);
|
|
1943
|
+
/** Whether the group-invite dialog is open. Local: nothing outside needs it. */
|
|
1944
|
+
const [groupOpen, setGroupOpen] = (0, react.useState)(false);
|
|
1945
|
+
/**
|
|
1946
|
+
* Close the dialog on Escape.
|
|
1947
|
+
*
|
|
1948
|
+
* The overlay's own click handler covers the pointer, but a keyboard user who
|
|
1949
|
+
* opened this with Enter has no other way out — and a modal with no key exit
|
|
1950
|
+
* is a trap. Bound only while open, so the listener is not paid for on every
|
|
1951
|
+
* page of a card that is mostly not showing a dialog.
|
|
1952
|
+
*/
|
|
1953
|
+
(0, react.useEffect)(() => {
|
|
1954
|
+
if (!groupOpen) return void 0;
|
|
1955
|
+
const onKey = (event) => {
|
|
1956
|
+
if (event.key === "Escape") setGroupOpen(false);
|
|
1957
|
+
};
|
|
1958
|
+
window.addEventListener("keydown", onKey);
|
|
1959
|
+
return () => {
|
|
1960
|
+
window.removeEventListener("keydown", onKey);
|
|
1961
|
+
};
|
|
1962
|
+
}, [groupOpen]);
|
|
1866
1963
|
const tabRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1867
1964
|
const tabsRef = (0, react.useRef)(null);
|
|
1868
1965
|
const measure = (0, react.useCallback)(() => {
|
|
@@ -1942,6 +2039,28 @@ window.__ModuleLoader__.load({
|
|
|
1942
2039
|
})
|
|
1943
2040
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("shell.star") ?? "Star on GitHub" })]
|
|
1944
2041
|
}),
|
|
2042
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2043
|
+
type: "button",
|
|
2044
|
+
className: "dsm-xdpool-star dsm-xdpool-group",
|
|
2045
|
+
title: t?.("shell.groupHint") ?? "扫码加入 XD 交流群",
|
|
2046
|
+
onClick: () => {
|
|
2047
|
+
setGroupOpen(true);
|
|
2048
|
+
},
|
|
2049
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2050
|
+
width: "15",
|
|
2051
|
+
height: "15",
|
|
2052
|
+
viewBox: "0 0 24 24",
|
|
2053
|
+
"aria-hidden": "true",
|
|
2054
|
+
focusable: "false",
|
|
2055
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2056
|
+
fill: "currentColor",
|
|
2057
|
+
d: "M9.1 3.4c-3.6 0-6.5 2.4-6.5 5.4 0 1.7 1 3.2 2.6 4.2l-.7 2.1 2.4-1.2c.7.2 1.4.3 2.2.3h.4a5.6 5.6 0 0 1-.2-1.5c0-3 2.9-5.4 6.5-5.4h.5C15.7 5.1 12.7 3.4 9.1 3.4zm-2.3 2.9a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8zm4.6 0a.9.9 0 1 1 0 1.8.9.9 0 0 1 0-1.8z"
|
|
2058
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2059
|
+
fill: "currentColor",
|
|
2060
|
+
d: "M21.4 12.7c0-2.5-2.4-4.5-5.4-4.5s-5.4 2-5.4 4.5 2.4 4.5 5.4 4.5c.6 0 1.2-.1 1.8-.2l2 1-.6-1.7c1.4-.9 2.2-2.2 2.2-3.6zm-7.2-1.4a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6zm3.6 0a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"
|
|
2061
|
+
})]
|
|
2062
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("shell.group") ?? "交流群" })]
|
|
2063
|
+
}),
|
|
1945
2064
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1946
2065
|
className: "dsm-xdpool-navtabs",
|
|
1947
2066
|
role: "tablist",
|
|
@@ -1976,6 +2095,81 @@ window.__ModuleLoader__.load({
|
|
|
1976
2095
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1977
2096
|
className: "dsm-xdpool-body",
|
|
1978
2097
|
children
|
|
2098
|
+
}),
|
|
2099
|
+
!groupOpen ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2100
|
+
className: "dsm-xdpool-groupmask",
|
|
2101
|
+
role: "dialog",
|
|
2102
|
+
"aria-modal": "true",
|
|
2103
|
+
"aria-label": t?.("shell.groupTitle") ?? "XD 交流群",
|
|
2104
|
+
onClick: () => {
|
|
2105
|
+
setGroupOpen(false);
|
|
2106
|
+
},
|
|
2107
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2108
|
+
className: "dsm-xdpool-groupcard",
|
|
2109
|
+
onClick: (event) => {
|
|
2110
|
+
event.stopPropagation();
|
|
2111
|
+
},
|
|
2112
|
+
children: [
|
|
2113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2114
|
+
className: "dsm-xdpool-groupcardhead",
|
|
2115
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t?.("shell.groupTitle") ?? "XD 交流群" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2116
|
+
type: "button",
|
|
2117
|
+
className: "dsm-xdpool-groupclose",
|
|
2118
|
+
"aria-label": t?.("shell.groupClose") ?? "关闭",
|
|
2119
|
+
onClick: () => {
|
|
2120
|
+
setGroupOpen(false);
|
|
2121
|
+
},
|
|
2122
|
+
children: "×"
|
|
2123
|
+
})]
|
|
2124
|
+
}),
|
|
2125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2126
|
+
className: "dsm-xdpool-groupcodes",
|
|
2127
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2128
|
+
className: "dsm-xdpool-groupcode",
|
|
2129
|
+
children: [
|
|
2130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
2131
|
+
className: "dsm-xdpool-groupqr",
|
|
2132
|
+
src: COMMUNITY_QR_WECHAT,
|
|
2133
|
+
alt: t?.("shell.groupWechatAlt") ?? "微信机器人二维码:扫码添加后发送 XD,即可被拉进群",
|
|
2134
|
+
width: 220,
|
|
2135
|
+
height: 220
|
|
2136
|
+
}),
|
|
2137
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2138
|
+
className: "dsm-xdpool-groupcode-label",
|
|
2139
|
+
children: t?.("shell.groupWechat") ?? "微信(机器人)"
|
|
2140
|
+
}),
|
|
2141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2142
|
+
className: "dsm-xdpool-groupcode-step",
|
|
2143
|
+
children: t?.("shell.groupWechatStep") ?? "扫码添加机器人后,发送「XD」拉你进群"
|
|
2144
|
+
})
|
|
2145
|
+
]
|
|
2146
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2147
|
+
className: "dsm-xdpool-groupcode",
|
|
2148
|
+
children: [
|
|
2149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
2150
|
+
className: "dsm-xdpool-groupqr",
|
|
2151
|
+
src: COMMUNITY_QR_QQ,
|
|
2152
|
+
alt: t?.("shell.groupQqAlt") ?? "QQ 群二维码:直接扫码加入 XD 交流群",
|
|
2153
|
+
width: 220,
|
|
2154
|
+
height: 220
|
|
2155
|
+
}),
|
|
2156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2157
|
+
className: "dsm-xdpool-groupcode-label",
|
|
2158
|
+
children: t?.("shell.groupQq") ?? "QQ 群"
|
|
2159
|
+
}),
|
|
2160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2161
|
+
className: "dsm-xdpool-groupcode-step",
|
|
2162
|
+
children: t?.("shell.groupQqStep") ?? "直接扫码即可加入"
|
|
2163
|
+
})
|
|
2164
|
+
]
|
|
2165
|
+
})]
|
|
2166
|
+
}),
|
|
2167
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2168
|
+
className: "dsm-xdpool-groupnote",
|
|
2169
|
+
children: t?.("shell.groupNote") ?? "两个群都在;哪个方便用哪个。二维码失效了在 GitHub 提 issue 说一声,会更新。"
|
|
2170
|
+
})
|
|
2171
|
+
]
|
|
2172
|
+
})
|
|
1979
2173
|
})
|
|
1980
2174
|
]
|
|
1981
2175
|
});
|
|
@@ -2025,7 +2219,7 @@ window.__ModuleLoader__.load({
|
|
|
2025
2219
|
*/
|
|
2026
2220
|
/** How many recent calls the detail table lists. */
|
|
2027
2221
|
const DETAIL_ROWS = 12;
|
|
2028
|
-
function fmtInt
|
|
2222
|
+
function fmtInt(value) {
|
|
2029
2223
|
return value.toLocaleString("en-US");
|
|
2030
2224
|
}
|
|
2031
2225
|
/** Milliseconds as `1.2s` / `340ms`, because `1200ms` is not a unit anyone reads. */
|
|
@@ -2034,7 +2228,7 @@ window.__ModuleLoader__.load({
|
|
|
2034
2228
|
}
|
|
2035
2229
|
/** Tokens per second, kept integer: 12.4 tok/s reads as false precision. */
|
|
2036
2230
|
function fmtTps(value) {
|
|
2037
|
-
return value === null ? "—" : `${fmtInt
|
|
2231
|
+
return value === null ? "—" : `${fmtInt(value)}`;
|
|
2038
2232
|
}
|
|
2039
2233
|
function fmtClock(at) {
|
|
2040
2234
|
const d = new Date(at);
|
|
@@ -2061,12 +2255,38 @@ window.__ModuleLoader__.load({
|
|
|
2061
2255
|
return region === "global" ? t?.("ledger.global") ?? "Global" : t?.("ledger.cn") ?? "CN";
|
|
2062
2256
|
}
|
|
2063
2257
|
/**
|
|
2258
|
+
* The channel a call went through, as the table's first column.
|
|
2259
|
+
*
|
|
2260
|
+
* A provider id (`opencode`, `raccoon`, `codearts`) is printed as-is, because that
|
|
2261
|
+
* is the same spelling the aggregate page and the model picker use — inventing a
|
|
2262
|
+
* second name here would make one vendor look like two. `workbuddy` and `free` get
|
|
2263
|
+
* a translated label since they are lanes of this plugin rather than vendors.
|
|
2264
|
+
*
|
|
2265
|
+
* Falls back to the region for a sample filed before this field existed, so an old
|
|
2266
|
+
* in-memory window still renders something true instead of a blank cell.
|
|
2267
|
+
*/
|
|
2268
|
+
function channelLabel(call, t) {
|
|
2269
|
+
const channel = call.channel ?? "";
|
|
2270
|
+
if (channel === "workbuddy" || channel === "") return regionLabel(call.region, t);
|
|
2271
|
+
if (channel === "free") return t?.("ledger.channelFree") ?? "Free lane";
|
|
2272
|
+
return channel;
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2064
2275
|
* In / out / cached for one call, or `—` when that call reported no usage.
|
|
2065
2276
|
*
|
|
2066
2277
|
* `—` rather than `0`: a call that never received a usage block is a missing
|
|
2067
2278
|
* measurement, and the model table's share column would then divide a total by
|
|
2068
2279
|
* rows that contributed nothing and quietly shrink it.
|
|
2069
2280
|
*/
|
|
2281
|
+
/**
|
|
2282
|
+
* The three numeric cells for one call: input, output, cached.
|
|
2283
|
+
*
|
|
2284
|
+
* A fixed-length TUPLE, not `string[]`, so the header can never drift out of step
|
|
2285
|
+
* with the body. The table renders these through `.map`, which means a fourth
|
|
2286
|
+
* header added without a fourth value here — or a third value dropped — would
|
|
2287
|
+
* silently shift every numeric column against its title. That is precisely the
|
|
2288
|
+
* misalignment this was rebuilt to fix, and a `string[]` type cannot see it.
|
|
2289
|
+
*/
|
|
2070
2290
|
function tokenCells(call) {
|
|
2071
2291
|
if (call.usage === void 0) return [
|
|
2072
2292
|
"—",
|
|
@@ -2074,9 +2294,9 @@ window.__ModuleLoader__.load({
|
|
|
2074
2294
|
"—"
|
|
2075
2295
|
];
|
|
2076
2296
|
return [
|
|
2077
|
-
fmtInt
|
|
2078
|
-
fmtInt
|
|
2079
|
-
fmtInt
|
|
2297
|
+
fmtInt(call.usage.inputTokens),
|
|
2298
|
+
fmtInt(call.usage.outputTokens),
|
|
2299
|
+
fmtInt(call.usage.cachedTokens)
|
|
2080
2300
|
];
|
|
2081
2301
|
}
|
|
2082
2302
|
function LedgerPage({ t, usage: shared }) {
|
|
@@ -2198,20 +2418,20 @@ window.__ModuleLoader__.load({
|
|
|
2198
2418
|
children: [
|
|
2199
2419
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2200
2420
|
label: t?.("ledger.tokens") ?? "Tokens",
|
|
2201
|
-
value: summary.usageReported ? fmtInt
|
|
2421
|
+
value: summary.usageReported ? fmtInt(totals?.input ?? 0) : t?.("ledger.notReported") ?? "not reported",
|
|
2202
2422
|
sub: summary.usageReported ? t?.("ledger.tokenSplit", {
|
|
2203
|
-
input: fmtInt
|
|
2204
|
-
output: fmtInt
|
|
2205
|
-
}) ?? `in ${fmtInt
|
|
2423
|
+
input: fmtInt(totals?.input ?? 0),
|
|
2424
|
+
output: fmtInt(totals?.output ?? 0)
|
|
2425
|
+
}) ?? `in ${fmtInt(totals?.input ?? 0)} · out ${fmtInt(totals?.output ?? 0)}` : t?.("ledger.notReportedHint") ?? "the gateway ignored stream_options.include_usage"
|
|
2206
2426
|
}),
|
|
2207
2427
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2208
2428
|
label: t?.("ledger.turns") ?? "Turns",
|
|
2209
|
-
value: fmtInt
|
|
2429
|
+
value: fmtInt(summary.turns),
|
|
2210
2430
|
sub: t?.("ledger.callsSub", { calls: summary.calls }) ?? `${summary.calls} upstream calls`
|
|
2211
2431
|
}),
|
|
2212
2432
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2213
2433
|
label: t?.("ledger.speed") ?? "Speed",
|
|
2214
|
-
value: totals?.avgTps === null || totals?.avgTps === void 0 ? "—" : `${fmtInt
|
|
2434
|
+
value: totals?.avgTps === null || totals?.avgTps === void 0 ? "—" : `${fmtInt(totals.avgTps)} tok/s`,
|
|
2215
2435
|
sub: totals?.avgTtftMs === null || totals?.avgTtftMs === void 0 ? t?.("ledger.speedNone") ?? "no timing reported" : t?.("ledger.ttft", { ms: fmtMs(totals.avgTtftMs) }) ?? `ttft ${fmtMs(totals.avgTtftMs)}`
|
|
2216
2436
|
}),
|
|
2217
2437
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
@@ -2220,11 +2440,11 @@ window.__ModuleLoader__.load({
|
|
|
2220
2440
|
const rate = cacheHitRate(summary);
|
|
2221
2441
|
return rate === null || !summary.usageReported ? "—" : `${Math.round(rate * 100)}%`;
|
|
2222
2442
|
})(),
|
|
2223
|
-
sub: summary.usageReported ? t?.("ledger.cacheSub", { cached: fmtInt
|
|
2443
|
+
sub: summary.usageReported ? t?.("ledger.cacheSub", { cached: fmtInt(summary.rows.reduce((n, r) => n + r.cachedTokens, 0)) }) ?? "" : ""
|
|
2224
2444
|
}),
|
|
2225
2445
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2226
2446
|
label: t?.("ledger.requests") ?? "Requests",
|
|
2227
|
-
value: fmtInt
|
|
2447
|
+
value: fmtInt(summary.calls),
|
|
2228
2448
|
sub: summary.failedTurns === 0 ? t?.("ledger.allOk") ?? "no turn failed outright" : t?.("ledger.failedSub", {
|
|
2229
2449
|
failed: summary.failedTurns,
|
|
2230
2450
|
recovered: summary.recoveredTurns
|
|
@@ -2246,7 +2466,7 @@ window.__ModuleLoader__.load({
|
|
|
2246
2466
|
className: "dsm-xdpool-table",
|
|
2247
2467
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
2248
2468
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colTime") ?? "Time" }),
|
|
2249
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.
|
|
2469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colChannel") ?? "Channel" }),
|
|
2250
2470
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colModel") ?? "Model" }),
|
|
2251
2471
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colAccount") ?? "Account" }),
|
|
2252
2472
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
@@ -2269,7 +2489,7 @@ window.__ModuleLoader__.load({
|
|
|
2269
2489
|
"data-ok": entry.ok,
|
|
2270
2490
|
children: [
|
|
2271
2491
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtClock(entry.at) }),
|
|
2272
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children:
|
|
2492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: channelLabel(entry, t) }),
|
|
2273
2493
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2274
2494
|
className: "dsm-xdpool-ellipsis",
|
|
2275
2495
|
title: entry.model,
|
|
@@ -2304,7 +2524,7 @@ window.__ModuleLoader__.load({
|
|
|
2304
2524
|
className: "dsm-xdpool-table",
|
|
2305
2525
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
2306
2526
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colAccount") ?? "Account" }),
|
|
2307
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.
|
|
2527
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colChannel") ?? "Channel" }),
|
|
2308
2528
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2309
2529
|
className: "dsm-xdpool-num",
|
|
2310
2530
|
children: t?.("ledger.colTurns") ?? "Turns"
|
|
@@ -2328,22 +2548,22 @@ window.__ModuleLoader__.load({
|
|
|
2328
2548
|
title: row.account,
|
|
2329
2549
|
children: row.account === "" ? t?.("ledger.unattributed") ?? "(unattributed)" : row.account
|
|
2330
2550
|
}),
|
|
2331
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children:
|
|
2551
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: channelLabel(row, t) }),
|
|
2332
2552
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2333
2553
|
className: "dsm-xdpool-num",
|
|
2334
|
-
children: fmtInt
|
|
2554
|
+
children: fmtInt(row.turns)
|
|
2335
2555
|
}),
|
|
2336
2556
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2337
2557
|
className: "dsm-xdpool-num",
|
|
2338
|
-
children: fmtInt
|
|
2558
|
+
children: fmtInt(row.calls)
|
|
2339
2559
|
}),
|
|
2340
2560
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2341
2561
|
className: "dsm-xdpool-num",
|
|
2342
|
-
children: fmtInt
|
|
2562
|
+
children: fmtInt(row.inputTokens)
|
|
2343
2563
|
}),
|
|
2344
2564
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2345
2565
|
className: "dsm-xdpool-num",
|
|
2346
|
-
children: fmtInt
|
|
2566
|
+
children: fmtInt(row.outputTokens)
|
|
2347
2567
|
}),
|
|
2348
2568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2349
2569
|
className: "dsm-xdpool-ellipsis",
|
|
@@ -2364,8 +2584,8 @@ window.__ModuleLoader__.load({
|
|
|
2364
2584
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
2365
2585
|
className: "dsm-xdpool-table",
|
|
2366
2586
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
2587
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colChannel") ?? "Channel" }),
|
|
2367
2588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colModel") ?? "Model" }),
|
|
2368
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("ledger.colRegion") ?? "Region" }),
|
|
2369
2589
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
2370
2590
|
className: "dsm-xdpool-num",
|
|
2371
2591
|
children: t?.("ledger.colCalls") ?? "Calls"
|
|
@@ -2385,15 +2605,15 @@ window.__ModuleLoader__.load({
|
|
|
2385
2605
|
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: summary.rows.map((row) => {
|
|
2386
2606
|
const share = (totals?.input ?? 0) === 0 ? 0 : Math.round(row.inputTokens / (totals?.input ?? 1) * 100);
|
|
2387
2607
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
2608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: channelLabel(row, t) }),
|
|
2388
2609
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2389
2610
|
className: "dsm-xdpool-ellipsis",
|
|
2390
2611
|
title: row.model,
|
|
2391
2612
|
children: row.model
|
|
2392
2613
|
}),
|
|
2393
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: regionLabel(row.region, t) }),
|
|
2394
2614
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2395
2615
|
className: "dsm-xdpool-num",
|
|
2396
|
-
children: fmtInt
|
|
2616
|
+
children: fmtInt(row.calls)
|
|
2397
2617
|
}),
|
|
2398
2618
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2399
2619
|
className: "dsm-xdpool-num",
|
|
@@ -2407,7 +2627,7 @@ window.__ModuleLoader__.load({
|
|
|
2407
2627
|
className: "dsm-xdpool-num",
|
|
2408
2628
|
children: summary.usageReported ? `${share}%` : "—"
|
|
2409
2629
|
})
|
|
2410
|
-
] }, `${row.region}:${row.model}`);
|
|
2630
|
+
] }, `${row.channel}:${row.region}:${row.model}`);
|
|
2411
2631
|
}) })]
|
|
2412
2632
|
})
|
|
2413
2633
|
})]
|
|
@@ -7718,6 +7938,24 @@ window.__ModuleLoader__.load({
|
|
|
7718
7938
|
"website": "https://openai.com/",
|
|
7719
7939
|
"local": false,
|
|
7720
7940
|
"source": "fcc"
|
|
7941
|
+
},
|
|
7942
|
+
{
|
|
7943
|
+
"id": "custom_openai",
|
|
7944
|
+
"name": "Custom OpenAI-compatible",
|
|
7945
|
+
"category": "apikey",
|
|
7946
|
+
"auth": {
|
|
7947
|
+
"kind": "apiKey",
|
|
7948
|
+
"env": "CUSTOM_OPENAI_API_KEY",
|
|
7949
|
+
"url": null
|
|
7950
|
+
},
|
|
7951
|
+
"baseUrl": "",
|
|
7952
|
+
"format": "openai-chat",
|
|
7953
|
+
"models": [],
|
|
7954
|
+
"notes": "Any OpenAI-compatible endpoint — vLLM, LiteLLM, one-api, LM Studio, a self-hosted gateway. Paste the base URL (ending in /v1) and the key on this card, then press Fetch models.",
|
|
7955
|
+
"website": null,
|
|
7956
|
+
"local": false,
|
|
7957
|
+
"baseUrlOverrideEnv": "CUSTOM_OPENAI_BASE_URL",
|
|
7958
|
+
"custom": true
|
|
7721
7959
|
}
|
|
7722
7960
|
].map((entry) => Object.freeze({
|
|
7723
7961
|
...entry,
|
|
@@ -7729,6 +7967,7 @@ window.__ModuleLoader__.load({
|
|
|
7729
7967
|
"openai-responses": "/responses",
|
|
7730
7968
|
"anthropic-messages": "/messages"
|
|
7731
7969
|
});
|
|
7970
|
+
typeof process === "undefined" || process.platform;
|
|
7732
7971
|
Object.freeze({
|
|
7733
7972
|
"openai-chat": "chat",
|
|
7734
7973
|
"openai-responses": "responses",
|
|
@@ -8378,14 +8617,23 @@ window.__ModuleLoader__.load({
|
|
|
8378
8617
|
[provider.id]: next
|
|
8379
8618
|
}));
|
|
8380
8619
|
},
|
|
8620
|
+
onStrategy: (strategy) => {
|
|
8621
|
+
patch({
|
|
8622
|
+
kind: "provider",
|
|
8623
|
+
providerId: provider.id,
|
|
8624
|
+
distribution: strategy
|
|
8625
|
+
});
|
|
8626
|
+
},
|
|
8381
8627
|
onSave: async () => {
|
|
8382
8628
|
const draft = drafts[provider.id];
|
|
8383
8629
|
if (draft === void 0) return;
|
|
8630
|
+
const appending = draft.extraKey.trim() === "" ? {} : { appendApiKey: draft.extraKey.trim() };
|
|
8384
8631
|
if (await patch({
|
|
8385
8632
|
kind: "provider",
|
|
8386
8633
|
providerId: provider.id,
|
|
8387
8634
|
...draft.apiKey === "" ? {} : { apiKey: draft.apiKey },
|
|
8388
|
-
...draft.baseUrl === "" ? {} : { baseUrl: draft.baseUrl }
|
|
8635
|
+
...draft.baseUrl === "" ? {} : { baseUrl: draft.baseUrl },
|
|
8636
|
+
...appending
|
|
8389
8637
|
})) {
|
|
8390
8638
|
setDrafts((prev) => {
|
|
8391
8639
|
const next = { ...prev };
|
|
@@ -8494,7 +8742,7 @@ window.__ModuleLoader__.load({
|
|
|
8494
8742
|
]
|
|
8495
8743
|
});
|
|
8496
8744
|
}
|
|
8497
|
-
function ProviderRow({ provider, contributed, modelRows, probes, probing, busy, flash, draft, open, laneEnabled, t, onOpen, onDraft, onSave, onClear, onToggle, onTest, onDiscover, onTestModel, chosen, onToggleModel, modelQuery, onModelQuery, onEditModels, onSelectAllModels, onSaveModels, onDiscardModels }) {
|
|
8745
|
+
function ProviderRow({ provider, contributed, modelRows, probes, probing, busy, flash, draft, open, laneEnabled, t, onOpen, onDraft, onSave, onStrategy, onClear, onToggle, onTest, onDiscover, onTestModel, chosen, onToggleModel, modelQuery, onModelQuery, onEditModels, onSelectAllModels, onSaveModels, onDiscardModels }) {
|
|
8498
8746
|
const enabled = provider.enabled;
|
|
8499
8747
|
const effective = enabled && laneEnabled;
|
|
8500
8748
|
const keyState = provider.authKind === "none" ? t?.("agg.noKeyNeeded") ?? "No key needed" : provider.configured ? t?.("agg.keySet") ?? "Key set" : t?.("agg.noKey") ?? "No key";
|
|
@@ -8707,7 +8955,8 @@ window.__ModuleLoader__.load({
|
|
|
8707
8955
|
onChange: (event) => {
|
|
8708
8956
|
onDraft({
|
|
8709
8957
|
apiKey: event.target.value,
|
|
8710
|
-
baseUrl: draft?.baseUrl ?? ""
|
|
8958
|
+
baseUrl: draft?.baseUrl ?? "",
|
|
8959
|
+
extraKey: draft?.extraKey ?? ""
|
|
8711
8960
|
});
|
|
8712
8961
|
}
|
|
8713
8962
|
})]
|
|
@@ -8720,12 +8969,45 @@ window.__ModuleLoader__.load({
|
|
|
8720
8969
|
onChange: (event) => {
|
|
8721
8970
|
onDraft({
|
|
8722
8971
|
apiKey: draft?.apiKey ?? "",
|
|
8723
|
-
baseUrl: event.target.value
|
|
8972
|
+
baseUrl: event.target.value,
|
|
8973
|
+
extraKey: draft?.extraKey ?? ""
|
|
8724
8974
|
});
|
|
8725
8975
|
}
|
|
8726
8976
|
})]
|
|
8727
8977
|
})]
|
|
8728
8978
|
}) : null,
|
|
8979
|
+
provider.authKind === "apiKey" && provider.keyCount >= 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8980
|
+
className: "dsm-xdpool-agg-fields",
|
|
8981
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
8982
|
+
className: "dsm-xdpool-agg-field",
|
|
8983
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("agg.extraKey", { count: provider.keyCount }) ?? `Add another key (${String(provider.keyCount)} stored)` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8984
|
+
type: "password",
|
|
8985
|
+
autoComplete: "off",
|
|
8986
|
+
value: draft?.extraKey ?? "",
|
|
8987
|
+
placeholder: t?.("agg.extraKeyPlaceholder") ?? "Appended after the existing ones",
|
|
8988
|
+
onChange: (event) => {
|
|
8989
|
+
onDraft({
|
|
8990
|
+
apiKey: draft?.apiKey ?? "",
|
|
8991
|
+
baseUrl: draft?.baseUrl ?? "",
|
|
8992
|
+
extraKey: event.target.value
|
|
8993
|
+
});
|
|
8994
|
+
}
|
|
8995
|
+
})]
|
|
8996
|
+
}), provider.keyCount > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
8997
|
+
className: "dsm-xdpool-agg-field",
|
|
8998
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("row.distTitle") ?? "Account usage" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
8999
|
+
value: provider.distribution ?? "priority",
|
|
9000
|
+
disabled: busy,
|
|
9001
|
+
onChange: (event) => {
|
|
9002
|
+
onStrategy(event.target.value);
|
|
9003
|
+
},
|
|
9004
|
+
children: DIST_OPTIONS.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9005
|
+
value: option,
|
|
9006
|
+
children: distLabel(option, t)
|
|
9007
|
+
}, option))
|
|
9008
|
+
})]
|
|
9009
|
+
}) : null]
|
|
9010
|
+
}) : null,
|
|
8729
9011
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
8730
9012
|
className: "dsm-xdpool-switch dsm-xdpool-agg-switch",
|
|
8731
9013
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -9291,29 +9573,6 @@ window.__ModuleLoader__.load({
|
|
|
9291
9573
|
}
|
|
9292
9574
|
return rows;
|
|
9293
9575
|
}
|
|
9294
|
-
function creditStatusesFrom(envelope) {
|
|
9295
|
-
const value = envelope.value;
|
|
9296
|
-
if (value === null || typeof value !== "object") return [];
|
|
9297
|
-
const accounts = value.accounts;
|
|
9298
|
-
if (!Array.isArray(accounts)) return [];
|
|
9299
|
-
const rows = [];
|
|
9300
|
-
for (const entry of accounts) {
|
|
9301
|
-
if (entry === null || typeof entry !== "object") continue;
|
|
9302
|
-
const accountId = entry.accountId;
|
|
9303
|
-
if (typeof accountId !== "string" || accountId === "") continue;
|
|
9304
|
-
const nickname = entry.nickname;
|
|
9305
|
-
const status = entry.status;
|
|
9306
|
-
const statusRow = status !== null && typeof status === "object" ? status : void 0;
|
|
9307
|
-
const todayCredit = statusRow?.["todayCredit"];
|
|
9308
|
-
rows.push({
|
|
9309
|
-
accountId,
|
|
9310
|
-
...typeof nickname === "string" && nickname !== "" ? { nickname } : {},
|
|
9311
|
-
...statusRow?.["todayCheckedIn"] === true ? { todayCheckedIn: true } : {},
|
|
9312
|
-
...typeof todayCredit === "number" && Number.isFinite(todayCredit) ? { todayCredit } : {}
|
|
9313
|
-
});
|
|
9314
|
-
}
|
|
9315
|
-
return rows;
|
|
9316
|
-
}
|
|
9317
9576
|
function autoCheckinFrom(envelope) {
|
|
9318
9577
|
const value = envelope.value;
|
|
9319
9578
|
if (value === null || typeof value !== "object") return void 0;
|
|
@@ -9926,520 +10185,143 @@ window.__ModuleLoader__.load({
|
|
|
9926
10185
|
});
|
|
9927
10186
|
}
|
|
9928
10187
|
//#endregion
|
|
9929
|
-
//#region src/client/
|
|
10188
|
+
//#region src/client/RoutingPage.tsx
|
|
9930
10189
|
/**
|
|
9931
|
-
* The
|
|
9932
|
-
*
|
|
9933
|
-
* This page observes; it does not compress. billion-context is a separate
|
|
9934
|
-
* plugin that very likely already runs here — on a stock desktop profile it is
|
|
9935
|
-
* mounted by the profile's bundle layer and listens on its own loopback port.
|
|
9936
|
-
* Everything below is read from that proxy's own JSON, relayed through
|
|
9937
|
-
* `src/billion.ts`, and nothing on this page can change what the proxy does.
|
|
9938
|
-
*
|
|
9939
|
-
* Three states, three different things to say, and the difference matters more
|
|
9940
|
-
* than it looks:
|
|
10190
|
+
* The routing page: chains that are themselves models.
|
|
9941
10191
|
*
|
|
9942
|
-
*
|
|
9943
|
-
*
|
|
9944
|
-
*
|
|
9945
|
-
*
|
|
9946
|
-
* - **unreachable** — a proxy IS recorded but did not answer. Most often a
|
|
9947
|
-
* restart in progress: the origin file is rewritten on every respawn. This
|
|
9948
|
-
* must not read as an empty dashboard.
|
|
9949
|
-
* - **ready** — the numbers.
|
|
10192
|
+
* A chain reads as one model id to the picker (`combo:<slug>`), and at call time
|
|
10193
|
+
* it is tried member by member until one answers. The point of the page is to
|
|
10194
|
+
* make ORDER visible and editable — order is the whole semantics, so members
|
|
10195
|
+
* render as a numbered list and never as an unordered set of tags.
|
|
9950
10196
|
*
|
|
9951
|
-
*
|
|
9952
|
-
*
|
|
9953
|
-
*
|
|
9954
|
-
*
|
|
10197
|
+
* Split out of {@link ./PoolCard} for the same reason as the aggregate page: it
|
|
10198
|
+
* reads its own document (`POOL_COMBOS_PATH`) and must not ride the pool card's
|
|
10199
|
+
* 30-second poll, because a routing document only changes when this page writes
|
|
10200
|
+
* it.
|
|
9955
10201
|
*
|
|
9956
|
-
*
|
|
10202
|
+
* Two deliberate honesty choices, both about not lying to the user:
|
|
9957
10203
|
*
|
|
9958
|
-
*
|
|
9959
|
-
*
|
|
9960
|
-
*
|
|
9961
|
-
*
|
|
9962
|
-
*
|
|
10204
|
+
* 1. A member the roster lost is shown, not silently dropped. The server keeps
|
|
10205
|
+
* the chain verbatim and reports the unresolved ids; the panel prints them.
|
|
10206
|
+
* Dropping them would make the panel look correct while a different model
|
|
10207
|
+
* quietly answered.
|
|
10208
|
+
* 2. `missing` is only a WARNING, never a block on saving. The user may be
|
|
10209
|
+
* configuring a chain for a key they intend to paste in next. Refusing the
|
|
10210
|
+
* save would be a worse lie than the warning.
|
|
9963
10211
|
*
|
|
9964
|
-
* @module dsh-xdpool/client/
|
|
10212
|
+
* @module dsh-xdpool/client/RoutingPage
|
|
9965
10213
|
*/
|
|
9966
|
-
/** Poll cadence, matching the other pages that watch a live process. */
|
|
9967
|
-
const POLL_MS$1 = 15e3;
|
|
9968
|
-
function fmtInt(value) {
|
|
9969
|
-
return value.toLocaleString("en-US");
|
|
9970
|
-
}
|
|
9971
|
-
/** A token count in millions or billions, because 486,201,338 stops being a number. */
|
|
9972
|
-
function compact$1(value) {
|
|
9973
|
-
if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
|
|
9974
|
-
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
9975
|
-
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
9976
|
-
return String(value);
|
|
9977
|
-
}
|
|
9978
|
-
/** `288.6K tokens`, or an em dash when the proxy has no measurement. */
|
|
9979
|
-
function tokens(value) {
|
|
9980
|
-
return value === 0 ? "—" : `${compact$1(value)} tokens`;
|
|
9981
|
-
}
|
|
9982
|
-
/** Local clock, or an em dash when the proxy never saw a request. */
|
|
9983
|
-
function fmtLastSeen(iso) {
|
|
9984
|
-
if (iso === "") return "—";
|
|
9985
|
-
const at = Date.parse(iso);
|
|
9986
|
-
if (Number.isNaN(at)) return "—";
|
|
9987
|
-
const d = new Date(at);
|
|
9988
|
-
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
|
9989
|
-
}
|
|
9990
10214
|
/**
|
|
9991
|
-
*
|
|
10215
|
+
* Flatten a draft into the member ids the server stores.
|
|
9992
10216
|
*
|
|
9993
|
-
*
|
|
9994
|
-
*
|
|
9995
|
-
*
|
|
9996
|
-
*
|
|
10217
|
+
* Platform order first, then model order within each platform. Duplicates are
|
|
10218
|
+
* dropped: the same pair twice would spend two attempts on one endpoint and report
|
|
10219
|
+
* a fallback that never happened. A pool model is its bare id; anything else is
|
|
10220
|
+
* `owner/model`, the same composite id the aggregate catalog publishes.
|
|
9997
10221
|
*/
|
|
9998
|
-
function
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
for (const
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10222
|
+
function flattenDraft(platforms) {
|
|
10223
|
+
const out = [];
|
|
10224
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10225
|
+
for (const platform of platforms) for (const model of platform.models) {
|
|
10226
|
+
const id = platform.owner === "workbuddy" ? model : `${platform.owner}/${model}`;
|
|
10227
|
+
if (seen.has(id)) continue;
|
|
10228
|
+
seen.add(id);
|
|
10229
|
+
out.push(id);
|
|
10005
10230
|
}
|
|
10006
|
-
return
|
|
10231
|
+
return out;
|
|
10007
10232
|
}
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10233
|
+
function draftOf(combo) {
|
|
10234
|
+
/**
|
|
10235
|
+
* `missing` members are kept, grouped under their own owner, so the editor shows
|
|
10236
|
+
* the chain the user actually configured rather than the shorter one the roster
|
|
10237
|
+
* can serve right now. Dropping them would make a save silently delete members
|
|
10238
|
+
* the user never asked to remove — the "保存了没法用" class of data loss.
|
|
10239
|
+
*/
|
|
10240
|
+
const platforms = combo.platforms.map((group) => ({
|
|
10241
|
+
owner: group.owner,
|
|
10242
|
+
models: [...group.models]
|
|
10243
|
+
}));
|
|
10244
|
+
const known = new Set(combo.models);
|
|
10245
|
+
for (const modelId of combo.missing) {
|
|
10246
|
+
if (known.has(modelId)) continue;
|
|
10247
|
+
const slash = modelId.indexOf("/");
|
|
10248
|
+
const owner = slash === -1 ? "workbuddy" : modelId.slice(0, slash);
|
|
10249
|
+
const model = slash === -1 ? modelId : modelId.slice(slash + 1);
|
|
10250
|
+
const group = platforms.find((entry) => entry.owner === owner);
|
|
10251
|
+
if (group === void 0) platforms.push({
|
|
10252
|
+
owner,
|
|
10253
|
+
models: [model]
|
|
10254
|
+
});
|
|
10255
|
+
else if (!group.models.includes(model)) group.models.push(model);
|
|
10029
10256
|
}
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10257
|
+
return {
|
|
10258
|
+
key: combo.id,
|
|
10259
|
+
id: combo.id,
|
|
10260
|
+
name: combo.name,
|
|
10261
|
+
platforms,
|
|
10262
|
+
flat: [...combo.models, ...combo.missing.filter((id) => !combo.models.includes(id))],
|
|
10263
|
+
strategy: combo.strategy,
|
|
10264
|
+
stickyLimit: combo.stickyLimit,
|
|
10265
|
+
enabled: combo.enabled
|
|
10266
|
+
};
|
|
10034
10267
|
}
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10268
|
+
function blankDraft() {
|
|
10269
|
+
return {
|
|
10270
|
+
key: `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
10271
|
+
name: "",
|
|
10272
|
+
platforms: [],
|
|
10273
|
+
flat: [],
|
|
10274
|
+
strategy: "fallback",
|
|
10275
|
+
stickyLimit: 1,
|
|
10276
|
+
enabled: true
|
|
10277
|
+
};
|
|
10044
10278
|
}
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10279
|
+
/** Compact counts: 1_260_000 -> "1.3M". Chains quote context windows, so this is load-bearing. */
|
|
10280
|
+
function compact(value) {
|
|
10281
|
+
if (value >= 1e6) return `${Math.round(value / 1e5) / 10}M`;
|
|
10282
|
+
if (value >= 1e3) return `${Math.round(value / 100) / 10}K`;
|
|
10283
|
+
return String(value);
|
|
10284
|
+
}
|
|
10285
|
+
function RoutingPage({ t }) {
|
|
10286
|
+
const [status, setStatus] = (0, react.useState)(void 0);
|
|
10287
|
+
const [error, setError] = (0, react.useState)(void 0);
|
|
10288
|
+
const [flash, setFlash] = (0, react.useState)(void 0);
|
|
10289
|
+
const [drafts, setDrafts] = (0, react.useState)([]);
|
|
10290
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
10291
|
+
/** Key of the chain being edited, or `undefined` when nothing is open. */
|
|
10292
|
+
const [editing, setEditing] = (0, react.useState)(void 0);
|
|
10293
|
+
/** Model id chosen in the add-member row. */
|
|
10294
|
+
const [picked, setPicked] = (0, react.useState)("");
|
|
10295
|
+
/** True once the drafts were seeded from the server, so later polls don't stomp edits. */
|
|
10296
|
+
const seeded = (0, react.useRef)(false);
|
|
10048
10297
|
const mounted = (0, react.useRef)(true);
|
|
10049
10298
|
(0, react.useEffect)(() => () => {
|
|
10050
10299
|
mounted.current = false;
|
|
10051
10300
|
}, []);
|
|
10052
10301
|
const load = (0, react.useCallback)(async (signal) => {
|
|
10053
10302
|
try {
|
|
10054
|
-
const response = await fetch(
|
|
10303
|
+
const response = await fetch(POOL_COMBOS_PATH, {
|
|
10055
10304
|
headers: { accept: "application/json" },
|
|
10056
10305
|
credentials: "same-origin",
|
|
10057
10306
|
...signal === void 0 ? {} : { signal }
|
|
10058
10307
|
});
|
|
10308
|
+
const value = await response.json().catch(() => void 0);
|
|
10059
10309
|
if (!response.ok) {
|
|
10060
|
-
|
|
10310
|
+
const detail = value;
|
|
10311
|
+
if (mounted.current) setError(detail?.error ?? `HTTP ${response.status}`);
|
|
10061
10312
|
return;
|
|
10062
10313
|
}
|
|
10063
|
-
const value = await response.json().catch(() => void 0);
|
|
10064
10314
|
if (mounted.current && signal?.aborted !== true) {
|
|
10065
|
-
|
|
10066
|
-
|
|
10315
|
+
const next = value;
|
|
10316
|
+
setStatus(next);
|
|
10317
|
+
setError(void 0);
|
|
10318
|
+
if (!seeded.current) {
|
|
10319
|
+
seeded.current = true;
|
|
10320
|
+
setDrafts((next.combos ?? []).map(draftOf));
|
|
10321
|
+
}
|
|
10067
10322
|
}
|
|
10068
|
-
} catch {
|
|
10069
|
-
if (mounted.current
|
|
10070
|
-
}
|
|
10071
|
-
}, []);
|
|
10072
|
-
(0, react.useEffect)(() => {
|
|
10073
|
-
const controller = new AbortController();
|
|
10074
|
-
load(controller.signal);
|
|
10075
|
-
const timer = window.setInterval(() => {
|
|
10076
|
-
load(controller.signal);
|
|
10077
|
-
}, POLL_MS$1);
|
|
10078
|
-
return () => {
|
|
10079
|
-
window.clearInterval(timer);
|
|
10080
|
-
controller.abort();
|
|
10081
|
-
};
|
|
10082
|
-
}, [load]);
|
|
10083
|
-
/**
|
|
10084
|
-
* Fleet totals over the rows on screen.
|
|
10085
|
-
*
|
|
10086
|
-
* Summing the rows this page will render is what keeps the KPI row from
|
|
10087
|
-
* disagreeing with the table under it — the same rule the pool dashboard
|
|
10088
|
-
* follows, and for the same reason: a dashboard that gets trusted must not be
|
|
10089
|
-
* able to contradict itself.
|
|
10090
|
-
*/
|
|
10091
|
-
const totals = (0, react.useMemo)(() => {
|
|
10092
|
-
const sessions = doc?.sessions ?? [];
|
|
10093
|
-
let requests = 0;
|
|
10094
|
-
let input = 0;
|
|
10095
|
-
let cached = 0;
|
|
10096
|
-
let output = 0;
|
|
10097
|
-
let saved = 0;
|
|
10098
|
-
let live = 0;
|
|
10099
|
-
for (const row of sessions) {
|
|
10100
|
-
requests += row.requests;
|
|
10101
|
-
input += row.inputTokens;
|
|
10102
|
-
cached += row.cachedTokens;
|
|
10103
|
-
output += row.outputTokens;
|
|
10104
|
-
saved += row.tokensSaved;
|
|
10105
|
-
if (row.live) live += 1;
|
|
10106
|
-
}
|
|
10107
|
-
return {
|
|
10108
|
-
sessions,
|
|
10109
|
-
requests,
|
|
10110
|
-
input,
|
|
10111
|
-
cached,
|
|
10112
|
-
output,
|
|
10113
|
-
saved,
|
|
10114
|
-
live
|
|
10115
|
-
};
|
|
10116
|
-
}, [doc]);
|
|
10117
|
-
const hitRate = (0, react.useMemo)(() => fleetHitRate(totals.sessions), [totals.sessions]);
|
|
10118
|
-
const ready = doc?.state === "ready";
|
|
10119
|
-
/**
|
|
10120
|
-
* The rows start hidden, and the user opens them on purpose.
|
|
10121
|
-
*
|
|
10122
|
-
* The summary above is what this page is for; the fleet table is the
|
|
10123
|
-
* detail behind it. Collapsed is also the honest default on a machine with 50
|
|
10124
|
-
* sessions, where an expanded table pushes every other control off screen.
|
|
10125
|
-
*/
|
|
10126
|
-
const [rowsOpen, setRowsOpen] = (0, react.useState)(false);
|
|
10127
|
-
const dashboard = proxyLink(doc?.origin, "#/overview");
|
|
10128
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10129
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: BILLION_CARD_CSS }),
|
|
10130
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageHero, {
|
|
10131
|
-
title: t?.("billion.title") ?? "billion-context",
|
|
10132
|
-
sub: t?.("billion.sub") ?? "Read-only view of the compressor running on this machine. This plugin never configures it.",
|
|
10133
|
-
actions: dashboard === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10134
|
-
type: "button",
|
|
10135
|
-
className: "dsm-btn dsm-btn-ghost",
|
|
10136
|
-
onClick: () => {
|
|
10137
|
-
openProxyDashboard(dashboard);
|
|
10138
|
-
},
|
|
10139
|
-
children: t?.("billion.openDashboard") ?? "Open its dashboard"
|
|
10140
|
-
}),
|
|
10141
|
-
pills: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10142
|
-
className: "dsm-xdpool-pill",
|
|
10143
|
-
"data-tone": ready ? "on" : "off",
|
|
10144
|
-
children: ready ? t?.("billion.pillRunning", { version: doc?.version ?? "?" }) ?? `running · v${doc?.version ?? "?"}` : t?.("billion.pillNotRunning") ?? "not running"
|
|
10145
|
-
}), ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10146
|
-
className: "dsm-xdpool-pill",
|
|
10147
|
-
"data-tone": "off",
|
|
10148
|
-
children: t?.("billion.pillReadOnly") ?? "read-only"
|
|
10149
|
-
}) : null] })
|
|
10150
|
-
}),
|
|
10151
|
-
failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10152
|
-
className: "dsm-xdpool-note",
|
|
10153
|
-
role: "alert",
|
|
10154
|
-
children: t?.("billion.failed") ?? "Could not reach the pool relay. The pool itself is unaffected."
|
|
10155
|
-
}) : null,
|
|
10156
|
-
doc === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10157
|
-
className: "dsm-xdpool-note",
|
|
10158
|
-
children: t?.("billion.loading") ?? "Looking for a billion-context proxy on this machine…"
|
|
10159
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [doc.state !== "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
10160
|
-
className: "dsm-xdpool-sec",
|
|
10161
|
-
children: [
|
|
10162
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
10163
|
-
className: "dsm-xdpool-sectitle",
|
|
10164
|
-
children: doc.state === "missing" ? t?.("billion.notInstalled") ?? "billion-context is not installed here" : t?.("billion.notAnswering") ?? "billion-context did not answer"
|
|
10165
|
-
}),
|
|
10166
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10167
|
-
className: "dsm-xdpool-note",
|
|
10168
|
-
children: doc.reason ?? ""
|
|
10169
|
-
}),
|
|
10170
|
-
doc.state === "missing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
10171
|
-
className: "dsm-xdpool-steps",
|
|
10172
|
-
children: [
|
|
10173
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", { children: [
|
|
10174
|
-
"dsh plugin --profile ",
|
|
10175
|
-
dshProfileHint(t),
|
|
10176
|
-
" add billion-context"
|
|
10177
|
-
] }) }),
|
|
10178
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("billion.stepRestart") ?? "Restart DSH so the profile bundle layer mounts it." }),
|
|
10179
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("billion.stepBack") ?? "Come back to this page; it fills in on its own." })
|
|
10180
|
-
]
|
|
10181
|
-
}) : null
|
|
10182
|
-
]
|
|
10183
|
-
}) : null, ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10184
|
-
className: "dsm-xdpool-kpis",
|
|
10185
|
-
children: [
|
|
10186
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
10187
|
-
label: t?.("billion.sessions") ?? "Sessions",
|
|
10188
|
-
value: fmtInt(doc.totalSessions),
|
|
10189
|
-
sub: totals.live === 0 ? t?.("billion.noLive") ?? "none active right now" : t?.("billion.liveSub", { count: totals.live }) ?? `${totals.live} active right now`
|
|
10190
|
-
}),
|
|
10191
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
10192
|
-
label: t?.("billion.requests") ?? "Requests",
|
|
10193
|
-
value: fmtInt(totals.requests),
|
|
10194
|
-
sub: t?.("billion.requestsSub") ?? "through the proxy, all lanes"
|
|
10195
|
-
}),
|
|
10196
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
10197
|
-
label: t?.("billion.saved") ?? "Tokens saved",
|
|
10198
|
-
value: compact$1(totals.saved),
|
|
10199
|
-
sub: t?.("billion.savedSub") ?? "by compression, net of what it cost"
|
|
10200
|
-
}),
|
|
10201
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
10202
|
-
label: t?.("billion.tokens") ?? "Tokens",
|
|
10203
|
-
value: compact$1(totals.input),
|
|
10204
|
-
sub: t?.("billion.tokensSub", {
|
|
10205
|
-
cached: compact$1(totals.cached),
|
|
10206
|
-
out: compact$1(totals.output)
|
|
10207
|
-
}) ?? `${compact$1(totals.cached)} cached · ${compact$1(totals.output)} out`
|
|
10208
|
-
}),
|
|
10209
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
10210
|
-
label: t?.("billion.cache") ?? "Cache hit",
|
|
10211
|
-
value: hitRate === null ? "—" : `${Math.round(hitRate * 100)}%`,
|
|
10212
|
-
sub: hitRate === null ? t?.("billion.cacheNone") ?? "nothing measured yet" : t?.("billion.cacheSub") ?? "of everything sent"
|
|
10213
|
-
})
|
|
10214
|
-
]
|
|
10215
|
-
}), totals.sessions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10216
|
-
className: "dsm-xdpool-note",
|
|
10217
|
-
children: t?.("billion.empty") ?? "The proxy is up but has seen no requests yet. Chat and this fills in."
|
|
10218
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
10219
|
-
className: "dsm-xdpool-sec",
|
|
10220
|
-
children: [
|
|
10221
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10222
|
-
className: "dsm-xdpool-sechead",
|
|
10223
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
10224
|
-
className: "dsm-xdpool-sectitle",
|
|
10225
|
-
children: t?.("billion.perSession") ?? "Per session"
|
|
10226
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10227
|
-
className: "dsm-xdpool-secbtns",
|
|
10228
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10229
|
-
type: "button",
|
|
10230
|
-
className: "dsm-xdpool-secghost",
|
|
10231
|
-
"aria-expanded": rowsOpen,
|
|
10232
|
-
onClick: () => {
|
|
10233
|
-
setRowsOpen((open) => !open);
|
|
10234
|
-
},
|
|
10235
|
-
children: rowsOpen ? t?.("billion.hideRows") ?? "Hide rows" : t?.("billion.showRows", { count: totals.sessions.length }) ?? `Show ${totals.sessions.length} rows`
|
|
10236
|
-
}), dashboard === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
10237
|
-
className: "dsm-xdpool-secghost",
|
|
10238
|
-
href: dashboard,
|
|
10239
|
-
target: "_blank",
|
|
10240
|
-
rel: "noopener noreferrer",
|
|
10241
|
-
children: t?.("billion.openDashboard") ?? "Open the proxy's own dashboard"
|
|
10242
|
-
})]
|
|
10243
|
-
})]
|
|
10244
|
-
}),
|
|
10245
|
-
rowsOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10246
|
-
className: "dsm-xdpool-tablewrap",
|
|
10247
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
10248
|
-
className: "dsm-xdpool-table",
|
|
10249
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
10250
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("billion.colSession") ?? "Session" }),
|
|
10251
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("billion.colLast") ?? "Last seen" }),
|
|
10252
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t?.("billion.colModel") ?? "Model" }),
|
|
10253
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
10254
|
-
className: "dsm-xdpool-num",
|
|
10255
|
-
children: t?.("billion.colRequests") ?? "Requests"
|
|
10256
|
-
}),
|
|
10257
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
10258
|
-
className: "dsm-xdpool-num",
|
|
10259
|
-
children: t?.("billion.colContext") ?? "Context"
|
|
10260
|
-
}),
|
|
10261
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
10262
|
-
className: "dsm-xdpool-num",
|
|
10263
|
-
children: t?.("billion.colBlocks") ?? "Blocks"
|
|
10264
|
-
}),
|
|
10265
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
10266
|
-
className: "dsm-xdpool-num",
|
|
10267
|
-
children: t?.("billion.colSaved") ?? "Saved"
|
|
10268
|
-
}),
|
|
10269
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
|
|
10270
|
-
className: "dsm-xdpool-num",
|
|
10271
|
-
children: t?.("billion.colCache") ?? "Cache"
|
|
10272
|
-
})
|
|
10273
|
-
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: totals.sessions.map((row) => {
|
|
10274
|
-
const detail = proxyLink(doc?.origin, `#/sessions/${encodeURIComponent(row.id)}`);
|
|
10275
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
|
|
10276
|
-
"data-live": row.live,
|
|
10277
|
-
children: [
|
|
10278
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
|
|
10279
|
-
className: "dsm-xdpool-ellipsis",
|
|
10280
|
-
title: row.id,
|
|
10281
|
-
children: [detail === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: row.id }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
10282
|
-
className: "dsm-xdpool-sesslink",
|
|
10283
|
-
href: detail,
|
|
10284
|
-
target: "_blank",
|
|
10285
|
-
rel: "noopener noreferrer",
|
|
10286
|
-
title: row.id,
|
|
10287
|
-
children: row.id
|
|
10288
|
-
}), row.live ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10289
|
-
className: "dsm-xdpool-live",
|
|
10290
|
-
"aria-label": "live"
|
|
10291
|
-
}) : null]
|
|
10292
|
-
}),
|
|
10293
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtLastSeen(row.lastSeen) }),
|
|
10294
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10295
|
-
className: "dsm-xdpool-ellipsis",
|
|
10296
|
-
title: row.lastModel,
|
|
10297
|
-
children: row.lastModel === "" ? "—" : row.lastModel
|
|
10298
|
-
}),
|
|
10299
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10300
|
-
className: "dsm-xdpool-num",
|
|
10301
|
-
children: fmtInt(row.requests)
|
|
10302
|
-
}),
|
|
10303
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10304
|
-
className: "dsm-xdpool-num",
|
|
10305
|
-
title: t?.("billion.contextTitle", {
|
|
10306
|
-
source: row.contextTokensSource,
|
|
10307
|
-
window: row.contextWindow
|
|
10308
|
-
}) ?? `source ${row.contextTokensSource} · window ${row.contextWindow}`,
|
|
10309
|
-
children: tokens(row.contextTokens)
|
|
10310
|
-
}),
|
|
10311
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10312
|
-
className: "dsm-xdpool-num",
|
|
10313
|
-
children: fmtInt(row.blocks)
|
|
10314
|
-
}),
|
|
10315
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10316
|
-
className: "dsm-xdpool-num",
|
|
10317
|
-
children: compact$1(row.tokensSaved)
|
|
10318
|
-
}),
|
|
10319
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
10320
|
-
className: "dsm-xdpool-num",
|
|
10321
|
-
children: row.cacheHitPct < 0 ? "—" : `${row.cacheHitPct}%`
|
|
10322
|
-
})
|
|
10323
|
-
]
|
|
10324
|
-
}, row.id);
|
|
10325
|
-
}) })]
|
|
10326
|
-
})
|
|
10327
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10328
|
-
className: "dsm-xdpool-note",
|
|
10329
|
-
children: t?.("billion.rowsHint") ?? "Hidden by default. The summary above is the whole fleet; open the rows or the proxy’s own dashboard when you need a specific session."
|
|
10330
|
-
}),
|
|
10331
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10332
|
-
className: "dsm-xdpool-note",
|
|
10333
|
-
children: t?.("billion.note") ?? "These numbers are the proxy’s own. Nothing here is an estimate, and this page never changes a setting."
|
|
10334
|
-
})
|
|
10335
|
-
]
|
|
10336
|
-
})] }) : null] })
|
|
10337
|
-
] });
|
|
10338
|
-
}
|
|
10339
|
-
/**
|
|
10340
|
-
* The profile name for the install command.
|
|
10341
|
-
*
|
|
10342
|
-
* The host publishes it as an env var; when that is absent the command still
|
|
10343
|
-
* works with `--profile` omitted on a single-profile install, so the hint says
|
|
10344
|
-
* so rather than printing an empty flag.
|
|
10345
|
-
*/
|
|
10346
|
-
function dshProfileHint(t) {
|
|
10347
|
-
const profile = typeof process === "undefined" ? void 0 : process.env.DSH_PROFILE;
|
|
10348
|
-
if (typeof profile === "string" && profile !== "") return profile;
|
|
10349
|
-
return t?.("billion.defaultProfile") ?? "<profile>";
|
|
10350
|
-
}
|
|
10351
|
-
//#endregion
|
|
10352
|
-
//#region src/client/RoutingPage.tsx
|
|
10353
|
-
/**
|
|
10354
|
-
* The routing page: chains that are themselves models.
|
|
10355
|
-
*
|
|
10356
|
-
* A chain reads as one model id to the picker (`combo:<slug>`), and at call time
|
|
10357
|
-
* it is tried member by member until one answers. The point of the page is to
|
|
10358
|
-
* make ORDER visible and editable — order is the whole semantics, so members
|
|
10359
|
-
* render as a numbered list and never as an unordered set of tags.
|
|
10360
|
-
*
|
|
10361
|
-
* Split out of {@link ./PoolCard} for the same reason as the aggregate page: it
|
|
10362
|
-
* reads its own document (`POOL_COMBOS_PATH`) and must not ride the pool card's
|
|
10363
|
-
* 30-second poll, because a routing document only changes when this page writes
|
|
10364
|
-
* it.
|
|
10365
|
-
*
|
|
10366
|
-
* Two deliberate honesty choices, both about not lying to the user:
|
|
10367
|
-
*
|
|
10368
|
-
* 1. A member the roster lost is shown, not silently dropped. The server keeps
|
|
10369
|
-
* the chain verbatim and reports the unresolved ids; the panel prints them.
|
|
10370
|
-
* Dropping them would make the panel look correct while a different model
|
|
10371
|
-
* quietly answered.
|
|
10372
|
-
* 2. `missing` is only a WARNING, never a block on saving. The user may be
|
|
10373
|
-
* configuring a chain for a key they intend to paste in next. Refusing the
|
|
10374
|
-
* save would be a worse lie than the warning.
|
|
10375
|
-
*
|
|
10376
|
-
* @module dsh-xdpool/client/RoutingPage
|
|
10377
|
-
*/
|
|
10378
|
-
function draftOf(combo) {
|
|
10379
|
-
return {
|
|
10380
|
-
key: combo.id,
|
|
10381
|
-
id: combo.id,
|
|
10382
|
-
name: combo.name,
|
|
10383
|
-
models: [...combo.models, ...combo.missing],
|
|
10384
|
-
strategy: combo.strategy,
|
|
10385
|
-
stickyLimit: combo.stickyLimit,
|
|
10386
|
-
enabled: combo.enabled
|
|
10387
|
-
};
|
|
10388
|
-
}
|
|
10389
|
-
function blankDraft() {
|
|
10390
|
-
return {
|
|
10391
|
-
key: `new-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
10392
|
-
name: "",
|
|
10393
|
-
models: [],
|
|
10394
|
-
strategy: "fallback",
|
|
10395
|
-
stickyLimit: 1,
|
|
10396
|
-
enabled: true
|
|
10397
|
-
};
|
|
10398
|
-
}
|
|
10399
|
-
/** Compact counts: 1_260_000 -> "1.3M". Chains quote context windows, so this is load-bearing. */
|
|
10400
|
-
function compact(value) {
|
|
10401
|
-
if (value >= 1e6) return `${Math.round(value / 1e5) / 10}M`;
|
|
10402
|
-
if (value >= 1e3) return `${Math.round(value / 100) / 10}K`;
|
|
10403
|
-
return String(value);
|
|
10404
|
-
}
|
|
10405
|
-
function RoutingPage({ t }) {
|
|
10406
|
-
const [status, setStatus] = (0, react.useState)(void 0);
|
|
10407
|
-
const [error, setError] = (0, react.useState)(void 0);
|
|
10408
|
-
const [flash, setFlash] = (0, react.useState)(void 0);
|
|
10409
|
-
const [drafts, setDrafts] = (0, react.useState)([]);
|
|
10410
|
-
const [saving, setSaving] = (0, react.useState)(false);
|
|
10411
|
-
/** Key of the chain being edited, or `undefined` when nothing is open. */
|
|
10412
|
-
const [editing, setEditing] = (0, react.useState)(void 0);
|
|
10413
|
-
/** Model id chosen in the add-member row. */
|
|
10414
|
-
const [picked, setPicked] = (0, react.useState)("");
|
|
10415
|
-
/** True once the drafts were seeded from the server, so later polls don't stomp edits. */
|
|
10416
|
-
const seeded = (0, react.useRef)(false);
|
|
10417
|
-
const mounted = (0, react.useRef)(true);
|
|
10418
|
-
(0, react.useEffect)(() => () => {
|
|
10419
|
-
mounted.current = false;
|
|
10420
|
-
}, []);
|
|
10421
|
-
const load = (0, react.useCallback)(async (signal) => {
|
|
10422
|
-
try {
|
|
10423
|
-
const response = await fetch(POOL_COMBOS_PATH, {
|
|
10424
|
-
headers: { accept: "application/json" },
|
|
10425
|
-
credentials: "same-origin",
|
|
10426
|
-
...signal === void 0 ? {} : { signal }
|
|
10427
|
-
});
|
|
10428
|
-
const value = await response.json().catch(() => void 0);
|
|
10429
|
-
if (!response.ok) {
|
|
10430
|
-
const detail = value;
|
|
10431
|
-
if (mounted.current) setError(detail?.error ?? `HTTP ${response.status}`);
|
|
10432
|
-
return;
|
|
10433
|
-
}
|
|
10434
|
-
if (mounted.current && signal?.aborted !== true) {
|
|
10435
|
-
const next = value;
|
|
10436
|
-
setStatus(next);
|
|
10437
|
-
setError(void 0);
|
|
10438
|
-
setDrafts((prev) => seeded.current ? prev : (next.combos ?? []).map(draftOf));
|
|
10439
|
-
seeded.current = true;
|
|
10440
|
-
}
|
|
10441
|
-
} catch (cause) {
|
|
10442
|
-
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
10323
|
+
} catch (cause) {
|
|
10324
|
+
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
10443
10325
|
}
|
|
10444
10326
|
}, []);
|
|
10445
10327
|
(0, react.useEffect)(() => {
|
|
@@ -10471,14 +10353,18 @@ window.__ModuleLoader__.load({
|
|
|
10471
10353
|
method: "POST",
|
|
10472
10354
|
headers: { "Content-Type": "application/json" },
|
|
10473
10355
|
credentials: "same-origin",
|
|
10474
|
-
body: JSON.stringify(drafts.map((draft) => ({
|
|
10356
|
+
body: JSON.stringify({ combos: drafts.map((draft) => ({
|
|
10475
10357
|
...draft.id === void 0 ? {} : { id: draft.id },
|
|
10476
10358
|
name: draft.name,
|
|
10477
|
-
|
|
10359
|
+
platforms: draft.platforms.map((group) => ({
|
|
10360
|
+
owner: group.owner,
|
|
10361
|
+
models: group.models
|
|
10362
|
+
})),
|
|
10363
|
+
models: draft.flat,
|
|
10478
10364
|
strategy: draft.strategy,
|
|
10479
10365
|
stickyLimit: draft.stickyLimit,
|
|
10480
10366
|
enabled: draft.enabled
|
|
10481
|
-
})))
|
|
10367
|
+
})) })
|
|
10482
10368
|
});
|
|
10483
10369
|
if (!response.ok) {
|
|
10484
10370
|
const detail = await response.json().catch(() => ({}));
|
|
@@ -10546,20 +10432,10 @@ window.__ModuleLoader__.load({
|
|
|
10546
10432
|
current !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChainEditor, {
|
|
10547
10433
|
draft: current,
|
|
10548
10434
|
roster,
|
|
10549
|
-
picked,
|
|
10550
10435
|
busy: saving,
|
|
10551
10436
|
candidateById,
|
|
10552
10437
|
t,
|
|
10553
|
-
|
|
10554
|
-
onChange: patchDraft,
|
|
10555
|
-
onAddMember: () => {
|
|
10556
|
-
if (picked === "" || current.models.includes(picked)) return;
|
|
10557
|
-
patchDraft({ models: [...current.models, picked] });
|
|
10558
|
-
setPicked("");
|
|
10559
|
-
},
|
|
10560
|
-
onDropMember: (modelId) => {
|
|
10561
|
-
patchDraft({ models: current.models.filter((id) => id !== modelId) });
|
|
10562
|
-
}
|
|
10438
|
+
onChange: patchDraft
|
|
10563
10439
|
}) : null,
|
|
10564
10440
|
status === void 0 ? null : drafts.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10565
10441
|
className: "dsm-xdpool-panel-empty",
|
|
@@ -10603,60 +10479,197 @@ window.__ModuleLoader__.load({
|
|
|
10603
10479
|
* typed.
|
|
10604
10480
|
*/
|
|
10605
10481
|
function ChainRow({ draft, editable, candidateById, t, onEdit, onDelete }) {
|
|
10606
|
-
|
|
10482
|
+
/**
|
|
10483
|
+
* Members the roster cannot serve right now, computed from the FLATTENED draft.
|
|
10484
|
+
*
|
|
10485
|
+
* Flattened rather than read off the groups, so the check matches what would be
|
|
10486
|
+
* saved: a group holds bare ids and the roster holds composite ones, and
|
|
10487
|
+
* comparing the two shapes directly would mark every aggregate member missing.
|
|
10488
|
+
*/
|
|
10489
|
+
const flat = draft.flat;
|
|
10490
|
+
const missing = flat.filter((model) => candidateById.has(model) === false);
|
|
10607
10491
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10608
10492
|
className: "dsm-xdpool-rt-row",
|
|
10609
10493
|
"data-off": draft.enabled ? "false" : "true",
|
|
10610
|
-
children: [
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
"data-tone": draft.enabled ? "ok" : "off",
|
|
10624
|
-
children: draft.enabled ? t?.("route.enabled") ?? "Enabled" : t?.("route.chainDisabled") ?? "Disabled"
|
|
10625
|
-
}),
|
|
10626
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10627
|
-
className: "dsm-xdpool-pills",
|
|
10628
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10494
|
+
children: [
|
|
10495
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10496
|
+
className: "dsm-xdpool-rt-rowhead",
|
|
10497
|
+
children: [
|
|
10498
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10499
|
+
className: "dsm-xdpool-rt-name",
|
|
10500
|
+
children: draft.name === "" ? t?.("route.newChain") ?? "New chain" : draft.name
|
|
10501
|
+
}),
|
|
10502
|
+
draft.id !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10503
|
+
className: "dsm-xdpool-rt-cid",
|
|
10504
|
+
children: draft.id
|
|
10505
|
+
}) : null,
|
|
10506
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10629
10507
|
className: "dsm-xdpool-pill",
|
|
10630
|
-
"data-tone": "off",
|
|
10631
|
-
children: t?.("route.
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10508
|
+
"data-tone": draft.enabled ? "ok" : "off",
|
|
10509
|
+
children: draft.enabled ? t?.("route.enabled") ?? "Enabled" : t?.("route.chainDisabled") ?? "Disabled"
|
|
10510
|
+
}),
|
|
10511
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10512
|
+
className: "dsm-xdpool-pills",
|
|
10513
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10514
|
+
className: "dsm-xdpool-pill",
|
|
10515
|
+
"data-tone": "off",
|
|
10516
|
+
children: t?.("route.platformCount", { count: draft.platforms.length }) ?? `${draft.platforms.length} platform(s)`
|
|
10517
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10518
|
+
className: "dsm-xdpool-pill",
|
|
10519
|
+
"data-tone": "off",
|
|
10520
|
+
children: t?.("route.memberCount", { count: flat.length }) ?? `${flat.length} model(s)`
|
|
10521
|
+
})]
|
|
10522
|
+
}),
|
|
10523
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10524
|
+
className: "dsm-xdpool-pageactions",
|
|
10525
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10526
|
+
type: "button",
|
|
10527
|
+
className: "dsm-btn dsm-btn-outline",
|
|
10528
|
+
onClick: onEdit,
|
|
10529
|
+
children: editable ? t?.("route.editOpen") ?? "Editing" : t?.("route.edit") ?? "Edit"
|
|
10530
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10531
|
+
type: "button",
|
|
10532
|
+
className: "dsm-xdpool-rt-drop",
|
|
10533
|
+
onClick: onDelete,
|
|
10534
|
+
children: t?.("route.removeMember") ?? "Remove"
|
|
10535
|
+
})]
|
|
10635
10536
|
})
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10537
|
+
]
|
|
10538
|
+
}),
|
|
10539
|
+
draft.platforms.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10540
|
+
className: "dsm-xdpool-rt-warn",
|
|
10541
|
+
children: t?.("route.noMembers") ?? "Add at least one member."
|
|
10542
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
10543
|
+
className: "dsm-xdpool-rt-lanes",
|
|
10544
|
+
children: draft.platforms.map((platform, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
10545
|
+
className: "dsm-xdpool-rt-lane",
|
|
10546
|
+
children: [
|
|
10547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10548
|
+
className: "dsm-xdpool-rt-ord",
|
|
10549
|
+
children: index + 1
|
|
10550
|
+
}),
|
|
10551
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10552
|
+
className: "dsm-xdpool-rt-lanename",
|
|
10553
|
+
title: platform.owner,
|
|
10554
|
+
children: ownerName(platform.owner, t)
|
|
10555
|
+
}),
|
|
10556
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10557
|
+
className: "dsm-xdpool-rt-lanecount",
|
|
10558
|
+
children: ["· ", platform.models.length]
|
|
10559
|
+
})
|
|
10560
|
+
]
|
|
10561
|
+
}, `${platform.owner}-${index}`))
|
|
10562
|
+
}),
|
|
10563
|
+
missing.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10564
|
+
className: "dsm-xdpool-rt-warn",
|
|
10565
|
+
children: t?.("route.missing", { models: missing.join(", ") }) ?? missing.join(", ")
|
|
10566
|
+
}) : null
|
|
10567
|
+
]
|
|
10656
10568
|
});
|
|
10657
10569
|
}
|
|
10658
|
-
/**
|
|
10659
|
-
|
|
10570
|
+
/**
|
|
10571
|
+
* A readable name for one platform.
|
|
10572
|
+
*
|
|
10573
|
+
* `workbuddy` is this plugin's own pool lane, so it gets the product name the cards
|
|
10574
|
+
* use. Every other owner IS a provider id, and it is printed as-is: the aggregate
|
|
10575
|
+
* page, the model picker and this page then show one spelling for one channel, and
|
|
10576
|
+
* inventing a second one here would make the same vendor look like two.
|
|
10577
|
+
*/
|
|
10578
|
+
function ownerName(owner, t) {
|
|
10579
|
+
if (owner === "workbuddy") return t?.("route.ownerPool") ?? "XD Pool(WorkBuddy)";
|
|
10580
|
+
return owner;
|
|
10581
|
+
}
|
|
10582
|
+
/**
|
|
10583
|
+
* The model id WITHOUT its provider prefix.
|
|
10584
|
+
*
|
|
10585
|
+
* A candidate's id is the composite the picker and the aggregate catalog use
|
|
10586
|
+
* (`provider/model`), while a platform group stores bare ids — the owner sits in the
|
|
10587
|
+
* field beside them, so carrying the prefix in both would let the two disagree. A
|
|
10588
|
+
* pool candidate's id is already bare (`hy3`), which is why this checks the prefix
|
|
10589
|
+
* instead of always slicing.
|
|
10590
|
+
*/
|
|
10591
|
+
function bareModelOf(candidate) {
|
|
10592
|
+
const prefix = `${candidate.owner}/`;
|
|
10593
|
+
return candidate.id.startsWith(prefix) ? candidate.id.slice(prefix.length) : candidate.id;
|
|
10594
|
+
}
|
|
10595
|
+
/**
|
|
10596
|
+
* The editor for exactly one chain — TWO LEVELS, like the router walks it.
|
|
10597
|
+
*
|
|
10598
|
+
* Platform order first, then model order inside each platform. That is the
|
|
10599
|
+
* documented fallback semantics ("same platform: try the next model; platform
|
|
10600
|
+
* exhausted: the next platform"), and it is the shape a reader thinks in. The
|
|
10601
|
+
* previous editor was a single flat list of model ids, which could express the same
|
|
10602
|
+
* sequence but could not SHOW the grouping — so moving "all of WorkBuddy's models
|
|
10603
|
+
* together" meant clicking one at a time and hoping none was missed, and the
|
|
10604
|
+
* platform boundaries had to be re-derived by eye from the ids.
|
|
10605
|
+
*
|
|
10606
|
+
* Each platform card carries its own controls (up / down / remove, and the same for
|
|
10607
|
+
* every model inside it), which is why the pickers are local state: a chain can have
|
|
10608
|
+
* a dozen platforms open at once and one shared "picked" slot would make selecting a
|
|
10609
|
+
* model in one card silently change what another card was about to add.
|
|
10610
|
+
*/
|
|
10611
|
+
function ChainEditor({ draft, roster, busy, candidateById, t, onChange }) {
|
|
10612
|
+
/**
|
|
10613
|
+
* One pending selection per platform, keyed by owner, plus one for the
|
|
10614
|
+
* "add platform" row. Local because it is transient editor state: it must not
|
|
10615
|
+
* travel into the draft, or an unsaved picker value would be saved as part of the
|
|
10616
|
+
* chain and reappear on the next open.
|
|
10617
|
+
*/
|
|
10618
|
+
const [pickByLane, setPickByLane] = (0, react.useState)({});
|
|
10619
|
+
const [pickLane, setPickLane] = (0, react.useState)("");
|
|
10620
|
+
/** Owners already in the chain — a platform appears at most once. */
|
|
10621
|
+
const used = draft.platforms.map((platform) => platform.owner);
|
|
10622
|
+
/**
|
|
10623
|
+
* Platforms still available to add, with how many models each could offer.
|
|
10624
|
+
*
|
|
10625
|
+
* Counted from the roster so the dropdown says what it is worth adding: an empty
|
|
10626
|
+
* platform would produce a card with nothing to put in it.
|
|
10627
|
+
*/
|
|
10628
|
+
const addable = (0, react.useMemo)(() => {
|
|
10629
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10630
|
+
for (const candidate of roster) {
|
|
10631
|
+
if (used.includes(candidate.owner)) continue;
|
|
10632
|
+
counts.set(candidate.owner, (counts.get(candidate.owner) ?? 0) + 1);
|
|
10633
|
+
}
|
|
10634
|
+
return [...counts.entries()].map(([owner, count]) => ({
|
|
10635
|
+
owner,
|
|
10636
|
+
count
|
|
10637
|
+
}));
|
|
10638
|
+
}, [roster, used.join("\0")]);
|
|
10639
|
+
/**
|
|
10640
|
+
* Apply one platform edit and RECOMPUTE `flat` from the new structure.
|
|
10641
|
+
*
|
|
10642
|
+
* Every path that changes the platform shape goes through here, so `flat` is only
|
|
10643
|
+
* ever re-derived as the direct result of a user action — never on render. That is
|
|
10644
|
+
* the difference between "the editor reorders what I asked it to" and "saving an
|
|
10645
|
+
* untouched chain silently rewrote my fallback order".
|
|
10646
|
+
*/
|
|
10647
|
+
const commit = (platforms) => {
|
|
10648
|
+
onChange({
|
|
10649
|
+
platforms,
|
|
10650
|
+
flat: flattenDraft(platforms)
|
|
10651
|
+
});
|
|
10652
|
+
};
|
|
10653
|
+
const patchLane = (index, update) => {
|
|
10654
|
+
commit(draft.platforms.map((platform, at) => at === index ? {
|
|
10655
|
+
...platform,
|
|
10656
|
+
...update
|
|
10657
|
+
} : platform));
|
|
10658
|
+
};
|
|
10659
|
+
const moveLane = (index, delta) => {
|
|
10660
|
+
const next = [...draft.platforms];
|
|
10661
|
+
const target = index + delta;
|
|
10662
|
+
if (target < 0 || target >= next.length) return;
|
|
10663
|
+
const [moved] = next.splice(index, 1);
|
|
10664
|
+
if (moved === void 0) return;
|
|
10665
|
+
next.splice(target, 0, moved);
|
|
10666
|
+
commit(next);
|
|
10667
|
+
};
|
|
10668
|
+
const dropLane = (index) => {
|
|
10669
|
+
commit(draft.platforms.filter((_, at) => at !== index));
|
|
10670
|
+
};
|
|
10671
|
+
/** Models of one owner that are not in this platform's group yet. */
|
|
10672
|
+
const candidatesFor = (owner, group) => roster.filter((candidate) => candidate.owner === owner && !group.models.includes(bareModelOf(candidate)));
|
|
10660
10673
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10661
10674
|
className: "dsm-xdpool-rt-editor",
|
|
10662
10675
|
children: [
|
|
@@ -10724,75 +10737,218 @@ window.__ModuleLoader__.load({
|
|
|
10724
10737
|
className: "dsm-xdpool-rt-hint",
|
|
10725
10738
|
children: t?.("route.stickyHint") ?? ""
|
|
10726
10739
|
}),
|
|
10727
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10740
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10728
10741
|
className: "dsm-xdpool-rt-hint",
|
|
10729
|
-
children: t?.("route.
|
|
10730
|
-
}),
|
|
10742
|
+
children: t?.("route.platformOrder") ?? "Platform order (higher is tried first)"
|
|
10743
|
+
}),
|
|
10744
|
+
draft.platforms.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10731
10745
|
className: "dsm-xdpool-rt-warn",
|
|
10732
|
-
children: t?.("route.noMembers") ?? ""
|
|
10733
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
10734
|
-
className: "dsm-xdpool-rt-
|
|
10735
|
-
children: draft.
|
|
10736
|
-
const
|
|
10737
|
-
|
|
10738
|
-
|
|
10746
|
+
children: t?.("route.noMembers") ?? "Add at least one member."
|
|
10747
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10748
|
+
className: "dsm-xdpool-rt-laneboxes",
|
|
10749
|
+
children: draft.platforms.map((platform, index) => {
|
|
10750
|
+
const group = { models: platform.models };
|
|
10751
|
+
const choices = candidatesFor(platform.owner, group);
|
|
10752
|
+
const picked = pickByLane[platform.owner] ?? "";
|
|
10753
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10754
|
+
className: "dsm-xdpool-rt-lanebox",
|
|
10739
10755
|
children: [
|
|
10740
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10741
|
-
className: "dsm-xdpool-rt-
|
|
10742
|
-
children:
|
|
10756
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10757
|
+
className: "dsm-xdpool-rt-lanebox-head",
|
|
10758
|
+
children: [
|
|
10759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10760
|
+
className: "dsm-xdpool-rt-ord",
|
|
10761
|
+
children: index + 1
|
|
10762
|
+
}),
|
|
10763
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10764
|
+
className: "dsm-xdpool-rt-lanebox-name",
|
|
10765
|
+
children: ownerName(platform.owner, t)
|
|
10766
|
+
}),
|
|
10767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10768
|
+
className: "dsm-xdpool-rt-cid",
|
|
10769
|
+
children: platform.owner
|
|
10770
|
+
})
|
|
10771
|
+
]
|
|
10743
10772
|
}),
|
|
10744
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10745
|
-
className: "dsm-xdpool-rt-
|
|
10746
|
-
|
|
10747
|
-
|
|
10773
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10774
|
+
className: "dsm-xdpool-rt-laneacts",
|
|
10775
|
+
children: [
|
|
10776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10777
|
+
type: "button",
|
|
10778
|
+
className: "dsm-xdpool-rt-drop",
|
|
10779
|
+
disabled: index === 0,
|
|
10780
|
+
onClick: () => {
|
|
10781
|
+
moveLane(index, -1);
|
|
10782
|
+
},
|
|
10783
|
+
children: t?.("route.moveUp") ?? "Up"
|
|
10784
|
+
}),
|
|
10785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10786
|
+
type: "button",
|
|
10787
|
+
className: "dsm-xdpool-rt-drop",
|
|
10788
|
+
disabled: index === draft.platforms.length - 1,
|
|
10789
|
+
onClick: () => {
|
|
10790
|
+
moveLane(index, 1);
|
|
10791
|
+
},
|
|
10792
|
+
children: t?.("route.moveDown") ?? "Down"
|
|
10793
|
+
}),
|
|
10794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10795
|
+
type: "button",
|
|
10796
|
+
className: "dsm-xdpool-rt-drop",
|
|
10797
|
+
onClick: () => {
|
|
10798
|
+
dropLane(index);
|
|
10799
|
+
},
|
|
10800
|
+
children: t?.("route.removePlatform") ?? "Remove"
|
|
10801
|
+
})
|
|
10802
|
+
]
|
|
10748
10803
|
}),
|
|
10749
|
-
|
|
10750
|
-
className: "dsm-xdpool-
|
|
10751
|
-
"
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10804
|
+
platform.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10805
|
+
className: "dsm-xdpool-rt-hint",
|
|
10806
|
+
children: t?.("route.platformNoModels") ?? "No model added to this platform yet."
|
|
10807
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
10808
|
+
className: "dsm-xdpool-rt-members",
|
|
10809
|
+
children: platform.models.map((model, modelIndex) => {
|
|
10810
|
+
const fullId = platform.owner === "workbuddy" ? model : `${platform.owner}/${model}`;
|
|
10811
|
+
const candidate = candidateById.get(fullId);
|
|
10812
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
10813
|
+
className: "dsm-xdpool-rt-member",
|
|
10814
|
+
children: [
|
|
10815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10816
|
+
className: "dsm-xdpool-rt-ord",
|
|
10817
|
+
children: modelIndex + 1
|
|
10818
|
+
}),
|
|
10819
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10820
|
+
className: "dsm-xdpool-rt-mid",
|
|
10821
|
+
title: fullId,
|
|
10822
|
+
children: model
|
|
10823
|
+
}),
|
|
10824
|
+
candidate === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10825
|
+
className: "dsm-xdpool-agg-tag dsm-xdpool-rt-mtag",
|
|
10826
|
+
"data-tone": "busy",
|
|
10827
|
+
children: t?.("route.unusable") ?? "needs a key"
|
|
10828
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10829
|
+
className: "dsm-xdpool-agg-tag dsm-xdpool-rt-mtag",
|
|
10830
|
+
"data-tone": "ok",
|
|
10831
|
+
children: compact(candidate.contextWindow)
|
|
10832
|
+
}),
|
|
10833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10834
|
+
type: "button",
|
|
10835
|
+
className: "dsm-xdpool-rt-drop",
|
|
10836
|
+
disabled: modelIndex === 0,
|
|
10837
|
+
onClick: () => {
|
|
10838
|
+
const next = [...platform.models];
|
|
10839
|
+
const [moved] = next.splice(modelIndex, 1);
|
|
10840
|
+
if (moved === void 0) return;
|
|
10841
|
+
next.splice(modelIndex - 1, 0, moved);
|
|
10842
|
+
patchLane(index, { models: next });
|
|
10843
|
+
},
|
|
10844
|
+
children: t?.("route.moveUp") ?? "Up"
|
|
10845
|
+
}),
|
|
10846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10847
|
+
type: "button",
|
|
10848
|
+
className: "dsm-xdpool-rt-drop",
|
|
10849
|
+
disabled: modelIndex === platform.models.length - 1,
|
|
10850
|
+
onClick: () => {
|
|
10851
|
+
const next = [...platform.models];
|
|
10852
|
+
const [moved] = next.splice(modelIndex, 1);
|
|
10853
|
+
if (moved === void 0) return;
|
|
10854
|
+
next.splice(modelIndex + 1, 0, moved);
|
|
10855
|
+
patchLane(index, { models: next });
|
|
10856
|
+
},
|
|
10857
|
+
children: t?.("route.moveDown") ?? "Down"
|
|
10858
|
+
}),
|
|
10859
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10860
|
+
type: "button",
|
|
10861
|
+
className: "dsm-xdpool-rt-drop",
|
|
10862
|
+
onClick: () => {
|
|
10863
|
+
patchLane(index, { models: platform.models.filter((entry) => entry !== model) });
|
|
10864
|
+
},
|
|
10865
|
+
children: t?.("route.removeMember") ?? "Remove"
|
|
10866
|
+
})
|
|
10867
|
+
]
|
|
10868
|
+
}, `${model}-${modelIndex}`);
|
|
10869
|
+
})
|
|
10757
10870
|
}),
|
|
10758
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10871
|
+
choices.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10872
|
+
className: "dsm-xdpool-rt-addrow",
|
|
10873
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
10874
|
+
className: "dsm-xdpool-rt-pick",
|
|
10875
|
+
value: picked,
|
|
10876
|
+
disabled: busy,
|
|
10877
|
+
onChange: (event) => {
|
|
10878
|
+
setPickByLane((prev) => ({
|
|
10879
|
+
...prev,
|
|
10880
|
+
[platform.owner]: event.target.value
|
|
10881
|
+
}));
|
|
10882
|
+
},
|
|
10883
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
10884
|
+
value: "",
|
|
10885
|
+
children: t?.("route.pickModel") ?? "Pick a model…"
|
|
10886
|
+
}), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
10887
|
+
value: bareModelOf(choice),
|
|
10888
|
+
children: [
|
|
10889
|
+
choice.name,
|
|
10890
|
+
" — ",
|
|
10891
|
+
bareModelOf(choice)
|
|
10892
|
+
]
|
|
10893
|
+
}, choice.id))]
|
|
10894
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10895
|
+
type: "button",
|
|
10896
|
+
className: "dsm-btn dsm-btn-outline",
|
|
10897
|
+
disabled: busy || picked === "",
|
|
10898
|
+
onClick: () => {
|
|
10899
|
+
if (picked === "" || platform.models.includes(picked)) return;
|
|
10900
|
+
patchLane(index, { models: [...platform.models, picked] });
|
|
10901
|
+
setPickByLane((prev) => ({
|
|
10902
|
+
...prev,
|
|
10903
|
+
[platform.owner]: ""
|
|
10904
|
+
}));
|
|
10905
|
+
},
|
|
10906
|
+
children: t?.("route.addModel") ?? "Add model"
|
|
10907
|
+
})]
|
|
10765
10908
|
})
|
|
10766
10909
|
]
|
|
10767
|
-
}, `${
|
|
10910
|
+
}, `${platform.owner}-${index}`);
|
|
10768
10911
|
})
|
|
10769
|
-
})
|
|
10770
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10912
|
+
}),
|
|
10913
|
+
addable.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10771
10914
|
className: "dsm-xdpool-rt-addrow",
|
|
10772
10915
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
10773
10916
|
className: "dsm-xdpool-rt-pick",
|
|
10774
|
-
value:
|
|
10917
|
+
value: pickLane,
|
|
10918
|
+
disabled: busy,
|
|
10775
10919
|
onChange: (event) => {
|
|
10776
|
-
|
|
10920
|
+
setPickLane(event.target.value);
|
|
10777
10921
|
},
|
|
10778
10922
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
10779
10923
|
value: "",
|
|
10780
|
-
children: t?.("route.
|
|
10781
|
-
}),
|
|
10782
|
-
value:
|
|
10924
|
+
children: t?.("route.pickPlatform") ?? "Pick a platform…"
|
|
10925
|
+
}), addable.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
10926
|
+
value: entry.owner,
|
|
10783
10927
|
children: [
|
|
10784
|
-
|
|
10785
|
-
"
|
|
10786
|
-
|
|
10928
|
+
ownerName(entry.owner, t),
|
|
10929
|
+
"(",
|
|
10930
|
+
entry.count,
|
|
10931
|
+
")"
|
|
10787
10932
|
]
|
|
10788
|
-
},
|
|
10933
|
+
}, entry.owner))]
|
|
10789
10934
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10790
10935
|
type: "button",
|
|
10791
10936
|
className: "dsm-btn dsm-btn-outline",
|
|
10792
|
-
disabled: busy ||
|
|
10793
|
-
onClick:
|
|
10794
|
-
|
|
10937
|
+
disabled: busy || pickLane === "",
|
|
10938
|
+
onClick: () => {
|
|
10939
|
+
if (pickLane === "" || used.includes(pickLane)) return;
|
|
10940
|
+
commit([...draft.platforms, {
|
|
10941
|
+
owner: pickLane,
|
|
10942
|
+
models: []
|
|
10943
|
+
}]);
|
|
10944
|
+
setPickLane("");
|
|
10945
|
+
},
|
|
10946
|
+
children: t?.("route.addPlatform") ?? "Add platform"
|
|
10795
10947
|
})]
|
|
10948
|
+
}),
|
|
10949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10950
|
+
className: "dsm-xdpool-rt-hint",
|
|
10951
|
+
children: t?.("route.platformHint") ?? "Higher is tried first; the same applies to models inside one platform."
|
|
10796
10952
|
})
|
|
10797
10953
|
]
|
|
10798
10954
|
});
|
|
@@ -11608,6 +11764,35 @@ window.__ModuleLoader__.load({
|
|
|
11608
11764
|
*
|
|
11609
11765
|
* @module dsh-xdpool/client/ChannelPoolSection
|
|
11610
11766
|
*/
|
|
11767
|
+
let poolSnapshot;
|
|
11768
|
+
/**
|
|
11769
|
+
* `provider.status` answered recently, kept across unmounts.
|
|
11770
|
+
*
|
|
11771
|
+
* One request counts EVERY named provider's accounts, so its cost is the sum of
|
|
11772
|
+
* thirteen pool walks. Re-running that on the next visit, for numbers that cannot
|
|
11773
|
+
* have moved, is what made this section feel slow on every open while the login page
|
|
11774
|
+
* (static metadata, no counting) appeared instantly.
|
|
11775
|
+
*
|
|
11776
|
+
* Module-level rather than component state on purpose: switching settings tabs
|
|
11777
|
+
* unmounts the section, and the whole point is that coming back is cheap.
|
|
11778
|
+
*/
|
|
11779
|
+
let providerStatusCache;
|
|
11780
|
+
const PROVIDER_STATUS_TTL_MS = 3e4;
|
|
11781
|
+
/**
|
|
11782
|
+
* Drop the snapshot after anything that changes the data behind it.
|
|
11783
|
+
*
|
|
11784
|
+
* Logins, deletions, toggles and manual balance refreshes all make the snapshot
|
|
11785
|
+
* stale in a way a silent revalidation cannot hide: showing yesterday's account
|
|
11786
|
+
* list for a card the user just added an account to is worse than showing the
|
|
11787
|
+
* "reading" line for a moment.
|
|
11788
|
+
*
|
|
11789
|
+
* The provider-status cache is dropped with it, or a rescan that just found an
|
|
11790
|
+
* account would be invisible behind a 30-second-old count.
|
|
11791
|
+
*/
|
|
11792
|
+
function invalidatePoolSnapshot() {
|
|
11793
|
+
poolSnapshot = void 0;
|
|
11794
|
+
providerStatusCache = void 0;
|
|
11795
|
+
}
|
|
11611
11796
|
/** How many automatic retries a cold-start mount gets before giving up. */
|
|
11612
11797
|
const LOAD_RETRIES = 5;
|
|
11613
11798
|
/** Gap between those retries, in milliseconds. */
|
|
@@ -11683,9 +11868,14 @@ window.__ModuleLoader__.load({
|
|
|
11683
11868
|
*/
|
|
11684
11869
|
function expiryText(expiresAt, t) {
|
|
11685
11870
|
if (expiresAt === void 0 || !Number.isFinite(expiresAt) || expiresAt <= 0) return t?.("channelpool.expiryNever") ?? "no expiry";
|
|
11686
|
-
const
|
|
11687
|
-
if (
|
|
11688
|
-
|
|
11871
|
+
const remainingMs = expiresAt - Date.now();
|
|
11872
|
+
if (remainingMs <= 0) return t?.("channelpool.expiryExpired") ?? "expired";
|
|
11873
|
+
const minutes = Math.floor(remainingMs / 6e4);
|
|
11874
|
+
if (minutes < 60) return minutes <= 1 ? "<1m" : `${String(minutes)}m`;
|
|
11875
|
+
const hours = Math.floor(remainingMs / 36e5);
|
|
11876
|
+
if (hours < 24) return `${String(hours)}h`;
|
|
11877
|
+
const days = Math.floor(remainingMs / 864e5);
|
|
11878
|
+
return days === 1 ? "1d" : `${String(days)}d`;
|
|
11689
11879
|
}
|
|
11690
11880
|
/** The vendor's currency, spelled the way that vendor spells it. */
|
|
11691
11881
|
function creditUnitLabel(channel, t) {
|
|
@@ -11699,9 +11889,31 @@ window.__ModuleLoader__.load({
|
|
|
11699
11889
|
function amount(value) {
|
|
11700
11890
|
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
11701
11891
|
}
|
|
11892
|
+
async function cachedProviderStatus(providers) {
|
|
11893
|
+
const key = [...providers].sort().join("\0");
|
|
11894
|
+
const hit = providerStatusCache;
|
|
11895
|
+
if (hit !== void 0 && hit.key === key && Date.now() - hit.at < PROVIDER_STATUS_TTL_MS) return hit.answer;
|
|
11896
|
+
const answer = await callPack("provider.status", { providers: [...providers] });
|
|
11897
|
+
providerStatusCache = {
|
|
11898
|
+
at: Date.now(),
|
|
11899
|
+
key,
|
|
11900
|
+
answer
|
|
11901
|
+
};
|
|
11902
|
+
return answer;
|
|
11903
|
+
}
|
|
11702
11904
|
function ChannelPoolSection({ t }) {
|
|
11703
|
-
const [entries, setEntries] = (0, react.useState)([]);
|
|
11704
|
-
const [error, setError] = (0, react.useState)(
|
|
11905
|
+
const [entries, setEntries] = (0, react.useState)(() => poolSnapshot?.entries ?? []);
|
|
11906
|
+
const [error, setError] = (0, react.useState)(() => poolSnapshot?.error);
|
|
11907
|
+
/**
|
|
11908
|
+
* True until the first wave has answered once.
|
|
11909
|
+
*
|
|
11910
|
+
* The section used to render NOTHING while loading, so a cold start and a
|
|
11911
|
+
* broken RPC bridge looked identical: blank (#95). The bridge failing is a
|
|
11912
|
+
* real, common failure — `provider.status` answers `ok: false`, the map comes
|
|
11913
|
+
* back empty, `scanProviders` is empty and the old code returned `<></>` with
|
|
11914
|
+
* `error === undefined`. Silence was indistinguishable from "no accounts".
|
|
11915
|
+
*/
|
|
11916
|
+
const [loading, setLoading] = (0, react.useState)(() => poolSnapshot === void 0);
|
|
11705
11917
|
const [notice, setNotice] = (0, react.useState)(void 0);
|
|
11706
11918
|
/** provider → the open list, or `undefined` for a card with both folded. */
|
|
11707
11919
|
const [folds, setFolds] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
@@ -11714,6 +11926,15 @@ window.__ModuleLoader__.load({
|
|
|
11714
11926
|
const entriesRef = (0, react.useRef)(entries);
|
|
11715
11927
|
entriesRef.current = entries;
|
|
11716
11928
|
const mountedRef = (0, react.useRef)(true);
|
|
11929
|
+
/**
|
|
11930
|
+
* Which load is current.
|
|
11931
|
+
*
|
|
11932
|
+
* Two things make a stale answer arrive late: the retry ladder re-runs the
|
|
11933
|
+
* whole chain, and a remount starts a new chain while the old request is
|
|
11934
|
+
* still in flight. Stamping each run is what keeps an older wave-2 result
|
|
11935
|
+
* from painting over a newer wave-1 one.
|
|
11936
|
+
*/
|
|
11937
|
+
const loadSeq = (0, react.useRef)(0);
|
|
11717
11938
|
(0, react.useEffect)(() => () => {
|
|
11718
11939
|
mountedRef.current = false;
|
|
11719
11940
|
}, []);
|
|
@@ -11726,34 +11947,27 @@ window.__ModuleLoader__.load({
|
|
|
11726
11947
|
});
|
|
11727
11948
|
};
|
|
11728
11949
|
const load = async () => {
|
|
11950
|
+
loadSeq.current += 1;
|
|
11729
11951
|
try {
|
|
11730
11952
|
const channels = (await fetchChannels()).filter((channel) => !REGION_OWNED_CHANNELS.has(channel.id));
|
|
11731
|
-
const statusAnswer = await
|
|
11953
|
+
const statusAnswer = await cachedProviderStatus(channels.map((channel) => channel.id));
|
|
11732
11954
|
const statuses = providerStatusesFrom(statusAnswer);
|
|
11733
11955
|
const scanProviders = channels.map((channel) => channel.id).filter((id) => (statuses.get(id)?.accountsTotal ?? 0) > 0);
|
|
11734
11956
|
const perProvider = await Promise.all(scanProviders.map(async (provider) => {
|
|
11735
11957
|
const wantBalances = channels.find((channel) => channel.id === provider)?.credits !== void 0;
|
|
11736
11958
|
const accountsPromise = callPack("account.list", { provider });
|
|
11737
11959
|
const modelsPromise = callPack("model.list", { provider });
|
|
11738
|
-
const
|
|
11739
|
-
const statusesPromise = wantBalances && channels.find((c) => c.id === provider)?.credits?.status === true ? callPack("credits.status", { provider }) : Promise.resolve(void 0);
|
|
11740
|
-
const [accounts, models, balances, creditStatuses] = await Promise.all([
|
|
11741
|
-
accountsPromise.catch(() => refused("account.list failed")),
|
|
11742
|
-
modelsPromise.catch(() => refused("model.list failed")),
|
|
11743
|
-
balancesPromise.catch(() => refused("credits.balances failed")),
|
|
11744
|
-
statusesPromise.catch(() => refused("credits.status failed"))
|
|
11745
|
-
]);
|
|
11960
|
+
const [accounts, models] = await Promise.all([accountsPromise.catch(() => refused("account.list failed")), modelsPromise.catch(() => refused("model.list failed"))]);
|
|
11746
11961
|
const modelRows = modelsFrom(models.value);
|
|
11747
11962
|
const modelEmptyBecauseRefused = modelRows.length === 0 && models.ok !== true;
|
|
11748
|
-
const balanceRows = balances === void 0 ? [] : balancesFrom(balances);
|
|
11749
11963
|
return [provider, {
|
|
11750
11964
|
accounts: dedupeAccounts(accountsFrom(accounts)),
|
|
11751
11965
|
models: sortChannelModels(modelRows),
|
|
11752
11966
|
facts: /* @__PURE__ */ new Map(),
|
|
11753
|
-
balances:
|
|
11754
|
-
statuses:
|
|
11755
|
-
balancePending:
|
|
11756
|
-
balanceError:
|
|
11967
|
+
balances: [],
|
|
11968
|
+
statuses: [],
|
|
11969
|
+
balancePending: wantBalances,
|
|
11970
|
+
balanceError: void 0,
|
|
11757
11971
|
modelError: modelEmptyBecauseRefused ? models.error?.message ?? "model.list refused" : void 0
|
|
11758
11972
|
}];
|
|
11759
11973
|
}));
|
|
@@ -11764,25 +11978,48 @@ window.__ModuleLoader__.load({
|
|
|
11764
11978
|
facts: /* @__PURE__ */ new Map(),
|
|
11765
11979
|
status: statuses.get(channel.id)
|
|
11766
11980
|
}));
|
|
11981
|
+
poolSnapshot = {
|
|
11982
|
+
entries: next,
|
|
11983
|
+
error: void 0
|
|
11984
|
+
};
|
|
11767
11985
|
if (!mountedRef.current) return;
|
|
11986
|
+
const seq = loadSeq.current;
|
|
11768
11987
|
setEntries(next);
|
|
11769
11988
|
setError(void 0);
|
|
11989
|
+
setLoading(false);
|
|
11770
11990
|
if (next.length === 0) {
|
|
11771
|
-
if (!statusAnswer.ok)
|
|
11991
|
+
if (!statusAnswer.ok) {
|
|
11992
|
+
const message = statusAnswer.error?.message ?? "provider.status refused";
|
|
11993
|
+
poolSnapshot = {
|
|
11994
|
+
entries: next,
|
|
11995
|
+
error: message
|
|
11996
|
+
};
|
|
11997
|
+
setError(message);
|
|
11998
|
+
}
|
|
11772
11999
|
return;
|
|
11773
12000
|
}
|
|
12001
|
+
for (const entry of next) {
|
|
12002
|
+
if (entry.channel.credits === void 0) continue;
|
|
12003
|
+
refreshBalances(entry.channel.id, false, true, seq);
|
|
12004
|
+
}
|
|
11774
12005
|
const idsByProvider = {};
|
|
11775
12006
|
for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
|
|
11776
12007
|
const facts = await fetchChannelModels(next.map((entry) => entry.channel.id), idsByProvider).catch(() => /* @__PURE__ */ new Map());
|
|
11777
|
-
if (!mountedRef.current) return;
|
|
12008
|
+
if (!mountedRef.current || loadSeq.current !== seq) return;
|
|
11778
12009
|
if (facts.size === 0) return;
|
|
11779
12010
|
setEntries((previous) => previous.map((entry) => ({
|
|
11780
12011
|
...entry,
|
|
11781
12012
|
facts: new Map((facts.get(entry.channel.id) ?? []).map((model) => [model.id, model]))
|
|
11782
12013
|
})));
|
|
11783
12014
|
} catch (cause) {
|
|
12015
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
12016
|
+
poolSnapshot = {
|
|
12017
|
+
entries: entriesRef.current,
|
|
12018
|
+
error: message
|
|
12019
|
+
};
|
|
11784
12020
|
if (!mountedRef.current) return;
|
|
11785
|
-
setError(
|
|
12021
|
+
setError(message);
|
|
12022
|
+
setLoading(false);
|
|
11786
12023
|
}
|
|
11787
12024
|
};
|
|
11788
12025
|
/**
|
|
@@ -11936,8 +12173,20 @@ window.__ModuleLoader__.load({
|
|
|
11936
12173
|
await load();
|
|
11937
12174
|
}
|
|
11938
12175
|
};
|
|
11939
|
-
|
|
11940
|
-
|
|
12176
|
+
/**
|
|
12177
|
+
* Read one channel's balances and merge them into its card.
|
|
12178
|
+
*
|
|
12179
|
+
* Wave 2 of the load calls this with `quiet` true: the card is already on
|
|
12180
|
+
* screen, so the provider must not be marked busy (that would disable the
|
|
12181
|
+
* claim button for a network read the reader did not ask for) and a failure
|
|
12182
|
+
* must land in `balanceError` rather than in the section-wide notice.
|
|
12183
|
+
*
|
|
12184
|
+
* `seq` is the load that asked for this read. A reply from an older load
|
|
12185
|
+
* cannot overwrite a newer card, which is what the previous version got wrong
|
|
12186
|
+
* when a remount overlapped an in-flight refresh.
|
|
12187
|
+
*/
|
|
12188
|
+
const refreshBalances = async (provider, force, quiet = false, seq) => {
|
|
12189
|
+
if (!quiet) markBusy(provider, true);
|
|
11941
12190
|
setEntries((previous) => previous.map((entry) => entry.channel.id === provider ? {
|
|
11942
12191
|
...entry,
|
|
11943
12192
|
balancePending: true
|
|
@@ -11947,7 +12196,21 @@ window.__ModuleLoader__.load({
|
|
|
11947
12196
|
provider,
|
|
11948
12197
|
force: true
|
|
11949
12198
|
} : { provider });
|
|
11950
|
-
if (
|
|
12199
|
+
if (seq !== void 0 && loadSeq.current !== seq) return;
|
|
12200
|
+
if (!mountedRef.current) {
|
|
12201
|
+
if (envelope.ok) {
|
|
12202
|
+
const rows = balancesFrom(envelope);
|
|
12203
|
+
poolSnapshot = {
|
|
12204
|
+
entries: (poolSnapshot?.entries ?? []).map((entry) => entry.channel.id === provider ? {
|
|
12205
|
+
...entry,
|
|
12206
|
+
balancePending: false,
|
|
12207
|
+
balances: rows
|
|
12208
|
+
} : entry),
|
|
12209
|
+
error: poolSnapshot?.error
|
|
12210
|
+
};
|
|
12211
|
+
}
|
|
12212
|
+
return;
|
|
12213
|
+
}
|
|
11951
12214
|
if (!envelope.ok) {
|
|
11952
12215
|
setEntries((previous) => previous.map((entry) => entry.channel.id === provider ? {
|
|
11953
12216
|
...entry,
|
|
@@ -11964,7 +12227,7 @@ window.__ModuleLoader__.load({
|
|
|
11964
12227
|
balances: rows
|
|
11965
12228
|
} : entry));
|
|
11966
12229
|
} finally {
|
|
11967
|
-
if (mountedRef.current) markBusy(provider, false);
|
|
12230
|
+
if (mountedRef.current && !quiet) markBusy(provider, false);
|
|
11968
12231
|
}
|
|
11969
12232
|
};
|
|
11970
12233
|
const claim = async (provider) => {
|
|
@@ -12008,12 +12271,24 @@ window.__ModuleLoader__.load({
|
|
|
12008
12271
|
return;
|
|
12009
12272
|
}
|
|
12010
12273
|
if (action === "test") setNotice(t?.("channelpool.testOk") ?? "Reachable.");
|
|
12274
|
+
invalidatePoolSnapshot();
|
|
12011
12275
|
await load();
|
|
12012
12276
|
} finally {
|
|
12013
12277
|
if (mountedRef.current) markBusy(provider, false);
|
|
12014
12278
|
}
|
|
12015
12279
|
};
|
|
12016
|
-
if (entries.length === 0 && error === void 0)
|
|
12280
|
+
if (entries.length === 0 && error === void 0) {
|
|
12281
|
+
if (!loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
12282
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
12283
|
+
className: "dsm-xdpool-cpool",
|
|
12284
|
+
"aria-label": t?.("channelpool.title") ?? "Channels",
|
|
12285
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
12286
|
+
className: "dsm-xdpool-note",
|
|
12287
|
+
role: "status",
|
|
12288
|
+
children: t?.("channelpool.loading") ?? "Reading scan channels…"
|
|
12289
|
+
})
|
|
12290
|
+
});
|
|
12291
|
+
}
|
|
12017
12292
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
12018
12293
|
className: "dsm-xdpool-cpool",
|
|
12019
12294
|
"aria-label": t?.("channelpool.title") ?? "Channels",
|
|
@@ -12408,7 +12683,7 @@ window.__ModuleLoader__.load({
|
|
|
12408
12683
|
*
|
|
12409
12684
|
* @module dsh-xdpool/version
|
|
12410
12685
|
*/
|
|
12411
|
-
const POOL_VERSION = "2.
|
|
12686
|
+
const POOL_VERSION = "2.6.0";
|
|
12412
12687
|
//#endregion
|
|
12413
12688
|
//#region src/client/PoolCard.tsx
|
|
12414
12689
|
/**
|
|
@@ -12535,13 +12810,6 @@ window.__ModuleLoader__.load({
|
|
|
12535
12810
|
* the two cache-friendly extremes, then the two spreading modes. The row is
|
|
12536
12811
|
* laid out one chip per mode, so this list length is also the column count.
|
|
12537
12812
|
*/
|
|
12538
|
-
const DIST_OPTIONS = [
|
|
12539
|
-
"sticky",
|
|
12540
|
-
"priority",
|
|
12541
|
-
"balanced",
|
|
12542
|
-
"round-robin",
|
|
12543
|
-
"expiry"
|
|
12544
|
-
];
|
|
12545
12813
|
/**
|
|
12546
12814
|
* The two supplier cards, in the order they are shown.
|
|
12547
12815
|
*
|
|
@@ -12601,22 +12869,6 @@ window.__ModuleLoader__.load({
|
|
|
12601
12869
|
const reported = (status?.accounts ?? []).map((account) => account.credits?.total).filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
12602
12870
|
return reported.length === 0 ? void 0 : formatNumber(reported.reduce((sum, value) => sum + value, 0));
|
|
12603
12871
|
}
|
|
12604
|
-
/** Localized name of one distribution mode. */
|
|
12605
|
-
function distLabel(option, t) {
|
|
12606
|
-
if (option === "sticky") return t?.("row.distSticky") ?? "Per conversation";
|
|
12607
|
-
if (option === "priority") return t?.("row.distPriority") ?? "Priority";
|
|
12608
|
-
if (option === "balanced") return t?.("row.distBalanced") ?? "Balanced";
|
|
12609
|
-
if (option === "expiry") return t?.("row.distExpiry") ?? "Expiring first";
|
|
12610
|
-
return t?.("row.distRoundRobin") ?? "Round-robin";
|
|
12611
|
-
}
|
|
12612
|
-
/** One-line explanation of what a mode does to caching and spend. */
|
|
12613
|
-
function distHint(option, t) {
|
|
12614
|
-
if (option === "sticky") return t?.("row.distStickyHint") ?? "";
|
|
12615
|
-
if (option === "priority") return t?.("row.distPriorityHint") ?? "";
|
|
12616
|
-
if (option === "balanced") return t?.("row.distBalancedHint") ?? "";
|
|
12617
|
-
if (option === "expiry") return t?.("row.distExpiryHint") ?? "";
|
|
12618
|
-
return t?.("row.distRoundRobinHint") ?? "";
|
|
12619
|
-
}
|
|
12620
12872
|
/** Build the draft from the server's selection + catalog flags. */
|
|
12621
12873
|
function draftFromStatus(status) {
|
|
12622
12874
|
const selection = status.selection;
|
|
@@ -12740,11 +12992,37 @@ window.__ModuleLoader__.load({
|
|
|
12740
12992
|
* already expanded is a wall of text where the user asked for a summary, and
|
|
12741
12993
|
* the disclosure is one click away.
|
|
12742
12994
|
*/
|
|
12995
|
+
/**
|
|
12996
|
+
* Which disclosure pane is open, keyed by pane id.
|
|
12997
|
+
*
|
|
12998
|
+
* ACCORDION WITHIN A CARD: opening one pane closes the others of the SAME
|
|
12999
|
+
* card. The panes are tall — the account list is one row per account and the
|
|
13000
|
+
* model list is one card per model — so two open at once means the second one
|
|
13001
|
+
* is off-screen below the first, and the user who just clicked "Models"
|
|
13002
|
+
* sees nothing happen (reported as "点了咋没起作用,实际都续在后面").
|
|
13003
|
+
*
|
|
13004
|
+
* Cross-CARD independence is kept: the pane ids carry the region, so opening
|
|
13005
|
+
* the domestic model list must not close the international one. Collapsing is
|
|
13006
|
+
* scoped to the prefix before the last separator.
|
|
13007
|
+
*
|
|
13008
|
+
* All panes start closed: a card that arrives with a 200-row account list
|
|
13009
|
+
* already expanded is a wall of text where the user asked for a summary.
|
|
13010
|
+
*/
|
|
12743
13011
|
const [openedPanes, setOpenedPanes] = (0, react.useState)({});
|
|
12744
13012
|
const setPane = (paneId, open) => {
|
|
12745
|
-
setOpenedPanes((prev) =>
|
|
12746
|
-
|
|
12747
|
-
|
|
13013
|
+
setOpenedPanes((prev) => {
|
|
13014
|
+
if (open === false) return prev[paneId] === false ? prev : {
|
|
13015
|
+
...prev,
|
|
13016
|
+
[paneId]: false
|
|
13017
|
+
};
|
|
13018
|
+
const cardPrefix = paneId.slice(0, paneId.lastIndexOf("-"));
|
|
13019
|
+
const next = {};
|
|
13020
|
+
for (const [id, value] of Object.entries(prev)) {
|
|
13021
|
+
if (id !== paneId && value === true && id.startsWith(`${cardPrefix}-`)) continue;
|
|
13022
|
+
next[id] = value;
|
|
13023
|
+
}
|
|
13024
|
+
next[paneId] = true;
|
|
13025
|
+
return next;
|
|
12748
13026
|
});
|
|
12749
13027
|
};
|
|
12750
13028
|
const [error, setError] = (0, react.useState)(void 0);
|
|
@@ -12791,10 +13069,6 @@ window.__ModuleLoader__.load({
|
|
|
12791
13069
|
id: "ledger",
|
|
12792
13070
|
label: t?.("shell.tabLedger") ?? "Dashboard"
|
|
12793
13071
|
},
|
|
12794
|
-
{
|
|
12795
|
-
id: "billion",
|
|
12796
|
-
label: t?.("shell.tabBillion") ?? "billion-context"
|
|
12797
|
-
},
|
|
12798
13072
|
{
|
|
12799
13073
|
id: "routing",
|
|
12800
13074
|
label: t?.("shell.tabRouting") ?? "Routing"
|
|
@@ -12848,6 +13122,16 @@ window.__ModuleLoader__.load({
|
|
|
12848
13122
|
*/
|
|
12849
13123
|
const [draftByRegion, setDraftByRegion] = (0, react.useState)({});
|
|
12850
13124
|
/**
|
|
13125
|
+
* Whether a region's model pane is showing the WHOLE roster.
|
|
13126
|
+
*
|
|
13127
|
+
* Per region, and default off, because the pane opens on a card the user
|
|
13128
|
+
* scrolled to reach: fourteen full rows of mostly-disabled models pushed
|
|
13129
|
+
* everything else a screen and a half further down (the reported "无限往下
|
|
13130
|
+
* 续"). The enabled rows are what "my models" means; the rest are one click
|
|
13131
|
+
* away and the button says how many there are.
|
|
13132
|
+
*/
|
|
13133
|
+
const [showAllModels, setShowAllModels] = (0, react.useState)({});
|
|
13134
|
+
/**
|
|
12851
13135
|
* The draft one region card is holding, if it holds any.
|
|
12852
13136
|
*
|
|
12853
13137
|
* Keyed by region rather than "whatever tab is in front": the two gateways
|
|
@@ -13278,11 +13562,14 @@ window.__ModuleLoader__.load({
|
|
|
13278
13562
|
const here = statusOf(region);
|
|
13279
13563
|
const local = draftByRegion[region];
|
|
13280
13564
|
const rows = local ?? (here === void 0 ? {} : draftFromStatus(here));
|
|
13565
|
+
const listed = new Set(Object.keys(rows));
|
|
13566
|
+
const missing = (here?.selection.enabledModelIds ?? []).filter((id) => !listed.has(id));
|
|
13281
13567
|
return {
|
|
13282
13568
|
draft: rows,
|
|
13283
13569
|
editable: settingsWritable,
|
|
13284
13570
|
dirty: local !== void 0 && here !== void 0 && draftIsDirty(here, local),
|
|
13285
|
-
enabled: Object.values(rows).filter((entry) => entry.enabled).length
|
|
13571
|
+
enabled: Object.values(rows).filter((entry) => entry.enabled).length,
|
|
13572
|
+
missing
|
|
13286
13573
|
};
|
|
13287
13574
|
};
|
|
13288
13575
|
/**
|
|
@@ -13675,7 +13962,7 @@ window.__ModuleLoader__.load({
|
|
|
13675
13962
|
children: page === "aggregate" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AggregatePage, { t }) : page === "channels" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChannelsPage, { t }) : page === "ledger" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LedgerPage, {
|
|
13676
13963
|
t,
|
|
13677
13964
|
usage
|
|
13678
|
-
}) : page === "
|
|
13965
|
+
}) : page === "routing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoutingPage, { t }) : page === "gateway" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GatewayPage, { t }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
13679
13966
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: POOL_CARD_CSS }),
|
|
13680
13967
|
usage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
13681
13968
|
className: "dsm-xdpool-card dsm-xdpool-usage",
|
|
@@ -14056,70 +14343,92 @@ window.__ModuleLoader__.load({
|
|
|
14056
14343
|
children: models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
14057
14344
|
className: "dsm-xdpool-region-empty",
|
|
14058
14345
|
children: t?.("row.regionNoModels") ?? "No model published by this gateway yet."
|
|
14059
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
className: "dsm-xdpool-region-pane-title",
|
|
14063
|
-
children: t?.("row.modelsEnabledCount", {
|
|
14064
|
-
enabled: card.modelState.enabled,
|
|
14065
|
-
total: models.length
|
|
14066
|
-
}) ?? `${card.modelState.enabled} / ${models.length} enabled`
|
|
14067
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14068
|
-
className: "dsm-xdpool-models-actions",
|
|
14346
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
14347
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14348
|
+
className: "dsm-xdpool-region-pane-head",
|
|
14069
14349
|
children: [
|
|
14070
|
-
|
|
14071
|
-
className: "dsm-xdpool-
|
|
14072
|
-
|
|
14073
|
-
|
|
14350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14351
|
+
className: "dsm-xdpool-region-pane-title",
|
|
14352
|
+
children: t?.("row.modelsEnabledCount", {
|
|
14353
|
+
enabled: card.modelState.enabled,
|
|
14354
|
+
total: models.length
|
|
14355
|
+
}) ?? `${card.modelState.enabled} / ${models.length} enabled`
|
|
14074
14356
|
}),
|
|
14075
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
onClick: () => {
|
|
14080
|
-
discardModels(region);
|
|
14081
|
-
},
|
|
14082
|
-
children: t?.("row.modelsDiscard") ?? "Discard"
|
|
14357
|
+
card.modelState.missing.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14358
|
+
className: "dsm-xdpool-region-pane-warn",
|
|
14359
|
+
title: card.modelState.missing.join(", "),
|
|
14360
|
+
children: t?.("row.modelsMissingSaved", { count: card.modelState.missing.length }) ?? `${card.modelState.missing.length} saved model(s) not offered right now`
|
|
14083
14361
|
}),
|
|
14084
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14363
|
+
className: "dsm-xdpool-models-actions",
|
|
14364
|
+
children: [
|
|
14365
|
+
card.status?.catalogSource !== "fallback" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14366
|
+
className: "dsm-xdpool-catalog-offline",
|
|
14367
|
+
title: card.status.catalogError ?? void 0,
|
|
14368
|
+
children: t?.("row.catalogOffline") ?? "built-in list (offline)"
|
|
14369
|
+
}),
|
|
14370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14371
|
+
type: "button",
|
|
14372
|
+
className: "dsm-btn dsm-btn-outline",
|
|
14373
|
+
disabled: !card.modelState.dirty,
|
|
14374
|
+
onClick: () => {
|
|
14375
|
+
discardModels(region);
|
|
14376
|
+
},
|
|
14377
|
+
children: t?.("row.modelsDiscard") ?? "Discard"
|
|
14378
|
+
}),
|
|
14379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14380
|
+
type: "button",
|
|
14381
|
+
className: "dsm-btn dsm-btn-primary",
|
|
14382
|
+
disabled: !card.modelState.dirty || card.modelState.enabled === 0,
|
|
14383
|
+
onClick: () => {
|
|
14384
|
+
saveModels(region);
|
|
14385
|
+
},
|
|
14386
|
+
children: savingModelsRegion === region ? t?.("row.modelsSaving") ?? "Saving…" : t?.("row.modelsSave") ?? "Save"
|
|
14387
|
+
})
|
|
14388
|
+
]
|
|
14092
14389
|
})
|
|
14093
14390
|
]
|
|
14094
|
-
})
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14391
|
+
}),
|
|
14392
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14393
|
+
className: "dsm-xdpool-model-list",
|
|
14394
|
+
children: (showAllModels[region] === true ? [...models] : models.filter((model) => card.modelState.draft[model.id]?.enabled ?? model.enabled)).sort((a, b) => Number(isFreeNow(b, now, region)) - Number(isFreeNow(a, now, region))).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
|
|
14395
|
+
model,
|
|
14396
|
+
region,
|
|
14397
|
+
paidTwin: paidAlternativeFor(model, models),
|
|
14398
|
+
t,
|
|
14399
|
+
draft: card.modelState.draft[model.id] ?? {
|
|
14400
|
+
enabled: model.enabled,
|
|
14401
|
+
images: model.supportsImages
|
|
14402
|
+
},
|
|
14403
|
+
editable: card.modelState.editable,
|
|
14404
|
+
onToggle: (id) => {
|
|
14405
|
+
toggleModel(region, id);
|
|
14406
|
+
},
|
|
14407
|
+
onToggleImage: (id) => {
|
|
14408
|
+
toggleModelImage(region, id);
|
|
14409
|
+
},
|
|
14410
|
+
onBudget: (id, budget) => {
|
|
14411
|
+
setModelBudget(region, id, budget);
|
|
14412
|
+
},
|
|
14413
|
+
onTest: () => {
|
|
14414
|
+
testModel(XDPOOL_REGION_PROVIDER[region], model.id);
|
|
14415
|
+
},
|
|
14416
|
+
probe: probes[model.id],
|
|
14417
|
+
probing: probingModel === model.id
|
|
14418
|
+
}, model.id))
|
|
14419
|
+
}),
|
|
14420
|
+
models.length > models.filter((model) => card.modelState.draft[model.id]?.enabled ?? model.enabled).length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14421
|
+
type: "button",
|
|
14422
|
+
className: "dsm-xdpool-model-more",
|
|
14423
|
+
onClick: () => {
|
|
14424
|
+
setShowAllModels((prev) => ({
|
|
14425
|
+
...prev,
|
|
14426
|
+
[region]: !(prev[region] === true)
|
|
14427
|
+
}));
|
|
14118
14428
|
},
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
})] })
|
|
14429
|
+
children: showAllModels[region] === true ? t?.("row.modelsShowLess") ?? "只显示已启用" : t?.("row.modelsShowAll", { count: models.length - models.filter((model) => card.modelState.draft[model.id]?.enabled ?? model.enabled).length }) ?? `显示其余 ${models.length} 个`
|
|
14430
|
+
}) : null
|
|
14431
|
+
] })
|
|
14123
14432
|
}),
|
|
14124
14433
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegionCardPane, {
|
|
14125
14434
|
id: distPaneId,
|
|
@@ -14279,6 +14588,27 @@ window.__ModuleLoader__.load({
|
|
|
14279
14588
|
job?.detail === void 0 || job.detail.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14280
14589
|
className: "dsm-xdpool-auto-job-detail",
|
|
14281
14590
|
children: job.detail.join(" · ")
|
|
14591
|
+
}),
|
|
14592
|
+
job?.items === void 0 || job.items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14593
|
+
className: "dsm-xdpool-auto-job-items",
|
|
14594
|
+
children: [
|
|
14595
|
+
"claimed",
|
|
14596
|
+
"already",
|
|
14597
|
+
"skipped",
|
|
14598
|
+
"failed"
|
|
14599
|
+
].map((status) => {
|
|
14600
|
+
const group = job.items?.filter((item) => item.status === status) ?? [];
|
|
14601
|
+
if (group.length === 0) return null;
|
|
14602
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
14603
|
+
className: `dsm-xdpool-auto-job-item dsm-xdpool-auto-job-item-${status}`,
|
|
14604
|
+
title: group.map((item) => item.title + (item.note === void 0 ? "" : ` (${item.note})`)).join("\n"),
|
|
14605
|
+
children: [
|
|
14606
|
+
t?.(`row.autoItem.${status}`) ?? status,
|
|
14607
|
+
" ",
|
|
14608
|
+
group.length
|
|
14609
|
+
]
|
|
14610
|
+
}, status);
|
|
14611
|
+
})
|
|
14282
14612
|
})
|
|
14283
14613
|
]
|
|
14284
14614
|
}, kind);
|
|
@@ -14700,6 +15030,20 @@ window.__ModuleLoader__.load({
|
|
|
14700
15030
|
global: "xdpool-global"
|
|
14701
15031
|
};
|
|
14702
15032
|
function ModelRow({ model, region, paidTwin, t, draft, editable, onToggle, onToggleImage, onBudget, onTest, probe, probing }) {
|
|
15033
|
+
/**
|
|
15034
|
+
* A per-MOUNTED-ROW scope for the two budget radios.
|
|
15035
|
+
*
|
|
15036
|
+
* HTML groups radios by `name`, and React renders both region cards at once —
|
|
15037
|
+
* the collapsible panes stay in the DOM with `hidden`, they are not unmounted.
|
|
15038
|
+
* A `name` built from the model id alone is therefore the SAME name in the
|
|
15039
|
+
* cn card and the global card, so the two rows joined one radio group: the
|
|
15040
|
+
* browser keeps only the last checked member, and whichever card renders
|
|
15041
|
+
* later (global) silently un-checks the cn row's "1M". Adding the region
|
|
15042
|
+
* would not be enough either — the settings page and the sidebar pane can
|
|
15043
|
+
* both mount the same region — so the scope has to name the row instance,
|
|
15044
|
+
* which is what `useId` is for.
|
|
15045
|
+
*/
|
|
15046
|
+
const radioScope = (0, react.useId)();
|
|
14703
15047
|
const now = /* @__PURE__ */ new Date();
|
|
14704
15048
|
const tag = tagFor(model, now, region);
|
|
14705
15049
|
const promo = promoStatusFor(model, now, region);
|
|
@@ -14768,7 +15112,7 @@ window.__ModuleLoader__.load({
|
|
|
14768
15112
|
"aria-label": t?.("row.modelContextBudget") ?? "Context",
|
|
14769
15113
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
14770
15114
|
type: "radio",
|
|
14771
|
-
name: `budget-${
|
|
15115
|
+
name: `budget-${radioScope}`,
|
|
14772
15116
|
checked: currentBudget === DEFAULT_CONTEXT_BUDGET,
|
|
14773
15117
|
disabled: !editable,
|
|
14774
15118
|
onChange: () => {
|
|
@@ -14776,7 +15120,7 @@ window.__ModuleLoader__.load({
|
|
|
14776
15120
|
}
|
|
14777
15121
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(DEFAULT_CONTEXT_BUDGET) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
14778
15122
|
type: "radio",
|
|
14779
|
-
name: `budget-${
|
|
15123
|
+
name: `budget-${radioScope}`,
|
|
14780
15124
|
checked: currentBudget === native,
|
|
14781
15125
|
disabled: !editable,
|
|
14782
15126
|
onChange: () => {
|
|
@@ -15072,6 +15416,9 @@ window.__ModuleLoader__.load({
|
|
|
15072
15416
|
"facts.probeOk": "answered",
|
|
15073
15417
|
"facts.probeFailed": "no answer",
|
|
15074
15418
|
"row.modelsEnabledCount": "{enabled} / {total} enabled",
|
|
15419
|
+
"row.modelsMissingSaved": "{count} saved model(s) not offered right now",
|
|
15420
|
+
"row.modelsShowAll": "Show the other {count}",
|
|
15421
|
+
"row.modelsShowLess": "Show only enabled",
|
|
15075
15422
|
"row.modelsSave": "Save",
|
|
15076
15423
|
"row.modelsSaving": "Saving…",
|
|
15077
15424
|
"row.modelsDiscard": "Discard",
|
|
@@ -15138,6 +15485,10 @@ window.__ModuleLoader__.load({
|
|
|
15138
15485
|
"row.autoBusy": "Saving…",
|
|
15139
15486
|
"row.autoHintOn": "Checks in, reports activity, claims task and streak rewards, and runs the buddy trip — every day.",
|
|
15140
15487
|
"row.autoHintOff": "Off: no background requests are made for you.",
|
|
15488
|
+
"row.autoItem.claimed": "claimed",
|
|
15489
|
+
"row.autoItem.already": "already",
|
|
15490
|
+
"row.autoItem.skipped": "not yet",
|
|
15491
|
+
"row.autoItem.failed": "failed",
|
|
15141
15492
|
"row.autoJob_report": "Activity report",
|
|
15142
15493
|
"row.autoJob_tasks": "Task rewards",
|
|
15143
15494
|
"row.autoJob_checkin": "Daily check-in",
|
|
@@ -15193,6 +15544,8 @@ window.__ModuleLoader__.load({
|
|
|
15193
15544
|
"agg.enableHint": "Saving a key adds this vendor by itself. Use this switch to take it back out without forgetting the key.",
|
|
15194
15545
|
"agg.key": "API key",
|
|
15195
15546
|
"agg.keyPlaceholder": "Paste the key",
|
|
15547
|
+
"agg.extraKey": "Add another key ({count} stored)",
|
|
15548
|
+
"agg.extraKeyPlaceholder": "Appended after the existing ones",
|
|
15196
15549
|
"agg.baseUrl": "Endpoint",
|
|
15197
15550
|
"agg.baseUrlPlaceholder": "Blank uses the default",
|
|
15198
15551
|
"agg.save": "Save",
|
|
@@ -15245,9 +15598,23 @@ window.__ModuleLoader__.load({
|
|
|
15245
15598
|
"route.chainName": "Chain name",
|
|
15246
15599
|
"route.chainNamePlaceholder": "e.g. write-then-fallback",
|
|
15247
15600
|
"route.id": "Model id",
|
|
15601
|
+
"route.ownerPool": "XD Pool (WorkBuddy)",
|
|
15602
|
+
"route.platformOrder": "Platform order (higher is tried first)",
|
|
15603
|
+
"route.platformNoModels": "No model added to this platform yet.",
|
|
15604
|
+
"route.platformHint": "Higher is tried first. The same applies to the models inside one platform.",
|
|
15605
|
+
"route.platformCount": "{count} platform(s)",
|
|
15606
|
+
"route.pickModel": "Pick a model…",
|
|
15607
|
+
"route.pickPlatform": "Pick a platform…",
|
|
15608
|
+
"route.addModel": "Add model",
|
|
15609
|
+
"route.addPlatform": "Add platform",
|
|
15610
|
+
"route.moveUp": "Up",
|
|
15611
|
+
"route.moveDown": "Down",
|
|
15612
|
+
"route.removePlatform": "Remove",
|
|
15248
15613
|
"route.idHint": "Pool models (like hy3) and provider models (like agnes/agnes-2.0-flash) can be mixed in one chain.",
|
|
15249
15614
|
"route.members": "Members, in the order they are tried",
|
|
15250
15615
|
"route.addMember": "Add",
|
|
15616
|
+
"route.addSiblings": "+{count} same-model route(s)",
|
|
15617
|
+
"route.addSiblingsHint": "Alternate lanes for the SAME model, appended after it: a rate-limited provider is escaped by switching route, not by switching model.",
|
|
15251
15618
|
"route.removeMember": "Remove",
|
|
15252
15619
|
"route.noMembers": "Add at least one member — a chain with nothing to call cannot answer.",
|
|
15253
15620
|
"route.strategy": "Strategy",
|
|
@@ -15295,6 +15662,17 @@ window.__ModuleLoader__.load({
|
|
|
15295
15662
|
"shell.brand": "XD Pool",
|
|
15296
15663
|
"shell.star": "Star",
|
|
15297
15664
|
"shell.starHint": "Open this plugin on GitHub",
|
|
15665
|
+
"shell.group": "Community",
|
|
15666
|
+
"shell.groupHint": "Scan a code to join the XD community group",
|
|
15667
|
+
"shell.groupTitle": "XD community group",
|
|
15668
|
+
"shell.groupClose": "Close",
|
|
15669
|
+
"shell.groupWechat": "WeChat (bot)",
|
|
15670
|
+
"shell.groupWechatAlt": "WeChat bot code: scan to add the bot, then send XD to be pulled into the group",
|
|
15671
|
+
"shell.groupWechatStep": "Scan to add the bot, then send \"XD\" to be pulled in",
|
|
15672
|
+
"shell.groupQq": "QQ group",
|
|
15673
|
+
"shell.groupQqAlt": "QQ group code: scan it to join the XD community group",
|
|
15674
|
+
"shell.groupQqStep": "Just scan — no message needed",
|
|
15675
|
+
"shell.groupNote": "Both groups are live; use whichever you prefer. If a code stops working, open an issue on GitHub and it will be refreshed.",
|
|
15298
15676
|
"shell.tabPool": "Account pool",
|
|
15299
15677
|
"shell.tabChannels": "Login channels",
|
|
15300
15678
|
"shell.tabAggregate": "Providers",
|
|
@@ -15303,8 +15681,6 @@ window.__ModuleLoader__.load({
|
|
|
15303
15681
|
"shell.subAggregate": "Bring your own API keys: any OpenAI-compatible provider, reachable as extra models.",
|
|
15304
15682
|
"shell.tabLedger": "Dashboard",
|
|
15305
15683
|
"shell.subLedger": "What this machine sent and got back, since the pool last started.",
|
|
15306
|
-
"shell.tabBillion": "billion-context",
|
|
15307
|
-
"shell.subBillion": "A read-only window onto the compressor running beside this pool.",
|
|
15308
15684
|
"shell.tabRouting": "Routing",
|
|
15309
15685
|
"shell.subRouting": "Chains that try models in order, so one busy or rate-limited model falls through to the next.",
|
|
15310
15686
|
"shell.tabGateway": "Gateway",
|
|
@@ -15371,6 +15747,8 @@ window.__ModuleLoader__.load({
|
|
|
15371
15747
|
"ledger.accounts": "By account",
|
|
15372
15748
|
"ledger.models": "By model",
|
|
15373
15749
|
"ledger.colTime": "Time",
|
|
15750
|
+
"ledger.colChannel": "Channel",
|
|
15751
|
+
"ledger.channelFree": "Free lane",
|
|
15374
15752
|
"ledger.colRegion": "Region",
|
|
15375
15753
|
"ledger.colModel": "Model",
|
|
15376
15754
|
"ledger.colAccount": "Account",
|
|
@@ -15387,48 +15765,9 @@ window.__ModuleLoader__.load({
|
|
|
15387
15765
|
"ledger.unattributed": "(unattributed)",
|
|
15388
15766
|
"ledger.cn": "CN",
|
|
15389
15767
|
"ledger.global": "Global",
|
|
15390
|
-
"billion.title": "billion-context",
|
|
15391
|
-
"billion.sub": "What the compressor on this machine is doing. This page reads it; it cannot change it.",
|
|
15392
|
-
"billion.pillRunning": "running · v{version}",
|
|
15393
|
-
"billion.pillNotRunning": "not running",
|
|
15394
|
-
"billion.pillReadOnly": "read-only",
|
|
15395
|
-
"billion.failed": "Could not reach the pool relay. The pool itself is unaffected.",
|
|
15396
|
-
"billion.loading": "Looking for a billion-context proxy on this machine…",
|
|
15397
|
-
"billion.notInstalled": "billion-context is not installed on this machine",
|
|
15398
|
-
"billion.notAnswering": "A billion-context proxy is registered but did not answer",
|
|
15399
|
-
"billion.stepRestart": "Restart DSH so the profile bundle layer mounts it.",
|
|
15400
|
-
"billion.stepBack": "Come back to this page; it fills in on its own.",
|
|
15401
|
-
"billion.defaultProfile": "<profile>",
|
|
15402
|
-
"billion.sessions": "Sessions",
|
|
15403
|
-
"billion.noLive": "none active right now",
|
|
15404
|
-
"billion.liveSub": "{count} still active right now",
|
|
15405
|
-
"billion.requests": "Requests",
|
|
15406
|
-
"billion.requestsSub": "through the proxy, every lane",
|
|
15407
|
-
"billion.saved": "Tokens saved",
|
|
15408
|
-
"billion.savedSub": "by compression, net of what it cost",
|
|
15409
|
-
"billion.tokens": "Tokens",
|
|
15410
|
-
"billion.tokensSub": "{cached} cached · {out} out",
|
|
15411
|
-
"billion.cache": "Cache hit",
|
|
15412
|
-
"billion.cacheNone": "nothing measured yet",
|
|
15413
|
-
"billion.cacheSub": "of everything sent",
|
|
15414
|
-
"billion.empty": "The proxy is up but has seen no requests yet. Chat and this fills in.",
|
|
15415
|
-
"billion.perSession": "Per session",
|
|
15416
|
-
"billion.showRows": "Show {count} rows",
|
|
15417
|
-
"billion.hideRows": "Hide rows",
|
|
15418
|
-
"billion.openDashboard": "Open its dashboard",
|
|
15419
|
-
"billion.rowsHint": "The fleet summary above is the whole picture. Open the rows for this proxy’s numbers, or the proxy’s own dashboard when you need a single session.",
|
|
15420
|
-
"billion.note": "These are the proxy’s own numbers, read over loopback. Nothing here is an estimate.",
|
|
15421
|
-
"billion.colSession": "Session",
|
|
15422
|
-
"billion.colLast": "Last seen",
|
|
15423
|
-
"billion.colModel": "Model",
|
|
15424
|
-
"billion.colRequests": "Requests",
|
|
15425
|
-
"billion.colContext": "Context",
|
|
15426
|
-
"billion.colBlocks": "Blocks",
|
|
15427
|
-
"billion.colSaved": "Saved",
|
|
15428
|
-
"billion.colCache": "Cache",
|
|
15429
|
-
"billion.contextTitle": "measured {source} · model window {window}",
|
|
15430
15768
|
"channelpool.title": "Scan channels",
|
|
15431
15769
|
"channelpool.accountCount": "{count} account(s)",
|
|
15770
|
+
"channelpool.loading": "Reading scan channels…",
|
|
15432
15771
|
"channelpool.disabled": "disabled",
|
|
15433
15772
|
"channelpool.noModels": "No models published yet.",
|
|
15434
15773
|
"channelpool.modelError": "Could not list models: {message}",
|
|
@@ -15474,6 +15813,7 @@ window.__ModuleLoader__.load({
|
|
|
15474
15813
|
"channelpool.searchHit": "{count} shown",
|
|
15475
15814
|
"channelpool.searchNone": "No model matches “{query}”.",
|
|
15476
15815
|
"channelpool.expiryNever": "no expiry",
|
|
15816
|
+
"channelpool.expiryExpired": "expired",
|
|
15477
15817
|
"channelpool.modelFree": "free",
|
|
15478
15818
|
"channelpool.modelDead": "dead",
|
|
15479
15819
|
"channelpool.testOk": "Reachable.",
|
|
@@ -15587,6 +15927,9 @@ window.__ModuleLoader__.load({
|
|
|
15587
15927
|
"facts.probeOk": "有应答",
|
|
15588
15928
|
"facts.probeFailed": "无应答",
|
|
15589
15929
|
"row.modelsEnabledCount": "已启用 {enabled} / {total}",
|
|
15930
|
+
"row.modelsMissingSaved": "另有 {count} 个已保存的模型当前未提供",
|
|
15931
|
+
"row.modelsShowAll": "显示其余 {count} 个",
|
|
15932
|
+
"row.modelsShowLess": "只显示已启用",
|
|
15590
15933
|
"row.modelsSave": "保存",
|
|
15591
15934
|
"row.modelsSaving": "保存中…",
|
|
15592
15935
|
"row.modelsDiscard": "放弃修改",
|
|
@@ -15653,6 +15996,10 @@ window.__ModuleLoader__.load({
|
|
|
15653
15996
|
"row.autoBusy": "保存中…",
|
|
15654
15997
|
"row.autoHintOn": "每天自动签到、上报活跃、领取任务奖励与连登奖励,并照看猫猫旅行。",
|
|
15655
15998
|
"row.autoHintOff": "已关闭:不会替你发起任何后台请求。",
|
|
15999
|
+
"row.autoItem.claimed": "已领",
|
|
16000
|
+
"row.autoItem.already": "早已领过",
|
|
16001
|
+
"row.autoItem.skipped": "暂不可领",
|
|
16002
|
+
"row.autoItem.failed": "失败",
|
|
15656
16003
|
"row.autoJob_report": "活跃上报",
|
|
15657
16004
|
"row.autoJob_tasks": "任务奖励",
|
|
15658
16005
|
"row.autoJob_checkin": "每日签到",
|
|
@@ -15708,6 +16055,8 @@ window.__ModuleLoader__.load({
|
|
|
15708
16055
|
"agg.enableHint": "保存 key 会自动接入这家;用这个开关可以在保留 key 的情况下先摘出去。",
|
|
15709
16056
|
"agg.key": "API Key",
|
|
15710
16057
|
"agg.keyPlaceholder": "粘贴 key",
|
|
16058
|
+
"agg.extraKey": "再加一把 key(已存 {count} 把)",
|
|
16059
|
+
"agg.extraKeyPlaceholder": "追加在已有 key 之后",
|
|
15711
16060
|
"agg.baseUrl": "接口地址",
|
|
15712
16061
|
"agg.baseUrlPlaceholder": "留空用默认",
|
|
15713
16062
|
"agg.save": "保存",
|
|
@@ -15760,9 +16109,23 @@ window.__ModuleLoader__.load({
|
|
|
15760
16109
|
"route.chainName": "链的名字",
|
|
15761
16110
|
"route.chainNamePlaceholder": "例如 主力-兜底",
|
|
15762
16111
|
"route.id": "模型 id",
|
|
16112
|
+
"route.ownerPool": "XD Pool(WorkBuddy)",
|
|
16113
|
+
"route.platformOrder": "平台顺序(越靠上越优先)",
|
|
16114
|
+
"route.platformNoModels": "这个平台还没有添加模型",
|
|
16115
|
+
"route.platformHint": "平台越靠上越优先;同一个平台里模型也一样。",
|
|
16116
|
+
"route.platformCount": "{count} 个平台",
|
|
16117
|
+
"route.pickModel": "选择模型…",
|
|
16118
|
+
"route.pickPlatform": "选择平台…",
|
|
16119
|
+
"route.addModel": "加模型",
|
|
16120
|
+
"route.addPlatform": "加平台",
|
|
16121
|
+
"route.moveUp": "上移",
|
|
16122
|
+
"route.moveDown": "下移",
|
|
16123
|
+
"route.removePlatform": "移除",
|
|
15763
16124
|
"route.idHint": "池内模型(比如 hy3)和渠道模型(比如 agnes/agnes-2.0-flash)可以混在同一条链里。",
|
|
15764
16125
|
"route.members": "成员,按尝试顺序排列",
|
|
15765
16126
|
"route.addMember": "加入",
|
|
16127
|
+
"route.addSiblings": "+{count} 条同模型线路",
|
|
16128
|
+
"route.addSiblingsHint": "同一个模型的其他线路,排在该模型后面:某家限流时先换线路,而不是换模型(换模型会改上下文窗口、工具调用和文风)。",
|
|
15766
16129
|
"route.removeMember": "移除",
|
|
15767
16130
|
"route.noMembers": "至少加一个成员——空链没有东西可调,答不了话。",
|
|
15768
16131
|
"route.strategy": "策略",
|
|
@@ -15810,6 +16173,17 @@ window.__ModuleLoader__.load({
|
|
|
15810
16173
|
"shell.brand": "XD Pool",
|
|
15811
16174
|
"shell.star": "点Star",
|
|
15812
16175
|
"shell.starHint": "在 GitHub 上给这个插件点个星",
|
|
16176
|
+
"shell.group": "交流群",
|
|
16177
|
+
"shell.groupHint": "扫码加入 XD 交流群",
|
|
16178
|
+
"shell.groupTitle": "XD 交流群",
|
|
16179
|
+
"shell.groupClose": "关闭",
|
|
16180
|
+
"shell.groupWechat": "微信(机器人)",
|
|
16181
|
+
"shell.groupWechatAlt": "微信机器人二维码:扫码添加后发送 XD,即可被拉进群",
|
|
16182
|
+
"shell.groupWechatStep": "扫码添加机器人后,发送「XD」拉你进群",
|
|
16183
|
+
"shell.groupQq": "QQ 群",
|
|
16184
|
+
"shell.groupQqAlt": "QQ 群二维码:直接扫码加入 XD 交流群",
|
|
16185
|
+
"shell.groupQqStep": "直接扫码即可加入",
|
|
16186
|
+
"shell.groupNote": "两个群都在,哪个方便用哪个。二维码失效了在 GitHub 提个 issue 说一声,会更新。",
|
|
15813
16187
|
"shell.tabPool": "账号池",
|
|
15814
16188
|
"shell.tabChannels": "扫码登录",
|
|
15815
16189
|
"shell.tabAggregate": "聚合渠道",
|
|
@@ -15818,8 +16192,6 @@ window.__ModuleLoader__.load({
|
|
|
15818
16192
|
"shell.subAggregate": "带上你自己的 API key:任何兼容 OpenAI 的服务都能变成额外模型。",
|
|
15819
16193
|
"shell.tabLedger": "数据看板",
|
|
15820
16194
|
"shell.subLedger": "这台机器发出去什么、收回来什么,从池子上次启动算起。",
|
|
15821
|
-
"shell.tabBillion": "billion-context",
|
|
15822
|
-
"shell.subBillion": "一个只读的窗口,看着旁边跑着的那台压缩器。",
|
|
15823
16195
|
"shell.tabRouting": "路由",
|
|
15824
16196
|
"shell.subRouting": "把几个模型串成一条链,按顺序试;一个忙了或被限流,就自动落到下一个。",
|
|
15825
16197
|
"shell.tabGateway": "网关",
|
|
@@ -15886,6 +16258,8 @@ window.__ModuleLoader__.load({
|
|
|
15886
16258
|
"ledger.accounts": "按账号看",
|
|
15887
16259
|
"ledger.models": "按模型看",
|
|
15888
16260
|
"ledger.colTime": "时间",
|
|
16261
|
+
"ledger.colChannel": "渠道",
|
|
16262
|
+
"ledger.channelFree": "免密通道",
|
|
15889
16263
|
"ledger.colRegion": "线路",
|
|
15890
16264
|
"ledger.colModel": "模型",
|
|
15891
16265
|
"ledger.colAccount": "账号",
|
|
@@ -15902,48 +16276,9 @@ window.__ModuleLoader__.load({
|
|
|
15902
16276
|
"ledger.unattributed": "(没归属到账号)",
|
|
15903
16277
|
"ledger.cn": "国内版",
|
|
15904
16278
|
"ledger.global": "国际版",
|
|
15905
|
-
"billion.title": "billion-context",
|
|
15906
|
-
"billion.sub": "这台机器上的压缩器正在做什么。这一页只看不改,它动不了那个代理。",
|
|
15907
|
-
"billion.pillRunning": "运行中 · v{version}",
|
|
15908
|
-
"billion.pillNotRunning": "没在运行",
|
|
15909
|
-
"billion.pillReadOnly": "只读",
|
|
15910
|
-
"billion.failed": "连不上池子的中继接口,池子本身不受影响。",
|
|
15911
|
-
"billion.loading": "正在这台机器上找 billion-context 代理…",
|
|
15912
|
-
"billion.notInstalled": "这台机器上没装 billion-context",
|
|
15913
|
-
"billion.notAnswering": "登记过一个 billion-context 代理,但它没应答",
|
|
15914
|
-
"billion.stepRestart": "重启 DSH,让 profile 的 bundle 层把它挂上。",
|
|
15915
|
-
"billion.stepBack": "回到这一页,数字会自己填上。",
|
|
15916
|
-
"billion.defaultProfile": "<profile>",
|
|
15917
|
-
"billion.sessions": "会话数",
|
|
15918
|
-
"billion.noLive": "当前没有活跃会话",
|
|
15919
|
-
"billion.liveSub": "当前还有 {count} 个在跑",
|
|
15920
|
-
"billion.requests": "请求数",
|
|
15921
|
-
"billion.requestsSub": "经过代理的,全部线路",
|
|
15922
|
-
"billion.saved": "省下的 Token",
|
|
15923
|
-
"billion.savedSub": "压缩净省的,已经扣掉它自己花掉的",
|
|
15924
|
-
"billion.tokens": "Token 总量",
|
|
15925
|
-
"billion.tokensSub": "{cached} 来自缓存 · {out} 输出",
|
|
15926
|
-
"billion.cache": "缓存命中",
|
|
15927
|
-
"billion.cacheNone": "还没有测量数据",
|
|
15928
|
-
"billion.cacheSub": "在送出去的所有 token 里",
|
|
15929
|
-
"billion.empty": "代理起来了,但还没收到过请求,聊两句这里就填上了。",
|
|
15930
|
-
"billion.perSession": "按会话看",
|
|
15931
|
-
"billion.showRows": "展开 {count} 行",
|
|
15932
|
-
"billion.hideRows": "收起",
|
|
15933
|
-
"billion.openDashboard": "打开它自己的仪表盘",
|
|
15934
|
-
"billion.rowsHint": "上面的全局数字就是全部情况。要看这一个代理的明细就展开表格,要看单个会话就去它自己的仪表盘。",
|
|
15935
|
-
"billion.note": "这些是代理自己报的数字,走的是本机回环。这里没有一个是估算值。",
|
|
15936
|
-
"billion.colSession": "会话",
|
|
15937
|
-
"billion.colLast": "最后出现",
|
|
15938
|
-
"billion.colModel": "模型",
|
|
15939
|
-
"billion.colRequests": "请求",
|
|
15940
|
-
"billion.colContext": "上下文",
|
|
15941
|
-
"billion.colBlocks": "压缩块",
|
|
15942
|
-
"billion.colSaved": "省下",
|
|
15943
|
-
"billion.colCache": "缓存",
|
|
15944
|
-
"billion.contextTitle": "实测 {source} · 模型窗口 {window}",
|
|
15945
16279
|
"channelpool.title": "扫码渠道",
|
|
15946
16280
|
"channelpool.accountCount": "{count} 个账号",
|
|
16281
|
+
"channelpool.loading": "正在读取扫码渠道…",
|
|
15947
16282
|
"channelpool.disabled": "已停用",
|
|
15948
16283
|
"channelpool.noModels": "该渠道还没有发布模型。",
|
|
15949
16284
|
"channelpool.modelError": "读取模型列表失败:{message}",
|
|
@@ -15989,6 +16324,7 @@ window.__ModuleLoader__.load({
|
|
|
15989
16324
|
"channelpool.searchHit": "显示 {count} 个",
|
|
15990
16325
|
"channelpool.searchNone": "没有匹配「{query}」的模型。",
|
|
15991
16326
|
"channelpool.expiryNever": "长期有效",
|
|
16327
|
+
"channelpool.expiryExpired": "已过期",
|
|
15992
16328
|
"channelpool.modelFree": "免费",
|
|
15993
16329
|
"channelpool.modelDead": "已下线",
|
|
15994
16330
|
"channelpool.testOk": "连接正常。",
|