@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.
Files changed (30) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/index-CMuJQyt1.js +105 -0
  3. package/.output/public/assets/index-DciyfYBk.css +1 -0
  4. package/.output/public/assets/{main-CJ4MreBr.js → main-BLYgekFx.js} +1 -1
  5. package/.output/server/_libs/lucide-react.mjs +85 -111
  6. package/.output/server/_libs/radix-ui__react-id.mjs +1 -1
  7. package/.output/server/_ssr/{index-9uTJ4xYR.mjs → index-P66uoVEU.mjs} +677 -304
  8. package/.output/server/_ssr/index.mjs +2 -2
  9. package/.output/server/_ssr/{router-BKnjB_zi.mjs → router-DpLCKk51.mjs} +45 -14
  10. package/.output/server/{_tanstack-start-manifest_v-IsglLVKy.mjs → _tanstack-start-manifest_v-C9Wq6YdJ.mjs} +1 -1
  11. package/.output/server/index.mjs +22 -22
  12. package/package.json +1 -1
  13. package/src/components/ProxyViewer.tsx +99 -180
  14. package/src/components/proxy-viewer/ConversationGroup.tsx +70 -66
  15. package/src/components/proxy-viewer/ConversationHeader.tsx +15 -39
  16. package/src/components/proxy-viewer/LogEntry.tsx +68 -9
  17. package/src/components/proxy-viewer/LogEntryHeader.tsx +62 -75
  18. package/src/components/proxy-viewer/ThreadConnector.tsx +78 -65
  19. package/src/components/proxy-viewer/TurnGroup.tsx +83 -0
  20. package/src/components/ui/crab-variants.tsx +456 -0
  21. package/src/lib/stopReason.ts +7 -6
  22. package/src/proxy/formats/anthropic/handler.ts +2 -5
  23. package/src/proxy/formats/openai/handler.ts +33 -7
  24. package/src/proxy/formats/openai/schemas.ts +1 -0
  25. package/src/proxy/formats/openai/stream.ts +24 -0
  26. package/src/proxy/handler.ts +8 -2
  27. package/src/proxy/schemas.ts +6 -3
  28. package/styles/globals.css +38 -0
  29. package/.output/public/assets/index-CdnotuLh.js +0 -105
  30. package/.output/public/assets/index-vP91146S.css +0 -1
@@ -1,103 +1,116 @@
1
- import type { JSX } from "react";
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 is the last entry in the group. */
13
- isLast: boolean;
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
- * Renders the vertical timeline connector on the left side of a log entry
18
- * in thread view. A continuous line runs through intermediate entries, with:
19
- * - Line starts from the top for the first entry
20
- * - Line continues through intermediate entries
21
- * - Line ends at a turn-boundary dot/cap when stop_reason is end_turn/stop
22
- * - Dimmed/dashed line for pending (in-flight) entries
23
- * - A solid dot marks turn boundaries
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
- isLast,
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
- // Tool use: the turn continues
34
- const isToolUse = stopReason === "tool_use";
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 items-stretch h-full w-6 shrink-0 relative">
48
- {/* Top line segment */}
49
- <div className="absolute left-1/2 -translate-x-1/2 w-0.5 top-0 flex flex-col items-center">
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
- {/* Center marker */}
60
- <div className="absolute left-1/2 -translate-x-1/2 top-4 flex items-center justify-center z-10">
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
- <div
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
- ) : isToolUse ? (
71
- <div
72
- className="size-2 rounded-full bg-muted-foreground/25"
73
- title="Tool Use — turn continues"
74
- />
75
- ) : isPending ? (
76
- <div
77
- className="size-2.5 rounded-full border-2 border-dashed border-muted-foreground/30 animate-pulse"
78
- title="Response pending"
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
- <div className="size-1.5 rounded-full bg-muted-foreground/30" />
96
+ <Crab className="size-3.5 text-muted-foreground/40" />
82
97
  )}
83
98
  </div>
84
99
 
85
- {/* Bottom line segment */}
86
- <div
87
- className="absolute left-1/2 -translate-x-1/2 w-0.5 top-4 flex flex-col items-center"
88
- style={{ bottom: 0 }}
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 flex-1",
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
+ }