@tonyclaw/llm-inspector 1.16.4 → 1.16.5
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/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-C1w4KUGZ.js +1 -0
- package/.output/public/assets/ReplayDialog-DR2Sgq_g.js +1 -0
- package/.output/public/assets/RequestAnatomy-DAre35kj.js +1 -0
- package/.output/public/assets/ResponseView-ackes7_g.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-GrXwIGKA.js +1 -0
- package/.output/public/assets/index-BGzHFOEX.css +1 -0
- package/.output/public/assets/index-DX88k9br.js +101 -0
- package/.output/public/assets/json-viewer-C_QUhGeu.js +14 -0
- package/.output/public/assets/{main-DbWwVQFh.js → main-CDMdNDY_.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +104 -84
- package/.output/server/_ssr/CompareDrawer-ftkJxyk6.mjs +1040 -0
- package/.output/server/_ssr/ReplayDialog-DcmE3lj5.mjs +321 -0
- package/.output/server/_ssr/RequestAnatomy-rK_LNMdG.mjs +351 -0
- package/.output/server/_ssr/ResponseView-CbQ4n-aJ.mjs +601 -0
- package/.output/server/_ssr/StreamingChunkSequence-84FZkIzv.mjs +301 -0
- package/.output/server/_ssr/{index-C-z-fZtq.mjs → index-CDjLoMsk.mjs} +1026 -2455
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-B-qpM5xC.mjs +510 -0
- package/.output/server/_ssr/{router-CNM9Kbi0.mjs → router-BrdjOUEW.mjs} +24 -14
- package/.output/server/{_tanstack-start-manifest_v-BWfLeIsC.mjs → _tanstack-start-manifest_v-DmOZEcJ3.mjs} +1 -1
- package/.output/server/index.mjs +68 -26
- package/package.json +1 -1
- package/src/components/OnboardingBanner.tsx +2 -2
- package/src/components/ProxyViewer.tsx +38 -26
- package/src/components/ProxyViewerContainer.tsx +3 -24
- package/src/components/proxy-viewer/ConversationGroup.tsx +1 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +4 -1
- package/src/components/proxy-viewer/LogEntry.tsx +213 -181
- package/src/components/proxy-viewer/LogEntryHeader.tsx +134 -36
- package/src/components/proxy-viewer/ThreadConnector.tsx +17 -2
- package/src/components/proxy-viewer/TurnGroup.tsx +94 -71
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
- package/src/components/proxy-viewer/anatomy/types.ts +39 -0
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +3 -23
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +5 -3
- package/src/components/proxy-viewer/lazy.ts +37 -0
- package/src/components/proxy-viewer/log-formats/anthropic.ts +146 -0
- package/src/components/proxy-viewer/log-formats/openai.ts +127 -0
- package/src/components/proxy-viewer/log-formats/types.ts +7 -0
- package/src/components/proxy-viewer/log-formats/unknown.ts +4 -0
- package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
- package/src/components/proxy-viewer/viewerState.ts +8 -0
- package/src/components/ui/crab-variants.tsx +11 -0
- package/src/components/ui/json-expansion-button.tsx +56 -0
- package/src/components/ui/json-viewer-bulk.ts +97 -0
- package/src/components/ui/json-viewer.tsx +58 -183
- package/src/lib/utils.ts +2 -3
- package/src/routes/api/logs.stream.ts +26 -16
- package/.output/public/assets/index-DRRCmu5p.css +0 -1
- package/.output/public/assets/index-X7CHS7fS.js +0 -107
package/.output/server/index.mjs
CHANGED
|
@@ -35,54 +35,96 @@ const headers = ((m) => function headersRouteRule(event) {
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
const assets = {
|
|
38
|
+
"/assets/alibaba-TTwafVwX.svg": {
|
|
39
|
+
"type": "image/svg+xml",
|
|
40
|
+
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
41
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
42
|
+
"size": 5915,
|
|
43
|
+
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
44
|
+
},
|
|
38
45
|
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
39
46
|
"type": "image/jpeg",
|
|
40
47
|
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
41
|
-
"mtime": "2026-06-
|
|
48
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
42
49
|
"size": 6918,
|
|
43
50
|
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
44
51
|
},
|
|
45
|
-
"/assets/
|
|
46
|
-
"type": "
|
|
47
|
-
"etag": '"
|
|
48
|
-
"mtime": "2026-06-
|
|
49
|
-
"size":
|
|
50
|
-
"path": "../public/assets/
|
|
52
|
+
"/assets/CompareDrawer-C1w4KUGZ.js": {
|
|
53
|
+
"type": "text/javascript; charset=utf-8",
|
|
54
|
+
"etag": '"4a10-U7vRVdbG90QCagHyB/PsN2Edv9Y"',
|
|
55
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
56
|
+
"size": 18960,
|
|
57
|
+
"path": "../public/assets/CompareDrawer-C1w4KUGZ.js"
|
|
58
|
+
},
|
|
59
|
+
"/assets/json-viewer-C_QUhGeu.js": {
|
|
60
|
+
"type": "text/javascript; charset=utf-8",
|
|
61
|
+
"etag": '"1e60c-NxhepLRPlTKqcPUc59jhJtOyGpg"',
|
|
62
|
+
"mtime": "2026-06-15T12:23:59.074Z",
|
|
63
|
+
"size": 124428,
|
|
64
|
+
"path": "../public/assets/json-viewer-C_QUhGeu.js"
|
|
51
65
|
},
|
|
52
|
-
"/assets/index-
|
|
66
|
+
"/assets/index-BGzHFOEX.css": {
|
|
53
67
|
"type": "text/css; charset=utf-8",
|
|
54
|
-
"etag": '"
|
|
55
|
-
"mtime": "2026-06-
|
|
56
|
-
"size":
|
|
57
|
-
"path": "../public/assets/index-
|
|
68
|
+
"etag": '"16d02-Bx7KW/5wmN1RfMrbWqhaqF7lGHk"',
|
|
69
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
70
|
+
"size": 93442,
|
|
71
|
+
"path": "../public/assets/index-BGzHFOEX.css"
|
|
72
|
+
},
|
|
73
|
+
"/assets/ReplayDialog-DR2Sgq_g.js": {
|
|
74
|
+
"type": "text/javascript; charset=utf-8",
|
|
75
|
+
"etag": '"11b1-xYgR1cdDnesImArvyIDX3s2xRMU"',
|
|
76
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
77
|
+
"size": 4529,
|
|
78
|
+
"path": "../public/assets/ReplayDialog-DR2Sgq_g.js"
|
|
58
79
|
},
|
|
59
|
-
"/assets/
|
|
80
|
+
"/assets/ResponseView-ackes7_g.js": {
|
|
60
81
|
"type": "text/javascript; charset=utf-8",
|
|
61
|
-
"etag": '"
|
|
62
|
-
"mtime": "2026-06-
|
|
63
|
-
"size":
|
|
64
|
-
"path": "../public/assets/
|
|
82
|
+
"etag": '"6e82-UP1WD/TqChwiuI7DV8LDxr8mcoI"',
|
|
83
|
+
"mtime": "2026-06-15T12:23:59.074Z",
|
|
84
|
+
"size": 28290,
|
|
85
|
+
"path": "../public/assets/ResponseView-ackes7_g.js"
|
|
65
86
|
},
|
|
66
87
|
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
67
88
|
"type": "image/svg+xml",
|
|
68
89
|
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
69
|
-
"mtime": "2026-06-
|
|
90
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
70
91
|
"size": 11256,
|
|
71
92
|
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
72
93
|
},
|
|
94
|
+
"/assets/RequestAnatomy-DAre35kj.js": {
|
|
95
|
+
"type": "text/javascript; charset=utf-8",
|
|
96
|
+
"etag": '"141b-+jsq3YAE5FysxZARhUPCzQUpXJE"',
|
|
97
|
+
"mtime": "2026-06-15T12:23:59.074Z",
|
|
98
|
+
"size": 5147,
|
|
99
|
+
"path": "../public/assets/RequestAnatomy-DAre35kj.js"
|
|
100
|
+
},
|
|
101
|
+
"/assets/main-CDMdNDY_.js": {
|
|
102
|
+
"type": "text/javascript; charset=utf-8",
|
|
103
|
+
"etag": '"505ae-1nSD340FKDRNgunwXT0ErZchYkU"',
|
|
104
|
+
"mtime": "2026-06-15T12:23:59.074Z",
|
|
105
|
+
"size": 329134,
|
|
106
|
+
"path": "../public/assets/main-CDMdNDY_.js"
|
|
107
|
+
},
|
|
108
|
+
"/assets/StreamingChunkSequence-GrXwIGKA.js": {
|
|
109
|
+
"type": "text/javascript; charset=utf-8",
|
|
110
|
+
"etag": '"d72-eczKwdvLd8GmG1QNwNn/d+VEOq0"',
|
|
111
|
+
"mtime": "2026-06-15T12:23:59.074Z",
|
|
112
|
+
"size": 3442,
|
|
113
|
+
"path": "../public/assets/StreamingChunkSequence-GrXwIGKA.js"
|
|
114
|
+
},
|
|
115
|
+
"/assets/index-DX88k9br.js": {
|
|
116
|
+
"type": "text/javascript; charset=utf-8",
|
|
117
|
+
"etag": '"738e0-qSt/VXgdC2y0xa/XDlbeCzFrP6k"',
|
|
118
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
119
|
+
"size": 473312,
|
|
120
|
+
"path": "../public/assets/index-DX88k9br.js"
|
|
121
|
+
},
|
|
73
122
|
"/assets/qwen-CONDcHqt.png": {
|
|
74
123
|
"type": "image/png",
|
|
75
124
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
76
|
-
"mtime": "2026-06-
|
|
125
|
+
"mtime": "2026-06-15T12:23:59.073Z",
|
|
77
126
|
"size": 357059,
|
|
78
127
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
79
|
-
},
|
|
80
|
-
"/assets/index-X7CHS7fS.js": {
|
|
81
|
-
"type": "text/javascript; charset=utf-8",
|
|
82
|
-
"etag": '"9c554-RW/7IDotWnzOK06cvwJGAOWPrdk"',
|
|
83
|
-
"mtime": "2026-06-15T02:31:02.875Z",
|
|
84
|
-
"size": 640340,
|
|
85
|
-
"path": "../public/assets/index-X7CHS7fS.js"
|
|
86
128
|
}
|
|
87
129
|
};
|
|
88
130
|
function readAsset(id) {
|
package/package.json
CHANGED
|
@@ -46,10 +46,10 @@ export function OnboardingBanner(): JSX.Element | null {
|
|
|
46
46
|
onClick={() => {
|
|
47
47
|
void markSeen();
|
|
48
48
|
}}
|
|
49
|
-
className="inline-flex items-center gap-1.5 text-xs
|
|
49
|
+
className="inline-flex items-center gap-1.5 text-xs h-8 px-3 rounded-md border border-amber-500/40 text-amber-700 dark:text-amber-300 hover:bg-amber-500/10 transition-colors shrink-0"
|
|
50
50
|
aria-label="Dismiss onboarding tip"
|
|
51
51
|
>
|
|
52
|
-
<Check className="size-3" />
|
|
52
|
+
<Check className="size-3.5" />
|
|
53
53
|
Got it
|
|
54
54
|
</button>
|
|
55
55
|
<button
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { type JSX, useCallback, useEffect, useMemo, useRef, useState, Suspense } from "react";
|
|
2
2
|
import { Download } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import type { CapturedLog } from "../proxy/schemas";
|
|
@@ -11,8 +11,9 @@ import { crabVariants } from "./ui/crab-variants";
|
|
|
11
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
|
12
12
|
import { SettingsDialog } from "./providers/SettingsDialog";
|
|
13
13
|
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
|
|
14
|
-
import {
|
|
14
|
+
import { LazyCompareDrawer } from "./proxy-viewer/lazy";
|
|
15
15
|
import { buildValidPredecessors } from "./proxy-viewer/viewerState";
|
|
16
|
+
import { useKeyboardNavigation } from "./proxy-viewer/useKeyboardNavigation";
|
|
16
17
|
|
|
17
18
|
function truncateSessionId(id: string): string {
|
|
18
19
|
if (id.length <= 30) return id;
|
|
@@ -119,6 +120,9 @@ export function ProxyViewer({
|
|
|
119
120
|
const [crabEntrancePhase, setCrabEntrancePhase] = useState<"hidden" | "playing" | "done">(
|
|
120
121
|
"hidden",
|
|
121
122
|
);
|
|
123
|
+
const logListRef = useRef<HTMLDivElement>(null);
|
|
124
|
+
const logListWrapperRef = useRef<HTMLDivElement>(null);
|
|
125
|
+
useKeyboardNavigation(logListRef, logListWrapperRef);
|
|
122
126
|
|
|
123
127
|
useEffect(() => {
|
|
124
128
|
const perCrabDuration = 400;
|
|
@@ -162,9 +166,9 @@ export function ProxyViewer({
|
|
|
162
166
|
);
|
|
163
167
|
|
|
164
168
|
return (
|
|
165
|
-
<div className="max-w-[
|
|
169
|
+
<div className="max-w-[1400px] xl:max-w-[1600px] 2xl:max-w-[1800px] mx-auto px-6 pb-6">
|
|
166
170
|
{/* Brand row */}
|
|
167
|
-
<div className="flex items-end
|
|
171
|
+
<div className="flex items-end pt-6 pb-8 relative">
|
|
168
172
|
<h1 className="text-lg font-bold flex items-end gap-2 absolute left-1/2 -translate-x-1/2 whitespace-nowrap">
|
|
169
173
|
{/* Crab family — hover to animate together */}
|
|
170
174
|
<span className="flex items-end gap-1 group cursor-default" aria-hidden="true">
|
|
@@ -217,7 +221,7 @@ export function ProxyViewer({
|
|
|
217
221
|
</div>
|
|
218
222
|
|
|
219
223
|
{/* Controls + Filters */}
|
|
220
|
-
<div className="flex items-center gap-3
|
|
224
|
+
<div className="flex items-center gap-3 mb-4">
|
|
221
225
|
<Select value={selectedSession} onValueChange={onSessionChange}>
|
|
222
226
|
<SelectTrigger className="flex-1 max-w-[350px] text-xs">
|
|
223
227
|
<SelectValue placeholder="All sessions" />
|
|
@@ -248,7 +252,7 @@ export function ProxyViewer({
|
|
|
248
252
|
<button
|
|
249
253
|
type="button"
|
|
250
254
|
onClick={() => onViewModeChange("simple")}
|
|
251
|
-
className={`
|
|
255
|
+
className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
|
|
252
256
|
viewMode === "simple"
|
|
253
257
|
? "bg-muted text-foreground"
|
|
254
258
|
: "text-muted-foreground hover:bg-muted/50"
|
|
@@ -259,7 +263,7 @@ export function ProxyViewer({
|
|
|
259
263
|
<button
|
|
260
264
|
type="button"
|
|
261
265
|
onClick={() => onViewModeChange("full")}
|
|
262
|
-
className={`
|
|
266
|
+
className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
|
|
263
267
|
viewMode === "full"
|
|
264
268
|
? "bg-muted text-foreground"
|
|
265
269
|
: "text-muted-foreground hover:bg-muted/50"
|
|
@@ -282,14 +286,14 @@ export function ProxyViewer({
|
|
|
282
286
|
void handleExport();
|
|
283
287
|
}}
|
|
284
288
|
disabled={exporting}
|
|
285
|
-
className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1"
|
|
289
|
+
className="h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5 rounded-md hover:bg-muted"
|
|
286
290
|
title="Export all logs as JSON ZIP"
|
|
287
291
|
>
|
|
288
292
|
{exporting ? (
|
|
289
293
|
<span>Exporting...</span>
|
|
290
294
|
) : (
|
|
291
295
|
<>
|
|
292
|
-
<Download className="size-3" />
|
|
296
|
+
<Download className="size-3.5" />
|
|
293
297
|
<span>Export</span>
|
|
294
298
|
</>
|
|
295
299
|
)}
|
|
@@ -298,7 +302,7 @@ export function ProxyViewer({
|
|
|
298
302
|
<button
|
|
299
303
|
type="button"
|
|
300
304
|
onClick={onClearAll}
|
|
301
|
-
className="text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
|
305
|
+
className="h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer rounded-md hover:bg-muted"
|
|
302
306
|
title="Clear all logs"
|
|
303
307
|
>
|
|
304
308
|
Clear
|
|
@@ -306,7 +310,7 @@ export function ProxyViewer({
|
|
|
306
310
|
</div>
|
|
307
311
|
|
|
308
312
|
{/* Log list */}
|
|
309
|
-
<div
|
|
313
|
+
<div>
|
|
310
314
|
{logs.length === 0 ? (
|
|
311
315
|
<div className="text-center text-muted-foreground py-16 space-y-4">
|
|
312
316
|
<p className="text-sm">No requests captured yet.</p>
|
|
@@ -314,27 +318,35 @@ export function ProxyViewer({
|
|
|
314
318
|
<CopyableCommand command="LLM_BASE_URL=http://localhost:25947/proxy <your-tool>" />
|
|
315
319
|
</div>
|
|
316
320
|
) : (
|
|
317
|
-
<div
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
<div
|
|
322
|
+
ref={logListWrapperRef}
|
|
323
|
+
tabIndex={0}
|
|
324
|
+
className="flex flex-col gap-2 focus:outline-none"
|
|
325
|
+
>
|
|
326
|
+
<div ref={logListRef}>
|
|
327
|
+
{groups.map((group) => (
|
|
328
|
+
<ConversationGroup
|
|
329
|
+
key={group.id}
|
|
330
|
+
group={group}
|
|
331
|
+
viewMode={viewMode}
|
|
332
|
+
strip={strip}
|
|
333
|
+
cacheTrends={cacheTrends}
|
|
334
|
+
onCompareWithPrevious={handleCompareWithPrevious}
|
|
335
|
+
comparisonPredecessors={comparisonPredecessors}
|
|
336
|
+
onClearGroup={onClearGroup}
|
|
337
|
+
standalone={groups.length === 1}
|
|
338
|
+
/>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
331
341
|
</div>
|
|
332
342
|
)}
|
|
333
343
|
</div>
|
|
334
344
|
|
|
335
345
|
{/* Compare drawer — sibling of the log list, not a route change. */}
|
|
336
346
|
{comparePair !== null && (
|
|
337
|
-
<
|
|
347
|
+
<Suspense fallback={null}>
|
|
348
|
+
<LazyCompareDrawer left={comparePair[0]} right={comparePair[1]} onClose={closeCompare} />
|
|
349
|
+
</Suspense>
|
|
338
350
|
)}
|
|
339
351
|
</div>
|
|
340
352
|
);
|
|
@@ -67,8 +67,6 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
67
67
|
// connection never re-opens on filter change — we always carry the full
|
|
68
68
|
// set and derive the displayed view with `useMemo` below.
|
|
69
69
|
const [allLogs, setAllLogs] = useState<CapturedLog[]>([]);
|
|
70
|
-
const [sessions, setSessions] = useState<string[]>([]);
|
|
71
|
-
const [models, setModels] = useState<string[]>([]);
|
|
72
70
|
const [selectedSession, setSelectedSession] = useState("__all__");
|
|
73
71
|
const [selectedModel, setSelectedModel] = useState("__all__");
|
|
74
72
|
const [viewMode, setViewMode] = useState<"simple" | "full">("simple");
|
|
@@ -78,7 +76,6 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
78
76
|
|
|
79
77
|
// O(1) log lookup by id
|
|
80
78
|
const logIndexRef = useRef<Map<number, number>>(new Map());
|
|
81
|
-
const logsRef = useRef<CapturedLog[]>([]);
|
|
82
79
|
|
|
83
80
|
// Debounce buffer for SSE updates
|
|
84
81
|
const pendingUpdatesRef = useRef<CapturedLog[]>([]);
|
|
@@ -90,6 +87,8 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
90
87
|
() => filterLogs(allLogs, selectedSession, selectedModel),
|
|
91
88
|
[allLogs, selectedSession, selectedModel],
|
|
92
89
|
);
|
|
90
|
+
const sessions = useMemo(() => extractSessions(allLogs), [allLogs]);
|
|
91
|
+
const models = useMemo(() => extractModels(allLogs), [allLogs]);
|
|
93
92
|
|
|
94
93
|
const flushUpdates = useCallback(() => {
|
|
95
94
|
flushTimerRef.current = null;
|
|
@@ -99,8 +98,6 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
99
98
|
|
|
100
99
|
setAllLogs((prev) => {
|
|
101
100
|
let next = prev;
|
|
102
|
-
let sessionsChanged = false;
|
|
103
|
-
let modelsChanged = false;
|
|
104
101
|
for (const log of updates) {
|
|
105
102
|
const idx = logIndexRef.current.get(log.id);
|
|
106
103
|
if (idx !== undefined) {
|
|
@@ -110,19 +107,10 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
110
107
|
logIndexRef.current.set(log.id, next.length);
|
|
111
108
|
next = [...next, log];
|
|
112
109
|
}
|
|
113
|
-
if (log.sessionId !== null && log.sessionId !== "" && !sessions.includes(log.sessionId)) {
|
|
114
|
-
sessionsChanged = true;
|
|
115
|
-
}
|
|
116
|
-
if (log.model !== null && log.model !== "" && !models.includes(log.model)) {
|
|
117
|
-
modelsChanged = true;
|
|
118
|
-
}
|
|
119
110
|
}
|
|
120
|
-
logsRef.current = next;
|
|
121
|
-
if (sessionsChanged) setSessions((s) => extractSessions(next));
|
|
122
|
-
if (modelsChanged) setModels((m) => extractModels(next));
|
|
123
111
|
return next;
|
|
124
112
|
});
|
|
125
|
-
}, [
|
|
113
|
+
}, []);
|
|
126
114
|
|
|
127
115
|
const scheduleUpdate = useCallback(
|
|
128
116
|
(log: CapturedLog) => {
|
|
@@ -173,10 +161,7 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
173
161
|
if (log !== undefined) idx.set(log.id, i);
|
|
174
162
|
}
|
|
175
163
|
logIndexRef.current = idx;
|
|
176
|
-
logsRef.current = update.logs;
|
|
177
164
|
setAllLogs(update.logs);
|
|
178
|
-
setSessions(extractSessions(update.logs));
|
|
179
|
-
setModels(extractModels(update.logs));
|
|
180
165
|
setError(null);
|
|
181
166
|
} else if (update.type === "update") {
|
|
182
167
|
scheduleUpdate(update.log);
|
|
@@ -223,10 +208,7 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
223
208
|
return;
|
|
224
209
|
}
|
|
225
210
|
logIndexRef.current.clear();
|
|
226
|
-
logsRef.current = [];
|
|
227
211
|
setAllLogs([]);
|
|
228
|
-
setSessions([]);
|
|
229
|
-
setModels([]);
|
|
230
212
|
setError(null);
|
|
231
213
|
} catch (err) {
|
|
232
214
|
setError(err instanceof Error ? err.message : "Unknown error clearing logs");
|
|
@@ -257,9 +239,6 @@ export function ProxyViewerContainer(): JSX.Element {
|
|
|
257
239
|
if (log !== undefined) idx.set(log.id, i);
|
|
258
240
|
}
|
|
259
241
|
logIndexRef.current = idx;
|
|
260
|
-
logsRef.current = remaining;
|
|
261
|
-
setSessions(extractSessions(remaining));
|
|
262
|
-
setModels(extractModels(remaining));
|
|
263
242
|
return remaining;
|
|
264
243
|
});
|
|
265
244
|
setError(null);
|
|
@@ -78,11 +78,14 @@ export function ConversationHeader({
|
|
|
78
78
|
<div
|
|
79
79
|
role="button"
|
|
80
80
|
tabIndex={0}
|
|
81
|
+
data-nav-id={`conv-${conversationId}`}
|
|
82
|
+
data-nav-action={expanded ? "collapse" : "expand"}
|
|
81
83
|
className={cn(
|
|
82
84
|
"flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
|
|
83
85
|
"hover:bg-muted/50",
|
|
84
86
|
"select-none",
|
|
85
87
|
"border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
|
|
88
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
86
89
|
)}
|
|
87
90
|
onClick={onToggle}
|
|
88
91
|
onKeyDown={(e) => {
|
|
@@ -174,7 +177,7 @@ export function ConversationHeader({
|
|
|
174
177
|
onClick={handleClearClick}
|
|
175
178
|
aria-label={`Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`}
|
|
176
179
|
title="Clear this group"
|
|
177
|
-
className="text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-
|
|
180
|
+
className="text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer"
|
|
178
181
|
>
|
|
179
182
|
<Trash2 className="size-3.5" />
|
|
180
183
|
</button>
|