@jsonstudio/rcc 0.89.1086 → 0.89.1136
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/dist/build-info.js +2 -2
- package/dist/cli.js +39 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/gemini/gemini-protocol-client.js +5 -0
- package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
- package/dist/commands/provider-update.js +355 -5
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/docs/daemon-admin-ui.html +604 -91
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/index.d.ts +37 -1
- package/dist/manager/modules/quota/index.js +378 -18
- package/dist/manager/modules/quota/index.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.d.ts +3 -0
- package/dist/manager/quota/provider-quota-center.js +88 -24
- package/dist/manager/quota/provider-quota-center.js.map +1 -1
- package/dist/manager/quota/provider-quota-store.js +5 -2
- package/dist/manager/quota/provider-quota-store.js.map +1 -1
- package/dist/manager/types.d.ts +5 -0
- package/dist/providers/core/config/service-profiles.js +1 -1
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +5 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.js +26 -38
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +10 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/server/handlers/handler-utils.d.ts +1 -1
- package/dist/server/handlers/handler-utils.js +82 -4
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +26 -3
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/handlers/sse-dispatcher.js +1 -4
- package/dist/server/handlers/sse-dispatcher.js.map +1 -1
- package/dist/server/runtime/http-server/colored-logger.d.ts +1 -1
- package/dist/server/runtime/http-server/colored-logger.js +22 -10
- package/dist/server/runtime/http-server/colored-logger.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.d.ts +1 -1
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +10 -14
- package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +108 -115
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +132 -7
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +22 -0
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.d.ts +3 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +56 -0
- package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -0
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js +100 -4
- package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +24 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js +25 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
- package/dist/server/runtime/http-server/executor-provider.js +74 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/index.d.ts +7 -1
- package/dist/server/runtime/http-server/index.js +171 -14
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/middleware.d.ts +2 -1
- package/dist/server/runtime/http-server/middleware.js +7 -6
- package/dist/server/runtime/http-server/middleware.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.d.ts +1 -1
- package/dist/server/runtime/http-server/provider-utils.js +19 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +9 -10
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +10 -0
- package/dist/server/runtime/http-server/routes.js +14 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/stats-manager.d.ts +7 -0
- package/dist/server/runtime/http-server/stats-manager.js +22 -3
- package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +10 -0
- package/dist/server/utils/http-error-mapper.js +85 -7
- package/dist/server/utils/http-error-mapper.js.map +1 -1
- package/dist/server/utils/request-id-manager.js +9 -5
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/server/utils/sse-request-parser.js +2 -1
- package/dist/server/utils/sse-request-parser.js.map +1 -1
- package/dist/server/utils/utf8-chunk-buffer.d.ts +15 -30
- package/dist/server/utils/utf8-chunk-buffer.js +78 -88
- package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
- package/dist/server/utils/warmup-storm-tracker.js +1 -1
- package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
- package/dist/token-daemon/token-daemon.js +7 -1
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/tools/provider-update/fetch-models.js +8 -5
- package/dist/tools/provider-update/fetch-models.js.map +1 -1
- package/dist/tools/provider-update/probe-context.d.ts +24 -0
- package/dist/tools/provider-update/probe-context.js +199 -0
- package/dist/tools/provider-update/probe-context.js.map +1 -0
- package/dist/tools/provider-update/types.d.ts +1 -0
- package/package.json +6 -4
- package/scripts/scan-apply-patch-samples.mjs +362 -0
- package/scripts/scan-exec-command-samples.mjs +269 -0
- package/scripts/scan-tool-shape-samples.mjs +291 -0
- package/scripts/tools/sync-apply-patch-regressions.mjs +86 -0
- package/scripts/verify-apply-patch-regressions.mjs +119 -0
- package/scripts/verify-tool-arguments.mjs +1 -2
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.container {
|
|
35
|
-
max-width:
|
|
35
|
+
max-width: 1680px;
|
|
36
36
|
margin: 22px auto 40px;
|
|
37
37
|
padding: 0 16px;
|
|
38
38
|
}
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
align-items: center;
|
|
51
51
|
justify-content: space-between;
|
|
52
52
|
gap: 14px;
|
|
53
|
+
flex-wrap: wrap;
|
|
53
54
|
margin-bottom: 14px;
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -156,6 +157,7 @@
|
|
|
156
157
|
display: flex;
|
|
157
158
|
gap: 6px;
|
|
158
159
|
margin: 14px 0 10px;
|
|
160
|
+
flex-wrap: wrap;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
.tab {
|
|
@@ -178,6 +180,15 @@
|
|
|
178
180
|
gap: 12px;
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
.grid.grid-wide-left {
|
|
184
|
+
grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Ensure grid children can shrink without overflowing into the next column */
|
|
188
|
+
.grid > .card {
|
|
189
|
+
min-width: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
181
192
|
@media (max-width: 980px) {
|
|
182
193
|
.grid {
|
|
183
194
|
grid-template-columns: minmax(0, 1fr);
|
|
@@ -199,9 +210,7 @@
|
|
|
199
210
|
.table {
|
|
200
211
|
width: 100%;
|
|
201
212
|
border-collapse: collapse;
|
|
202
|
-
|
|
203
|
-
overflow: hidden;
|
|
204
|
-
border: 1px solid var(--border);
|
|
213
|
+
table-layout: fixed;
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
.table th,
|
|
@@ -210,6 +219,8 @@
|
|
|
210
219
|
font-size: 12px;
|
|
211
220
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
212
221
|
vertical-align: top;
|
|
222
|
+
overflow-wrap: anywhere;
|
|
223
|
+
word-break: break-word;
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
.table th {
|
|
@@ -219,10 +230,52 @@
|
|
|
219
230
|
background: rgba(255, 255, 255, 0.03);
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
.table tr.group-row td {
|
|
234
|
+
background: rgba(255, 255, 255, 0.02);
|
|
235
|
+
color: rgba(255, 255, 255, 0.86);
|
|
236
|
+
font-weight: 650;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.indent {
|
|
240
|
+
padding-left: 22px !important;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.table-wrap {
|
|
244
|
+
width: 100%;
|
|
245
|
+
max-width: 100%;
|
|
246
|
+
overflow: auto;
|
|
247
|
+
border-radius: 12px;
|
|
248
|
+
border: 1px solid var(--border);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.table-wrap .table {
|
|
252
|
+
border: 0;
|
|
253
|
+
min-width: 860px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.table td.actions-cell {
|
|
257
|
+
width: 220px;
|
|
258
|
+
overflow: visible;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.actions {
|
|
262
|
+
display: flex;
|
|
263
|
+
gap: 8px;
|
|
264
|
+
flex-wrap: wrap;
|
|
265
|
+
justify-content: flex-end;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.truncate {
|
|
269
|
+
white-space: nowrap;
|
|
270
|
+
overflow: hidden;
|
|
271
|
+
text-overflow: ellipsis;
|
|
272
|
+
}
|
|
273
|
+
|
|
222
274
|
.mono {
|
|
223
275
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
|
224
276
|
"Courier New", monospace;
|
|
225
277
|
color: var(--muted);
|
|
278
|
+
word-break: break-all;
|
|
226
279
|
}
|
|
227
280
|
|
|
228
281
|
.notice {
|
|
@@ -246,6 +299,25 @@
|
|
|
246
299
|
border-radius: 12px;
|
|
247
300
|
padding: 10px 12px;
|
|
248
301
|
}
|
|
302
|
+
|
|
303
|
+
@media (max-width: 640px) {
|
|
304
|
+
header {
|
|
305
|
+
flex-direction: column;
|
|
306
|
+
align-items: flex-start;
|
|
307
|
+
}
|
|
308
|
+
.statusline {
|
|
309
|
+
width: 100%;
|
|
310
|
+
justify-content: flex-start;
|
|
311
|
+
}
|
|
312
|
+
.row {
|
|
313
|
+
align-items: stretch;
|
|
314
|
+
}
|
|
315
|
+
.row > input,
|
|
316
|
+
.row > select,
|
|
317
|
+
.row > button {
|
|
318
|
+
max-width: 100%;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
249
321
|
</style>
|
|
250
322
|
</head>
|
|
251
323
|
<body>
|
|
@@ -255,13 +327,15 @@
|
|
|
255
327
|
<div class="title">
|
|
256
328
|
<h1>RouteCodex Daemon Admin</h1>
|
|
257
329
|
<p>
|
|
258
|
-
|
|
259
|
-
|
|
330
|
+
Writes to <span class="mono">~/.routecodex/config.json</span>. If
|
|
331
|
+
<span class="mono">httpserver.apikey</span> is configured, remote access is allowed
|
|
332
|
+
with the key; otherwise this page is localhost-only.
|
|
260
333
|
</p>
|
|
261
334
|
</div>
|
|
262
335
|
<div class="statusline">
|
|
263
336
|
<div class="pill"><span id="statusDot" class="dot"></span><span id="statusText">connecting…</span></div>
|
|
264
337
|
<div class="pill"><span class="mono" id="serverId">serverId: —</span></div>
|
|
338
|
+
<button id="restartRuntimeBtn" class="primary">Restart runtime</button>
|
|
265
339
|
</div>
|
|
266
340
|
</header>
|
|
267
341
|
|
|
@@ -280,12 +354,14 @@
|
|
|
280
354
|
|
|
281
355
|
<div class="tabs">
|
|
282
356
|
<button class="tab active" data-tab="providers">Provider Pool</button>
|
|
357
|
+
<button class="tab" data-tab="tokens">Token Stats</button>
|
|
283
358
|
<button class="tab" data-tab="credentials">Auth Provider Pool</button>
|
|
359
|
+
<button class="tab" data-tab="quota">Quota Pool</button>
|
|
284
360
|
<button class="tab" data-tab="routing">Runtime Routing Pool</button>
|
|
285
361
|
</div>
|
|
286
362
|
|
|
287
363
|
<section id="panelProviders" data-panel="providers">
|
|
288
|
-
<div class="grid">
|
|
364
|
+
<div class="grid grid-wide-left">
|
|
289
365
|
<div class="card" style="box-shadow: none;">
|
|
290
366
|
<p class="section-title">Providers in <span class="mono">virtualrouter.providers</span></p>
|
|
291
367
|
<p class="section-sub">
|
|
@@ -295,20 +371,23 @@
|
|
|
295
371
|
<button id="refreshProvidersBtn" class="primary">Refresh</button>
|
|
296
372
|
<button id="newProviderBtn">New provider…</button>
|
|
297
373
|
</div>
|
|
298
|
-
<
|
|
299
|
-
<
|
|
300
|
-
<
|
|
301
|
-
<
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
374
|
+
<div class="table-wrap">
|
|
375
|
+
<table class="table">
|
|
376
|
+
<thead>
|
|
377
|
+
<tr>
|
|
378
|
+
<th>id</th>
|
|
379
|
+
<th>type</th>
|
|
380
|
+
<th>enabled</th>
|
|
381
|
+
<th>baseURL</th>
|
|
382
|
+
<th>models</th>
|
|
383
|
+
<th>compat</th>
|
|
384
|
+
<th>auth</th>
|
|
385
|
+
<th></th>
|
|
386
|
+
</tr>
|
|
387
|
+
</thead>
|
|
388
|
+
<tbody id="providersTbody"></tbody>
|
|
389
|
+
</table>
|
|
390
|
+
</div>
|
|
312
391
|
</div>
|
|
313
392
|
|
|
314
393
|
<div class="card" style="box-shadow: none;">
|
|
@@ -318,7 +397,7 @@
|
|
|
318
397
|
</p>
|
|
319
398
|
|
|
320
399
|
<div class="notice" style="margin-bottom: 10px;">
|
|
321
|
-
If you use <span class="mono">httpserver.apikey</span>, set it above so API calls don’t return 401.
|
|
400
|
+
If you use <span class="mono">httpserver.apikey</span>, set it above so API calls don’t return 401. Without apikey configured, this page is localhost-only.
|
|
322
401
|
</div>
|
|
323
402
|
|
|
324
403
|
<div class="row" style="margin-bottom: 10px;">
|
|
@@ -412,6 +491,37 @@
|
|
|
412
491
|
</div>
|
|
413
492
|
</section>
|
|
414
493
|
|
|
494
|
+
<section id="panelTokens" data-panel="tokens" style="display:none;">
|
|
495
|
+
<div class="grid">
|
|
496
|
+
<div class="card" style="box-shadow:none;">
|
|
497
|
+
<p class="section-title">Token usage (session + historical)</p>
|
|
498
|
+
<p class="section-sub">
|
|
499
|
+
Session stats reset on server restart. Historical totals are aggregated from
|
|
500
|
+
<span class="mono">~/.routecodex/logs/provider-stats.jsonl</span> (best-effort).
|
|
501
|
+
</p>
|
|
502
|
+
<div class="row" style="margin-bottom: 10px;">
|
|
503
|
+
<button id="refreshTokensBtn" class="primary">Refresh</button>
|
|
504
|
+
</div>
|
|
505
|
+
<div class="notice mono" id="tokenTotalsBox" style="white-space: pre-wrap;"></div>
|
|
506
|
+
<div class="table-wrap" style="margin-top: 10px;">
|
|
507
|
+
<table class="table">
|
|
508
|
+
<thead>
|
|
509
|
+
<tr>
|
|
510
|
+
<th>providerKey</th>
|
|
511
|
+
<th>model</th>
|
|
512
|
+
<th>session req/err</th>
|
|
513
|
+
<th>session tokens in/out/total</th>
|
|
514
|
+
<th>historical req/err</th>
|
|
515
|
+
<th>historical tokens in/out/total</th>
|
|
516
|
+
</tr>
|
|
517
|
+
</thead>
|
|
518
|
+
<tbody id="tokensTbody"></tbody>
|
|
519
|
+
</table>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
</section>
|
|
524
|
+
|
|
415
525
|
<section id="panelCredentials" data-panel="credentials" style="display:none;">
|
|
416
526
|
<div class="grid">
|
|
417
527
|
<div class="card" style="box-shadow:none;">
|
|
@@ -422,19 +532,21 @@
|
|
|
422
532
|
<div class="row" style="margin-bottom: 10px;">
|
|
423
533
|
<button id="refreshCredentialsBtn" class="primary">Refresh</button>
|
|
424
534
|
</div>
|
|
425
|
-
<
|
|
426
|
-
<
|
|
427
|
-
<
|
|
428
|
-
<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
535
|
+
<div class="table-wrap">
|
|
536
|
+
<table class="table">
|
|
537
|
+
<thead>
|
|
538
|
+
<tr>
|
|
539
|
+
<th>kind</th>
|
|
540
|
+
<th>provider</th>
|
|
541
|
+
<th>alias</th>
|
|
542
|
+
<th>status</th>
|
|
543
|
+
<th>expires</th>
|
|
544
|
+
<th>secretRef</th>
|
|
545
|
+
</tr>
|
|
546
|
+
</thead>
|
|
547
|
+
<tbody id="credentialsTbody"></tbody>
|
|
548
|
+
</table>
|
|
549
|
+
</div>
|
|
438
550
|
</div>
|
|
439
551
|
|
|
440
552
|
<div class="card" style="box-shadow:none;">
|
|
@@ -474,6 +586,52 @@
|
|
|
474
586
|
</div>
|
|
475
587
|
</section>
|
|
476
588
|
|
|
589
|
+
<section id="panelQuota" data-panel="quota" style="display:none;">
|
|
590
|
+
<div class="grid">
|
|
591
|
+
<div class="card" style="box-shadow:none;">
|
|
592
|
+
<p class="section-title">Quota (daemon)</p>
|
|
593
|
+
<p class="section-sub">
|
|
594
|
+
VirtualRouter consumes this via <span class="mono">quotaView</span>. When
|
|
595
|
+
<span class="mono">inPool=false</span>, the provider is treated as removed from the route pool.
|
|
596
|
+
</p>
|
|
597
|
+
<div class="row" style="margin-bottom: 10px;">
|
|
598
|
+
<button id="refreshQuotaBtn" class="primary">Refresh</button>
|
|
599
|
+
<button id="resetQuotaBtn" class="danger">Reset quota module</button>
|
|
600
|
+
</div>
|
|
601
|
+
<div class="table-wrap">
|
|
602
|
+
<table class="table">
|
|
603
|
+
<thead>
|
|
604
|
+
<tr>
|
|
605
|
+
<th>providerKey</th>
|
|
606
|
+
<th>auth</th>
|
|
607
|
+
<th>inPool</th>
|
|
608
|
+
<th>reason</th>
|
|
609
|
+
<th>cooldownUntil</th>
|
|
610
|
+
<th>blacklistUntil</th>
|
|
611
|
+
<th>errCount</th>
|
|
612
|
+
<th></th>
|
|
613
|
+
</tr>
|
|
614
|
+
</thead>
|
|
615
|
+
<tbody id="quotaTbody"></tbody>
|
|
616
|
+
</table>
|
|
617
|
+
</div>
|
|
618
|
+
<div id="quotaOpLog" class="log" style="margin-top: 10px; display:none;"></div>
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<div class="card" style="box-shadow:none;">
|
|
622
|
+
<p class="section-title">Notes</p>
|
|
623
|
+
<div class="notice">
|
|
624
|
+
<div style="margin-bottom: 6px;">
|
|
625
|
+
Use this view to confirm 429/backoff/blacklist decisions and whether a provider is currently eligible.
|
|
626
|
+
</div>
|
|
627
|
+
<div>
|
|
628
|
+
If a provider looks stuck, try <span class="mono">Reset quota module</span>, then <span class="mono">Restart runtime</span>.
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
</section>
|
|
634
|
+
|
|
477
635
|
<section id="panelRouting" data-panel="routing" style="display:none;">
|
|
478
636
|
<div class="grid">
|
|
479
637
|
<div class="card" style="box-shadow:none;">
|
|
@@ -495,18 +653,20 @@
|
|
|
495
653
|
<div class="row" style="margin-bottom: 10px;">
|
|
496
654
|
<button id="refreshRuntimesBtn" class="primary">Refresh</button>
|
|
497
655
|
</div>
|
|
498
|
-
<
|
|
499
|
-
<
|
|
500
|
-
<
|
|
501
|
-
<
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
656
|
+
<div class="table-wrap">
|
|
657
|
+
<table class="table">
|
|
658
|
+
<thead>
|
|
659
|
+
<tr>
|
|
660
|
+
<th>providerKey</th>
|
|
661
|
+
<th>runtimeKey</th>
|
|
662
|
+
<th>family</th>
|
|
663
|
+
<th>protocol</th>
|
|
664
|
+
<th>series</th>
|
|
665
|
+
</tr>
|
|
666
|
+
</thead>
|
|
667
|
+
<tbody id="runtimesTbody"></tbody>
|
|
668
|
+
</table>
|
|
669
|
+
</div>
|
|
510
670
|
</div>
|
|
511
671
|
</div>
|
|
512
672
|
</section>
|
|
@@ -520,7 +680,10 @@
|
|
|
520
680
|
const el = $(id);
|
|
521
681
|
if (!el) return;
|
|
522
682
|
el.style.display = value ? "block" : "none";
|
|
523
|
-
|
|
683
|
+
const raw = value || "";
|
|
684
|
+
const max = 12000;
|
|
685
|
+
const out = raw.length > max ? raw.slice(0, max) + "\n…(truncated)" : raw;
|
|
686
|
+
el.textContent = out;
|
|
524
687
|
}
|
|
525
688
|
|
|
526
689
|
function getApiKey() {
|
|
@@ -569,10 +732,71 @@
|
|
|
569
732
|
});
|
|
570
733
|
const panels = [
|
|
571
734
|
{ name: "providers", el: $("panelProviders") },
|
|
735
|
+
{ name: "tokens", el: $("panelTokens") },
|
|
572
736
|
{ name: "credentials", el: $("panelCredentials") },
|
|
737
|
+
{ name: "quota", el: $("panelQuota") },
|
|
573
738
|
{ name: "routing", el: $("panelRouting") }
|
|
574
739
|
];
|
|
575
740
|
for (const p of panels) p.el.style.display = p.name === name ? "block" : "none";
|
|
741
|
+
|
|
742
|
+
// Light auto-refresh on tab switch to avoid showing stale "Unauthorized" after setting apikey.
|
|
743
|
+
void maybeRefreshTab(name);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function getActiveTab() {
|
|
747
|
+
const active = document.querySelector(".tab.active");
|
|
748
|
+
const name = active ? active.getAttribute("data-tab") : null;
|
|
749
|
+
return name || "providers";
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const tabLastRefreshedAt = {
|
|
753
|
+
providers: 0,
|
|
754
|
+
tokens: 0,
|
|
755
|
+
credentials: 0,
|
|
756
|
+
quota: 0,
|
|
757
|
+
routing: 0
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
async function maybeRefreshTab(name) {
|
|
761
|
+
const key = name in tabLastRefreshedAt ? name : "providers";
|
|
762
|
+
const now = Date.now();
|
|
763
|
+
if (now - tabLastRefreshedAt[key] < 1500) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
tabLastRefreshedAt[key] = now;
|
|
767
|
+
try {
|
|
768
|
+
if (key === "providers") await refreshProviders();
|
|
769
|
+
else if (key === "tokens") await refreshTokens();
|
|
770
|
+
else if (key === "credentials") await refreshCredentials();
|
|
771
|
+
else if (key === "quota") await refreshQuota();
|
|
772
|
+
else if (key === "routing") await refreshRuntimes();
|
|
773
|
+
} catch {
|
|
774
|
+
// ignore refresh failures on tab switch
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function textOf(value) {
|
|
779
|
+
if (value === null || value === undefined) return "";
|
|
780
|
+
return String(value);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function createCell(tag, text, className, opts = {}) {
|
|
784
|
+
const el = document.createElement(tag);
|
|
785
|
+
if (className) el.className = className;
|
|
786
|
+
const s = textOf(text);
|
|
787
|
+
el.textContent = s;
|
|
788
|
+
if (opts.title && s) el.title = s;
|
|
789
|
+
return el;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function createErrorRow(colSpan, message) {
|
|
793
|
+
const tr = document.createElement("tr");
|
|
794
|
+
const td = document.createElement("td");
|
|
795
|
+
td.colSpan = colSpan;
|
|
796
|
+
td.className = "mono";
|
|
797
|
+
td.textContent = `Failed to load: ${textOf(message)}`;
|
|
798
|
+
tr.appendChild(td);
|
|
799
|
+
return tr;
|
|
576
800
|
}
|
|
577
801
|
|
|
578
802
|
function presetFor(type) {
|
|
@@ -640,29 +864,67 @@
|
|
|
640
864
|
|
|
641
865
|
async function refreshProviders() {
|
|
642
866
|
const body = $("providersTbody");
|
|
643
|
-
body.
|
|
867
|
+
body.replaceChildren();
|
|
644
868
|
try {
|
|
645
869
|
const data = await apiFetch("/config/providers");
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
`;
|
|
660
|
-
|
|
870
|
+
const list = Array.isArray(data.providers) ? data.providers : [];
|
|
871
|
+
const grouped = new Map();
|
|
872
|
+
for (const p of list) {
|
|
873
|
+
const t = textOf(p.type || "unknown") || "unknown";
|
|
874
|
+
if (!grouped.has(t)) grouped.set(t, []);
|
|
875
|
+
grouped.get(t).push(p);
|
|
876
|
+
}
|
|
877
|
+
const types = Array.from(grouped.keys()).sort((a, b) => a.localeCompare(b));
|
|
878
|
+
for (const type of types) {
|
|
879
|
+
const groupRow = document.createElement("tr");
|
|
880
|
+
groupRow.className = "group-row";
|
|
881
|
+
const groupCell = document.createElement("td");
|
|
882
|
+
groupCell.colSpan = 8;
|
|
883
|
+
groupCell.textContent = `${type} (${grouped.get(type).length})`;
|
|
884
|
+
groupRow.appendChild(groupCell);
|
|
885
|
+
body.appendChild(groupRow);
|
|
886
|
+
|
|
887
|
+
const items = grouped.get(type);
|
|
888
|
+
items.sort((a, b) => textOf(a.id).localeCompare(textOf(b.id)));
|
|
889
|
+
for (const p of items) {
|
|
890
|
+
const tr = document.createElement("tr");
|
|
891
|
+
tr.appendChild(createCell("td", p.id || "", "mono indent"));
|
|
892
|
+
tr.appendChild(createCell("td", p.type || "", ""));
|
|
893
|
+
tr.appendChild(createCell("td", String(Boolean(p.enabled)), ""));
|
|
894
|
+
tr.appendChild(createCell("td", p.baseURL || "", "mono truncate", { title: true }));
|
|
895
|
+
const preview = Array.isArray(p.modelsPreview) ? p.modelsPreview.map((x) => textOf(x)).filter(Boolean) : [];
|
|
896
|
+
const modelSummary = preview.length ? `${p.modelCount || 0}: ${preview.join(", ")}${(p.modelCount || 0) > preview.length ? ", …" : ""}` : String(p.modelCount || 0);
|
|
897
|
+
tr.appendChild(createCell("td", modelSummary, "mono truncate", { title: true }));
|
|
898
|
+
tr.appendChild(createCell("td", p.compatibilityProfile || "", "mono truncate", { title: true }));
|
|
899
|
+
tr.appendChild(createCell("td", p.authType || "", ""));
|
|
900
|
+
const actionsTd = document.createElement("td");
|
|
901
|
+
actionsTd.className = "actions-cell";
|
|
902
|
+
const box = document.createElement("div");
|
|
903
|
+
box.className = "actions";
|
|
904
|
+
const edit = document.createElement("button");
|
|
905
|
+
edit.textContent = "Edit";
|
|
906
|
+
edit.setAttribute("data-action", "edit");
|
|
907
|
+
edit.setAttribute("data-id", textOf(p.id));
|
|
908
|
+
const test = document.createElement("button");
|
|
909
|
+
test.textContent = "Test";
|
|
910
|
+
test.setAttribute("data-action", "test");
|
|
911
|
+
test.setAttribute("data-id", textOf(p.id));
|
|
912
|
+
test.disabled = !(p && p.enabled !== false && Number(p.modelCount || 0) > 0);
|
|
913
|
+
const del = document.createElement("button");
|
|
914
|
+
del.textContent = "Delete";
|
|
915
|
+
del.className = "danger";
|
|
916
|
+
del.setAttribute("data-action", "delete");
|
|
917
|
+
del.setAttribute("data-id", textOf(p.id));
|
|
918
|
+
box.appendChild(edit);
|
|
919
|
+
box.appendChild(test);
|
|
920
|
+
box.appendChild(del);
|
|
921
|
+
actionsTd.appendChild(box);
|
|
922
|
+
tr.appendChild(actionsTd);
|
|
923
|
+
body.appendChild(tr);
|
|
924
|
+
}
|
|
661
925
|
}
|
|
662
926
|
} catch (e) {
|
|
663
|
-
|
|
664
|
-
tr.innerHTML = `<td colspan="7" class="mono">Failed to load: ${e.message}</td>`;
|
|
665
|
-
body.appendChild(tr);
|
|
927
|
+
body.appendChild(createErrorRow(8, e && e.message ? e.message : e));
|
|
666
928
|
}
|
|
667
929
|
}
|
|
668
930
|
|
|
@@ -775,26 +1037,22 @@
|
|
|
775
1037
|
|
|
776
1038
|
async function refreshCredentials() {
|
|
777
1039
|
const body = $("credentialsTbody");
|
|
778
|
-
body.
|
|
1040
|
+
body.replaceChildren();
|
|
779
1041
|
try {
|
|
780
1042
|
const items = await apiFetch("/daemon/credentials");
|
|
781
1043
|
for (const c of items || []) {
|
|
782
1044
|
const tr = document.createElement("tr");
|
|
783
1045
|
const exp = c.expiresInSec == null ? "—" : `${c.expiresInSec}s`;
|
|
784
|
-
tr.
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
<td class="mono">${c.secretRef || "—"}</td>
|
|
791
|
-
`;
|
|
1046
|
+
tr.appendChild(createCell("td", c.kind || "", ""));
|
|
1047
|
+
tr.appendChild(createCell("td", c.provider || "", "mono"));
|
|
1048
|
+
tr.appendChild(createCell("td", c.alias || "", "mono"));
|
|
1049
|
+
tr.appendChild(createCell("td", c.status || "", ""));
|
|
1050
|
+
tr.appendChild(createCell("td", exp, "mono"));
|
|
1051
|
+
tr.appendChild(createCell("td", c.secretRef || "—", "mono"));
|
|
792
1052
|
body.appendChild(tr);
|
|
793
1053
|
}
|
|
794
1054
|
} catch (e) {
|
|
795
|
-
|
|
796
|
-
tr.innerHTML = `<td colspan="6" class="mono">Failed to load: ${e.message}</td>`;
|
|
797
|
-
body.appendChild(tr);
|
|
1055
|
+
body.appendChild(createErrorRow(6, e && e.message ? e.message : e));
|
|
798
1056
|
}
|
|
799
1057
|
}
|
|
800
1058
|
|
|
@@ -865,24 +1123,236 @@
|
|
|
865
1123
|
|
|
866
1124
|
async function refreshRuntimes() {
|
|
867
1125
|
const body = $("runtimesTbody");
|
|
868
|
-
body.
|
|
1126
|
+
body.replaceChildren();
|
|
869
1127
|
try {
|
|
870
1128
|
const items = await apiFetch("/providers/runtimes");
|
|
871
1129
|
for (const r of items || []) {
|
|
872
1130
|
const tr = document.createElement("tr");
|
|
873
|
-
tr.
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1131
|
+
tr.appendChild(createCell("td", r.providerKey || "", "mono truncate", { title: true }));
|
|
1132
|
+
tr.appendChild(createCell("td", r.runtimeKey || "", "mono truncate", { title: true }));
|
|
1133
|
+
tr.appendChild(createCell("td", r.family || "", ""));
|
|
1134
|
+
tr.appendChild(createCell("td", r.protocol || "", ""));
|
|
1135
|
+
tr.appendChild(createCell("td", r.series || "", ""));
|
|
1136
|
+
body.appendChild(tr);
|
|
1137
|
+
}
|
|
1138
|
+
} catch (e) {
|
|
1139
|
+
body.appendChild(createErrorRow(5, e && e.message ? e.message : e));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function formatEpochMs(ms) {
|
|
1144
|
+
if (typeof ms !== "number" || !Number.isFinite(ms) || ms <= 0) return "—";
|
|
1145
|
+
try {
|
|
1146
|
+
return new Date(ms).toLocaleString();
|
|
1147
|
+
} catch {
|
|
1148
|
+
return String(ms);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
async function refreshQuota() {
|
|
1153
|
+
const body = $("quotaTbody");
|
|
1154
|
+
body.replaceChildren();
|
|
1155
|
+
setLog("quotaOpLog", "");
|
|
1156
|
+
try {
|
|
1157
|
+
const out = await apiFetch("/quota/providers");
|
|
1158
|
+
const list = Array.isArray(out.providers) ? out.providers : [];
|
|
1159
|
+
for (const q of list) {
|
|
1160
|
+
const tr = document.createElement("tr");
|
|
1161
|
+
tr.appendChild(createCell("td", q.providerKey || "", "mono truncate", { title: true }));
|
|
1162
|
+
tr.appendChild(createCell("td", q.authType || "", ""));
|
|
1163
|
+
tr.appendChild(createCell("td", String(Boolean(q.inPool)), ""));
|
|
1164
|
+
tr.appendChild(createCell("td", q.reason || "", ""));
|
|
1165
|
+
tr.appendChild(createCell("td", formatEpochMs(q.cooldownUntil), "mono"));
|
|
1166
|
+
tr.appendChild(createCell("td", formatEpochMs(q.blacklistUntil), "mono"));
|
|
1167
|
+
tr.appendChild(createCell("td", q.consecutiveErrorCount ?? 0, "mono"));
|
|
1168
|
+
const actionsTd = document.createElement("td");
|
|
1169
|
+
actionsTd.className = "actions-cell";
|
|
1170
|
+
const box = document.createElement("div");
|
|
1171
|
+
box.className = "actions";
|
|
1172
|
+
const recover = document.createElement("button");
|
|
1173
|
+
recover.textContent = "Recover";
|
|
1174
|
+
recover.setAttribute("data-action", "quota-recover");
|
|
1175
|
+
recover.setAttribute("data-key", textOf(q.providerKey));
|
|
1176
|
+
const reset = document.createElement("button");
|
|
1177
|
+
reset.textContent = "Reset";
|
|
1178
|
+
reset.setAttribute("data-action", "quota-reset");
|
|
1179
|
+
reset.setAttribute("data-key", textOf(q.providerKey));
|
|
1180
|
+
const disable = document.createElement("button");
|
|
1181
|
+
disable.textContent = "Disable…";
|
|
1182
|
+
disable.className = "danger";
|
|
1183
|
+
disable.setAttribute("data-action", "quota-disable");
|
|
1184
|
+
disable.setAttribute("data-key", textOf(q.providerKey));
|
|
1185
|
+
box.appendChild(recover);
|
|
1186
|
+
box.appendChild(reset);
|
|
1187
|
+
box.appendChild(disable);
|
|
1188
|
+
actionsTd.appendChild(box);
|
|
1189
|
+
tr.appendChild(actionsTd);
|
|
1190
|
+
body.appendChild(tr);
|
|
1191
|
+
}
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
body.appendChild(createErrorRow(8, e && e.message ? e.message : e));
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function formatInt(n) {
|
|
1198
|
+
const v = typeof n === "number" && Number.isFinite(n) ? n : 0;
|
|
1199
|
+
try { return v.toLocaleString(); } catch { return String(v); }
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function formatTokensRow(label, totals) {
|
|
1203
|
+
const inTok = formatInt(totals.totalPromptTokens);
|
|
1204
|
+
const outTok = formatInt(totals.totalCompletionTokens);
|
|
1205
|
+
const totTok = formatInt(totals.totalOutputTokens);
|
|
1206
|
+
const req = formatInt(totals.requestCount);
|
|
1207
|
+
const err = formatInt(totals.errorCount);
|
|
1208
|
+
return `${label}: requests=${req} (err=${err}) tokens in/out/total=${inTok}/${outTok}/${totTok}`;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
async function refreshTokens() {
|
|
1212
|
+
const body = $("tokensTbody");
|
|
1213
|
+
body.replaceChildren();
|
|
1214
|
+
$("tokenTotalsBox").textContent = "";
|
|
1215
|
+
try {
|
|
1216
|
+
const out = await apiFetch("/daemon/stats");
|
|
1217
|
+
const session = out && out.session ? out.session : null;
|
|
1218
|
+
const historical = out && out.historical ? out.historical : null;
|
|
1219
|
+
const totals = out && out.totals ? out.totals : null;
|
|
1220
|
+
|
|
1221
|
+
const sessionTotals = totals && totals.session ? totals.session : { requestCount: 0, errorCount: 0, totalPromptTokens: 0, totalCompletionTokens: 0, totalOutputTokens: 0 };
|
|
1222
|
+
const historicalTotals = totals && totals.historical ? totals.historical : { requestCount: 0, errorCount: 0, totalPromptTokens: 0, totalCompletionTokens: 0, totalOutputTokens: 0 };
|
|
1223
|
+
|
|
1224
|
+
const lines = [];
|
|
1225
|
+
lines.push(formatTokensRow("ALL (session)", sessionTotals));
|
|
1226
|
+
lines.push(formatTokensRow("ALL (historical)", historicalTotals));
|
|
1227
|
+
$("tokenTotalsBox").textContent = lines.join("\n");
|
|
1228
|
+
|
|
1229
|
+
const sessionRows = session && Array.isArray(session.totals) ? session.totals : [];
|
|
1230
|
+
const histRows = historical && Array.isArray(historical.totals) ? historical.totals : [];
|
|
1231
|
+
|
|
1232
|
+
const byKey = new Map();
|
|
1233
|
+
const keyOf = (r) => `${textOf(r.providerKey)}|${textOf(r.model || "")}`;
|
|
1234
|
+
for (const r of sessionRows) {
|
|
1235
|
+
if (!r || !r.providerKey) continue;
|
|
1236
|
+
byKey.set(keyOf(r), { providerKey: textOf(r.providerKey), model: textOf(r.model || ""), session: r, historical: null });
|
|
1237
|
+
}
|
|
1238
|
+
for (const r of histRows) {
|
|
1239
|
+
if (!r || !r.providerKey) continue;
|
|
1240
|
+
const k = keyOf(r);
|
|
1241
|
+
const existing = byKey.get(k) || { providerKey: textOf(r.providerKey), model: textOf(r.model || ""), session: null, historical: null };
|
|
1242
|
+
existing.historical = r;
|
|
1243
|
+
byKey.set(k, existing);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const rows = Array.from(byKey.values()).sort((a, b) => {
|
|
1247
|
+
const ak = `${a.providerKey}.${a.model}`;
|
|
1248
|
+
const bk = `${b.providerKey}.${b.model}`;
|
|
1249
|
+
return ak.localeCompare(bk);
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
for (const row of rows) {
|
|
1253
|
+
const tr = document.createElement("tr");
|
|
1254
|
+
tr.appendChild(createCell("td", row.providerKey, "mono truncate", { title: true }));
|
|
1255
|
+
tr.appendChild(createCell("td", row.model || "—", "mono truncate", { title: true }));
|
|
1256
|
+
|
|
1257
|
+
const s = row.session;
|
|
1258
|
+
const sReqErr = s ? `${formatInt(s.requestCount)} / ${formatInt(s.errorCount)}` : "—";
|
|
1259
|
+
const sTok = s ? `${formatInt(s.totalPromptTokens)}/${formatInt(s.totalCompletionTokens)}/${formatInt(s.totalOutputTokens)}` : "—";
|
|
1260
|
+
tr.appendChild(createCell("td", sReqErr, "mono"));
|
|
1261
|
+
tr.appendChild(createCell("td", sTok, "mono"));
|
|
1262
|
+
|
|
1263
|
+
const h = row.historical;
|
|
1264
|
+
const hReqErr = h ? `${formatInt(h.requestCount)} / ${formatInt(h.errorCount)}` : "—";
|
|
1265
|
+
const hTok = h ? `${formatInt(h.totalPromptTokens)}/${formatInt(h.totalCompletionTokens)}/${formatInt(h.totalOutputTokens)}` : "—";
|
|
1266
|
+
tr.appendChild(createCell("td", hReqErr, "mono"));
|
|
1267
|
+
tr.appendChild(createCell("td", hTok, "mono"));
|
|
1268
|
+
|
|
880
1269
|
body.appendChild(tr);
|
|
881
1270
|
}
|
|
882
1271
|
} catch (e) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1272
|
+
body.appendChild(createErrorRow(6, e && e.message ? e.message : e));
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
async function testProviderFromPool(providerId) {
|
|
1277
|
+
setLog("providerOpLog", "");
|
|
1278
|
+
try {
|
|
1279
|
+
const detail = await apiFetch(`/config/providers/${encodeURIComponent(providerId)}`);
|
|
1280
|
+
const provider = detail && detail.provider ? detail.provider : null;
|
|
1281
|
+
const models = provider && provider.models && typeof provider.models === "object" ? Object.keys(provider.models) : [];
|
|
1282
|
+
if (!models.length) {
|
|
1283
|
+
throw new Error("No models configured for this provider");
|
|
1284
|
+
}
|
|
1285
|
+
const modelId = models[0];
|
|
1286
|
+
const directModel = `${providerId}.${modelId}`;
|
|
1287
|
+
const payload = { model: directModel, input: [{ role: "user", content: "ping" }], stream: false };
|
|
1288
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
1289
|
+
const apiKey = getApiKey();
|
|
1290
|
+
if (apiKey) headers.set("x-api-key", apiKey);
|
|
1291
|
+
const started = Date.now();
|
|
1292
|
+
const resp = await fetch("/v1/responses", { method: "POST", headers, body: JSON.stringify(payload) });
|
|
1293
|
+
const text = await resp.text();
|
|
1294
|
+
const ms = Date.now() - started;
|
|
1295
|
+
if (!resp.ok) {
|
|
1296
|
+
throw new Error(`HTTP ${resp.status} (${ms}ms): ${text}`);
|
|
1297
|
+
}
|
|
1298
|
+
let json = null;
|
|
1299
|
+
try { json = text ? JSON.parse(text) : null; } catch { json = null; }
|
|
1300
|
+
const summary =
|
|
1301
|
+
json && typeof json.output_text === "string"
|
|
1302
|
+
? json.output_text.slice(0, 200)
|
|
1303
|
+
: json && Array.isArray(json.output)
|
|
1304
|
+
? "(output items=" + json.output.length + ")"
|
|
1305
|
+
: "(ok)";
|
|
1306
|
+
setLog("providerOpLog", `Test OK (${ms}ms) model=${directModel}\n${summary}`);
|
|
1307
|
+
} catch (e) {
|
|
1308
|
+
setLog("providerOpLog", `Test failed: ${e && e.message ? e.message : e}`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
async function quotaAction(kind, providerKey) {
|
|
1313
|
+
setLog("quotaOpLog", "");
|
|
1314
|
+
if (!providerKey) return;
|
|
1315
|
+
try {
|
|
1316
|
+
if (kind === "recover") {
|
|
1317
|
+
await apiFetch(`/quota/providers/${encodeURIComponent(providerKey)}/recover`, { method: "POST" });
|
|
1318
|
+
await refreshQuota();
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
if (kind === "reset") {
|
|
1322
|
+
await apiFetch(`/quota/providers/${encodeURIComponent(providerKey)}/reset`, { method: "POST" });
|
|
1323
|
+
await refreshQuota();
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (kind === "disable") {
|
|
1327
|
+
const minutesRaw = prompt("Disable duration (minutes)", "60");
|
|
1328
|
+
if (!minutesRaw) return;
|
|
1329
|
+
const minutes = Number.parseFloat(minutesRaw);
|
|
1330
|
+
if (!Number.isFinite(minutes) || minutes <= 0) {
|
|
1331
|
+
throw new Error("Invalid minutes");
|
|
1332
|
+
}
|
|
1333
|
+
const modeRaw = prompt("Mode: cooldown or blacklist", "cooldown");
|
|
1334
|
+
const mode = (modeRaw || "cooldown").trim().toLowerCase() === "blacklist" ? "blacklist" : "cooldown";
|
|
1335
|
+
await apiFetch(`/quota/providers/${encodeURIComponent(providerKey)}/disable`, {
|
|
1336
|
+
method: "POST",
|
|
1337
|
+
body: JSON.stringify({ mode, durationMinutes: minutes })
|
|
1338
|
+
});
|
|
1339
|
+
await refreshQuota();
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
} catch (e) {
|
|
1343
|
+
setLog("quotaOpLog", `Action failed: ${e && e.message ? e.message : e}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
async function resetQuotaModule() {
|
|
1348
|
+
setLog("quotaOpLog", "");
|
|
1349
|
+
if (!confirm("Reset provider-quota module now? This clears cooldown/blacklist state.")) return;
|
|
1350
|
+
try {
|
|
1351
|
+
const out = await apiFetch("/daemon/modules/provider-quota/reset", { method: "POST" });
|
|
1352
|
+
setLog("quotaOpLog", `OK. resetAt=${out.resetAt || "—"}`);
|
|
1353
|
+
await refreshQuota();
|
|
1354
|
+
} catch (e) {
|
|
1355
|
+
setLog("quotaOpLog", `Reset failed: ${e.message}`);
|
|
886
1356
|
}
|
|
887
1357
|
}
|
|
888
1358
|
|
|
@@ -895,14 +1365,40 @@
|
|
|
895
1365
|
const value = ($("apiKeyInput").value || "").trim();
|
|
896
1366
|
setApiKey(value);
|
|
897
1367
|
$("apiKeyHint").textContent = value ? "saved (session only)" : "";
|
|
1368
|
+
Promise.resolve()
|
|
1369
|
+
.then(refreshStatus)
|
|
1370
|
+
.then(() => selectTab(getActiveTab()))
|
|
1371
|
+
.catch(() => {});
|
|
898
1372
|
});
|
|
899
1373
|
$("clearApiKeyBtn").addEventListener("click", () => {
|
|
900
1374
|
setApiKey("");
|
|
901
1375
|
$("apiKeyInput").value = "";
|
|
902
1376
|
$("apiKeyHint").textContent = "";
|
|
1377
|
+
Promise.resolve()
|
|
1378
|
+
.then(refreshStatus)
|
|
1379
|
+
.then(() => selectTab(getActiveTab()))
|
|
1380
|
+
.catch(() => {});
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
$("restartRuntimeBtn").addEventListener("click", async () => {
|
|
1384
|
+
setLog("providerOpLog", "");
|
|
1385
|
+
if (!confirm("Reload config from disk and rebuild runtime now?")) return;
|
|
1386
|
+
try {
|
|
1387
|
+
const out = await apiFetch("/daemon/restart", { method: "POST" });
|
|
1388
|
+
const warnings = Array.isArray(out.warnings) && out.warnings.length ? `\nWarnings:\n- ${out.warnings.join("\n- ")}` : "";
|
|
1389
|
+
setLog("providerOpLog", `Restarted.\nconfigPath: ${out.configPath || "—"}\nreloadedAt: ${out.reloadedAt || "—"}${warnings}`);
|
|
1390
|
+
await refreshStatus();
|
|
1391
|
+
await refreshProviders();
|
|
1392
|
+
await refreshCredentials();
|
|
1393
|
+
await refreshQuota();
|
|
1394
|
+
await refreshRuntimes();
|
|
1395
|
+
} catch (e) {
|
|
1396
|
+
setLog("providerOpLog", `Restart failed: ${e.message}`);
|
|
1397
|
+
}
|
|
903
1398
|
});
|
|
904
1399
|
|
|
905
1400
|
$("refreshProvidersBtn").addEventListener("click", refreshProviders);
|
|
1401
|
+
$("refreshTokensBtn").addEventListener("click", refreshTokens);
|
|
906
1402
|
$("newProviderBtn").addEventListener("click", () => {
|
|
907
1403
|
$("providerEditorTitle").textContent = "Provider editor (new)";
|
|
908
1404
|
$("providerIdInput").value = "";
|
|
@@ -914,6 +1410,10 @@
|
|
|
914
1410
|
if (!btn) return;
|
|
915
1411
|
const id = btn.getAttribute("data-id");
|
|
916
1412
|
const action = btn.getAttribute("data-action");
|
|
1413
|
+
if (action === "test" && id) {
|
|
1414
|
+
await testProviderFromPool(id);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
917
1417
|
if (action === "edit") await loadProvider(id);
|
|
918
1418
|
if (action === "delete") await deleteProvider(id);
|
|
919
1419
|
});
|
|
@@ -936,6 +1436,18 @@
|
|
|
936
1436
|
$("saveOauthBrowserBtn").addEventListener("click", saveSettings);
|
|
937
1437
|
$("oauthAuthorizeBtn").addEventListener("click", authorizeOauth);
|
|
938
1438
|
|
|
1439
|
+
$("refreshQuotaBtn").addEventListener("click", refreshQuota);
|
|
1440
|
+
$("quotaTbody").addEventListener("click", (ev) => {
|
|
1441
|
+
const el = ev.target;
|
|
1442
|
+
if (!el || el.tagName !== "BUTTON") return;
|
|
1443
|
+
const action = el.getAttribute("data-action");
|
|
1444
|
+
const key = el.getAttribute("data-key");
|
|
1445
|
+
if (action === "quota-recover") void quotaAction("recover", key);
|
|
1446
|
+
else if (action === "quota-reset") void quotaAction("reset", key);
|
|
1447
|
+
else if (action === "quota-disable") void quotaAction("disable", key);
|
|
1448
|
+
});
|
|
1449
|
+
$("resetQuotaBtn").addEventListener("click", resetQuotaModule);
|
|
1450
|
+
|
|
939
1451
|
$("loadRoutingBtn").addEventListener("click", loadRouting);
|
|
940
1452
|
$("saveRoutingBtn").addEventListener("click", saveRouting);
|
|
941
1453
|
$("refreshRuntimesBtn").addEventListener("click", refreshRuntimes);
|
|
@@ -950,9 +1462,10 @@
|
|
|
950
1462
|
await refreshStatus();
|
|
951
1463
|
await refreshProviders();
|
|
952
1464
|
await refreshCredentials();
|
|
1465
|
+
await refreshQuota();
|
|
1466
|
+
await refreshRuntimes();
|
|
953
1467
|
await loadSettings();
|
|
954
1468
|
})();
|
|
955
1469
|
</script>
|
|
956
1470
|
</body>
|
|
957
1471
|
</html>
|
|
958
|
-
|