agentbridge-cli 0.1.11__py3-none-any.whl

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.
@@ -0,0 +1,900 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}agentbridge monitor{% endblock %}
4
+
5
+ {% block head_scripts %}
6
+ <script src="https://unpkg.com/htmx.org@2.0.4"></script>
7
+ <script src="https://unpkg.com/htmx-ext-sse@2.2.4/sse.js"></script>
8
+ {% endblock %}
9
+
10
+ {% block page_styles %}
11
+ :root {
12
+ --dim: #929b9b;
13
+ --accent-soft: rgba(22, 133, 117, 0.08);
14
+ --danger-soft: #fff1f0;
15
+ --warn-soft: #fff7ed;
16
+ --blue: #286bd1;
17
+ --shadow: 0 14px 40px rgba(18, 27, 27, 0.06);
18
+ --sidebar-width: 360px;
19
+ --topbar-bg: rgba(255, 255, 255, 0.94);
20
+ }
21
+
22
+ .app {
23
+ grid-template-rows: var(--header-height) 1fr 32px;
24
+ }
25
+
26
+ .refresh-button,
27
+ .action-button {
28
+ height: 36px;
29
+ border: 1px solid var(--border);
30
+ border-radius: 7px;
31
+ background: var(--surface);
32
+ color: var(--muted);
33
+ display: inline-flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ gap: 7px;
37
+ padding: 0 11px;
38
+ text-decoration: none;
39
+ cursor: pointer;
40
+ }
41
+
42
+ .refresh-button:hover,
43
+ .action-button:hover {
44
+ color: var(--text);
45
+ background: var(--surface-muted);
46
+ }
47
+
48
+ .dashboard {
49
+ min-height: 0;
50
+ display: grid;
51
+ grid-template-columns: var(--sidebar-width) 1fr;
52
+ overflow: hidden;
53
+ border-bottom: 1px solid var(--border);
54
+ }
55
+
56
+ .sidebar {
57
+ min-width: 0;
58
+ background: var(--surface);
59
+ border-right: 1px solid var(--border);
60
+ display: flex;
61
+ flex-direction: column;
62
+ overflow: hidden;
63
+ }
64
+
65
+ .filter-wrap {
66
+ padding: 18px 16px 14px;
67
+ border-bottom: 1px solid var(--border);
68
+ }
69
+
70
+ .filter-field {
71
+ height: 42px;
72
+ border: 1px solid var(--border);
73
+ border-radius: 7px;
74
+ display: grid;
75
+ grid-template-columns: auto 1fr auto;
76
+ align-items: center;
77
+ gap: 8px;
78
+ padding: 0 10px;
79
+ background: var(--surface);
80
+ }
81
+
82
+ .filter-field svg {
83
+ color: var(--muted);
84
+ }
85
+
86
+ #request-filter {
87
+ width: 100%;
88
+ border: 0;
89
+ outline: 0;
90
+ color: var(--text);
91
+ background: transparent;
92
+ font-size: 14px;
93
+ }
94
+
95
+ #request-filter::placeholder {
96
+ color: var(--muted);
97
+ }
98
+
99
+ .kbd {
100
+ min-width: 32px;
101
+ height: 22px;
102
+ border: 1px solid var(--border);
103
+ border-radius: 5px;
104
+ background: var(--surface-muted);
105
+ color: var(--muted);
106
+ display: inline-flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ font-family: var(--mono);
110
+ font-size: 11px;
111
+ }
112
+
113
+ #request-list {
114
+ flex: 1;
115
+ min-height: 0;
116
+ overflow-y: auto;
117
+ padding: 14px 10px 18px;
118
+ }
119
+
120
+ .request-list-title {
121
+ margin: 0 2px 10px;
122
+ color: var(--text);
123
+ font-size: 14px;
124
+ font-weight: 700;
125
+ }
126
+
127
+ .request-group {
128
+ margin-top: 16px;
129
+ }
130
+
131
+ .request-group:first-of-type {
132
+ margin-top: 0;
133
+ }
134
+
135
+ .request-group-title {
136
+ margin: 0 2px 8px;
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 7px;
140
+ color: var(--text);
141
+ font-size: 13px;
142
+ font-weight: 650;
143
+ }
144
+
145
+ .request-group-title svg {
146
+ width: 14px;
147
+ height: 14px;
148
+ color: var(--muted);
149
+ }
150
+
151
+ .request-row {
152
+ width: 100%;
153
+ min-height: 62px;
154
+ display: grid;
155
+ grid-template-columns: 1fr auto;
156
+ gap: 10px;
157
+ align-items: center;
158
+ padding: 9px 10px;
159
+ border: 1px solid transparent;
160
+ border-left: 3px solid transparent;
161
+ border-radius: 7px;
162
+ color: var(--text);
163
+ cursor: pointer;
164
+ transition: background 0.12s ease, border-color 0.12s ease;
165
+ }
166
+
167
+ .request-row:hover {
168
+ background: var(--surface-muted);
169
+ }
170
+
171
+ .request-row.active,
172
+ .request-row-active {
173
+ background: var(--accent-soft);
174
+ border-color: rgba(22, 133, 117, 0.22);
175
+ border-left-color: var(--accent);
176
+ }
177
+
178
+ .request-main {
179
+ min-width: 0;
180
+ }
181
+
182
+ .request-title {
183
+ min-width: 0;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 8px;
187
+ font-weight: 700;
188
+ }
189
+
190
+ .request-id {
191
+ min-width: 0;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ white-space: nowrap;
195
+ font-family: var(--mono);
196
+ font-size: 13px;
197
+ }
198
+
199
+ .request-model {
200
+ margin-top: 4px;
201
+ color: var(--muted);
202
+ font-size: 12px;
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
205
+ white-space: nowrap;
206
+ }
207
+
208
+ .request-metrics {
209
+ color: var(--muted);
210
+ font-family: var(--mono);
211
+ font-size: 12px;
212
+ text-align: right;
213
+ line-height: 1.55;
214
+ white-space: nowrap;
215
+ }
216
+
217
+ .status-dot {
218
+ flex: none;
219
+ width: 8px;
220
+ height: 8px;
221
+ border-radius: 50%;
222
+ background: var(--dim);
223
+ }
224
+
225
+ .status-dot.streaming {
226
+ border: 2px solid #2186ff;
227
+ border-top-color: transparent;
228
+ background: transparent;
229
+ animation: spin 1s linear infinite;
230
+ }
231
+
232
+ .status-dot.error {
233
+ background: #df2f27;
234
+ }
235
+
236
+ .status-dot.idle {
237
+ background: var(--ok);
238
+ }
239
+
240
+ .badge {
241
+ flex: none;
242
+ border-radius: 5px;
243
+ padding: 3px 7px;
244
+ font-size: 11px;
245
+ font-weight: 700;
246
+ line-height: 1;
247
+ text-transform: uppercase;
248
+ letter-spacing: 0;
249
+ }
250
+
251
+ .badge-ok {
252
+ color: var(--accent-strong);
253
+ background: rgba(22, 133, 117, 0.08);
254
+ border: 1px solid rgba(22, 133, 117, 0.22);
255
+ }
256
+
257
+ .badge-err {
258
+ color: var(--danger);
259
+ background: rgba(180, 35, 24, 0.08);
260
+ border: 1px solid rgba(180, 35, 24, 0.18);
261
+ }
262
+
263
+ .badge-active {
264
+ color: #126bd8;
265
+ background: rgba(33, 134, 255, 0.08);
266
+ border: 1px solid rgba(33, 134, 255, 0.18);
267
+ }
268
+
269
+ .badge-model {
270
+ color: var(--muted);
271
+ background: var(--surface-muted);
272
+ border: 1px solid var(--border);
273
+ text-transform: none;
274
+ font-weight: 550;
275
+ }
276
+
277
+ .detail-panel {
278
+ min-width: 0;
279
+ overflow-y: auto;
280
+ padding: 22px 20px 20px;
281
+ }
282
+
283
+ .detail-empty {
284
+ height: 100%;
285
+ display: grid;
286
+ place-items: center;
287
+ color: var(--muted);
288
+ text-align: center;
289
+ }
290
+
291
+ .detail-empty h3 {
292
+ margin: 0 0 6px;
293
+ color: var(--text);
294
+ font-size: 18px;
295
+ }
296
+
297
+ .detail-shell {
298
+ display: flex;
299
+ flex-direction: column;
300
+ gap: 18px;
301
+ }
302
+
303
+ .detail-header {
304
+ display: grid;
305
+ grid-template-columns: auto 1fr auto;
306
+ align-items: center;
307
+ gap: 14px;
308
+ }
309
+
310
+ .request-icon {
311
+ width: 42px;
312
+ height: 42px;
313
+ border: 1px solid var(--border);
314
+ border-radius: 8px;
315
+ background: var(--surface);
316
+ display: grid;
317
+ place-items: center;
318
+ color: var(--text);
319
+ }
320
+
321
+ .detail-heading {
322
+ min-width: 0;
323
+ }
324
+
325
+ .detail-heading h3 {
326
+ margin: 0;
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 10px;
330
+ color: var(--text);
331
+ font-family: var(--mono);
332
+ font-size: 18px;
333
+ line-height: 1.3;
334
+ overflow-wrap: anywhere;
335
+ }
336
+
337
+ .detail-subline {
338
+ margin-top: 6px;
339
+ color: var(--muted);
340
+ display: flex;
341
+ flex-wrap: wrap;
342
+ gap: 12px;
343
+ font-size: 13px;
344
+ }
345
+
346
+ .detail-actions {
347
+ display: flex;
348
+ flex-wrap: wrap;
349
+ justify-content: flex-end;
350
+ gap: 8px;
351
+ }
352
+
353
+ .meta-grid {
354
+ display: grid;
355
+ grid-template-columns: 1fr 1.45fr 1.45fr 0.9fr 1fr 1.05fr 0.95fr;
356
+ gap: 0;
357
+ border: 1px solid var(--border);
358
+ border-radius: 8px;
359
+ background: var(--surface);
360
+ overflow: hidden;
361
+ }
362
+
363
+ .meta-item {
364
+ min-width: 0;
365
+ padding: 15px 16px;
366
+ border-right: 1px solid var(--border);
367
+ }
368
+
369
+ .meta-item:last-child {
370
+ border-right: 0;
371
+ }
372
+
373
+ .meta-label {
374
+ color: var(--muted);
375
+ font-size: 12px;
376
+ margin-bottom: 7px;
377
+ }
378
+
379
+ .meta-value {
380
+ color: var(--text);
381
+ font-size: 14px;
382
+ overflow-wrap: anywhere;
383
+ }
384
+
385
+ .detail-grid {
386
+ display: grid;
387
+ grid-template-columns: minmax(320px, 0.9fr) minmax(360px, 1.1fr);
388
+ gap: 12px;
389
+ align-items: start;
390
+ }
391
+
392
+ .panel {
393
+ min-width: 0;
394
+ border: 1px solid var(--border);
395
+ border-radius: 8px;
396
+ background: var(--surface);
397
+ overflow: hidden;
398
+ }
399
+
400
+ .panel-header {
401
+ min-height: 50px;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: space-between;
405
+ gap: 12px;
406
+ border-bottom: 1px solid var(--border);
407
+ padding: 0 16px;
408
+ }
409
+
410
+ .panel-title {
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 8px;
414
+ color: var(--text);
415
+ font-weight: 700;
416
+ }
417
+
418
+ .count-pill {
419
+ min-width: 22px;
420
+ height: 22px;
421
+ display: inline-flex;
422
+ align-items: center;
423
+ justify-content: center;
424
+ border-radius: 6px;
425
+ background: var(--surface-muted);
426
+ color: var(--muted);
427
+ font-size: 12px;
428
+ font-weight: 650;
429
+ }
430
+
431
+ .panel-body {
432
+ padding: 14px;
433
+ }
434
+
435
+ .message {
436
+ border: 1px solid var(--border);
437
+ border-radius: 8px;
438
+ background: var(--surface);
439
+ margin: 0 0 12px;
440
+ overflow: hidden;
441
+ }
442
+
443
+ .message:last-child {
444
+ margin-bottom: 0;
445
+ }
446
+
447
+ .message summary {
448
+ cursor: pointer;
449
+ list-style: none;
450
+ display: flex;
451
+ align-items: center;
452
+ justify-content: space-between;
453
+ gap: 10px;
454
+ padding: 12px 14px;
455
+ color: var(--accent-strong);
456
+ font-size: 12px;
457
+ font-weight: 750;
458
+ text-transform: uppercase;
459
+ }
460
+
461
+ .message summary::-webkit-details-marker {
462
+ display: none;
463
+ }
464
+
465
+ .message pre,
466
+ .response-pre,
467
+ #stream-output,
468
+ .error-pre {
469
+ margin: 0;
470
+ white-space: pre-wrap;
471
+ word-break: break-word;
472
+ font-family: var(--mono);
473
+ font-size: 12px;
474
+ line-height: 1.55;
475
+ color: #243030;
476
+ background: #fbfbfb;
477
+ border: 1px solid var(--border);
478
+ border-radius: 7px;
479
+ padding: 12px;
480
+ max-height: 320px;
481
+ overflow: auto;
482
+ }
483
+
484
+ .message pre {
485
+ margin: 0 14px 14px;
486
+ }
487
+
488
+ .live-stream {
489
+ margin-top: 14px;
490
+ border-top: 1px solid var(--border);
491
+ padding-top: 14px;
492
+ }
493
+
494
+ .live-stream-title {
495
+ display: flex;
496
+ align-items: center;
497
+ gap: 8px;
498
+ color: var(--text);
499
+ font-weight: 700;
500
+ margin-bottom: 8px;
501
+ }
502
+
503
+ .error-card {
504
+ border: 1px solid #f04438;
505
+ border-radius: 8px;
506
+ background: var(--danger-soft);
507
+ padding: 16px;
508
+ min-height: 410px;
509
+ }
510
+
511
+ .error-card-header {
512
+ display: flex;
513
+ align-items: flex-start;
514
+ justify-content: space-between;
515
+ gap: 14px;
516
+ margin-bottom: 14px;
517
+ }
518
+
519
+ .error-title {
520
+ display: flex;
521
+ gap: 10px;
522
+ color: var(--danger);
523
+ font-weight: 750;
524
+ font-size: 16px;
525
+ }
526
+
527
+ .error-title small {
528
+ display: block;
529
+ margin-top: 4px;
530
+ color: var(--text);
531
+ font-weight: 500;
532
+ }
533
+
534
+ .error-actions {
535
+ display: flex;
536
+ gap: 8px;
537
+ }
538
+
539
+ .error-pre {
540
+ border-color: var(--danger-border);
541
+ background: rgba(255, 255, 255, 0.64);
542
+ max-height: 430px;
543
+ }
544
+
545
+ .empty-state {
546
+ margin: 0;
547
+ color: var(--muted);
548
+ font-size: 13px;
549
+ }
550
+
551
+ .json-key { color: var(--blue); }
552
+ .json-string { color: var(--accent-strong); }
553
+ .json-number { color: var(--warn); }
554
+ .json-bool { color: #7c3aed; }
555
+ .json-null { color: var(--dim); }
556
+ .json-brace { color: var(--muted); }
557
+ .json-colon { color: var(--dim); }
558
+
559
+ .footer-status {
560
+ height: 32px;
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 8px;
564
+ padding: 0 20px;
565
+ color: var(--muted);
566
+ background: var(--surface);
567
+ font-size: 13px;
568
+ }
569
+
570
+ .footer-status .status-dot {
571
+ background: var(--ok);
572
+ }
573
+
574
+ .pool-status {
575
+ height: 42px;
576
+ display: inline-flex;
577
+ align-items: center;
578
+ gap: 12px;
579
+ border: 1px solid var(--border);
580
+ border-radius: 7px;
581
+ background: var(--surface);
582
+ padding: 0 12px;
583
+ }
584
+
585
+ .pool-health {
586
+ height: 28px;
587
+ display: inline-flex;
588
+ align-items: center;
589
+ gap: 8px;
590
+ border: 1px solid rgba(22, 133, 117, 0.18);
591
+ border-radius: 6px;
592
+ background: rgba(22, 133, 117, 0.06);
593
+ color: var(--accent-strong);
594
+ padding: 0 9px;
595
+ font-weight: 650;
596
+ }
597
+
598
+ .pool-health.error {
599
+ border-color: var(--danger-border);
600
+ background: var(--danger-soft);
601
+ color: var(--danger);
602
+ }
603
+
604
+ .pool-dot {
605
+ flex: none;
606
+ width: 8px;
607
+ height: 8px;
608
+ border-radius: 50%;
609
+ background: var(--border);
610
+ }
611
+
612
+ .pool-dot.available { background: var(--ok); }
613
+ .pool-dot.empty { background: var(--border); }
614
+ .pool-count { color: var(--text); white-space: nowrap; }
615
+
616
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
617
+ ::-webkit-scrollbar-track { background: transparent; }
618
+ ::-webkit-scrollbar-thumb { background: #ccd5d5; border-radius: 4px; }
619
+ ::-webkit-scrollbar-thumb:hover { background: #aebaba; }
620
+
621
+ @keyframes spin {
622
+ to { transform: rotate(360deg); }
623
+ }
624
+
625
+ @keyframes fadeIn {
626
+ from { opacity: 0; transform: translateY(2px); }
627
+ to { opacity: 1; transform: translateY(0); }
628
+ }
629
+
630
+ .detail-panel > * {
631
+ animation: fadeIn 0.15s ease-out;
632
+ }
633
+
634
+ @media (max-width: 980px) {
635
+ :root { --sidebar-width: 300px; }
636
+ header {
637
+ grid-template-columns: 1fr;
638
+ height: auto;
639
+ min-height: var(--header-height);
640
+ padding: 12px 14px;
641
+ }
642
+ .app {
643
+ grid-template-rows: auto 1fr 32px;
644
+ }
645
+ .main-nav,
646
+ .header-actions {
647
+ justify-self: stretch;
648
+ }
649
+ .header-actions {
650
+ justify-content: space-between;
651
+ }
652
+ .meta-grid,
653
+ .detail-grid {
654
+ grid-template-columns: 1fr;
655
+ }
656
+ .meta-item {
657
+ border-right: 0;
658
+ border-bottom: 1px solid var(--border);
659
+ }
660
+ .meta-item:last-child {
661
+ border-bottom: 0;
662
+ }
663
+ }
664
+
665
+ @media (max-width: 760px) {
666
+ html, body { overflow: auto; }
667
+ .app {
668
+ min-height: 100vh;
669
+ height: auto;
670
+ }
671
+ .dashboard {
672
+ grid-template-columns: 1fr;
673
+ }
674
+ .sidebar {
675
+ max-height: 50vh;
676
+ border-right: 0;
677
+ border-bottom: 1px solid var(--border);
678
+ }
679
+ .detail-header {
680
+ grid-template-columns: 1fr;
681
+ }
682
+ .detail-actions {
683
+ justify-content: flex-start;
684
+ }
685
+ .nav-item {
686
+ min-width: 0;
687
+ flex: 1;
688
+ }
689
+ }
690
+ {% endblock %}
691
+
692
+ {% block header_actions %}
693
+ <div hx-ext="sse" sse-connect="/dashboard/pool/stream" sse-swap="message" hx-swap="innerHTML"></div>
694
+ <button class="refresh-button" type="button" onclick="location.reload()">
695
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
696
+ <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
697
+ <path d="M3 21v-5h5"></path>
698
+ <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
699
+ <path d="M16 8h5V3"></path>
700
+ </svg>
701
+ Refresh
702
+ </button>
703
+ {% endblock %}
704
+
705
+ {% block content %}
706
+ <div class="dashboard">
707
+ <aside class="sidebar">
708
+ <div class="filter-wrap">
709
+ <label class="filter-field">
710
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
711
+ <circle cx="11" cy="11" r="8"></circle>
712
+ <path d="m21 21-4.3-4.3"></path>
713
+ </svg>
714
+ <input type="text" id="request-filter" placeholder="Filter requests..." autocomplete="off">
715
+ <span class="kbd">&#8984;K</span>
716
+ </label>
717
+ </div>
718
+ <div id="request-list" hx-ext="sse" sse-connect="/dashboard/requests" sse-swap="message" hx-swap="innerHTML">
719
+ <h3 class="request-list-title">Requests</h3>
720
+ <p class="empty-state">Loading...</p>
721
+ </div>
722
+ </aside>
723
+ <main class="detail-panel" id="detail">
724
+ <div class="detail-empty">
725
+ <div>
726
+ <h3>Request Detail</h3>
727
+ <p class="empty-state">Click a request to view details</p>
728
+ </div>
729
+ </div>
730
+ </main>
731
+ </div>
732
+ <footer class="footer-status">
733
+ <span class="status-dot"></span>
734
+ Live updates via SSE
735
+ </footer>
736
+ {% endblock %}
737
+
738
+ {% block scripts %}
739
+ <script>
740
+ (function() {
741
+ var initialSelectedId = new URLSearchParams(window.location.search).get('request_id');
742
+ var selectedId = /^chatcmpl-[a-f0-9]{8,32}$/.test(initialSelectedId || '') ? initialSelectedId : null;
743
+ var detailLoadedId = null;
744
+ var listEl = document.getElementById('request-list');
745
+ var filterEl = document.getElementById('request-filter');
746
+
747
+ function applyFilter() {
748
+ var term = filterEl.value.toLowerCase();
749
+ listEl.querySelectorAll('.request-row').forEach(function(r) {
750
+ r.style.display = (!term || r.textContent.toLowerCase().indexOf(term) !== -1) ? '' : 'none';
751
+ });
752
+ }
753
+
754
+ filterEl.addEventListener('input', applyFilter);
755
+ document.addEventListener('keydown', function(e) {
756
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
757
+ e.preventDefault();
758
+ filterEl.focus();
759
+ }
760
+ });
761
+
762
+ function findRequestRow(requestId) {
763
+ var rows = listEl.querySelectorAll('.request-row');
764
+ for (var i = 0; i < rows.length; i += 1) {
765
+ if (rows[i].getAttribute('data-request-id') === requestId) return rows[i];
766
+ }
767
+ return null;
768
+ }
769
+
770
+ function markSelected(row, options) {
771
+ options = options || {};
772
+ if (row) selectedId = row.getAttribute('data-request-id');
773
+ if (!selectedId) return;
774
+ listEl.querySelectorAll('.request-row').forEach(function(r) {
775
+ r.classList.toggle('active', r.getAttribute('data-request-id') === selectedId);
776
+ });
777
+ if (row && options.scroll !== false) {
778
+ row.scrollIntoView({ block: 'nearest' });
779
+ }
780
+ }
781
+
782
+ function loadSelectedDetail(options) {
783
+ options = options || {};
784
+ if (!selectedId || !window.htmx) return;
785
+ var row = findRequestRow(selectedId);
786
+ markSelected(row, { scroll: options.scroll });
787
+ if (detailLoadedId !== selectedId) {
788
+ window.htmx.ajax('GET', '/dashboard/request/' + selectedId, '#detail');
789
+ detailLoadedId = selectedId;
790
+ }
791
+ }
792
+
793
+ function updateSelectedUrl() {
794
+ if (!selectedId || !window.history || !window.history.pushState) return;
795
+ var nextUrl = '/dashboard?request_id=' + encodeURIComponent(selectedId);
796
+ if (window.location.pathname + window.location.search !== nextUrl) {
797
+ window.history.pushState({ requestId: selectedId }, '', nextUrl);
798
+ }
799
+ }
800
+
801
+ listEl.addEventListener('click', function(e) {
802
+ var row = e.target.closest('.request-row');
803
+ if (!row) return;
804
+ markSelected(row);
805
+ detailLoadedId = selectedId;
806
+ updateSelectedUrl();
807
+ });
808
+
809
+ window.addEventListener('popstate', function() {
810
+ var nextId = new URLSearchParams(window.location.search).get('request_id');
811
+ selectedId = /^chatcmpl-[a-f0-9]{8,32}$/.test(nextId || '') ? nextId : null;
812
+ detailLoadedId = null;
813
+ if (selectedId) {
814
+ loadSelectedDetail({ scroll: true });
815
+ }
816
+ });
817
+
818
+ document.body.addEventListener('click', function(e) {
819
+ var copyEl = e.target.closest('[data-copy]');
820
+ if (!copyEl) return;
821
+ navigator.clipboard.writeText(copyEl.getAttribute('data-copy') || '');
822
+ });
823
+
824
+ document.body.addEventListener('htmx:sseMessage', function(e) {
825
+ if (!listEl.contains(e.target)) return;
826
+ requestAnimationFrame(function() {
827
+ applyFilter();
828
+ });
829
+ });
830
+
831
+ document.body.addEventListener('htmx:afterSettle', function(e) {
832
+ if (e.detail.target && e.detail.target.id === 'request-list' && selectedId) {
833
+ loadSelectedDetail({ scroll: true });
834
+ } else if (e.detail.target && e.detail.target.id === 'request-list' && !selectedId) {
835
+ var firstRow = listEl.querySelector('.request-row');
836
+ if (firstRow) {
837
+ setTimeout(function() {
838
+ markSelected(firstRow);
839
+ if (window.htmx) {
840
+ window.htmx.ajax('GET', '/dashboard/request/' + selectedId, '#detail');
841
+ }
842
+ }, 20);
843
+ }
844
+ }
845
+ });
846
+
847
+ if (selectedId) {
848
+ loadSelectedDetail({ scroll: true });
849
+ }
850
+ })();
851
+ </script>
852
+ <script>
853
+ (function() {
854
+ function escapeHtml(s) {
855
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
856
+ }
857
+
858
+ function highlightJson(jsonStr) {
859
+ try {
860
+ var obj = JSON.parse(jsonStr);
861
+ var pretty = JSON.stringify(obj, null, 2);
862
+ } catch (e) { return null; }
863
+ return pretty.replace(/("(?:\\.|[^"\\])*")\s*:/g, function(m, key) {
864
+ return '<span class="json-key">' + escapeHtml(key) + '</span><span class="json-colon">:</span>';
865
+ }).replace(/"(?:\\.|[^"\\])*"/g, function(m) {
866
+ return '<span class="json-string">' + escapeHtml(m) + '</span>';
867
+ }).replace(/\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g, '<span class="json-number">$1</span>')
868
+ .replace(/\b(true|false)\b/g, '<span class="json-bool">$1</span>')
869
+ .replace(/\bnull\b/g, '<span class="json-null">null</span>')
870
+ .replace(/([{}\[\]])/g, '<span class="json-brace">$1</span>');
871
+ }
872
+
873
+ function processPreElement(pre) {
874
+ if (pre.classList.contains('json-hl')) return;
875
+ var text = pre.textContent;
876
+ var trimmed = text.trim();
877
+ if ((trimmed[0] === '{' || trimmed[0] === '[') && (trimmed[trimmed.length - 1] === '}' || trimmed[trimmed.length - 1] === ']')) {
878
+ var hl = highlightJson(trimmed);
879
+ if (hl) {
880
+ pre.innerHTML = hl;
881
+ pre.classList.add('json-hl');
882
+ }
883
+ }
884
+ }
885
+
886
+ function highlightAllJson(root) {
887
+ var pres = (root || document.getElementById('detail')).querySelectorAll('pre');
888
+ for (var i = 0; i < pres.length; i++) {
889
+ processPreElement(pres[i]);
890
+ }
891
+ }
892
+
893
+ document.body.addEventListener('htmx:afterSettle', function(e) {
894
+ if (e.detail.target && e.detail.target.id === 'detail') {
895
+ highlightAllJson();
896
+ }
897
+ });
898
+ })();
899
+ </script>
900
+ {% endblock %}