@jsonstudio/rcc 0.89.1121 → 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 +583 -87
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/index.d.ts +19 -1
- package/dist/manager/modules/quota/index.js +109 -1
- package/dist/manager/modules/quota/index.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 +0 -1
- 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/server/handlers/handler-utils.d.ts +1 -1
- package/dist/server/handlers/handler-utils.js +4 -4
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +2 -1
- 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.js +12 -6
- 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 +116 -98
- 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 +108 -15
- package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +2 -1
- package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -1
- 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 +8 -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 +9 -0
- package/dist/server/runtime/http-server/daemon-admin-routes.js +3 -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 +1 -0
- package/dist/server/runtime/http-server/index.js +42 -11
- package/dist/server/runtime/http-server/index.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 +5 -0
- package/dist/server/runtime/http-server/routes.js +9 -0
- 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 +5 -0
- package/dist/server/utils/http-error-mapper.js +70 -9
- 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/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 +3 -3
- package/scripts/scan-apply-patch-samples.mjs +148 -7
|
@@ -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>
|
|
@@ -282,12 +354,14 @@
|
|
|
282
354
|
|
|
283
355
|
<div class="tabs">
|
|
284
356
|
<button class="tab active" data-tab="providers">Provider Pool</button>
|
|
357
|
+
<button class="tab" data-tab="tokens">Token Stats</button>
|
|
285
358
|
<button class="tab" data-tab="credentials">Auth Provider Pool</button>
|
|
359
|
+
<button class="tab" data-tab="quota">Quota Pool</button>
|
|
286
360
|
<button class="tab" data-tab="routing">Runtime Routing Pool</button>
|
|
287
361
|
</div>
|
|
288
362
|
|
|
289
363
|
<section id="panelProviders" data-panel="providers">
|
|
290
|
-
<div class="grid">
|
|
364
|
+
<div class="grid grid-wide-left">
|
|
291
365
|
<div class="card" style="box-shadow: none;">
|
|
292
366
|
<p class="section-title">Providers in <span class="mono">virtualrouter.providers</span></p>
|
|
293
367
|
<p class="section-sub">
|
|
@@ -297,20 +371,23 @@
|
|
|
297
371
|
<button id="refreshProvidersBtn" class="primary">Refresh</button>
|
|
298
372
|
<button id="newProviderBtn">New provider…</button>
|
|
299
373
|
</div>
|
|
300
|
-
<
|
|
301
|
-
<
|
|
302
|
-
<
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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>
|
|
314
391
|
</div>
|
|
315
392
|
|
|
316
393
|
<div class="card" style="box-shadow: none;">
|
|
@@ -414,6 +491,37 @@
|
|
|
414
491
|
</div>
|
|
415
492
|
</section>
|
|
416
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
|
+
|
|
417
525
|
<section id="panelCredentials" data-panel="credentials" style="display:none;">
|
|
418
526
|
<div class="grid">
|
|
419
527
|
<div class="card" style="box-shadow:none;">
|
|
@@ -424,19 +532,21 @@
|
|
|
424
532
|
<div class="row" style="margin-bottom: 10px;">
|
|
425
533
|
<button id="refreshCredentialsBtn" class="primary">Refresh</button>
|
|
426
534
|
</div>
|
|
427
|
-
<
|
|
428
|
-
<
|
|
429
|
-
<
|
|
430
|
-
<
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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>
|
|
440
550
|
</div>
|
|
441
551
|
|
|
442
552
|
<div class="card" style="box-shadow:none;">
|
|
@@ -476,6 +586,52 @@
|
|
|
476
586
|
</div>
|
|
477
587
|
</section>
|
|
478
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
|
+
|
|
479
635
|
<section id="panelRouting" data-panel="routing" style="display:none;">
|
|
480
636
|
<div class="grid">
|
|
481
637
|
<div class="card" style="box-shadow:none;">
|
|
@@ -497,18 +653,20 @@
|
|
|
497
653
|
<div class="row" style="margin-bottom: 10px;">
|
|
498
654
|
<button id="refreshRuntimesBtn" class="primary">Refresh</button>
|
|
499
655
|
</div>
|
|
500
|
-
<
|
|
501
|
-
<
|
|
502
|
-
<
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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>
|
|
512
670
|
</div>
|
|
513
671
|
</div>
|
|
514
672
|
</section>
|
|
@@ -522,7 +680,10 @@
|
|
|
522
680
|
const el = $(id);
|
|
523
681
|
if (!el) return;
|
|
524
682
|
el.style.display = value ? "block" : "none";
|
|
525
|
-
|
|
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;
|
|
526
687
|
}
|
|
527
688
|
|
|
528
689
|
function getApiKey() {
|
|
@@ -571,10 +732,71 @@
|
|
|
571
732
|
});
|
|
572
733
|
const panels = [
|
|
573
734
|
{ name: "providers", el: $("panelProviders") },
|
|
735
|
+
{ name: "tokens", el: $("panelTokens") },
|
|
574
736
|
{ name: "credentials", el: $("panelCredentials") },
|
|
737
|
+
{ name: "quota", el: $("panelQuota") },
|
|
575
738
|
{ name: "routing", el: $("panelRouting") }
|
|
576
739
|
];
|
|
577
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;
|
|
578
800
|
}
|
|
579
801
|
|
|
580
802
|
function presetFor(type) {
|
|
@@ -642,29 +864,67 @@
|
|
|
642
864
|
|
|
643
865
|
async function refreshProviders() {
|
|
644
866
|
const body = $("providersTbody");
|
|
645
|
-
body.
|
|
867
|
+
body.replaceChildren();
|
|
646
868
|
try {
|
|
647
869
|
const data = await apiFetch("/config/providers");
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
`;
|
|
662
|
-
|
|
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
|
+
}
|
|
663
925
|
}
|
|
664
926
|
} catch (e) {
|
|
665
|
-
|
|
666
|
-
tr.innerHTML = `<td colspan="7" class="mono">Failed to load: ${e.message}</td>`;
|
|
667
|
-
body.appendChild(tr);
|
|
927
|
+
body.appendChild(createErrorRow(8, e && e.message ? e.message : e));
|
|
668
928
|
}
|
|
669
929
|
}
|
|
670
930
|
|
|
@@ -777,26 +1037,22 @@
|
|
|
777
1037
|
|
|
778
1038
|
async function refreshCredentials() {
|
|
779
1039
|
const body = $("credentialsTbody");
|
|
780
|
-
body.
|
|
1040
|
+
body.replaceChildren();
|
|
781
1041
|
try {
|
|
782
1042
|
const items = await apiFetch("/daemon/credentials");
|
|
783
1043
|
for (const c of items || []) {
|
|
784
1044
|
const tr = document.createElement("tr");
|
|
785
1045
|
const exp = c.expiresInSec == null ? "—" : `${c.expiresInSec}s`;
|
|
786
|
-
tr.
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
<td class="mono">${c.secretRef || "—"}</td>
|
|
793
|
-
`;
|
|
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"));
|
|
794
1052
|
body.appendChild(tr);
|
|
795
1053
|
}
|
|
796
1054
|
} catch (e) {
|
|
797
|
-
|
|
798
|
-
tr.innerHTML = `<td colspan="6" class="mono">Failed to load: ${e.message}</td>`;
|
|
799
|
-
body.appendChild(tr);
|
|
1055
|
+
body.appendChild(createErrorRow(6, e && e.message ? e.message : e));
|
|
800
1056
|
}
|
|
801
1057
|
}
|
|
802
1058
|
|
|
@@ -867,24 +1123,236 @@
|
|
|
867
1123
|
|
|
868
1124
|
async function refreshRuntimes() {
|
|
869
1125
|
const body = $("runtimesTbody");
|
|
870
|
-
body.
|
|
1126
|
+
body.replaceChildren();
|
|
871
1127
|
try {
|
|
872
1128
|
const items = await apiFetch("/providers/runtimes");
|
|
873
1129
|
for (const r of items || []) {
|
|
874
1130
|
const tr = document.createElement("tr");
|
|
875
|
-
tr.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
<td>${r.series || ""}</td>
|
|
881
|
-
`;
|
|
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 || "", ""));
|
|
882
1136
|
body.appendChild(tr);
|
|
883
1137
|
}
|
|
884
1138
|
} catch (e) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
|
|
1269
|
+
body.appendChild(tr);
|
|
1270
|
+
}
|
|
1271
|
+
} catch (e) {
|
|
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}`);
|
|
888
1356
|
}
|
|
889
1357
|
}
|
|
890
1358
|
|
|
@@ -897,11 +1365,19 @@
|
|
|
897
1365
|
const value = ($("apiKeyInput").value || "").trim();
|
|
898
1366
|
setApiKey(value);
|
|
899
1367
|
$("apiKeyHint").textContent = value ? "saved (session only)" : "";
|
|
1368
|
+
Promise.resolve()
|
|
1369
|
+
.then(refreshStatus)
|
|
1370
|
+
.then(() => selectTab(getActiveTab()))
|
|
1371
|
+
.catch(() => {});
|
|
900
1372
|
});
|
|
901
1373
|
$("clearApiKeyBtn").addEventListener("click", () => {
|
|
902
1374
|
setApiKey("");
|
|
903
1375
|
$("apiKeyInput").value = "";
|
|
904
1376
|
$("apiKeyHint").textContent = "";
|
|
1377
|
+
Promise.resolve()
|
|
1378
|
+
.then(refreshStatus)
|
|
1379
|
+
.then(() => selectTab(getActiveTab()))
|
|
1380
|
+
.catch(() => {});
|
|
905
1381
|
});
|
|
906
1382
|
|
|
907
1383
|
$("restartRuntimeBtn").addEventListener("click", async () => {
|
|
@@ -914,6 +1390,7 @@
|
|
|
914
1390
|
await refreshStatus();
|
|
915
1391
|
await refreshProviders();
|
|
916
1392
|
await refreshCredentials();
|
|
1393
|
+
await refreshQuota();
|
|
917
1394
|
await refreshRuntimes();
|
|
918
1395
|
} catch (e) {
|
|
919
1396
|
setLog("providerOpLog", `Restart failed: ${e.message}`);
|
|
@@ -921,6 +1398,7 @@
|
|
|
921
1398
|
});
|
|
922
1399
|
|
|
923
1400
|
$("refreshProvidersBtn").addEventListener("click", refreshProviders);
|
|
1401
|
+
$("refreshTokensBtn").addEventListener("click", refreshTokens);
|
|
924
1402
|
$("newProviderBtn").addEventListener("click", () => {
|
|
925
1403
|
$("providerEditorTitle").textContent = "Provider editor (new)";
|
|
926
1404
|
$("providerIdInput").value = "";
|
|
@@ -932,6 +1410,10 @@
|
|
|
932
1410
|
if (!btn) return;
|
|
933
1411
|
const id = btn.getAttribute("data-id");
|
|
934
1412
|
const action = btn.getAttribute("data-action");
|
|
1413
|
+
if (action === "test" && id) {
|
|
1414
|
+
await testProviderFromPool(id);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
935
1417
|
if (action === "edit") await loadProvider(id);
|
|
936
1418
|
if (action === "delete") await deleteProvider(id);
|
|
937
1419
|
});
|
|
@@ -954,6 +1436,18 @@
|
|
|
954
1436
|
$("saveOauthBrowserBtn").addEventListener("click", saveSettings);
|
|
955
1437
|
$("oauthAuthorizeBtn").addEventListener("click", authorizeOauth);
|
|
956
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
|
+
|
|
957
1451
|
$("loadRoutingBtn").addEventListener("click", loadRouting);
|
|
958
1452
|
$("saveRoutingBtn").addEventListener("click", saveRouting);
|
|
959
1453
|
$("refreshRuntimesBtn").addEventListener("click", refreshRuntimes);
|
|
@@ -968,6 +1462,8 @@
|
|
|
968
1462
|
await refreshStatus();
|
|
969
1463
|
await refreshProviders();
|
|
970
1464
|
await refreshCredentials();
|
|
1465
|
+
await refreshQuota();
|
|
1466
|
+
await refreshRuntimes();
|
|
971
1467
|
await loadSettings();
|
|
972
1468
|
})();
|
|
973
1469
|
</script>
|