@tonyclaw/llm-inspector 1.6.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 (286) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
  3. package/.output/public/assets/index-B3RwBPLW.css +1 -0
  4. package/.output/public/assets/index-s4lwsWvq.js +97 -0
  5. package/.output/public/assets/main-Cp8AM0Pa.js +17 -0
  6. package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
  7. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  8. package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
  9. package/.output/server/_chunks/ssr-renderer.mjs +17 -0
  10. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
  11. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
  12. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +1 -0
  13. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
  14. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -0
  15. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
  16. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
  17. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
  18. package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
  19. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
  20. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
  21. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
  22. package/.output/server/_libs/ajv-formats.mjs +330 -0
  23. package/.output/server/_libs/ajv.mjs +11444 -0
  24. package/.output/server/_libs/aria-hidden.mjs +122 -0
  25. package/.output/server/_libs/atomically.mjs +152 -0
  26. package/.output/server/_libs/bail.mjs +8 -0
  27. package/.output/server/_libs/character-entities.mjs +2130 -0
  28. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  29. package/.output/server/_libs/clsx.mjs +16 -0
  30. package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
  31. package/.output/server/_libs/conf.mjs +635 -0
  32. package/.output/server/_libs/cookie-es.mjs +58 -0
  33. package/.output/server/_libs/core-util-is.mjs +75 -0
  34. package/.output/server/_libs/croner.mjs +1 -0
  35. package/.output/server/_libs/crossws.mjs +1 -0
  36. package/.output/server/_libs/debounce-fn.mjs +69 -0
  37. package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
  38. package/.output/server/_libs/detect-node-es.mjs +1 -0
  39. package/.output/server/_libs/devlop.mjs +8 -0
  40. package/.output/server/_libs/dot-prop.mjs +265 -0
  41. package/.output/server/_libs/env-paths.mjs +57 -0
  42. package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
  43. package/.output/server/_libs/extend.mjs +97 -0
  44. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  45. package/.output/server/_libs/fast-uri.mjs +812 -0
  46. package/.output/server/_libs/floating-ui__core.mjs +725 -0
  47. package/.output/server/_libs/floating-ui__dom.mjs +622 -0
  48. package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
  49. package/.output/server/_libs/floating-ui__utils.mjs +320 -0
  50. package/.output/server/_libs/get-nonce.mjs +9 -0
  51. package/.output/server/_libs/h3-v2.mjs +276 -0
  52. package/.output/server/_libs/h3.mjs +400 -0
  53. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  54. package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
  55. package/.output/server/_libs/hookable.mjs +1 -0
  56. package/.output/server/_libs/html-url-attributes.mjs +26 -0
  57. package/.output/server/_libs/immediate.mjs +74 -0
  58. package/.output/server/_libs/inherits.mjs +50 -0
  59. package/.output/server/_libs/inline-style-parser.mjs +142 -0
  60. package/.output/server/_libs/is-plain-obj.mjs +10 -0
  61. package/.output/server/_libs/isarray.mjs +14 -0
  62. package/.output/server/_libs/isbot.mjs +20 -0
  63. package/.output/server/_libs/json-schema-traverse.mjs +180 -0
  64. package/.output/server/_libs/jszip.mjs +3049 -0
  65. package/.output/server/_libs/lie.mjs +273 -0
  66. package/.output/server/_libs/lucide-react.mjs +368 -0
  67. package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
  68. package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
  69. package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
  70. package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
  71. package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
  72. package/.output/server/_libs/micromark-factory-label.mjs +63 -0
  73. package/.output/server/_libs/micromark-factory-space.mjs +24 -0
  74. package/.output/server/_libs/micromark-factory-title.mjs +65 -0
  75. package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
  76. package/.output/server/_libs/micromark-util-character.mjs +44 -0
  77. package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
  78. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  79. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  80. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  81. package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
  82. package/.output/server/_libs/micromark-util-encode.mjs +1 -0
  83. package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
  84. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  85. package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
  86. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  87. package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
  88. package/.output/server/_libs/micromark.mjs +906 -0
  89. package/.output/server/_libs/mimic-function.mjs +47 -0
  90. package/.output/server/_libs/ohash.mjs +1 -0
  91. package/.output/server/_libs/pako.mjs +4223 -0
  92. package/.output/server/_libs/process-nextick-args.mjs +48 -0
  93. package/.output/server/_libs/property-information.mjs +1209 -0
  94. package/.output/server/_libs/radix-ui.mjs +1 -0
  95. package/.output/server/_libs/radix-ui__number.mjs +6 -0
  96. package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
  97. package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
  98. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
  99. package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
  100. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
  101. package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
  102. package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
  103. package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
  104. package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
  105. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
  106. package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
  107. package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
  108. package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
  109. package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
  110. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
  111. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
  112. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
  113. package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
  114. package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
  115. package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
  116. package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
  117. package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
  118. package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
  119. package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
  120. package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
  121. package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
  122. package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
  123. package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
  124. package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
  125. package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
  126. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
  127. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
  128. package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
  129. package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
  130. package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
  131. package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
  132. package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
  133. package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
  134. package/.output/server/_libs/radix-ui__react-toast.mjs +1 -0
  135. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
  136. package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
  137. package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
  138. package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
  139. package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
  140. package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
  141. package/.output/server/_libs/react-dom.mjs +9935 -0
  142. package/.output/server/_libs/react-markdown.mjs +147 -0
  143. package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
  144. package/.output/server/_libs/react-remove-scroll.mjs +328 -0
  145. package/.output/server/_libs/react-style-singleton.mjs +69 -0
  146. package/.output/server/_libs/react.mjs +515 -0
  147. package/.output/server/_libs/readable-stream.mjs +1518 -0
  148. package/.output/server/_libs/remark-parse.mjs +19 -0
  149. package/.output/server/_libs/remark-rehype.mjs +21 -0
  150. package/.output/server/_libs/rou3.mjs +8 -0
  151. package/.output/server/_libs/safe-buffer.mjs +64 -0
  152. package/.output/server/_libs/semver.mjs +1984 -0
  153. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  154. package/.output/server/_libs/seroval.mjs +1765 -0
  155. package/.output/server/_libs/setimmediate.mjs +1 -0
  156. package/.output/server/_libs/space-separated-tokens.mjs +6 -0
  157. package/.output/server/_libs/srvx.mjs +334 -0
  158. package/.output/server/_libs/stubborn-fs.mjs +91 -0
  159. package/.output/server/_libs/stubborn-utils.mjs +66 -0
  160. package/.output/server/_libs/style-to-js.mjs +72 -0
  161. package/.output/server/_libs/style-to-object.mjs +38 -0
  162. package/.output/server/_libs/tailwind-merge.mjs +3010 -0
  163. package/.output/server/_libs/tanstack__history.mjs +217 -0
  164. package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
  165. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  166. package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
  167. package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
  168. package/.output/server/_libs/tanstack__store.mjs +1 -0
  169. package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
  170. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  171. package/.output/server/_libs/tiny-warning.mjs +5 -0
  172. package/.output/server/_libs/trim-lines.mjs +41 -0
  173. package/.output/server/_libs/trough.mjs +85 -0
  174. package/.output/server/_libs/tslib.mjs +576 -0
  175. package/.output/server/_libs/ufo.mjs +54 -0
  176. package/.output/server/_libs/uint8array-extras.mjs +69 -0
  177. package/.output/server/_libs/unctx.mjs +1 -0
  178. package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
  179. package/.output/server/_libs/unified.mjs +661 -0
  180. package/.output/server/_libs/unist-util-is.mjs +100 -0
  181. package/.output/server/_libs/unist-util-position.mjs +27 -0
  182. package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
  183. package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
  184. package/.output/server/_libs/unist-util-visit.mjs +24 -0
  185. package/.output/server/_libs/unstorage.mjs +1 -0
  186. package/.output/server/_libs/use-callback-ref.mjs +66 -0
  187. package/.output/server/_libs/use-sidecar.mjs +106 -0
  188. package/.output/server/_libs/use-sync-external-store.mjs +1 -0
  189. package/.output/server/_libs/util-deprecate.mjs +12 -0
  190. package/.output/server/_libs/vfile-message.mjs +138 -0
  191. package/.output/server/_libs/vfile.mjs +467 -0
  192. package/.output/server/_libs/when-exit.mjs +53 -0
  193. package/.output/server/_libs/zod.mjs +4460 -0
  194. package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
  195. package/.output/server/_ssr/index.mjs +1176 -0
  196. package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
  197. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  198. package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
  199. package/.output/server/index.mjs +393 -0
  200. package/README.md +196 -0
  201. package/package.json +91 -0
  202. package/src/assets/logos/alibaba.svg +1 -0
  203. package/src/assets/logos/anthropic.svg +1 -0
  204. package/src/assets/logos/deepseek.svg +1 -0
  205. package/src/assets/logos/minimax.jpeg +0 -0
  206. package/src/assets/logos/openai.svg +1 -0
  207. package/src/assets/logos/qwen.png +0 -0
  208. package/src/assets/logos/zhipuai.svg +219 -0
  209. package/src/cli.ts +68 -0
  210. package/src/components/ProxyViewer.tsx +325 -0
  211. package/src/components/ProxyViewerContainer.tsx +211 -0
  212. package/src/components/providers/ProviderCard.tsx +186 -0
  213. package/src/components/providers/ProviderForm.tsx +259 -0
  214. package/src/components/providers/ProviderLogo.tsx +111 -0
  215. package/src/components/providers/ProvidersPanel.tsx +259 -0
  216. package/src/components/providers/SettingsDialog.tsx +39 -0
  217. package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
  218. package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
  219. package/src/components/proxy-viewer/LogEntry.tsx +225 -0
  220. package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
  221. package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
  222. package/src/components/proxy-viewer/ResponseView.tsx +161 -0
  223. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
  224. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
  225. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
  226. package/src/components/proxy-viewer/formats/index.tsx +24 -0
  227. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
  228. package/src/components/proxy-viewer/index.ts +8 -0
  229. package/src/components/ui/badge.tsx +47 -0
  230. package/src/components/ui/button.tsx +47 -0
  231. package/src/components/ui/collapsible.tsx +21 -0
  232. package/src/components/ui/dialog.tsx +129 -0
  233. package/src/components/ui/json-viewer.tsx +464 -0
  234. package/src/components/ui/scroll-area.tsx +54 -0
  235. package/src/components/ui/select.tsx +178 -0
  236. package/src/components/ui/separator.tsx +28 -0
  237. package/src/components/ui/tabs.tsx +88 -0
  238. package/src/components/ui/tooltip.tsx +51 -0
  239. package/src/index.css +11 -0
  240. package/src/lib/export-logs.ts +51 -0
  241. package/src/lib/utils.ts +22 -0
  242. package/src/proxy/chunkStorage.ts +118 -0
  243. package/src/proxy/constants.ts +36 -0
  244. package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
  245. package/src/proxy/formats/anthropic/handler.ts +74 -0
  246. package/src/proxy/formats/anthropic/index.ts +14 -0
  247. package/src/proxy/formats/anthropic/register.ts +4 -0
  248. package/src/proxy/formats/anthropic/schemas.ts +217 -0
  249. package/src/proxy/formats/anthropic/stream.ts +167 -0
  250. package/src/proxy/formats/handler.ts +46 -0
  251. package/src/proxy/formats/index.ts +12 -0
  252. package/src/proxy/formats/jsonSchema.ts +24 -0
  253. package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
  254. package/src/proxy/formats/openai/handler.ts +70 -0
  255. package/src/proxy/formats/openai/index.ts +25 -0
  256. package/src/proxy/formats/openai/provider.ts +50 -0
  257. package/src/proxy/formats/openai/register.ts +4 -0
  258. package/src/proxy/formats/openai/schemas.ts +150 -0
  259. package/src/proxy/formats/openai/stream.ts +153 -0
  260. package/src/proxy/formats/protocol.ts +50 -0
  261. package/src/proxy/formats/providerRegistry.ts +51 -0
  262. package/src/proxy/formats/providers/index.ts +3 -0
  263. package/src/proxy/formats/registry.ts +61 -0
  264. package/src/proxy/handler.ts +389 -0
  265. package/src/proxy/logIndex.ts +187 -0
  266. package/src/proxy/logger.ts +99 -0
  267. package/src/proxy/providers.ts +234 -0
  268. package/src/proxy/schemas.ts +160 -0
  269. package/src/proxy/socketTracker.ts +158 -0
  270. package/src/proxy/store.ts +386 -0
  271. package/src/router.tsx +16 -0
  272. package/src/routes/__root.tsx +38 -0
  273. package/src/routes/api/config.paths.ts +14 -0
  274. package/src/routes/api/health.ts +11 -0
  275. package/src/routes/api/logs.$id.chunks.ts +36 -0
  276. package/src/routes/api/logs.$id.replay.ts +262 -0
  277. package/src/routes/api/logs.$id.ts +22 -0
  278. package/src/routes/api/logs.stream.ts +64 -0
  279. package/src/routes/api/logs.ts +30 -0
  280. package/src/routes/api/models.ts +10 -0
  281. package/src/routes/api/providers.$providerId.ts +45 -0
  282. package/src/routes/api/providers.ts +37 -0
  283. package/src/routes/api/sessions.ts +10 -0
  284. package/src/routes/index.tsx +6 -0
  285. package/src/routes/proxy/$.ts +15 -0
  286. package/styles/globals.css +121 -0
