@tonyclaw/llm-inspector 1.14.8 → 1.15.0
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/index-CMuJQyt1.js +105 -0
- package/.output/public/assets/index-DciyfYBk.css +1 -0
- package/.output/public/assets/{main-CJ4MreBr.js → main-BLYgekFx.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +85 -111
- package/.output/server/_libs/radix-ui__react-id.mjs +1 -1
- package/.output/server/_ssr/{index-9uTJ4xYR.mjs → index-P66uoVEU.mjs} +677 -304
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-BKnjB_zi.mjs → router-DpLCKk51.mjs} +45 -14
- package/.output/server/{_tanstack-start-manifest_v-IsglLVKy.mjs → _tanstack-start-manifest_v-C9Wq6YdJ.mjs} +1 -1
- package/.output/server/index.mjs +22 -22
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +99 -180
- package/src/components/proxy-viewer/ConversationGroup.tsx +70 -66
- package/src/components/proxy-viewer/ConversationHeader.tsx +15 -39
- package/src/components/proxy-viewer/LogEntry.tsx +68 -9
- package/src/components/proxy-viewer/LogEntryHeader.tsx +62 -75
- package/src/components/proxy-viewer/ThreadConnector.tsx +78 -65
- package/src/components/proxy-viewer/TurnGroup.tsx +83 -0
- package/src/components/ui/crab-variants.tsx +456 -0
- package/src/lib/stopReason.ts +7 -6
- package/src/proxy/formats/anthropic/handler.ts +2 -5
- package/src/proxy/formats/openai/handler.ts +33 -7
- package/src/proxy/formats/openai/schemas.ts +1 -0
- package/src/proxy/formats/openai/stream.ts +24 -0
- package/src/proxy/handler.ts +8 -2
- package/src/proxy/schemas.ts +6 -3
- package/styles/globals.css +38 -0
- package/.output/public/assets/index-CdnotuLh.js +0 -105
- package/.output/public/assets/index-vP91146S.css +0 -1
|
@@ -1,103 +1,116 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type JSX, useMemo } from "react";
|
|
2
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
2
3
|
import { cn } from "../../lib/utils";
|
|
3
4
|
import type { StopReason } from "../../lib/stopReason";
|
|
5
|
+
import { getCrabVariant } from "../ui/crab-variants";
|
|
4
6
|
|
|
5
7
|
export type ThreadConnectorProps = {
|
|
6
|
-
/** The stop reason extracted from this log entry's response. */
|
|
7
8
|
stopReason: StopReason;
|
|
8
|
-
/** True when the response is still in-flight (responseStatus === null). */
|
|
9
9
|
isPending: boolean;
|
|
10
|
-
/** True when this is the first entry in the group. */
|
|
11
10
|
isFirst: boolean;
|
|
12
|
-
/** True when this
|
|
13
|
-
|
|
11
|
+
/** True when this entry starts a new turn (first overall, or after end_turn/stop). */
|
|
12
|
+
isTurnStart: boolean;
|
|
13
|
+
/** Seed for crab variant selection (0-11). */
|
|
14
|
+
crabIndex?: number;
|
|
15
|
+
/** When true, the boundary marker is clickable to collapse/expand the turn. */
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
collapsed?: boolean;
|
|
18
|
+
onToggle?: () => void;
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Vertical timeline connector for thread view. Uses flexbox layout (no
|
|
23
|
+
* absolute positioning) so the connector naturally tracks its sibling
|
|
24
|
+
* LogEntry height — no scroll jitter.
|
|
25
|
+
*
|
|
26
|
+
* Markers use the CrabLogo. At turn starts the crab is static (amber),
|
|
27
|
+
* during processing it "crawls" downward with a bounce animation, and
|
|
28
|
+
* at turn boundaries it stops with a glow.
|
|
24
29
|
*/
|
|
25
30
|
export function ThreadConnector({
|
|
26
31
|
stopReason,
|
|
27
32
|
isPending,
|
|
28
33
|
isFirst,
|
|
29
|
-
|
|
34
|
+
isTurnStart,
|
|
35
|
+
crabIndex = 0,
|
|
36
|
+
collapsible = false,
|
|
37
|
+
collapsed = false,
|
|
38
|
+
onToggle,
|
|
30
39
|
}: ThreadConnectorProps): JSX.Element {
|
|
31
|
-
// Turn boundary: "end_turn" for Anthropic, "stop" for OpenAI
|
|
32
40
|
const isBoundary = stopReason === "end_turn" || stopReason === "stop";
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
// Compute classes for each segment of the connector:
|
|
37
|
-
// - top half line (from previous entry down to this entry's top)
|
|
38
|
-
// - center dot/circle (this entry's response marker)
|
|
39
|
-
// - bottom half line (from this entry's bottom down to the next entry)
|
|
40
|
-
|
|
41
|
-
const lineClass = cn(
|
|
42
|
-
"w-0.5 bg-muted-foreground/30",
|
|
43
|
-
isPending && "border-dashed bg-transparent border-l-2 border-muted-foreground/20",
|
|
44
|
-
);
|
|
41
|
+
const isRunning = isPending && !isBoundary;
|
|
42
|
+
const Crab = useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
|
|
45
43
|
|
|
46
44
|
return (
|
|
47
|
-
<div className="flex
|
|
48
|
-
{/* Top line
|
|
49
|
-
<div className="
|
|
50
|
-
{!isFirst &&
|
|
51
|
-
<div
|
|
52
|
-
className={cn("w-0.5 grow", "bg-muted-foreground/30", "h-4")}
|
|
53
|
-
style={{ minHeight: "0.5rem" }}
|
|
54
|
-
/>
|
|
55
|
-
)}
|
|
56
|
-
{isFirst && <div className="h-4" />}
|
|
45
|
+
<div className="flex flex-col items-center w-6 shrink-0">
|
|
46
|
+
{/* Top: incoming line */}
|
|
47
|
+
<div className="flex justify-center h-2.5">
|
|
48
|
+
{!isFirst && <div className="w-0.5 bg-muted-foreground/30" />}
|
|
57
49
|
</div>
|
|
58
50
|
|
|
59
|
-
{/*
|
|
60
|
-
|
|
51
|
+
{/* Collapse toggle — sits above the crab, no overlap */}
|
|
52
|
+
{collapsible && (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={onToggle}
|
|
56
|
+
title={collapsed ? "Expand turn" : "Collapse turn"}
|
|
57
|
+
className="cursor-pointer flex justify-center py-0.5"
|
|
58
|
+
>
|
|
59
|
+
{collapsed ? (
|
|
60
|
+
<ChevronRight className="size-3 text-muted-foreground hover:text-foreground transition-colors" />
|
|
61
|
+
) : (
|
|
62
|
+
<ChevronDown className="size-3 text-muted-foreground hover:text-foreground transition-colors" />
|
|
63
|
+
)}
|
|
64
|
+
</button>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
{/* Center marker — CrabLogo */}
|
|
68
|
+
<div className="flex items-center justify-center py-0.5">
|
|
61
69
|
{isBoundary ? (
|
|
62
|
-
<
|
|
63
|
-
className={cn(
|
|
64
|
-
"size-2.5 rounded-full border-2",
|
|
65
|
-
"bg-background border-amber-400",
|
|
66
|
-
"shadow-[0_0_6px_rgba(251,191,36,0.4)]",
|
|
67
|
-
)}
|
|
70
|
+
<span
|
|
68
71
|
title={stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
>
|
|
73
|
+
<Crab
|
|
74
|
+
className={cn(
|
|
75
|
+
"size-3.5 text-amber-400",
|
|
76
|
+
"animate-crab-settle",
|
|
77
|
+
"drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]",
|
|
78
|
+
)}
|
|
79
|
+
/>
|
|
80
|
+
</span>
|
|
81
|
+
) : isTurnStart ? (
|
|
82
|
+
<span title="Start of turn">
|
|
83
|
+
<Crab
|
|
84
|
+
className={cn(
|
|
85
|
+
"size-3.5 text-emerald-400",
|
|
86
|
+
"animate-crab-appear",
|
|
87
|
+
"drop-shadow-[0_0_4px_rgba(52,211,153,0.5)]",
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
</span>
|
|
91
|
+
) : isRunning ? (
|
|
92
|
+
<span title="Processing…">
|
|
93
|
+
<Crab className={cn("size-3.5 text-amber-300/80", "animate-crab-crawl")} />
|
|
94
|
+
</span>
|
|
80
95
|
) : (
|
|
81
|
-
<
|
|
96
|
+
<Crab className="size-3.5 text-muted-foreground/40" />
|
|
82
97
|
)}
|
|
83
98
|
</div>
|
|
84
99
|
|
|
85
|
-
{/* Bottom line
|
|
86
|
-
<div
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{!isBoundary && (
|
|
100
|
+
{/* Bottom: outgoing line */}
|
|
101
|
+
<div className="flex-1 flex justify-center min-h-1">
|
|
102
|
+
{isBoundary ? (
|
|
103
|
+
<div className="w-0.5 bg-muted-foreground/10 h-4" />
|
|
104
|
+
) : (
|
|
91
105
|
<div
|
|
92
106
|
className={cn(
|
|
93
|
-
"w-0.5
|
|
107
|
+
"w-0.5 h-full",
|
|
94
108
|
isPending
|
|
95
109
|
? "border-dashed bg-transparent border-l-2 border-muted-foreground/20"
|
|
96
110
|
: "bg-muted-foreground/30",
|
|
97
111
|
)}
|
|
98
112
|
/>
|
|
99
113
|
)}
|
|
100
|
-
{isBoundary && <div className="w-0.5 h-4 bg-muted-foreground/10" />}
|
|
101
114
|
</div>
|
|
102
115
|
</div>
|
|
103
116
|
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useState, useMemo, type JSX, useCallback } from "react";
|
|
2
|
+
import type { CapturedLog } from "../../proxy/schemas";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import { extractStopReason, isTurnBoundary } from "../../lib/stopReason";
|
|
5
|
+
import { LogEntry } from "./LogEntry";
|
|
6
|
+
import { ThreadConnector } from "./ThreadConnector";
|
|
7
|
+
import type { CacheTrendEntry } from "./cacheTrend";
|
|
8
|
+
|
|
9
|
+
type TurnGroupProps = {
|
|
10
|
+
logs: CapturedLog[];
|
|
11
|
+
viewMode: "simple" | "full";
|
|
12
|
+
strip: boolean;
|
|
13
|
+
cacheTrends?: Map<number, CacheTrendEntry>;
|
|
14
|
+
onCompareWithPrevious: (log: CapturedLog) => void;
|
|
15
|
+
/** Turn index for alternating background colours. */
|
|
16
|
+
turnIndex?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function TurnGroup({
|
|
20
|
+
logs,
|
|
21
|
+
viewMode,
|
|
22
|
+
strip,
|
|
23
|
+
cacheTrends,
|
|
24
|
+
onCompareWithPrevious,
|
|
25
|
+
turnIndex = 0,
|
|
26
|
+
}: TurnGroupProps): JSX.Element {
|
|
27
|
+
const stopReasons = useMemo(() => logs.map((l) => extractStopReason(l)), [logs]);
|
|
28
|
+
const lastIdx = logs.length - 1;
|
|
29
|
+
const lastStop = stopReasons[lastIdx] ?? null;
|
|
30
|
+
const isComplete = lastStop !== null ? isTurnBoundary(lastStop) : false;
|
|
31
|
+
const isPending = logs[lastIdx]?.responseStatus === null;
|
|
32
|
+
const collapsible = isComplete && !isPending;
|
|
33
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
34
|
+
|
|
35
|
+
const toggleCollapse = useCallback(() => {
|
|
36
|
+
if (collapsible) setCollapsed((prev) => !prev);
|
|
37
|
+
}, [collapsible]);
|
|
38
|
+
|
|
39
|
+
// When collapsed, only show the boundary entry
|
|
40
|
+
const visibleLogs = useMemo(() => {
|
|
41
|
+
if (!collapsed) return logs;
|
|
42
|
+
const last = logs[lastIdx];
|
|
43
|
+
return last !== undefined ? [last] : [];
|
|
44
|
+
}, [logs, collapsed, lastIdx]);
|
|
45
|
+
|
|
46
|
+
const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={cn("border rounded-lg", isPending ? "border-amber-500/10" : "border-transparent")}
|
|
51
|
+
>
|
|
52
|
+
{visibleLogs.map((log, visibleIdx) => {
|
|
53
|
+
const originalIdx = collapsed ? lastIdx : visibleIdx;
|
|
54
|
+
const reason = stopReasons[originalIdx] ?? null;
|
|
55
|
+
const isBoundary = reason === "end_turn" || reason === "stop";
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div key={log.id} className="flex items-stretch">
|
|
59
|
+
<ThreadConnector
|
|
60
|
+
stopReason={reason}
|
|
61
|
+
isPending={log.responseStatus === null}
|
|
62
|
+
isFirst={originalIdx === 0}
|
|
63
|
+
isTurnStart={originalIdx === 0}
|
|
64
|
+
crabIndex={log.id % 12}
|
|
65
|
+
collapsible={collapsible && isBoundary && logs.length > 1}
|
|
66
|
+
collapsed={collapsed}
|
|
67
|
+
onToggle={toggleCollapse}
|
|
68
|
+
/>
|
|
69
|
+
<div className={cn("flex-1 min-w-0 mb-2 rounded-lg", bgClass)}>
|
|
70
|
+
<LogEntry
|
|
71
|
+
log={log}
|
|
72
|
+
viewMode={viewMode}
|
|
73
|
+
strip={strip}
|
|
74
|
+
cacheTrend={cacheTrends?.get(log.id) ?? null}
|
|
75
|
+
onCompareWithPrevious={() => onCompareWithPrevious(log)}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|