dsh-xdpool 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -52
- package/README.en.md +11 -2
- package/README.md +12 -3
- package/assets/community-qr.png +0 -0
- package/cordis.patch.yml +17 -41
- package/lib/bin.js +194 -30
- package/lib/client.js +285 -774
- package/lib/index.d.ts +54 -82
- package/lib/index.js +276 -409
- package/package.json +1 -4
- package/vendor/channel-pack/pack.js +8 -124
package/lib/client.js
CHANGED
|
@@ -7,44 +7,6 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
//#endregion
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
-
//#region src/client/sidebar-row.ts
|
|
11
|
-
/**
|
|
12
|
-
* The `sidebarRow` switch, read and written from one place.
|
|
13
|
-
*
|
|
14
|
-
* Two independent owners need this flag: the page card draws the checkbox, and
|
|
15
|
-
* `apply()` decides whether to register the sidebar row before that card has ever
|
|
16
|
-
* mounted. Both therefore have to read the same source of truth — the settings
|
|
17
|
-
* mirror the host exposes — instead of one asking the server and the other
|
|
18
|
-
* guessing.
|
|
19
|
-
*
|
|
20
|
-
* The scope is typed structurally here rather than imported from `PoolCard.tsx`:
|
|
21
|
-
* this module is pulled into both tsconfigs (the server one has no JSX setting),
|
|
22
|
-
* and the only thing it needs from that component is `getSnapshot().value`.
|
|
23
|
-
*
|
|
24
|
-
* The default is ON, and the read is fail-open on purpose: "the mirror has no
|
|
25
|
-
* opinion yet" and "the user turned it off" must not be the same answer, or a
|
|
26
|
-
* host that has not populated the mirror would silently hide a feature nobody
|
|
27
|
-
* switched off. That is also why only the literal `false` disables the row.
|
|
28
|
-
*/
|
|
29
|
-
/** Settings field the switch writes (declared in the plugin Config schema). */
|
|
30
|
-
const SIDEBAR_ROW_FIELD = "sidebarRow";
|
|
31
|
-
/**
|
|
32
|
-
* Whether the sidebar entry should exist.
|
|
33
|
-
*
|
|
34
|
-
* Never throws: a locked-down host makes `getSnapshot()` unusable, and the
|
|
35
|
-
* documented degradation there is the settings page, not a missing entry.
|
|
36
|
-
*/
|
|
37
|
-
function readSidebarRow(scope) {
|
|
38
|
-
if (scope === void 0) return true;
|
|
39
|
-
try {
|
|
40
|
-
const value = scope.getSnapshot().value;
|
|
41
|
-
if (value === null || typeof value !== "object") return true;
|
|
42
|
-
return value[SIDEBAR_ROW_FIELD] !== false;
|
|
43
|
-
} catch {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
10
|
//#region src/status-paths.ts
|
|
49
11
|
/**
|
|
50
12
|
* Node-free constants and types shared by the Host and browser halves of the
|
|
@@ -200,22 +162,6 @@ window.__ModuleLoader__.load({
|
|
|
200
162
|
*/
|
|
201
163
|
const POOL_RELAY_PATH = "/plugins/dsh-xdpool/relay";
|
|
202
164
|
/**
|
|
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
165
|
* The pack's own management route, which the card posts to directly.
|
|
220
166
|
*
|
|
221
167
|
* Not a re-export of anything: the vendored bundle registers this path itself
|
|
@@ -556,7 +502,6 @@ window.__ModuleLoader__.load({
|
|
|
556
502
|
.dsm-xdpool-usage-model{font-size:12px;font-weight:600;word-break:break-all}
|
|
557
503
|
.dsm-xdpool-usage-numbers{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:17px;word-break:break-word}
|
|
558
504
|
.dsm-xdpool-usage-note{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:17px}
|
|
559
|
-
.dsm-xdpool-sidebar-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:6px}
|
|
560
505
|
.dsm-xdpool-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
561
506
|
/* "Built-in list (offline)" chip: a warning tint, since the user is looking at
|
|
562
507
|
a SHORTER roster than the gateway offers and may wonder where models went. */
|
|
@@ -1150,6 +1095,31 @@ window.__ModuleLoader__.load({
|
|
|
1150
1095
|
.dsm-xdpool-star svg{fill:currentColor;transition:transform .5s cubic-bezier(.34,1.56,.64,1)}
|
|
1151
1096
|
.dsm-xdpool-star:hover svg{transform:rotate(72deg) scale(1.12)}
|
|
1152
1097
|
.dsm-xdpool-star:active svg{transform:rotate(72deg) scale(.94)}
|
|
1098
|
+
/* The group button, next to the star, sharing the brand row.
|
|
1099
|
+
The nav is a grid of 1fr auto; a second control in the same cell would
|
|
1100
|
+
STACK on top of the star rather than sit beside it (grid items in one cell
|
|
1101
|
+
overlap). So the track list grows to three: brand | star | group, and both
|
|
1102
|
+
buttons are pinned to row 1 on their own columns. Auto-placement alone puts
|
|
1103
|
+
them under the tab strip, because the strip comes first in the DOM. */
|
|
1104
|
+
.dsm-xdpool-nav{grid-template-columns:1fr auto auto}
|
|
1105
|
+
.dsm-xdpool-star{grid-column:2;grid-row:1}
|
|
1106
|
+
.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%)}
|
|
1107
|
+
.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%)}
|
|
1108
|
+
/* The pulse is on the icon only, and slow: this button is a door, not a call to
|
|
1109
|
+
action, and a card that nags is a card people mute. */
|
|
1110
|
+
.dsm-xdpool-group:hover svg{transform:scale(1.1)}
|
|
1111
|
+
.dsm-xdpool-group svg{fill:currentColor;transition:transform .28s cubic-bezier(.34,1.56,.64,1)}
|
|
1112
|
+
/* The invite dialog. Positioned against the shell (which is the containing
|
|
1113
|
+
block) so it covers the card and nothing of the host dialog around it. */
|
|
1114
|
+
.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)}
|
|
1115
|
+
.dsm-xdpool-groupcard{display:flex;flex-direction:column;align-items:center;gap:10px;max-width:340px;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%)}
|
|
1116
|
+
.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)}
|
|
1117
|
+
.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}
|
|
1118
|
+
.dsm-xdpool-groupclose:hover{color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
1119
|
+
/* A white plate under the code, always: a QR inverted onto a dark panel scans
|
|
1120
|
+
on some phones and not others, and there is no way to tell which from here. */
|
|
1121
|
+
.dsm-xdpool-groupqr{display:block;width:260px;height:260px;border-radius:12px;background:#fff;padding:6px;object-fit:contain}
|
|
1122
|
+
.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
1123
|
.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
1124
|
/* A raised surface, not a tinted wash: the glider has to read as "this one is
|
|
1155
1125
|
sitting on top of the strip" from across the panel, and a wash alone is
|
|
@@ -1197,29 +1167,6 @@ window.__ModuleLoader__.load({
|
|
|
1197
1167
|
/* Long model ids and account emails get clipped rather than wrapped: a wrapped
|
|
1198
1168
|
number column breaks the alignment that makes it scannable. */
|
|
1199
1169
|
|
|
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
1170
|
`.trim();
|
|
1224
1171
|
const ROUTING_CARD_CSS = `
|
|
1225
1172
|
|
|
@@ -1408,9 +1355,8 @@ window.__ModuleLoader__.load({
|
|
|
1408
1355
|
.dsm-btn-primary:hover:not(:disabled){opacity:.9}
|
|
1409
1356
|
/* The borderless third of the button family: links out to a vendor's console
|
|
1410
1357
|
and the destructive "forget this key" action. It had no rule at all, so the
|
|
1411
|
-
aggregate row's three links
|
|
1412
|
-
|
|
1413
|
-
looking like the primary one. */
|
|
1358
|
+
aggregate row's three links rendered as bare browser buttons — which is how a
|
|
1359
|
+
secondary action ends up looking like the primary one. */
|
|
1414
1360
|
.dsm-btn-ghost{border-color:transparent;background:transparent;color:var(--dsw-alias-label-tertiary,#8a90a0)}
|
|
1415
1361
|
.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
1362
|
.dsm-xdpool-panel-empty{color:var(--dsw-alias-label-tertiary,#999);font-size:14px;line-height:20px}
|
|
@@ -1743,6 +1689,10 @@ window.__ModuleLoader__.load({
|
|
|
1743
1689
|
*/
|
|
1744
1690
|
const POOL_CARD_AVATAR = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5ctx0rTtj83vTLbTWJXrW9ZaLuxnmqMxlv1XgflWtAuew/Kr1n4eDYyK37Lwypxwc0AZFsnHQVpQpwOAfwrprTwvGQMrxWvD4UiC7mUYA7CncDkIlHHA/KrIjHZQPwrrbPwzLey+XaQx8ddxyQPfsPzraT4eagV+U2LHHTzf/AK9LnS3HZnnYjPoPyqC4CwrukZVX34zXpbfD7VkBP2CKXHP7uYGvK/Fklt4diurvxHaz2sv3lYniKPAICnOM9efWnzIVjnte8TLaOlnpqLdX0wO3ccRxAdWbvgeg6niuH1XWrFpfK1rW2aVeXQfIo9gg5P4mvOvEXji4vtWnutGaSxjZfLTa5LbB2JP51yc80txK0lxI0sjHJZjkmpbuVY9JufGz2d39j8OTxiMtsaWU/I2fRD0I9RgGtrw54oea6urHVLtJZYiNkpUKGz244rxnYwAYqQp6HHFKrsmQpK564PWkFj6ImuA5wGWqTyE/exXmPhrxnNZSR22qymW0HCuwyyfj3Fd3e61Y2dn9qmuU8lh8rA53/T1qrisdLaRA7eldJYQjIzivL4vHlhFj99GD/v5/lWjF8TbOAK3mIR2wrH+lZ3sI9msIgxFdPYwKAK+f4fjLawjKsePSFjWtp3xrtri4EIvYrdiCd00DBenTjP8AKjmA+i7OFeK2EtDLEyxY3HgZr5lj/aBWP5R9oOPSzP8A8VWhaftJRQks3nqF6k2Z/wDiqTlcD6HslmsrAQPaXK3ABziIsGkPfIzxnvUo8K/uwZoQ7hcsxPJPc5rw62/ao01QDM+B0y1s4z+QNbtt+07pUyDcUIPT5XGfzWpuXdGF43+JOoaLqWoaNodqUvkBbzWupALZMcl1BwTnOAOoI9a+WPGOv3ut6lI2o6hc6g6nHmTSlsjtweB9B0r1/wCL/j/RPEKH+wRC1zeyeZdOGDPkDAXI5H4+leGtpk80lw9rA8scK7pMc7ATtBP4kVStce5nUoBPA61pafpi37m3V/LuSwVQwwN3TB9OcVWNhcJdvbTxPHMjlJFYYKkHmrHYsxyW9zaRRSyurxdEJOPfH1qO4FvGXheMq68q3Q/Q1oyWUiNDAJYpAuPldBnHXg08xRNcTJCEf5uC56gDp+fFAjEt4I5EeSSQAR4JQ8Fh7Gkjgmudy26vKI+i5yQCew+vpU88Nss+AzIC3IByFB6U/R3e21aFox5jIx+UH72B2/pQAzR7RL7U7W1lbZHM4UnOMUstgYLqVJCFWOQr1zuwagitWkljXDFWI3EITtrTmsxBcyJHlkXgFhjP0qJNrYltrYtWs8kmwxptAb5dg6V7L4D/AGcvFHjWBb3fFpCSAGNp4yWIJ546jj8PpXlXhLRNU13WLOz0O3+13hYFYQAQ2D356etfpj8MWlWygtdTjigv4FWOVI33KrAfMBSil1EtdzyPwb+xjoNgQ3ijUb7W525W3g/0dF+pGWP5iu3uv2Vfh1pcgtofDsN3cMnmN5tzK4VfQfN3r6VstMVY45AOSc8dvxpr6EL51eQmNlJG5e656VLkaK3U+co/2Uvh7qzNdW/h+3t5YG2tbwTSJHJgdxur42+NfhWXwP8AEfWNNj0SLSbFJEktLWFtyLEVGCCTk5wTz3Nfqd4rvH8H+HLnU7CznvxbFTJb24UOwLAEjPpnJ9ga+G/23ryfVPFHhu1h0dopDpi3P2tgczByfkXj+Ejn3NMTSPj+7W2lvIpI1aOQndjqQetd18JfCT+Kda1BX066v7KCzaSURrhN2f3aSdsM3H69q4G7tZ4WLyQyF0JGNvX/AOvX6LfsuaX4W0b4W2Vv4fv7W91S/hF5qrLneJ2GPLbuNg+UA+571lKTSNqFPnkct4D+AnhnxzZWk/ibwwltBGi+Q3mkTAEZ2llwcA8fMSeOuK8P+PXwHh8A/ES0s/CcFxPaXdotzFGxLuDuIYE9T0HPvX6M+GtOFragFQCxyeKp3/gOxvvGQ8S3iia4SxWziVuRGocsSPck4NTFtO53VIRkrHx/4C/ZSbR9UtNT8ZyWl6k8DiG22ZO8jjIYeh9iM15/+1X8PPDvhfU/Dt54asP7LS+glWaNfukrt2k574LD6KK/QTU83Go/u7dpdkBQZI2cnnP5DtXxF+2ldC3uNAsbgAyr5lwccfLwoAHYDFRTnJ1dWOrThCg7I+M5ovsssqt8ynIUkdRWj4VsZb7XrJIovNCybmB6YAJ5/KorsGaFfLcuzkYjHPPQV33hqfRvBdnNFrlyItSZ1ZkQb2UgfdwOnU9a7meUeWkk9ST+NJx6UU5Blh8pYZ5AFMD179nLXJ9K8e28NvdQQtdkRJHMh2uxPHzj7n1PFfpb4b0y3tNWgkwqrdxlrmFslRKP40PvnJr4u+BHi3T7rRrXT9M8IIz206fapWjQm4UhgQMjOR1xnrj1r7M8KTqxsobGbz7eSMzafO/VccNE3uPz455Fc1SfLNJnRThzRfc9c035YQkb7k7ZrSSMKTg8elYWkakhjZZ18uVSMqSK2redbhd0Z47VoncwaaepDfzpBDIblN1uVw3evzw/bT1KbUPFelQafBPbW1jbMEHnsA5Y/eRc4UYAB45IGe2f0WuLdbqFo3yAw7HFcB41+EHhX4haaNN8Y6clyEB8m6T5JYye6uOQfY1TVmI/HGXWtTUhPtM2BkAOd/55r279l34g654e8c3FhpWnadfT6tCsTPdS+RtVDnAI6kk9Paut+Pf7Ies/C63u/EOgOdf8NoxaR1BWa1X+9Io42/7Q/ECvnHR9WOha3p2pCOKUWN1HcBPMI3hGDYz2yBiocbo0hJQmpH7JaFeSS6fA97GkVwUHmIjblDY5APcVLf6rDbo3mSBSegJ5NfG2sft4eHodE/4pPw/qd3fsnKzqsccRx3bJz+FekfBDxtcfFfwhb+LNSP2a4mllTymfftKMV46ccelctTmgtj0IOE5Wue7WAV0aVhy+SBX5/wD7Znhy+m8V3OoQxzvZwW6vc3c33Sx+5EnYAeg9cmvvPTLgtECxJ4rgvif4I0zxpaJb6/a/a7SGQSLGWIBYA4zjrjNYxnyO7LnDnvE/I6OV4t2GZGI6jg5HSoySSSTknkk16/8AtE+CLfwb4yj/ALMhWDT7qEeVGgwqFeCP5frXj9epCSnFNHlTi4ScWKDg9cVo6RC19fwQFZJVdgrBZQhwfTPFUFZF6x7j7tWhpty5vbeK2hiSSSRUU7N3JPFUSfdn7P8A8NYNL0oW00wuWvsYYNh4iOjZ7MDgjrXv2nQXnha4Om6n85kk+0eZjAL9PMT0BwMjsc1xvwi8HJovhrR5oQVBjAklAxufAOeOB3r6CvPDcXiDTkS4Oy4i5hmxkqf8D3FYVoKWxrTqODKNh5Op2489AXIyCp5B9qU69L4flEbW0t1G5AXyRvc8/wB3j9Kx7AXGk3ktncj54yVYqcgehz6Gta6kENzZ3gYBdyq2e+Tj+tccJuMkjsqRjJXNyx8a6HfTLbJqFvBeEfNa3EgimU+hRsH8q3mVZEKsAykV8m/tM+BIvEHha8vrG6FrcxqP3rkljhgxCAdCcdeT2HWvLP2cf2s9X8Ga1ZeBfjA0x0ZlEVlqdyredbf3BJ1LRnpnqv0r0VqecfcN/fx6I/2LUyJ7K7ysck5G3ngxtng8H8a/OX9pj4CH4aeKjqOhRW7+Htcnke0QLl4H6tGeMY5+X247V+jXiu20rxN4Q1a2vWhutMuLZ1kO4YxtzkHsR1B7Yr8s/iB8S/F2q6dp2geJ0a20zTXYWAC7lcYwG83JDHHv36VEU1IOh5rP4b+ywtNd7IYk6kmvVv2ZPildeC/FKeGLy+P/AAjuqM/kJN/ywuMZGD23YwR0zivFNU1SfU2jBciMH5FP8zXZ/COTwna63NeeNZrgi3T/AEKCGFnLy85dj0AGBySOue1FVJxasXSbjJM/UjRblZoI9p5K5wK8G/aE/aZ0j4fxzaD4TNvrXicDEpJ3QWWR/GR95/8AZHTv6V4N8Sv2pdZ1WCbSPh002jaW0Kwy38uPtEwAwdmPuA+vJ+lfObtvkZ5WaWRiSzMckk9Tk/zrnhQvrM6Z17fCWPF3iXWPF93LfeIr6a+uWJO6Q8D2CjgfhXI1tTSA5XaM+tZcwDTMBgfXvXYklojkbvqx9lFBLKi3UjIrMF+Xt7k1vRf2bpXiBLZLR3W3uQrXFzIwOAeW2LjtyBk1zFb8t9ZX2kwXFxJ5es2LoiqVJW6hHTJ7MvA56r7ryxH6o/CvXYtT8PadGAPsYhTYuMdvT1r3G0vY4oPLU9emeK+R/gNqN7e+G9Jnu22vLEjOQMBc9FUdvqea+lLdVbyWimdm77n61LAg1xha6pLMhyk+xsH8j/KotZkT+wRKwykM6Mw/2Q4zUHjMtay6c5IAl3RnnvwafcQ/bPD17D3eI4+uK86fuzO+m+aCR5F8cZrq78FzW1gY3dg5h3xeYCw5256jNfnLr+uSalPC8mLe8tnzHL5vzpg5wOfWv0d1+YarZ2UBaLDjeeoO4j618W/tB/CuXQdRfxDo6Mlhctm9jjHEMn9//dPf0P1rt57SszjUG43OAj+JPilN63PibVLtWiMW2W7lkQKTlgF4HPesm58S6he28ttJdt9mmGJECbA34ZNc4sKkh2k3RjrnvU8bK3CEFRWxBYj3SzALjPQH6mryXnmiTyvlLnacf3RgAfpWbbSBHDk8nJUevGBViNgvywqAxP4CkBdQFl5JPqaJVEcZweT7VWSNpD+9cse/PFTiJUTAz+Jpk3KbqBgPyPrWROMTNgYFbVxgIcHNY9wQX49OaATdyGrFhGJb22jbADSqCT061XoBwQaCj9GPhT4gsV8PwwafMpEYAZwOGI7Zr6A8M6pypUG4R1HOO9fDvwB1r+0dDs0lKxRwSsiKvGAMdvx/GvrPwxqM0S4wSh5BHQUpAdb8RL1PsOmy4Ksl2AQfQg1taROJbPjoyYxXnHxJ1OT/AIRgSFwTDcxP+G7H9a6XwnqPnWcQB/gHPrXn1tJHZRfunmXiSSTS9U8gqNtrdHGT/A3I/Q4/CsrxXZ22r6dIssavHIuGUgEEGrP7QsTafpZ16Cf7KbTDTPzt2Z7+3+NcJ4e8Uvq9pCJI0y6gqwckEeopSfNFMuK5ZNHzp8avg3f+EJ5td0S3Nx4eugJHMS/8echHzKwHRCc4bpzg9OfFYWOGVVznv6V9j+PPj1o/h/T7zTNKlTVtXIaNoUGYkJBGGboR6gZ9K+Tl8mJSsmCzHLkDqa6qLk4+8ctVRUtCpbxNNOAAyqBhWHYVoLC8Y43/APAjmqht13s0B/dkdFPQ0ixsHwXlwfRjxXQYt6GrEVwMqRjrxUcsgKk9F7E1UCOBjzXx9eaJEDEb2J2njJ60EMrXV0BwB1rO6mprly0nzLtx0FQ0FpWCtDRtGudcvUtrNev33b7qD1NZ9ejfDDT/ADZJ5jJksdqpngf7R96Bnq/wu00+G0FpE7TW6yZLtwd5xu4HavpvwvrsjKkCMFOBjdXzr4VkNje/ZpkUsTu3lsd+v8q9i0d3jmiNvKuwkZye9Te4juPHUhm8J6mkoIk8gsDnOCvI/lVr4a+IFutLs5CeSgVhmom2anp8ltdsGSRChOR3H/1682+F+qPZ+dYSPh7eVosZ7q2D/KuOuup1UHuj2f4i6JF4o8N6jptygkhvLZ4WB9GUivzCude8R6cJ9Fn1i4S2sXe22RvjIVsY3DnHHrX6hx3w1CzaJpArMpUNnpnjNflXrdnPpWs6np96Ct1a3UsMobqGVyD/ACqcPrJplYh2s0VbgCQl4vvKMDHYjtShxOik8kiqkshjl3Rn7w5HaliuVACsu0e3Negcdkwkja2O+JsrViOdnjVgx3d+aaJVOMEGoARFJlHAB6igltFovLj79QF8ACRufU01rkYOOarFi3Uk0xctx0r72z6cCmUUUjQ//9k=";
|
|
1745
1691
|
//#endregion
|
|
1692
|
+
//#region src/client/community-qr.ts
|
|
1693
|
+
/** The group's invite code, as a data URI. */
|
|
1694
|
+
const COMMUNITY_QR_DATA_URI = "data:image/png;base64,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";
|
|
1695
|
+
//#endregion
|
|
1746
1696
|
//#region src/client/shell.tsx
|
|
1747
1697
|
/**
|
|
1748
1698
|
* The card shell: brand bar, page tabs, per-page hero.
|
|
@@ -1769,7 +1719,8 @@ window.__ModuleLoader__.load({
|
|
|
1769
1719
|
* highlight on the correct button at the correct x, in the wrong row.
|
|
1770
1720
|
*
|
|
1771
1721
|
* 3. **Nothing here is a gate.** The star button opens the repository in a new
|
|
1772
|
-
* tab and nothing else
|
|
1722
|
+
* tab and nothing else, and the group button beside it opens a dialog with
|
|
1723
|
+
* the WeChat invite code. A settings card that can refuse to render is a card
|
|
1773
1724
|
* that, on a machine without outbound network, looks exactly like a broken
|
|
1774
1725
|
* plugin.
|
|
1775
1726
|
*
|
|
@@ -1863,6 +1814,26 @@ window.__ModuleLoader__.load({
|
|
|
1863
1814
|
function PoolShell({ t, version, avatar, tabs, active, onSelect, hero, children }) {
|
|
1864
1815
|
/** Measured box of the active tab's button, in tab-strip coordinates. */
|
|
1865
1816
|
const [glider, setGlider] = (0, react.useState)(void 0);
|
|
1817
|
+
/** Whether the group-invite dialog is open. Local: nothing outside needs it. */
|
|
1818
|
+
const [groupOpen, setGroupOpen] = (0, react.useState)(false);
|
|
1819
|
+
/**
|
|
1820
|
+
* Close the dialog on Escape.
|
|
1821
|
+
*
|
|
1822
|
+
* The overlay's own click handler covers the pointer, but a keyboard user who
|
|
1823
|
+
* opened this with Enter has no other way out — and a modal with no key exit
|
|
1824
|
+
* is a trap. Bound only while open, so the listener is not paid for on every
|
|
1825
|
+
* page of a card that is mostly not showing a dialog.
|
|
1826
|
+
*/
|
|
1827
|
+
(0, react.useEffect)(() => {
|
|
1828
|
+
if (!groupOpen) return void 0;
|
|
1829
|
+
const onKey = (event) => {
|
|
1830
|
+
if (event.key === "Escape") setGroupOpen(false);
|
|
1831
|
+
};
|
|
1832
|
+
window.addEventListener("keydown", onKey);
|
|
1833
|
+
return () => {
|
|
1834
|
+
window.removeEventListener("keydown", onKey);
|
|
1835
|
+
};
|
|
1836
|
+
}, [groupOpen]);
|
|
1866
1837
|
const tabRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1867
1838
|
const tabsRef = (0, react.useRef)(null);
|
|
1868
1839
|
const measure = (0, react.useCallback)(() => {
|
|
@@ -1942,6 +1913,28 @@ window.__ModuleLoader__.load({
|
|
|
1942
1913
|
})
|
|
1943
1914
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("shell.star") ?? "Star on GitHub" })]
|
|
1944
1915
|
}),
|
|
1916
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1917
|
+
type: "button",
|
|
1918
|
+
className: "dsm-xdpool-star dsm-xdpool-group",
|
|
1919
|
+
title: t?.("shell.groupHint") ?? "扫码加入 XD 交流群",
|
|
1920
|
+
onClick: () => {
|
|
1921
|
+
setGroupOpen(true);
|
|
1922
|
+
},
|
|
1923
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1924
|
+
width: "15",
|
|
1925
|
+
height: "15",
|
|
1926
|
+
viewBox: "0 0 24 24",
|
|
1927
|
+
"aria-hidden": "true",
|
|
1928
|
+
focusable: "false",
|
|
1929
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1930
|
+
fill: "currentColor",
|
|
1931
|
+
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"
|
|
1932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1933
|
+
fill: "currentColor",
|
|
1934
|
+
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"
|
|
1935
|
+
})]
|
|
1936
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("shell.group") ?? "交流群" })]
|
|
1937
|
+
}),
|
|
1945
1938
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1946
1939
|
className: "dsm-xdpool-navtabs",
|
|
1947
1940
|
role: "tablist",
|
|
@@ -1976,6 +1969,46 @@ window.__ModuleLoader__.load({
|
|
|
1976
1969
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1977
1970
|
className: "dsm-xdpool-body",
|
|
1978
1971
|
children
|
|
1972
|
+
}),
|
|
1973
|
+
!groupOpen ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1974
|
+
className: "dsm-xdpool-groupmask",
|
|
1975
|
+
role: "dialog",
|
|
1976
|
+
"aria-modal": "true",
|
|
1977
|
+
"aria-label": t?.("shell.groupTitle") ?? "XD 交流群",
|
|
1978
|
+
onClick: () => {
|
|
1979
|
+
setGroupOpen(false);
|
|
1980
|
+
},
|
|
1981
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1982
|
+
className: "dsm-xdpool-groupcard",
|
|
1983
|
+
onClick: (event) => {
|
|
1984
|
+
event.stopPropagation();
|
|
1985
|
+
},
|
|
1986
|
+
children: [
|
|
1987
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1988
|
+
className: "dsm-xdpool-groupcardhead",
|
|
1989
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t?.("shell.groupTitle") ?? "XD 交流群" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1990
|
+
type: "button",
|
|
1991
|
+
className: "dsm-xdpool-groupclose",
|
|
1992
|
+
"aria-label": t?.("shell.groupClose") ?? "关闭",
|
|
1993
|
+
onClick: () => {
|
|
1994
|
+
setGroupOpen(false);
|
|
1995
|
+
},
|
|
1996
|
+
children: "×"
|
|
1997
|
+
})]
|
|
1998
|
+
}),
|
|
1999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
2000
|
+
className: "dsm-xdpool-groupqr",
|
|
2001
|
+
src: COMMUNITY_QR_DATA_URI,
|
|
2002
|
+
alt: t?.("shell.groupAlt") ?? "微信群二维码:用微信扫一扫加入 XD 交流群",
|
|
2003
|
+
width: 260,
|
|
2004
|
+
height: 260
|
|
2005
|
+
}),
|
|
2006
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2007
|
+
className: "dsm-xdpool-groupnote",
|
|
2008
|
+
children: t?.("shell.groupNote") ?? "用微信「扫一扫」加入 XD 交流群。二维码 7 天内有效,过期了在 GitHub 提 issue 说一声,会更新。"
|
|
2009
|
+
})
|
|
2010
|
+
]
|
|
2011
|
+
})
|
|
1979
2012
|
})
|
|
1980
2013
|
]
|
|
1981
2014
|
});
|
|
@@ -2025,7 +2058,7 @@ window.__ModuleLoader__.load({
|
|
|
2025
2058
|
*/
|
|
2026
2059
|
/** How many recent calls the detail table lists. */
|
|
2027
2060
|
const DETAIL_ROWS = 12;
|
|
2028
|
-
function fmtInt
|
|
2061
|
+
function fmtInt(value) {
|
|
2029
2062
|
return value.toLocaleString("en-US");
|
|
2030
2063
|
}
|
|
2031
2064
|
/** Milliseconds as `1.2s` / `340ms`, because `1200ms` is not a unit anyone reads. */
|
|
@@ -2034,7 +2067,7 @@ window.__ModuleLoader__.load({
|
|
|
2034
2067
|
}
|
|
2035
2068
|
/** Tokens per second, kept integer: 12.4 tok/s reads as false precision. */
|
|
2036
2069
|
function fmtTps(value) {
|
|
2037
|
-
return value === null ? "—" : `${fmtInt
|
|
2070
|
+
return value === null ? "—" : `${fmtInt(value)}`;
|
|
2038
2071
|
}
|
|
2039
2072
|
function fmtClock(at) {
|
|
2040
2073
|
const d = new Date(at);
|
|
@@ -2074,9 +2107,9 @@ window.__ModuleLoader__.load({
|
|
|
2074
2107
|
"—"
|
|
2075
2108
|
];
|
|
2076
2109
|
return [
|
|
2077
|
-
fmtInt
|
|
2078
|
-
fmtInt
|
|
2079
|
-
fmtInt
|
|
2110
|
+
fmtInt(call.usage.inputTokens),
|
|
2111
|
+
fmtInt(call.usage.outputTokens),
|
|
2112
|
+
fmtInt(call.usage.cachedTokens)
|
|
2080
2113
|
];
|
|
2081
2114
|
}
|
|
2082
2115
|
function LedgerPage({ t, usage: shared }) {
|
|
@@ -2198,20 +2231,20 @@ window.__ModuleLoader__.load({
|
|
|
2198
2231
|
children: [
|
|
2199
2232
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2200
2233
|
label: t?.("ledger.tokens") ?? "Tokens",
|
|
2201
|
-
value: summary.usageReported ? fmtInt
|
|
2234
|
+
value: summary.usageReported ? fmtInt(totals?.input ?? 0) : t?.("ledger.notReported") ?? "not reported",
|
|
2202
2235
|
sub: summary.usageReported ? t?.("ledger.tokenSplit", {
|
|
2203
|
-
input: fmtInt
|
|
2204
|
-
output: fmtInt
|
|
2205
|
-
}) ?? `in ${fmtInt
|
|
2236
|
+
input: fmtInt(totals?.input ?? 0),
|
|
2237
|
+
output: fmtInt(totals?.output ?? 0)
|
|
2238
|
+
}) ?? `in ${fmtInt(totals?.input ?? 0)} · out ${fmtInt(totals?.output ?? 0)}` : t?.("ledger.notReportedHint") ?? "the gateway ignored stream_options.include_usage"
|
|
2206
2239
|
}),
|
|
2207
2240
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2208
2241
|
label: t?.("ledger.turns") ?? "Turns",
|
|
2209
|
-
value: fmtInt
|
|
2242
|
+
value: fmtInt(summary.turns),
|
|
2210
2243
|
sub: t?.("ledger.callsSub", { calls: summary.calls }) ?? `${summary.calls} upstream calls`
|
|
2211
2244
|
}),
|
|
2212
2245
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2213
2246
|
label: t?.("ledger.speed") ?? "Speed",
|
|
2214
|
-
value: totals?.avgTps === null || totals?.avgTps === void 0 ? "—" : `${fmtInt
|
|
2247
|
+
value: totals?.avgTps === null || totals?.avgTps === void 0 ? "—" : `${fmtInt(totals.avgTps)} tok/s`,
|
|
2215
2248
|
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
2249
|
}),
|
|
2217
2250
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
@@ -2220,11 +2253,11 @@ window.__ModuleLoader__.load({
|
|
|
2220
2253
|
const rate = cacheHitRate(summary);
|
|
2221
2254
|
return rate === null || !summary.usageReported ? "—" : `${Math.round(rate * 100)}%`;
|
|
2222
2255
|
})(),
|
|
2223
|
-
sub: summary.usageReported ? t?.("ledger.cacheSub", { cached: fmtInt
|
|
2256
|
+
sub: summary.usageReported ? t?.("ledger.cacheSub", { cached: fmtInt(summary.rows.reduce((n, r) => n + r.cachedTokens, 0)) }) ?? "" : ""
|
|
2224
2257
|
}),
|
|
2225
2258
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(KpiCard, {
|
|
2226
2259
|
label: t?.("ledger.requests") ?? "Requests",
|
|
2227
|
-
value: fmtInt
|
|
2260
|
+
value: fmtInt(summary.calls),
|
|
2228
2261
|
sub: summary.failedTurns === 0 ? t?.("ledger.allOk") ?? "no turn failed outright" : t?.("ledger.failedSub", {
|
|
2229
2262
|
failed: summary.failedTurns,
|
|
2230
2263
|
recovered: summary.recoveredTurns
|
|
@@ -2331,19 +2364,19 @@ window.__ModuleLoader__.load({
|
|
|
2331
2364
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: regionLabel(row.region, t) }),
|
|
2332
2365
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2333
2366
|
className: "dsm-xdpool-num",
|
|
2334
|
-
children: fmtInt
|
|
2367
|
+
children: fmtInt(row.turns)
|
|
2335
2368
|
}),
|
|
2336
2369
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2337
2370
|
className: "dsm-xdpool-num",
|
|
2338
|
-
children: fmtInt
|
|
2371
|
+
children: fmtInt(row.calls)
|
|
2339
2372
|
}),
|
|
2340
2373
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2341
2374
|
className: "dsm-xdpool-num",
|
|
2342
|
-
children: fmtInt
|
|
2375
|
+
children: fmtInt(row.inputTokens)
|
|
2343
2376
|
}),
|
|
2344
2377
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2345
2378
|
className: "dsm-xdpool-num",
|
|
2346
|
-
children: fmtInt
|
|
2379
|
+
children: fmtInt(row.outputTokens)
|
|
2347
2380
|
}),
|
|
2348
2381
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2349
2382
|
className: "dsm-xdpool-ellipsis",
|
|
@@ -2393,7 +2426,7 @@ window.__ModuleLoader__.load({
|
|
|
2393
2426
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: regionLabel(row.region, t) }),
|
|
2394
2427
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2395
2428
|
className: "dsm-xdpool-num",
|
|
2396
|
-
children: fmtInt
|
|
2429
|
+
children: fmtInt(row.calls)
|
|
2397
2430
|
}),
|
|
2398
2431
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
2399
2432
|
className: "dsm-xdpool-num",
|
|
@@ -9047,7 +9080,6 @@ window.__ModuleLoader__.load({
|
|
|
9047
9080
|
* message.
|
|
9048
9081
|
*/
|
|
9049
9082
|
async function callPack(method, payload) {
|
|
9050
|
-
if (packRpcBridge === void 0) packRpcBridge = resolvePackRpcBridge();
|
|
9051
9083
|
if (packRpcBridge !== void 0) try {
|
|
9052
9084
|
const result = await packRpcBridge.call(method, payload);
|
|
9053
9085
|
if (result !== null && typeof result === "object") {
|
|
@@ -9291,29 +9323,6 @@ window.__ModuleLoader__.load({
|
|
|
9291
9323
|
}
|
|
9292
9324
|
return rows;
|
|
9293
9325
|
}
|
|
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
9326
|
function autoCheckinFrom(envelope) {
|
|
9318
9327
|
const value = envelope.value;
|
|
9319
9328
|
if (value === null || typeof value !== "object") return void 0;
|
|
@@ -9926,429 +9935,6 @@ window.__ModuleLoader__.load({
|
|
|
9926
9935
|
});
|
|
9927
9936
|
}
|
|
9928
9937
|
//#endregion
|
|
9929
|
-
//#region src/client/BillionPage.tsx
|
|
9930
|
-
/**
|
|
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:
|
|
9941
|
-
*
|
|
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.
|
|
9950
|
-
*
|
|
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.
|
|
9955
|
-
*
|
|
9956
|
-
* WHAT THIS PAGE DOES NOT REDRAW
|
|
9957
|
-
*
|
|
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.
|
|
9963
|
-
*
|
|
9964
|
-
* @module dsh-xdpool/client/BillionPage
|
|
9965
|
-
*/
|
|
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
|
-
/**
|
|
9991
|
-
* Fleet cache hit rate, or null when nothing was measured.
|
|
9992
|
-
*
|
|
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.
|
|
9997
|
-
*/
|
|
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;
|
|
10005
|
-
}
|
|
10006
|
-
return input === 0 ? null : cached / input;
|
|
10007
|
-
}
|
|
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;
|
|
10029
|
-
}
|
|
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();
|
|
10034
|
-
}
|
|
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");
|
|
10044
|
-
}
|
|
10045
|
-
function BillionPage({ t }) {
|
|
10046
|
-
const [doc, setDoc] = (0, react.useState)(void 0);
|
|
10047
|
-
const [failed, setFailed] = (0, react.useState)(false);
|
|
10048
|
-
const mounted = (0, react.useRef)(true);
|
|
10049
|
-
(0, react.useEffect)(() => () => {
|
|
10050
|
-
mounted.current = false;
|
|
10051
|
-
}, []);
|
|
10052
|
-
const load = (0, react.useCallback)(async (signal) => {
|
|
10053
|
-
try {
|
|
10054
|
-
const response = await fetch(POOL_BILLION_PATH, {
|
|
10055
|
-
headers: { accept: "application/json" },
|
|
10056
|
-
credentials: "same-origin",
|
|
10057
|
-
...signal === void 0 ? {} : { signal }
|
|
10058
|
-
});
|
|
10059
|
-
if (!response.ok) {
|
|
10060
|
-
if (mounted.current) setFailed(true);
|
|
10061
|
-
return;
|
|
10062
|
-
}
|
|
10063
|
-
const value = await response.json().catch(() => void 0);
|
|
10064
|
-
if (mounted.current && signal?.aborted !== true) {
|
|
10065
|
-
setDoc(value);
|
|
10066
|
-
setFailed(false);
|
|
10067
|
-
}
|
|
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
9938
|
//#region src/client/RoutingPage.tsx
|
|
10353
9939
|
/**
|
|
10354
9940
|
* The routing page: chains that are themselves models.
|
|
@@ -10435,8 +10021,10 @@ window.__ModuleLoader__.load({
|
|
|
10435
10021
|
const next = value;
|
|
10436
10022
|
setStatus(next);
|
|
10437
10023
|
setError(void 0);
|
|
10438
|
-
|
|
10439
|
-
|
|
10024
|
+
if (!seeded.current) {
|
|
10025
|
+
seeded.current = true;
|
|
10026
|
+
setDrafts((next.combos ?? []).map(draftOf));
|
|
10027
|
+
}
|
|
10440
10028
|
}
|
|
10441
10029
|
} catch (cause) {
|
|
10442
10030
|
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -11608,6 +11196,18 @@ window.__ModuleLoader__.load({
|
|
|
11608
11196
|
*
|
|
11609
11197
|
* @module dsh-xdpool/client/ChannelPoolSection
|
|
11610
11198
|
*/
|
|
11199
|
+
let poolSnapshot;
|
|
11200
|
+
/**
|
|
11201
|
+
* Drop the snapshot after anything that changes the data behind it.
|
|
11202
|
+
*
|
|
11203
|
+
* Logins, deletions, toggles and manual balance refreshes all make the snapshot
|
|
11204
|
+
* stale in a way a silent revalidation cannot hide: showing yesterday's account
|
|
11205
|
+
* list for a card the user just added an account to is worse than showing the
|
|
11206
|
+
* "reading" line for a moment.
|
|
11207
|
+
*/
|
|
11208
|
+
function invalidatePoolSnapshot() {
|
|
11209
|
+
poolSnapshot = void 0;
|
|
11210
|
+
}
|
|
11611
11211
|
/** How many automatic retries a cold-start mount gets before giving up. */
|
|
11612
11212
|
const LOAD_RETRIES = 5;
|
|
11613
11213
|
/** Gap between those retries, in milliseconds. */
|
|
@@ -11700,8 +11300,18 @@ window.__ModuleLoader__.load({
|
|
|
11700
11300
|
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
11701
11301
|
}
|
|
11702
11302
|
function ChannelPoolSection({ t }) {
|
|
11703
|
-
const [entries, setEntries] = (0, react.useState)([]);
|
|
11704
|
-
const [error, setError] = (0, react.useState)(
|
|
11303
|
+
const [entries, setEntries] = (0, react.useState)(() => poolSnapshot?.entries ?? []);
|
|
11304
|
+
const [error, setError] = (0, react.useState)(() => poolSnapshot?.error);
|
|
11305
|
+
/**
|
|
11306
|
+
* True until the first wave has answered once.
|
|
11307
|
+
*
|
|
11308
|
+
* The section used to render NOTHING while loading, so a cold start and a
|
|
11309
|
+
* broken RPC bridge looked identical: blank (#95). The bridge failing is a
|
|
11310
|
+
* real, common failure — `provider.status` answers `ok: false`, the map comes
|
|
11311
|
+
* back empty, `scanProviders` is empty and the old code returned `<></>` with
|
|
11312
|
+
* `error === undefined`. Silence was indistinguishable from "no accounts".
|
|
11313
|
+
*/
|
|
11314
|
+
const [loading, setLoading] = (0, react.useState)(() => poolSnapshot === void 0);
|
|
11705
11315
|
const [notice, setNotice] = (0, react.useState)(void 0);
|
|
11706
11316
|
/** provider → the open list, or `undefined` for a card with both folded. */
|
|
11707
11317
|
const [folds, setFolds] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
@@ -11714,6 +11324,15 @@ window.__ModuleLoader__.load({
|
|
|
11714
11324
|
const entriesRef = (0, react.useRef)(entries);
|
|
11715
11325
|
entriesRef.current = entries;
|
|
11716
11326
|
const mountedRef = (0, react.useRef)(true);
|
|
11327
|
+
/**
|
|
11328
|
+
* Which load is current.
|
|
11329
|
+
*
|
|
11330
|
+
* Two things make a stale answer arrive late: the retry ladder re-runs the
|
|
11331
|
+
* whole chain, and a remount starts a new chain while the old request is
|
|
11332
|
+
* still in flight. Stamping each run is what keeps an older wave-2 result
|
|
11333
|
+
* from painting over a newer wave-1 one.
|
|
11334
|
+
*/
|
|
11335
|
+
const loadSeq = (0, react.useRef)(0);
|
|
11717
11336
|
(0, react.useEffect)(() => () => {
|
|
11718
11337
|
mountedRef.current = false;
|
|
11719
11338
|
}, []);
|
|
@@ -11726,6 +11345,7 @@ window.__ModuleLoader__.load({
|
|
|
11726
11345
|
});
|
|
11727
11346
|
};
|
|
11728
11347
|
const load = async () => {
|
|
11348
|
+
loadSeq.current += 1;
|
|
11729
11349
|
try {
|
|
11730
11350
|
const channels = (await fetchChannels()).filter((channel) => !REGION_OWNED_CHANNELS.has(channel.id));
|
|
11731
11351
|
const statusAnswer = await callPack("provider.status", { providers: channels.map((channel) => channel.id) });
|
|
@@ -11735,25 +11355,17 @@ window.__ModuleLoader__.load({
|
|
|
11735
11355
|
const wantBalances = channels.find((channel) => channel.id === provider)?.credits !== void 0;
|
|
11736
11356
|
const accountsPromise = callPack("account.list", { provider });
|
|
11737
11357
|
const modelsPromise = callPack("model.list", { provider });
|
|
11738
|
-
const
|
|
11739
|
-
const statusesPromise = wantBalances && channels.find((c) => c.id === provider)?.credits?.status === true ? callPack("credits.status", { provider }) : Promise.resolve(void 0);
|
|
11740
|
-
const [accounts, models, balances, creditStatuses] = await Promise.all([
|
|
11741
|
-
accountsPromise.catch(() => refused("account.list failed")),
|
|
11742
|
-
modelsPromise.catch(() => refused("model.list failed")),
|
|
11743
|
-
balancesPromise.catch(() => refused("credits.balances failed")),
|
|
11744
|
-
statusesPromise.catch(() => refused("credits.status failed"))
|
|
11745
|
-
]);
|
|
11358
|
+
const [accounts, models] = await Promise.all([accountsPromise.catch(() => refused("account.list failed")), modelsPromise.catch(() => refused("model.list failed"))]);
|
|
11746
11359
|
const modelRows = modelsFrom(models.value);
|
|
11747
11360
|
const modelEmptyBecauseRefused = modelRows.length === 0 && models.ok !== true;
|
|
11748
|
-
const balanceRows = balances === void 0 ? [] : balancesFrom(balances);
|
|
11749
11361
|
return [provider, {
|
|
11750
11362
|
accounts: dedupeAccounts(accountsFrom(accounts)),
|
|
11751
11363
|
models: sortChannelModels(modelRows),
|
|
11752
11364
|
facts: /* @__PURE__ */ new Map(),
|
|
11753
|
-
balances:
|
|
11754
|
-
statuses:
|
|
11755
|
-
balancePending:
|
|
11756
|
-
balanceError:
|
|
11365
|
+
balances: [],
|
|
11366
|
+
statuses: [],
|
|
11367
|
+
balancePending: wantBalances,
|
|
11368
|
+
balanceError: void 0,
|
|
11757
11369
|
modelError: modelEmptyBecauseRefused ? models.error?.message ?? "model.list refused" : void 0
|
|
11758
11370
|
}];
|
|
11759
11371
|
}));
|
|
@@ -11764,25 +11376,48 @@ window.__ModuleLoader__.load({
|
|
|
11764
11376
|
facts: /* @__PURE__ */ new Map(),
|
|
11765
11377
|
status: statuses.get(channel.id)
|
|
11766
11378
|
}));
|
|
11379
|
+
poolSnapshot = {
|
|
11380
|
+
entries: next,
|
|
11381
|
+
error: void 0
|
|
11382
|
+
};
|
|
11767
11383
|
if (!mountedRef.current) return;
|
|
11384
|
+
const seq = loadSeq.current;
|
|
11768
11385
|
setEntries(next);
|
|
11769
11386
|
setError(void 0);
|
|
11387
|
+
setLoading(false);
|
|
11770
11388
|
if (next.length === 0) {
|
|
11771
|
-
if (!statusAnswer.ok)
|
|
11389
|
+
if (!statusAnswer.ok) {
|
|
11390
|
+
const message = statusAnswer.error?.message ?? "provider.status refused";
|
|
11391
|
+
poolSnapshot = {
|
|
11392
|
+
entries: next,
|
|
11393
|
+
error: message
|
|
11394
|
+
};
|
|
11395
|
+
setError(message);
|
|
11396
|
+
}
|
|
11772
11397
|
return;
|
|
11773
11398
|
}
|
|
11399
|
+
for (const entry of next) {
|
|
11400
|
+
if (entry.channel.credits === void 0) continue;
|
|
11401
|
+
refreshBalances(entry.channel.id, false, true, seq);
|
|
11402
|
+
}
|
|
11774
11403
|
const idsByProvider = {};
|
|
11775
11404
|
for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
|
|
11776
11405
|
const facts = await fetchChannelModels(next.map((entry) => entry.channel.id), idsByProvider).catch(() => /* @__PURE__ */ new Map());
|
|
11777
|
-
if (!mountedRef.current) return;
|
|
11406
|
+
if (!mountedRef.current || loadSeq.current !== seq) return;
|
|
11778
11407
|
if (facts.size === 0) return;
|
|
11779
11408
|
setEntries((previous) => previous.map((entry) => ({
|
|
11780
11409
|
...entry,
|
|
11781
11410
|
facts: new Map((facts.get(entry.channel.id) ?? []).map((model) => [model.id, model]))
|
|
11782
11411
|
})));
|
|
11783
11412
|
} catch (cause) {
|
|
11413
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
11414
|
+
poolSnapshot = {
|
|
11415
|
+
entries: entriesRef.current,
|
|
11416
|
+
error: message
|
|
11417
|
+
};
|
|
11784
11418
|
if (!mountedRef.current) return;
|
|
11785
|
-
setError(
|
|
11419
|
+
setError(message);
|
|
11420
|
+
setLoading(false);
|
|
11786
11421
|
}
|
|
11787
11422
|
};
|
|
11788
11423
|
/**
|
|
@@ -11936,8 +11571,20 @@ window.__ModuleLoader__.load({
|
|
|
11936
11571
|
await load();
|
|
11937
11572
|
}
|
|
11938
11573
|
};
|
|
11939
|
-
|
|
11940
|
-
|
|
11574
|
+
/**
|
|
11575
|
+
* Read one channel's balances and merge them into its card.
|
|
11576
|
+
*
|
|
11577
|
+
* Wave 2 of the load calls this with `quiet` true: the card is already on
|
|
11578
|
+
* screen, so the provider must not be marked busy (that would disable the
|
|
11579
|
+
* claim button for a network read the reader did not ask for) and a failure
|
|
11580
|
+
* must land in `balanceError` rather than in the section-wide notice.
|
|
11581
|
+
*
|
|
11582
|
+
* `seq` is the load that asked for this read. A reply from an older load
|
|
11583
|
+
* cannot overwrite a newer card, which is what the previous version got wrong
|
|
11584
|
+
* when a remount overlapped an in-flight refresh.
|
|
11585
|
+
*/
|
|
11586
|
+
const refreshBalances = async (provider, force, quiet = false, seq) => {
|
|
11587
|
+
if (!quiet) markBusy(provider, true);
|
|
11941
11588
|
setEntries((previous) => previous.map((entry) => entry.channel.id === provider ? {
|
|
11942
11589
|
...entry,
|
|
11943
11590
|
balancePending: true
|
|
@@ -11947,7 +11594,21 @@ window.__ModuleLoader__.load({
|
|
|
11947
11594
|
provider,
|
|
11948
11595
|
force: true
|
|
11949
11596
|
} : { provider });
|
|
11950
|
-
if (
|
|
11597
|
+
if (seq !== void 0 && loadSeq.current !== seq) return;
|
|
11598
|
+
if (!mountedRef.current) {
|
|
11599
|
+
if (envelope.ok) {
|
|
11600
|
+
const rows = balancesFrom(envelope);
|
|
11601
|
+
poolSnapshot = {
|
|
11602
|
+
entries: (poolSnapshot?.entries ?? []).map((entry) => entry.channel.id === provider ? {
|
|
11603
|
+
...entry,
|
|
11604
|
+
balancePending: false,
|
|
11605
|
+
balances: rows
|
|
11606
|
+
} : entry),
|
|
11607
|
+
error: poolSnapshot?.error
|
|
11608
|
+
};
|
|
11609
|
+
}
|
|
11610
|
+
return;
|
|
11611
|
+
}
|
|
11951
11612
|
if (!envelope.ok) {
|
|
11952
11613
|
setEntries((previous) => previous.map((entry) => entry.channel.id === provider ? {
|
|
11953
11614
|
...entry,
|
|
@@ -11964,7 +11625,7 @@ window.__ModuleLoader__.load({
|
|
|
11964
11625
|
balances: rows
|
|
11965
11626
|
} : entry));
|
|
11966
11627
|
} finally {
|
|
11967
|
-
if (mountedRef.current) markBusy(provider, false);
|
|
11628
|
+
if (mountedRef.current && !quiet) markBusy(provider, false);
|
|
11968
11629
|
}
|
|
11969
11630
|
};
|
|
11970
11631
|
const claim = async (provider) => {
|
|
@@ -12008,12 +11669,24 @@ window.__ModuleLoader__.load({
|
|
|
12008
11669
|
return;
|
|
12009
11670
|
}
|
|
12010
11671
|
if (action === "test") setNotice(t?.("channelpool.testOk") ?? "Reachable.");
|
|
11672
|
+
invalidatePoolSnapshot();
|
|
12011
11673
|
await load();
|
|
12012
11674
|
} finally {
|
|
12013
11675
|
if (mountedRef.current) markBusy(provider, false);
|
|
12014
11676
|
}
|
|
12015
11677
|
};
|
|
12016
|
-
if (entries.length === 0 && error === void 0)
|
|
11678
|
+
if (entries.length === 0 && error === void 0) {
|
|
11679
|
+
if (!loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
11680
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
11681
|
+
className: "dsm-xdpool-cpool",
|
|
11682
|
+
"aria-label": t?.("channelpool.title") ?? "Channels",
|
|
11683
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11684
|
+
className: "dsm-xdpool-note",
|
|
11685
|
+
role: "status",
|
|
11686
|
+
children: t?.("channelpool.loading") ?? "Reading scan channels…"
|
|
11687
|
+
})
|
|
11688
|
+
});
|
|
11689
|
+
}
|
|
12017
11690
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
12018
11691
|
className: "dsm-xdpool-cpool",
|
|
12019
11692
|
"aria-label": t?.("channelpool.title") ?? "Channels",
|
|
@@ -12408,7 +12081,7 @@ window.__ModuleLoader__.load({
|
|
|
12408
12081
|
*
|
|
12409
12082
|
* @module dsh-xdpool/version
|
|
12410
12083
|
*/
|
|
12411
|
-
const POOL_VERSION = "2.5.
|
|
12084
|
+
const POOL_VERSION = "2.5.1";
|
|
12412
12085
|
//#endregion
|
|
12413
12086
|
//#region src/client/PoolCard.tsx
|
|
12414
12087
|
/**
|
|
@@ -12704,23 +12377,6 @@ window.__ModuleLoader__.load({
|
|
|
12704
12377
|
/** Render pool health, per-account credits/cooldown, and the model directory. */
|
|
12705
12378
|
function PoolCard({ t, settingsScope }) {
|
|
12706
12379
|
const settingsWritable = settingsScope?.getSnapshot().writable === true;
|
|
12707
|
-
/**
|
|
12708
|
-
* Whether the plugin owns a row in the left sidebar (issue #70).
|
|
12709
|
-
*
|
|
12710
|
-
* Seeded from, and kept in step with, the settings mirror rather than from the
|
|
12711
|
-
* pool status: the row is registered by `apply()`, before any card exists, and
|
|
12712
|
-
* both sides must answer the same question the same way.
|
|
12713
|
-
*/
|
|
12714
|
-
const [sidebarRow, setSidebarRowState] = (0, react.useState)(() => readSidebarRow(settingsScope));
|
|
12715
|
-
const [sidebarRowBusy, setSidebarRowBusy] = (0, react.useState)(false);
|
|
12716
|
-
(0, react.useEffect)(() => {
|
|
12717
|
-
const unsubscribe = settingsScope?.subscribe?.(() => {
|
|
12718
|
-
setSidebarRowState(readSidebarRow(settingsScope));
|
|
12719
|
-
});
|
|
12720
|
-
return () => {
|
|
12721
|
-
unsubscribe?.();
|
|
12722
|
-
};
|
|
12723
|
-
}, [settingsScope]);
|
|
12724
12380
|
/** Which region tab is showing. A CN-only install never leaves this. */
|
|
12725
12381
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
12726
12382
|
/**
|
|
@@ -12791,10 +12447,6 @@ window.__ModuleLoader__.load({
|
|
|
12791
12447
|
id: "ledger",
|
|
12792
12448
|
label: t?.("shell.tabLedger") ?? "Dashboard"
|
|
12793
12449
|
},
|
|
12794
|
-
{
|
|
12795
|
-
id: "billion",
|
|
12796
|
-
label: t?.("shell.tabBillion") ?? "billion-context"
|
|
12797
|
-
},
|
|
12798
12450
|
{
|
|
12799
12451
|
id: "routing",
|
|
12800
12452
|
label: t?.("shell.tabRouting") ?? "Routing"
|
|
@@ -13381,18 +13033,11 @@ window.__ModuleLoader__.load({
|
|
|
13381
13033
|
const imageModelIds = Object.entries(local).filter(([, e]) => e.images).map(([id]) => id);
|
|
13382
13034
|
const contextBudgets = {};
|
|
13383
13035
|
for (const [id, entry] of Object.entries(local)) if (entry.budget !== void 0) contextBudgets[id] = entry.budget;
|
|
13384
|
-
|
|
13036
|
+
await write.call(settingsScope, MODEL_SELECTION_KEY[region], {
|
|
13385
13037
|
enabledModelIds,
|
|
13386
13038
|
imageModelIds,
|
|
13387
13039
|
contextBudgets
|
|
13388
|
-
})
|
|
13389
|
-
if (mounted.current) setError(t?.("row.modelsConflict") ?? "The settings changed elsewhere; review and save again");
|
|
13390
|
-
return;
|
|
13391
|
-
}
|
|
13392
|
-
if (await refresh(region) === void 0) {
|
|
13393
|
-
if (mounted.current) setFlash(t?.("row.modelsSaved") ?? "Saved");
|
|
13394
|
-
return;
|
|
13395
|
-
}
|
|
13040
|
+
});
|
|
13396
13041
|
setDraftFor(region, void 0);
|
|
13397
13042
|
if (mounted.current) setFlash(t?.("row.modelsSaved") ?? "Saved");
|
|
13398
13043
|
} catch (cause) {
|
|
@@ -13429,35 +13074,6 @@ window.__ModuleLoader__.load({
|
|
|
13429
13074
|
}
|
|
13430
13075
|
};
|
|
13431
13076
|
/**
|
|
13432
|
-
* Turn the sidebar row on or off (issue #70).
|
|
13433
|
-
*
|
|
13434
|
-
* Written through the same settings field `apply()` reads, so the two agree
|
|
13435
|
-
* without a new endpoint. The row itself is owned by the bundle registration,
|
|
13436
|
-
* so the mirror's change notification is what makes this take effect without a
|
|
13437
|
-
* restart on hosts that publish one; everywhere else it lands on next launch,
|
|
13438
|
-
* which the hint text says rather than implies.
|
|
13439
|
-
*/
|
|
13440
|
-
const setSidebarRow = async (next) => {
|
|
13441
|
-
const write = settingsScope?.set;
|
|
13442
|
-
if (write === void 0) {
|
|
13443
|
-
setError(t?.("row.modelsSaveError", { message: "settings scope is read-only" }) ?? "settings scope is read-only");
|
|
13444
|
-
return;
|
|
13445
|
-
}
|
|
13446
|
-
setSidebarRowBusy(true);
|
|
13447
|
-
setFlash(void 0);
|
|
13448
|
-
try {
|
|
13449
|
-
if (await write.call(settingsScope, "sidebarRow", next) === false) {
|
|
13450
|
-
if (mounted.current) setError(t?.("row.modelsConflict") ?? "The settings changed elsewhere; review and save again");
|
|
13451
|
-
return;
|
|
13452
|
-
}
|
|
13453
|
-
if (mounted.current) setSidebarRowState(next);
|
|
13454
|
-
} catch (cause) {
|
|
13455
|
-
if (mounted.current) setError(String(cause));
|
|
13456
|
-
} finally {
|
|
13457
|
-
if (mounted.current) setSidebarRowBusy(false);
|
|
13458
|
-
}
|
|
13459
|
-
};
|
|
13460
|
-
/**
|
|
13461
13077
|
* Switch the daily-points automation on or off.
|
|
13462
13078
|
*
|
|
13463
13079
|
* The whole `automation` object is written as one key, because that is how the
|
|
@@ -13675,7 +13291,7 @@ window.__ModuleLoader__.load({
|
|
|
13675
13291
|
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
13292
|
t,
|
|
13677
13293
|
usage
|
|
13678
|
-
}) : page === "
|
|
13294
|
+
}) : 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
13295
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: POOL_CARD_CSS }),
|
|
13680
13296
|
usage === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
13681
13297
|
className: "dsm-xdpool-card dsm-xdpool-usage",
|
|
@@ -13732,30 +13348,6 @@ window.__ModuleLoader__.load({
|
|
|
13732
13348
|
})
|
|
13733
13349
|
]
|
|
13734
13350
|
}),
|
|
13735
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
13736
|
-
className: "dsm-xdpool-card dsm-xdpool-sidebar-row",
|
|
13737
|
-
"aria-label": t?.("row.sidebarRowTitle") ?? "Sidebar entry",
|
|
13738
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13739
|
-
className: "dsm-xdpool-sidebar-row-head",
|
|
13740
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
13741
|
-
className: "dsm-xdpool-usage-title",
|
|
13742
|
-
children: t?.("row.sidebarRowTitle") ?? "Sidebar entry"
|
|
13743
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
13744
|
-
type: "button",
|
|
13745
|
-
role: "switch",
|
|
13746
|
-
"aria-checked": sidebarRow,
|
|
13747
|
-
disabled: !settingsWritable || sidebarRowBusy,
|
|
13748
|
-
className: `dsm-xdpool-auto-switch${sidebarRow ? " dsm-xdpool-auto-switch-on" : ""}`,
|
|
13749
|
-
onClick: () => {
|
|
13750
|
-
setSidebarRow(!sidebarRow);
|
|
13751
|
-
},
|
|
13752
|
-
children: sidebarRowBusy ? t?.("row.autoBusy") ?? "Saving…" : sidebarRow ? t?.("row.autoOn") ?? "On" : t?.("row.autoOff") ?? "Off"
|
|
13753
|
-
})]
|
|
13754
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
13755
|
-
className: "dsm-xdpool-usage-note",
|
|
13756
|
-
children: sidebarRow ? t?.("row.sidebarRowOn") ?? "XD Pool has its own row in the left sidebar. Turn it off to open the pool from Settings instead." : t?.("row.sidebarRowOff") ?? "The sidebar row is hidden; the pool still opens from Settings → XD Pool, and models are unaffected."
|
|
13757
|
-
})]
|
|
13758
|
-
}),
|
|
13759
13351
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
13760
13352
|
className: "dsm-xdpool-regions",
|
|
13761
13353
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -14700,6 +14292,20 @@ window.__ModuleLoader__.load({
|
|
|
14700
14292
|
global: "xdpool-global"
|
|
14701
14293
|
};
|
|
14702
14294
|
function ModelRow({ model, region, paidTwin, t, draft, editable, onToggle, onToggleImage, onBudget, onTest, probe, probing }) {
|
|
14295
|
+
/**
|
|
14296
|
+
* A per-MOUNTED-ROW scope for the two budget radios.
|
|
14297
|
+
*
|
|
14298
|
+
* HTML groups radios by `name`, and React renders both region cards at once —
|
|
14299
|
+
* the collapsible panes stay in the DOM with `hidden`, they are not unmounted.
|
|
14300
|
+
* A `name` built from the model id alone is therefore the SAME name in the
|
|
14301
|
+
* cn card and the global card, so the two rows joined one radio group: the
|
|
14302
|
+
* browser keeps only the last checked member, and whichever card renders
|
|
14303
|
+
* later (global) silently un-checks the cn row's "1M". Adding the region
|
|
14304
|
+
* would not be enough either — the settings page and the sidebar pane can
|
|
14305
|
+
* both mount the same region — so the scope has to name the row instance,
|
|
14306
|
+
* which is what `useId` is for.
|
|
14307
|
+
*/
|
|
14308
|
+
const radioScope = (0, react.useId)();
|
|
14703
14309
|
const now = /* @__PURE__ */ new Date();
|
|
14704
14310
|
const tag = tagFor(model, now, region);
|
|
14705
14311
|
const promo = promoStatusFor(model, now, region);
|
|
@@ -14768,7 +14374,7 @@ window.__ModuleLoader__.load({
|
|
|
14768
14374
|
"aria-label": t?.("row.modelContextBudget") ?? "Context",
|
|
14769
14375
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
14770
14376
|
type: "radio",
|
|
14771
|
-
name: `budget-${
|
|
14377
|
+
name: `budget-${radioScope}`,
|
|
14772
14378
|
checked: currentBudget === DEFAULT_CONTEXT_BUDGET,
|
|
14773
14379
|
disabled: !editable,
|
|
14774
14380
|
onChange: () => {
|
|
@@ -14776,7 +14382,7 @@ window.__ModuleLoader__.load({
|
|
|
14776
14382
|
}
|
|
14777
14383
|
}), /* @__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
14384
|
type: "radio",
|
|
14779
|
-
name: `budget-${
|
|
14385
|
+
name: `budget-${radioScope}`,
|
|
14780
14386
|
checked: currentBudget === native,
|
|
14781
14387
|
disabled: !editable,
|
|
14782
14388
|
onChange: () => {
|
|
@@ -15076,10 +14682,6 @@ window.__ModuleLoader__.load({
|
|
|
15076
14682
|
"row.modelsSaving": "Saving…",
|
|
15077
14683
|
"row.modelsDiscard": "Discard",
|
|
15078
14684
|
"row.modelsSaved": "Model selection saved",
|
|
15079
|
-
"row.sidebarRowTitle": "Sidebar entry",
|
|
15080
|
-
"row.sidebarRowOn": "XD Pool has its own row in the left sidebar. Turn it off to open the pool from Settings instead.",
|
|
15081
|
-
"row.sidebarRowOff": "The sidebar row is hidden; the pool still opens from Settings → XD Pool, and models are unaffected.",
|
|
15082
|
-
"row.modelsConflict": "The settings changed elsewhere; review and save again",
|
|
15083
14685
|
"row.modelsSaveError": "Could not save: {message}",
|
|
15084
14686
|
"row.modelsEmpty": "No model enabled — enable at least one before saving.",
|
|
15085
14687
|
"row.catalogOffline": "built-in list (offline)",
|
|
@@ -15295,6 +14897,12 @@ window.__ModuleLoader__.load({
|
|
|
15295
14897
|
"shell.brand": "XD Pool",
|
|
15296
14898
|
"shell.star": "Star",
|
|
15297
14899
|
"shell.starHint": "Open this plugin on GitHub",
|
|
14900
|
+
"shell.group": "Community",
|
|
14901
|
+
"shell.groupHint": "Scan the code to join the XD community group",
|
|
14902
|
+
"shell.groupTitle": "XD community group",
|
|
14903
|
+
"shell.groupAlt": "WeChat group code: scan it to join the XD community group",
|
|
14904
|
+
"shell.groupClose": "Close",
|
|
14905
|
+
"shell.groupNote": "Scan with WeChat to join the XD community group. The code is valid for 7 days; if it has expired, open an issue on GitHub and it will be refreshed.",
|
|
15298
14906
|
"shell.tabPool": "Account pool",
|
|
15299
14907
|
"shell.tabChannels": "Login channels",
|
|
15300
14908
|
"shell.tabAggregate": "Providers",
|
|
@@ -15303,8 +14911,6 @@ window.__ModuleLoader__.load({
|
|
|
15303
14911
|
"shell.subAggregate": "Bring your own API keys: any OpenAI-compatible provider, reachable as extra models.",
|
|
15304
14912
|
"shell.tabLedger": "Dashboard",
|
|
15305
14913
|
"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
14914
|
"shell.tabRouting": "Routing",
|
|
15309
14915
|
"shell.subRouting": "Chains that try models in order, so one busy or rate-limited model falls through to the next.",
|
|
15310
14916
|
"shell.tabGateway": "Gateway",
|
|
@@ -15387,48 +14993,9 @@ window.__ModuleLoader__.load({
|
|
|
15387
14993
|
"ledger.unattributed": "(unattributed)",
|
|
15388
14994
|
"ledger.cn": "CN",
|
|
15389
14995
|
"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
14996
|
"channelpool.title": "Scan channels",
|
|
15431
14997
|
"channelpool.accountCount": "{count} account(s)",
|
|
14998
|
+
"channelpool.loading": "Reading scan channels…",
|
|
15432
14999
|
"channelpool.disabled": "disabled",
|
|
15433
15000
|
"channelpool.noModels": "No models published yet.",
|
|
15434
15001
|
"channelpool.modelError": "Could not list models: {message}",
|
|
@@ -15591,10 +15158,6 @@ window.__ModuleLoader__.load({
|
|
|
15591
15158
|
"row.modelsSaving": "保存中…",
|
|
15592
15159
|
"row.modelsDiscard": "放弃修改",
|
|
15593
15160
|
"row.modelsSaved": "模型选择已保存",
|
|
15594
|
-
"row.sidebarRowTitle": "侧栏入口",
|
|
15595
|
-
"row.sidebarRowOn": "左侧栏会给 XD Pool 留一行入口。关掉后改为从「设置 → XD Pool」进入,模型池不受影响。",
|
|
15596
|
-
"row.sidebarRowOff": "侧栏入口已隐藏;仍可从「设置 → XD Pool」进入,模型池不受影响。",
|
|
15597
|
-
"row.modelsConflict": "设置已在别处被修改,请确认后重新保存",
|
|
15598
15161
|
"row.modelsSaveError": "保存失败:{message}",
|
|
15599
15162
|
"row.modelsEmpty": "至少要启用一个模型才能保存。",
|
|
15600
15163
|
"row.catalogOffline": "内置列表(离线)",
|
|
@@ -15810,6 +15373,12 @@ window.__ModuleLoader__.load({
|
|
|
15810
15373
|
"shell.brand": "XD Pool",
|
|
15811
15374
|
"shell.star": "点Star",
|
|
15812
15375
|
"shell.starHint": "在 GitHub 上给这个插件点个星",
|
|
15376
|
+
"shell.group": "交流群",
|
|
15377
|
+
"shell.groupHint": "扫码加入 XD 交流群",
|
|
15378
|
+
"shell.groupTitle": "XD 交流群",
|
|
15379
|
+
"shell.groupAlt": "微信群二维码:用微信扫一扫加入 XD 交流群",
|
|
15380
|
+
"shell.groupClose": "关闭",
|
|
15381
|
+
"shell.groupNote": "用微信「扫一扫」加入 XD 交流群。二维码 7 天内有效,过期了在 GitHub 提个 issue 说一声,会更新。",
|
|
15813
15382
|
"shell.tabPool": "账号池",
|
|
15814
15383
|
"shell.tabChannels": "扫码登录",
|
|
15815
15384
|
"shell.tabAggregate": "聚合渠道",
|
|
@@ -15818,8 +15387,6 @@ window.__ModuleLoader__.load({
|
|
|
15818
15387
|
"shell.subAggregate": "带上你自己的 API key:任何兼容 OpenAI 的服务都能变成额外模型。",
|
|
15819
15388
|
"shell.tabLedger": "数据看板",
|
|
15820
15389
|
"shell.subLedger": "这台机器发出去什么、收回来什么,从池子上次启动算起。",
|
|
15821
|
-
"shell.tabBillion": "billion-context",
|
|
15822
|
-
"shell.subBillion": "一个只读的窗口,看着旁边跑着的那台压缩器。",
|
|
15823
15390
|
"shell.tabRouting": "路由",
|
|
15824
15391
|
"shell.subRouting": "把几个模型串成一条链,按顺序试;一个忙了或被限流,就自动落到下一个。",
|
|
15825
15392
|
"shell.tabGateway": "网关",
|
|
@@ -15902,48 +15469,9 @@ window.__ModuleLoader__.load({
|
|
|
15902
15469
|
"ledger.unattributed": "(没归属到账号)",
|
|
15903
15470
|
"ledger.cn": "国内版",
|
|
15904
15471
|
"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
15472
|
"channelpool.title": "扫码渠道",
|
|
15946
15473
|
"channelpool.accountCount": "{count} 个账号",
|
|
15474
|
+
"channelpool.loading": "正在读取扫码渠道…",
|
|
15947
15475
|
"channelpool.disabled": "已停用",
|
|
15948
15476
|
"channelpool.noModels": "该渠道还没有发布模型。",
|
|
15949
15477
|
"channelpool.modelError": "读取模型列表失败:{message}",
|
|
@@ -16046,29 +15574,12 @@ window.__ModuleLoader__.load({
|
|
|
16046
15574
|
}, PoolCard));
|
|
16047
15575
|
installNavIcon(ctx, () => t("row.navLabel"));
|
|
16048
15576
|
try {
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
order: 20,
|
|
16056
|
-
label: () => t("row.navLabel")
|
|
16057
|
-
}, PanelIcon);
|
|
16058
|
-
else if (!wanted && disposeRow !== void 0) {
|
|
16059
|
-
disposeRow();
|
|
16060
|
-
disposeRow = void 0;
|
|
16061
|
-
}
|
|
16062
|
-
};
|
|
16063
|
-
ctx.slots.inject("sidebar.panellist", () => {
|
|
16064
|
-
syncSidebarRow();
|
|
16065
|
-
const unsubscribe = settingsScope?.subscribe?.(syncSidebarRow);
|
|
16066
|
-
return () => {
|
|
16067
|
-
unsubscribe?.();
|
|
16068
|
-
disposeRow?.();
|
|
16069
|
-
disposeRow = void 0;
|
|
16070
|
-
};
|
|
16071
|
-
});
|
|
15577
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
15578
|
+
name: "sidebar.panellist",
|
|
15579
|
+
id: "xdpool",
|
|
15580
|
+
order: 20,
|
|
15581
|
+
label: () => t("row.navLabel")
|
|
15582
|
+
}, PanelIcon));
|
|
16072
15583
|
ctx.slots.inject("main", () => ctx.slots.register({
|
|
16073
15584
|
name: "main",
|
|
16074
15585
|
key: "xdpool",
|