@@ -0,0 +1,161 @@
1
+ import { AlertTriangle, Zap } from "lucide-react";
2
+ import type { JSX } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../lib/utils";
5
+ import {
6
+ type InspectorResponse,
7
+ type OpenAIResponse,
8
+ InspectorResponseSchema,
9
+ parseOpenAIResponse,
10
+ } from "../../proxy/schemas";
11
+ import { formatViewFor } from "./formats";
12
+
13
+ export type ResponseViewProps = {
14
+ responseText: string | null;
15
+ responseStatus: number | null;
16
+ streaming: boolean;
17
+ inputTokens: number | null;
18
+ outputTokens: number | null;
19
+ cacheCreationInputTokens?: number | null;
20
+ cacheReadInputTokens?: number | null;
21
+ apiFormat?: "anthropic" | "openai" | "unknown";
22
+ };
23
+
24
+ function getStatusClasses(category: StatusCategory): string {
25
+ switch (category) {
26
+ case "success":
27
+ return "text-emerald-400";
28
+ case "client_error":
29
+ return "text-amber-400";
30
+ case "server_error":
31
+ return "text-red-400";
32
+ case "pending":
33
+ return "text-muted-foreground";
34
+ }
35
+ }
36
+
37
+ function parseResponse(
38
+ text: string,
39
+ apiFormat: "anthropic" | "openai" | "unknown" | undefined,
40
+ ): InspectorResponse | OpenAIResponse | null {
41
+ try {
42
+ const json: unknown = JSON.parse(text);
43
+ if (apiFormat === "openai") {
44
+ const result = parseOpenAIResponse(text);
45
+ if (result) return result;
46
+ }
47
+ const result = InspectorResponseSchema.safeParse(json);
48
+ if (result.success) return result.data;
49
+ return null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function StatusIndicator({ status }: { status: number | null }): JSX.Element {
56
+ const category = getStatusCategory(status);
57
+ const classes = getStatusClasses(category);
58
+
59
+ if (status === null) {
60
+ return <span className="text-xs text-muted-foreground italic">pending</span>;
61
+ }
62
+
63
+ return (
64
+ <span className={cn("flex items-center gap-1 text-xs font-mono font-semibold", classes)}>
65
+ {category === "server_error" && <AlertTriangle className="size-3" />}
66
+ {status}
67
+ </span>
68
+ );
69
+ }
70
+
71
+ function ErrorResponseView({ text }: { text: string }): JSX.Element {
72
+ return (
73
+ <div className="rounded-md border border-red-500/30 bg-red-500/5 p-3">
74
+ <pre className="text-xs text-red-300 whitespace-pre-wrap font-mono leading-relaxed overflow-auto max-h-[60vh]">
75
+ {text}
76
+ </pre>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ function MarkdownFallbackView({ text }: { text: string }): JSX.Element {
82
+ return (
83
+ <div className="prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1">
84
+ <ReactMarkdown>{text}</ReactMarkdown>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ export function ResponseView({
90
+ responseText,
91
+ responseStatus,
92
+ streaming,
93
+ inputTokens,
94
+ outputTokens,
95
+ cacheCreationInputTokens,
96
+ cacheReadInputTokens,
97
+ apiFormat,
98
+ }: ResponseViewProps): JSX.Element {
99
+ if (responseText === null) {
100
+ return (
101
+ <div className="flex items-center gap-2 py-3">
102
+ <StatusIndicator status={responseStatus} />
103
+ <span className="text-xs text-muted-foreground italic">No response</span>
104
+ </div>
105
+ );
106
+ }
107
+
108
+ const isError = responseStatus !== null && responseStatus >= 400;
109
+
110
+ if (isError) {
111
+ return (
112
+ <div className="space-y-2">
113
+ <StatusIndicator status={responseStatus} />
114
+ <ErrorResponseView text={responseText} />
115
+ </div>
116
+ );
117
+ }
118
+
119
+ const parsed = parseResponse(responseText, apiFormat);
120
+
121
+ if (parsed !== null) {
122
+ return (
123
+ <div className="space-y-2">
124
+ <StatusIndicator status={responseStatus} />
125
+ {formatViewFor(apiFormat, parsed)}
126
+ </div>
127
+ );
128
+ }
129
+
130
+ return (
131
+ <div className="space-y-2">
132
+ <div className="flex items-center gap-2">
133
+ <StatusIndicator status={responseStatus} />
134
+ {streaming && (inputTokens !== null || outputTokens !== null) && (
135
+ <span className="flex items-center gap-1 text-muted-foreground text-xs">
136
+ <Zap className="size-3" />
137
+ <span className="font-mono tabular-nums">
138
+ {inputTokens !== null ? formatTokens(inputTokens) : "—"} in /{" "}
139
+ {outputTokens !== null ? formatTokens(outputTokens) : "—"} out
140
+ </span>
141
+ {cacheCreationInputTokens !== null &&
142
+ cacheCreationInputTokens !== undefined &&
143
+ cacheCreationInputTokens > 0 && (
144
+ <span className="font-mono tabular-nums text-emerald-400">
145
+ Cache +{formatTokens(cacheCreationInputTokens)}
146
+ </span>
147
+ )}
148
+ {cacheReadInputTokens !== null &&
149
+ cacheReadInputTokens !== undefined &&
150
+ cacheReadInputTokens > 0 && (
151
+ <span className="font-mono tabular-nums text-purple-400">
152
+ Cache ~{formatTokens(cacheReadInputTokens)}
153
+ </span>
154
+ )}
155
+ </span>
156
+ )}
157
+ </div>
158
+ <MarkdownFallbackView text={responseText} />
159
+ </div>
160
+ );
161
+ }
@@ -0,0 +1,171 @@
1
+ import { useState, useEffect, useMemo, type JSX } from "react";
2
+ import { ChevronDown, ChevronRight, Loader2 } from "lucide-react";
3
+ import { Badge } from "../ui/badge";
4
+ import { JsonViewer } from "../ui/json-viewer";
5
+ import type { StreamingChunk } from "../../proxy/schemas";
6
+
7
+ export type StreamingChunkSequenceProps = {
8
+ logId: number;
9
+ truncated?: boolean;
10
+ };
11
+
12
+ type ChunkState =
13
+ | { status: "idle" }
14
+ | { status: "loading" }
15
+ | { status: "success"; chunks: StreamingChunk[] }
16
+ | { status: "error"; message: string };
17
+
18
+ type ChunkGroup = {
19
+ index: number;
20
+ chunks: StreamingChunk[];
21
+ };
22
+
23
+ export function StreamingChunkSequence({
24
+ logId,
25
+ truncated,
26
+ }: StreamingChunkSequenceProps): JSX.Element {
27
+ const [containerExpanded, setContainerExpanded] = useState(false);
28
+ const [chunkState, setChunkState] = useState<ChunkState>({ status: "idle" });
29
+ const [expandedIndices, setExpandedIndices] = useState<Set<number>>(new Set());
30
+
31
+ useEffect(() => {
32
+ if (!containerExpanded || chunkState.status !== "idle") return;
33
+
34
+ setChunkState({ status: "loading" });
35
+
36
+ fetch(`/api/logs/${logId}/chunks`)
37
+ .then((res) => {
38
+ if (!res.ok) {
39
+ return Promise.reject(new Error("Chunks not found"));
40
+ }
41
+ return res.json();
42
+ })
43
+ .then((data: { chunks: StreamingChunk[]; truncated?: boolean }) => {
44
+ setChunkState({ status: "success", chunks: data.chunks });
45
+ })
46
+ .catch(() => {
47
+ setChunkState({ status: "error", message: "Chunk data unavailable" });
48
+ });
49
+ }, [containerExpanded, logId, chunkState.status]);
50
+
51
+ const groups = useMemo<ChunkGroup[]>(() => {
52
+ if (chunkState.status !== "success") return [];
53
+
54
+ const map = new Map<number, StreamingChunk[]>();
55
+ for (const chunk of chunkState.chunks) {
56
+ const existing = map.get(chunk.index);
57
+ if (existing) {
58
+ existing.push(chunk);
59
+ } else {
60
+ map.set(chunk.index, [chunk]);
61
+ }
62
+ }
63
+
64
+ return Array.from(map.entries())
65
+ .map(([index, chunks]) => ({ index, chunks }))
66
+ .sort((a, b) => a.index - b.index);
67
+ }, [chunkState]);
68
+
69
+ const toggleIndex = (index: number) => {
70
+ setExpandedIndices((prev) => {
71
+ const next = new Set(prev);
72
+ if (next.has(index)) {
73
+ next.delete(index);
74
+ } else {
75
+ next.add(index);
76
+ }
77
+ return next;
78
+ });
79
+ };
80
+
81
+ function renderBody(): JSX.Element {
82
+ if (chunkState.status === "idle" || chunkState.status === "loading") {
83
+ return (
84
+ <div className="flex items-center gap-2 py-2 text-xs text-muted-foreground">
85
+ <Loader2 className="size-3 animate-spin" />
86
+ <span>Loading chunks...</span>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ if (chunkState.status === "error") {
92
+ return <div className="py-2 text-xs text-muted-foreground italic">{chunkState.message}</div>;
93
+ }
94
+
95
+ return (
96
+ <div className="p-2 space-y-1">
97
+ <div className="text-[10px] text-muted-foreground font-mono mb-2">
98
+ {groups.length} index group{groups.length !== 1 ? "s" : ""} available
99
+ </div>
100
+ {groups.map((group) => {
101
+ const isExpanded = expandedIndices.has(group.index);
102
+ return (
103
+ <div key={group.index} className="rounded border border-border bg-background">
104
+ <button
105
+ type="button"
106
+ className="flex items-center gap-2 w-full px-2 py-1.5 text-left hover:bg-muted/50 transition-colors cursor-pointer"
107
+ onClick={() => toggleIndex(group.index)}
108
+ >
109
+ {isExpanded ? (
110
+ <ChevronDown className="size-3 text-muted-foreground" />
111
+ ) : (
112
+ <ChevronRight className="size-3 text-muted-foreground" />
113
+ )}
114
+ <span className="text-[10px] text-muted-foreground font-mono">
115
+ [{group.index}] {group.chunks[0]?.type ?? ""}
116
+ </span>
117
+ <span className="text-[10px] text-muted-foreground font-mono">
118
+ {group.chunks.length} chunk{group.chunks.length !== 1 ? "s" : ""}
119
+ </span>
120
+ </button>
121
+ {isExpanded && (
122
+ <div className="px-2 pb-2 space-y-1">
123
+ {group.chunks.map((chunk) => (
124
+ <div key={chunk.index} className="rounded border border-border bg-muted/20 p-2">
125
+ <div className="flex items-center gap-2 mb-1">
126
+ <span className="text-[10px] text-muted-foreground font-mono">
127
+ +{chunk.timestamp}ms
128
+ </span>
129
+ <span className="text-[10px] text-muted-foreground font-mono">
130
+ {chunk.type}
131
+ </span>
132
+ </div>
133
+ <JsonViewer data={chunk} defaultExpandDepth={1} showCopy />
134
+ </div>
135
+ ))}
136
+ </div>
137
+ )}
138
+ </div>
139
+ );
140
+ })}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ return (
146
+ <div className="space-y-1">
147
+ <button
148
+ type="button"
149
+ className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
150
+ onClick={() => setContainerExpanded((v) => !v)}
151
+ >
152
+ {containerExpanded ? (
153
+ <ChevronDown className="size-3" />
154
+ ) : (
155
+ <ChevronRight className="size-3" />
156
+ )}
157
+ <span>Raw SSE Events</span>
158
+ <Badge variant="outline" className="text-[9px] px-1 py-0 h-4 font-mono ml-1">
159
+ {logId}
160
+ {truncated === true ? "+" : ""}
161
+ </Badge>
162
+ </button>
163
+
164
+ {containerExpanded === true ? (
165
+ <div className="rounded-md border border-border bg-muted/20 overflow-auto max-h-64">
166
+ {renderBody()}
167
+ </div>
168
+ ) : null}
169
+ </div>
170
+ );
171
+ }
@@ -0,0 +1,139 @@
1
+ import { Brain, ChevronDown, ChevronRight, Terminal } from "lucide-react";
2
+ import { type JSX, useState } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import type { ResponseContentBlockType } from "../../../../proxy/schemas";
5
+ import { Badge } from "../../../ui/badge";
6
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../ui/collapsible";
7
+ import { JsonViewer, safeJsonValue } from "../../../ui/json-viewer";
8
+ import { ScrollArea } from "../../../ui/scroll-area";
9
+
10
+ function assertNever(_value: never): JSX.Element {
11
+ return <></>;
12
+ }
13
+
14
+ function SystemReminderBlock({ text }: { text: string }): JSX.Element {
15
+ const [open, setOpen] = useState(false);
16
+
17
+ return (
18
+ <Collapsible open={open} onOpenChange={setOpen}>
19
+ <CollapsibleTrigger className="flex items-center gap-1.5 py-0.5 cursor-pointer hover:opacity-80 transition-opacity group">
20
+ {open ? (
21
+ <ChevronDown className="size-3 text-muted-foreground" />
22
+ ) : (
23
+ <ChevronRight className="size-3 text-muted-foreground" />
24
+ )}
25
+ <span className="text-muted-foreground text-xs italic select-none opacity-60">
26
+ [system-reminder]
27
+ </span>
28
+ </CollapsibleTrigger>
29
+ <CollapsibleContent>
30
+ <div className="pl-4 pt-1">
31
+ <div className="prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1">
32
+ <ReactMarkdown>{text}</ReactMarkdown>
33
+ </div>
34
+ </div>
35
+ </CollapsibleContent>
36
+ </Collapsible>
37
+ );
38
+ }
39
+
40
+ export function TextBlock({ text }: { text: string }): JSX.Element {
41
+ if (text.includes("<system-reminder>")) {
42
+ return <SystemReminderBlock text={text} />;
43
+ }
44
+
45
+ return (
46
+ <div className="prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1">
47
+ <ReactMarkdown>{text}</ReactMarkdown>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ export function ThinkingBlock({ thinking }: { thinking: string }): JSX.Element {
53
+ const [open, setOpen] = useState(false);
54
+
55
+ return (
56
+ <Collapsible open={open} onOpenChange={setOpen}>
57
+ <div className="border-l-2 border-purple-500/40 my-1">
58
+ <CollapsibleTrigger className="flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-purple-500/5 transition-colors rounded-r-sm group">
59
+ <Brain className="size-3.5 text-purple-400 shrink-0" />
60
+ <span className="text-xs font-medium text-purple-400">Thinking</span>
61
+ <Badge
62
+ variant="ghost"
63
+ className="text-[10px] text-muted-foreground px-1.5 py-0 h-4 font-mono"
64
+ >
65
+ {thinking.length.toLocaleString()} chars
66
+ </Badge>
67
+ <span className="flex-1" />
68
+ {open ? (
69
+ <ChevronDown className="size-3 text-muted-foreground" />
70
+ ) : (
71
+ <ChevronRight className="size-3 text-muted-foreground" />
72
+ )}
73
+ </CollapsibleTrigger>
74
+ <CollapsibleContent>
75
+ <div className="px-3 pb-2">
76
+ <ScrollArea className="max-h-[60vh]">
77
+ <pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono leading-relaxed">
78
+ {thinking}
79
+ </pre>
80
+ </ScrollArea>
81
+ </div>
82
+ </CollapsibleContent>
83
+ </div>
84
+ </Collapsible>
85
+ );
86
+ }
87
+
88
+ export function ToolUseBlock({
89
+ name,
90
+ input,
91
+ }: {
92
+ name: string;
93
+ input: Record<string, unknown>;
94
+ }): JSX.Element {
95
+ const [open, setOpen] = useState(false);
96
+
97
+ return (
98
+ <Collapsible open={open} onOpenChange={setOpen}>
99
+ <div className="border-l-2 border-blue-500/40 my-1">
100
+ <CollapsibleTrigger className="flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-blue-500/5 transition-colors rounded-r-sm group">
101
+ <Terminal className="size-3.5 text-blue-400 shrink-0" />
102
+ <Badge variant="outline" className="text-[10px] font-mono px-1.5 py-0 h-4">
103
+ {name}
104
+ </Badge>
105
+ <span className="flex-1" />
106
+ {open ? (
107
+ <ChevronDown className="size-3 text-muted-foreground" />
108
+ ) : (
109
+ <ChevronRight className="size-3 text-muted-foreground" />
110
+ )}
111
+ </CollapsibleTrigger>
112
+ <CollapsibleContent>
113
+ <div className="px-3 pb-2">
114
+ <ScrollArea className="max-h-[60vh]">
115
+ <JsonViewer data={safeJsonValue(input)} defaultExpandDepth={2} />
116
+ </ScrollArea>
117
+ </div>
118
+ </CollapsibleContent>
119
+ </div>
120
+ </Collapsible>
121
+ );
122
+ }
123
+
124
+ export function ResponseContentBlockRenderer({
125
+ block,
126
+ }: {
127
+ block: ResponseContentBlockType;
128
+ }): JSX.Element {
129
+ switch (block.type) {
130
+ case "text":
131
+ return <TextBlock text={block.text} />;
132
+ case "thinking":
133
+ return <ThinkingBlock thinking={block.thinking} />;
134
+ case "tool_use":
135
+ return <ToolUseBlock name={block.name} input={block.input} />;
136
+ default:
137
+ return assertNever(block);
138
+ }
139
+ }
@@ -0,0 +1,64 @@
1
+ import { StopCircle, Zap } from "lucide-react";
2
+ import type { JSX } from "react";
3
+ import type { InspectorResponse } from "../../../../proxy/schemas";
4
+ import { formatTokens } from "../../../../lib/utils";
5
+ import { Badge } from "../../../ui/badge";
6
+ import { Separator } from "../../../ui/separator";
7
+ import { ResponseContentBlockRenderer } from "./ContentBlocks";
8
+
9
+ export function StructuredResponseViewAnthropic({
10
+ response,
11
+ }: {
12
+ response: InspectorResponse;
13
+ }): JSX.Element {
14
+ return (
15
+ <div className="space-y-3">
16
+ <div className="flex items-center gap-2 flex-wrap">
17
+ <Badge variant="secondary" className="text-[10px] px-1.5 py-0 h-5 font-mono">
18
+ {response.model}
19
+ </Badge>
20
+
21
+ {response.stop_reason !== null && (
22
+ <Badge
23
+ variant="outline"
24
+ className="text-[10px] px-1.5 py-0 h-5 font-mono flex items-center gap-1"
25
+ >
26
+ <StopCircle className="size-2.5" />
27
+ {response.stop_reason}
28
+ </Badge>
29
+ )}
30
+
31
+ <span className="flex items-center gap-1 text-muted-foreground text-xs">
32
+ <Zap className="size-3" />
33
+ <span className="font-mono tabular-nums">
34
+ {formatTokens(response.usage.input_tokens)} in /{" "}
35
+ {formatTokens(response.usage.output_tokens)} out
36
+ </span>
37
+ {response.usage.cache_creation_input_tokens !== undefined &&
38
+ response.usage.cache_creation_input_tokens > 0 && (
39
+ <span className="font-mono tabular-nums text-emerald-400">
40
+ Cache +{formatTokens(response.usage.cache_creation_input_tokens)}
41
+ </span>
42
+ )}
43
+ {response.usage.cache_read_input_tokens !== undefined &&
44
+ response.usage.cache_read_input_tokens > 0 && (
45
+ <span className="font-mono tabular-nums text-purple-400">
46
+ Cache ~{formatTokens(response.usage.cache_read_input_tokens)}
47
+ </span>
48
+ )}
49
+ </span>
50
+ </div>
51
+
52
+ <Separator className="opacity-50" />
53
+
54
+ <div className="space-y-2">
55
+ {response.content.map((block, i) => (
56
+ <ResponseContentBlockRenderer key={i} block={block} />
57
+ ))}
58
+ {response.content.length === 0 && (
59
+ <p className="text-xs text-muted-foreground italic">Empty response content</p>
60
+ )}
61
+ </div>
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,24 @@
1
+ import type { JSX } from "react";
2
+ import type { InspectorResponse, OpenAIResponse } from "../../../proxy/schemas";
3
+ import { StructuredResponseViewAnthropic } from "./anthropic/ResponseView";
4
+ import { OpenAIResponseView } from "./openai/ResponseView";
5
+
6
+ export { StructuredResponseViewAnthropic } from "./anthropic/ResponseView";
7
+ export { OpenAIResponseView } from "./openai/ResponseView";
8
+ export { ResponseContentBlockRenderer } from "./anthropic/ContentBlocks";
9
+
10
+ function isOpenAIResponse(resp: unknown): resp is OpenAIResponse {
11
+ if (typeof resp !== "object" || resp === null) return false;
12
+ const desc = Object.getOwnPropertyDescriptor(resp, "object");
13
+ return desc?.value === "chat.completion";
14
+ }
15
+
16
+ export function formatViewFor(
17
+ apiFormat: "anthropic" | "openai" | "unknown" | undefined,
18
+ response: InspectorResponse | OpenAIResponse,
19
+ ): JSX.Element {
20
+ if (isOpenAIResponse(response)) {
21
+ return <OpenAIResponseView response={response} />;
22
+ }
23
+ return <StructuredResponseViewAnthropic response={response} />;
24
+ }
@@ -0,0 +1,80 @@
1
+ import { StopCircle, Zap } from "lucide-react";
2
+ import type { JSX } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import type { OpenAIResponse } from "../../../../proxy/schemas";
5
+ import { formatTokens } from "../../../../lib/utils";
6
+ import { Badge } from "../../../ui/badge";
7
+ import { Separator } from "../../../ui/separator";
8
+
9
+ export function OpenAIResponseView({ response }: { response: OpenAIResponse }): JSX.Element {
10
+ const choice = response.choices[0];
11
+ const message = choice?.message;
12
+
13
+ return (
14
+ <div className="space-y-3">
15
+ <div className="flex items-center gap-2 flex-wrap">
16
+ <Badge variant="secondary" className="text-[10px] px-1.5 py-0 h-5 font-mono">
17
+ {response.model}
18
+ </Badge>
19
+
20
+ {choice?.finish_reason !== null && choice?.finish_reason !== undefined && (
21
+ <Badge
22
+ variant="outline"
23
+ className="text-[10px] px-1.5 py-0 h-5 font-mono flex items-center gap-1"
24
+ >
25
+ <StopCircle className="size-2.5" />
26
+ {choice.finish_reason}
27
+ </Badge>
28
+ )}
29
+
30
+ <span className="flex items-center gap-1 text-muted-foreground text-xs">
31
+ <Zap className="size-3" />
32
+ <span className="font-mono tabular-nums">
33
+ {formatTokens(response.usage.prompt_tokens)} in /{" "}
34
+ {formatTokens(response.usage.completion_tokens)} out
35
+ </span>
36
+ </span>
37
+ </div>
38
+
39
+ <Separator className="opacity-50" />
40
+
41
+ <div className="space-y-2">
42
+ {message?.reasoning_content !== null &&
43
+ message?.reasoning_content !== undefined &&
44
+ message.reasoning_content.length > 0 && (
45
+ <div className="border border-purple-500/30 rounded-md p-3 bg-purple-500/5">
46
+ <div className="text-xs text-purple-400 font-mono mb-1">thinking</div>
47
+ <div className="text-sm text-purple-200 whitespace-pre-wrap">
48
+ {message.reasoning_content}
49
+ </div>
50
+ </div>
51
+ )}
52
+ {message?.content !== null &&
53
+ message?.content !== undefined &&
54
+ message.content.length > 0 && (
55
+ <div className="prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1">
56
+ <ReactMarkdown>{message.content}</ReactMarkdown>
57
+ </div>
58
+ )}
59
+ {message?.function_call !== null && message?.function_call !== undefined && (
60
+ <div className="border border-blue-500/30 rounded-md p-3 bg-blue-500/5">
61
+ <div className="text-xs text-blue-400 font-mono mb-1">function_call</div>
62
+ <div className="font-mono text-xs">
63
+ <span className="text-blue-300">{message.function_call.name}</span>
64
+ <span className="text-muted-foreground">({message.function_call.arguments})</span>
65
+ </div>
66
+ </div>
67
+ )}
68
+ {(message?.content === null ||
69
+ message?.content === undefined ||
70
+ message.content.length === 0) &&
71
+ (message?.reasoning_content === null ||
72
+ message?.reasoning_content === undefined ||
73
+ message.reasoning_content.length === 0) &&
74
+ (message?.function_call === null || message?.function_call === undefined) && (
75
+ <p className="text-xs text-muted-foreground italic">Empty response content</p>
76
+ )}
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,8 @@
1
+ export { ConversationGroup } from "./ConversationGroup";
2
+ export {
3
+ ConversationHeader,
4
+ getConversationId,
5
+ groupLogsByConversation,
6
+ } from "./ConversationHeader";
7
+ export type { ConversationGroupData } from "./ConversationHeader";
8
+ export { LogEntry } from "./LogEntry";