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/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 and the billion page's "open its dashboard"
1412
- rendered as bare browser buttons — which is how a secondary action ends up
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. A settings card that can refuse to render is a card
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$1(value) {
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$1(value)}`;
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$1(call.usage.inputTokens),
2078
- fmtInt$1(call.usage.outputTokens),
2079
- fmtInt$1(call.usage.cachedTokens)
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$1(totals?.input ?? 0) : t?.("ledger.notReported") ?? "not reported",
2421
+ value: summary.usageReported ? fmtInt(totals?.input ?? 0) : t?.("ledger.notReported") ?? "not reported",
2202
2422
  sub: summary.usageReported ? t?.("ledger.tokenSplit", {
2203
- input: fmtInt$1(totals?.input ?? 0),
2204
- output: fmtInt$1(totals?.output ?? 0)
2205
- }) ?? `in ${fmtInt$1(totals?.input ?? 0)} · out ${fmtInt$1(totals?.output ?? 0)}` : t?.("ledger.notReportedHint") ?? "the gateway ignored stream_options.include_usage"
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$1(summary.turns),
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$1(totals.avgTps)} tok/s`,
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$1(summary.rows.reduce((n, r) => n + r.cachedTokens, 0)) }) ?? "" : ""
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$1(summary.calls),
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.colRegion") ?? "Region" }),
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: regionLabel(entry.region, t) }),
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.colRegion") ?? "Region" }),
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: regionLabel(row.region, t) }),
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$1(row.turns)
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$1(row.calls)
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$1(row.inputTokens)
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$1(row.outputTokens)
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$1(row.calls)
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/BillionPage.tsx
10188
+ //#region src/client/RoutingPage.tsx
9930
10189
  /**
9931
- * The billion-context page: what the compressor on this machine is doing.
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
- * - **missing** — no proxy recorded. Normal on a machine that never installed
9943
- * it. The page shows the install command instead of an empty table, because
9944
- * an empty table here would be indistinguishable from "compression ran and
9945
- * saved nothing".
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
- * Every figure is the proxy's own. Nothing is re-derived, and in particular the
9952
- * cache hit rate below is weighted by the sessions actually shown rather than
9953
- * averaged across them: a page that averaged a 200-request session with a
9954
- * 1-request one would report a number that describes neither.
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
- * WHAT THIS PAGE DOES NOT REDRAW
10202
+ * Two deliberate honesty choices, both about not lying to the user:
9957
10203
  *
9958
- * billion-context ships its own dashboard, including a per-session detail view.
9959
- * The rows below therefore link out to it rather than re-implementing those
9960
- * screens, and the table itself starts collapsed: a fleet number is what this
9961
- * page is for, and 50 rows of session ids is a wall the user did not ask for.
9962
- * Both are deliberate — one answer, and the summary stays the summary.
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/BillionPage
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
- * Fleet cache hit rate, or null when nothing was measured.
10215
+ * Flatten a draft into the member ids the server stores.
9992
10216
  *
9993
- * Weighted by input tokens rather than averaged per session, and null — never
9994
- * zero — when no input was counted at all. The proxy reports `cacheHitPct: -1`
9995
- * for exactly that case, and rendering it as `0%` would claim the cache never
9996
- * helped, which is the opposite of what "not measured" means.
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 fleetHitRate(sessions) {
9999
- let input = 0;
10000
- let cached = 0;
10001
- for (const row of sessions) {
10002
- if (row.cacheHitPct < 0) continue;
10003
- input += row.inputTokens + row.cachedTokens;
10004
- cached += row.cachedTokens;
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 input === 0 ? null : cached / input;
10231
+ return out;
10007
10232
  }
10008
- /**
10009
- * A link into the proxy's own dashboard, or undefined when there is nothing to
10010
- * link to.
10011
- *
10012
- * Two things are checked here rather than trusted. The origin has to be an
10013
- * absolute http(s) URL, because a relative one would resolve against the HOST
10014
- * and silently open the wrong page; and the session id is percent-encoded,
10015
- * because these are proxy-chosen identifiers that may contain characters a
10016
- * hash route cannot carry raw.
10017
- *
10018
- * The hash is the proxy's own route shape (`#/sessions/<id>`), read from its
10019
- * shipped router — a deep link into someone else's UI, which is the whole
10020
- * reason the table links instead of redrawing.
10021
- */
10022
- function proxyLink(origin, hash) {
10023
- if (typeof origin !== "string" || origin === "") return void 0;
10024
- let base;
10025
- try {
10026
- base = new URL(origin);
10027
- } catch {
10028
- return;
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
- if (base.protocol !== "http:" && base.protocol !== "https:") return void 0;
10031
- const url = new URL("/__bili/", base);
10032
- url.hash = hash;
10033
- return url.toString();
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
- * Open the proxy's dashboard in a new tab.
10037
- *
10038
- * `_blank` with `noopener` for the same reason the channels page does it: the
10039
- * proxy owns that tab afterwards, and this page must not be able to reach back
10040
- * into it. The settings panel itself is never navigated away from.
10041
- */
10042
- function openProxyDashboard(url) {
10043
- window.open(url, "_blank", "noopener");
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
- function BillionPage({ t }) {
10046
- const [doc, setDoc] = (0, react.useState)(void 0);
10047
- const [failed, setFailed] = (0, react.useState)(false);
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(POOL_BILLION_PATH, {
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
- if (mounted.current) setFailed(true);
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
- setDoc(value);
10066
- setFailed(false);
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 && signal?.aborted !== true) setFailed(true);
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
- models: draft.models,
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
- onPick: setPicked,
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
- const missing = draft.models.filter((model) => candidateById.has(model) === false);
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: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10611
- className: "dsm-xdpool-rt-rowhead",
10612
- children: [
10613
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10614
- className: "dsm-xdpool-rt-name",
10615
- children: draft.name === "" ? t?.("route.newChain") ?? "New chain" : draft.name
10616
- }),
10617
- draft.id !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10618
- className: "dsm-xdpool-rt-cid",
10619
- children: draft.id
10620
- }) : null,
10621
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10622
- className: "dsm-xdpool-pill",
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.strategyLabel", {
10632
- strategy: draft.strategy === "round-robin" ? t?.("route.strategyRoundRobin") ?? "Round robin" : t?.("route.strategyFallback") ?? "Fallback",
10633
- members: t?.("route.memberCount", { count: draft.models.length }) ?? `${draft.models.length}`
10634
- }) ?? draft.strategy
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
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10638
- className: "dsm-xdpool-pageactions",
10639
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10640
- type: "button",
10641
- className: "dsm-btn dsm-btn-outline",
10642
- onClick: onEdit,
10643
- children: editable ? t?.("route.editOpen") ?? "Editing" : t?.("route.edit") ?? "Edit"
10644
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10645
- type: "button",
10646
- className: "dsm-xdpool-rt-drop",
10647
- onClick: onDelete,
10648
- children: t?.("route.removeMember") ?? "Remove"
10649
- })]
10650
- })
10651
- ]
10652
- }), missing.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10653
- className: "dsm-xdpool-rt-warn",
10654
- children: t?.("route.missing", { models: missing.join(", ") }) ?? missing.join(", ")
10655
- }) : null]
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
- /** The editor for exactly one chain, rendered full width above the list. */
10659
- function ChainEditor({ draft, roster, picked, busy, candidateById, t, onPick, onChange, onAddMember, onDropMember }) {
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.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10728
10741
  className: "dsm-xdpool-rt-hint",
10729
- children: t?.("route.members") ?? "Members"
10730
- }), draft.models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
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)("ol", {
10734
- className: "dsm-xdpool-rt-members",
10735
- children: draft.models.map((modelId, index) => {
10736
- const candidate = candidateById.get(modelId);
10737
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
10738
- className: "dsm-xdpool-rt-member",
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.jsx)("span", {
10741
- className: "dsm-xdpool-rt-ord",
10742
- children: index + 1
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.jsx)("span", {
10745
- className: "dsm-xdpool-rt-mid",
10746
- title: modelId,
10747
- children: modelId
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
- candidate === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10750
- className: "dsm-xdpool-agg-tag dsm-xdpool-rt-mtag",
10751
- "data-tone": "busy",
10752
- children: t?.("route.unusable") ?? "needs a key"
10753
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10754
- className: "dsm-xdpool-agg-tag dsm-xdpool-rt-mtag",
10755
- "data-tone": "ok",
10756
- children: compact(candidate.contextWindow)
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.jsx)("button", {
10759
- type: "button",
10760
- className: "dsm-xdpool-rt-drop",
10761
- onClick: () => {
10762
- onDropMember(modelId);
10763
- },
10764
- children: t?.("route.removeMember") ?? "Remove"
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
- }, `${modelId}-${index}`);
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: picked,
10917
+ value: pickLane,
10918
+ disabled: busy,
10775
10919
  onChange: (event) => {
10776
- onPick(event.target.value);
10920
+ setPickLane(event.target.value);
10777
10921
  },
10778
10922
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
10779
10923
  value: "",
10780
- children: t?.("route.id") ?? "Model id"
10781
- }), roster.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
10782
- value: candidate.id,
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
- candidate.id,
10785
- " — ",
10786
- candidate.name
10928
+ ownerName(entry.owner, t),
10929
+ "(",
10930
+ entry.count,
10931
+ ")"
10787
10932
  ]
10788
- }, candidate.id))]
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 || picked === "",
10793
- onClick: onAddMember,
10794
- children: t?.("route.addMember") ?? "Add"
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 days = Math.ceil((expiresAt - Date.now()) / 864e5);
11687
- if (days <= 0) return t?.("channelpool.expiryNever") ?? "no expiry";
11688
- return days === 1 ? "1d" : `${days}d`;
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)(void 0);
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 callPack("provider.status", { providers: channels.map((channel) => channel.id) });
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 balancesPromise = wantBalances ? callPack("credits.balances", { provider }) : Promise.resolve(void 0);
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: balanceRows,
11754
- statuses: creditStatuses === void 0 ? [] : creditStatusesFrom(creditStatuses),
11755
- balancePending: false,
11756
- balanceError: balances !== void 0 && !balances.ok && balanceRows.length === 0 ? balances.error?.message ?? "credits.balances refused" : void 0,
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) setError(statusAnswer.error?.message ?? "provider.status refused");
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(cause instanceof Error ? cause.message : String(cause));
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
- const refreshBalances = async (provider, force) => {
11940
- markBusy(provider, true);
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 (!mountedRef.current) return;
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) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
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.5.0";
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) => prev[paneId] === open ? prev : {
12746
- ...prev,
12747
- [paneId]: open
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 === "billion" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BillionPage, { t }) : 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: [
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: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
14060
- className: "dsm-xdpool-region-pane-head",
14061
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
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
- card.status?.catalogSource !== "fallback" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
14071
- className: "dsm-xdpool-catalog-offline",
14072
- title: card.status.catalogError ?? void 0,
14073
- children: t?.("row.catalogOffline") ?? "built-in list (offline)"
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)("button", {
14076
- type: "button",
14077
- className: "dsm-btn dsm-btn-outline",
14078
- disabled: !card.modelState.dirty,
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.jsx)("button", {
14085
- type: "button",
14086
- className: "dsm-btn dsm-btn-primary",
14087
- disabled: !card.modelState.dirty || card.modelState.enabled === 0,
14088
- onClick: () => {
14089
- saveModels(region);
14090
- },
14091
- children: savingModelsRegion === region ? t?.("row.modelsSaving") ?? "Saving…" : t?.("row.modelsSave") ?? "Save"
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
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
14096
- className: "dsm-xdpool-model-list",
14097
- children: [...models].sort((a, b) => Number(isFreeNow(b, now, region)) - Number(isFreeNow(a, now, region))).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
14098
- model,
14099
- region,
14100
- paidTwin: paidAlternativeFor(model, models),
14101
- t,
14102
- draft: card.modelState.draft[model.id] ?? {
14103
- enabled: model.enabled,
14104
- images: model.supportsImages
14105
- },
14106
- editable: card.modelState.editable,
14107
- onToggle: (id) => {
14108
- toggleModel(region, id);
14109
- },
14110
- onToggleImage: (id) => {
14111
- toggleModelImage(region, id);
14112
- },
14113
- onBudget: (id, budget) => {
14114
- setModelBudget(region, id, budget);
14115
- },
14116
- onTest: () => {
14117
- testModel(XDPOOL_REGION_PROVIDER[region], model.id);
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
- probe: probes[model.id],
14120
- probing: probingModel === model.id
14121
- }, model.id))
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-${model.id}`,
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-${model.id}`,
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": "连接正常。",