@tonyclaw/agent-inspector 2.0.40 → 2.0.41

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 (39) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-DCg6S2cl.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-DsU6QETm.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-TTTVCMe9.js → ReplayDialog-ClVgjSDE.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-c8ru0pS-.js → RequestAnatomy-BhqvLQp9.js} +1 -1
  6. package/.output/public/assets/ResponseView-BZAJoK6B.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-CaORmoKX.js +1 -0
  8. package/.output/public/assets/_sessionId-BHqywvM3.js +1 -0
  9. package/.output/public/assets/index-B_LPYuM0.js +1 -0
  10. package/.output/public/assets/{main-CKYe6UUv.js → main-CI3HFEcV.js} +2 -2
  11. package/.output/server/{_sessionId-CkwhOzdS.mjs → _sessionId-DQ0ljHQ8.mjs} +2 -2
  12. package/.output/server/_ssr/{CompareDrawer-BHDiuYMw.mjs → CompareDrawer-nkVa9epn.mjs} +4 -3
  13. package/.output/server/_ssr/{ProxyViewerContainer-0c2_DBMX.mjs → ProxyViewerContainer-CXA7iH3H.mjs} +117 -45
  14. package/.output/server/_ssr/{ReplayDialog-CTjwzDDH.mjs → ReplayDialog-CK71-ucq.mjs} +3 -3
  15. package/.output/server/_ssr/{RequestAnatomy-D36oujv2.mjs → RequestAnatomy-DDCUJJ3X.mjs} +2 -2
  16. package/.output/server/_ssr/{ResponseView-ClZ3oODE.mjs → ResponseView-C6ZA7V3B.mjs} +2 -2
  17. package/.output/server/_ssr/{StreamingChunkSequence-Ck5l4p1m.mjs → StreamingChunkSequence-DW5v_o6G.mjs} +2 -2
  18. package/.output/server/_ssr/{index-DKBYH0TC.mjs → index-Ckex98yq.mjs} +2 -2
  19. package/.output/server/_ssr/index.mjs +2 -2
  20. package/.output/server/_ssr/{router-D4I3ZyVS.mjs → router-ChJIVv7-.mjs} +3 -3
  21. package/.output/server/{_tanstack-start-manifest_v-D4vSn_wD.mjs → _tanstack-start-manifest_v-CJ4__weU.mjs} +1 -1
  22. package/.output/server/index.mjs +59 -59
  23. package/package.json +1 -1
  24. package/src/components/ProxyViewer.tsx +5 -2
  25. package/src/components/providers/ProviderCard.tsx +6 -3
  26. package/src/components/providers/ProviderForm.tsx +6 -3
  27. package/src/components/providers/ProvidersPanel.tsx +6 -1
  28. package/src/components/providers/SettingsDialog.tsx +5 -2
  29. package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
  30. package/src/components/proxy-viewer/LogEntry.tsx +8 -5
  31. package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
  32. package/src/components/ui/json-viewer.tsx +7 -2
  33. package/src/lib/clipboard.ts +67 -0
  34. package/.output/public/assets/CompareDrawer-p0uYxW7_.js +0 -1
  35. package/.output/public/assets/ProxyViewerContainer-BGqeiYKD.js +0 -115
  36. package/.output/public/assets/ResponseView-D59jTMy4.js +0 -1
  37. package/.output/public/assets/StreamingChunkSequence-UlAMVF9j.js +0 -1
  38. package/.output/public/assets/_sessionId-EHPexdPl.js +0 -1
  39. package/.output/public/assets/index-CZP-XfpB.js +0 -1
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  import type { CapturedLog } from "../contracts";
15
15
  import { exportLogsAsZip, type ExportMode } from "../lib/export-logs";
16
+ import { copyTextToClipboard } from "../lib/clipboard";
16
17
  import type { CaptureMode, TimeDisplayFormat } from "../lib/runtimeConfig";
17
18
  import { formatTimestampRange } from "../lib/timeDisplay";
18
19
  import { cn, formatContextWindowTokens, formatTokens } from "../lib/utils";
@@ -276,7 +277,8 @@ function CopyableCommand({ command }: { command: string }): JSX.Element {
276
277
  const [copied, setCopied] = useState(false);
277
278
 
278
279
  const handleCopy = useCallback(() => {
279
- void window.navigator.clipboard.writeText(command).then(() => {
280
+ void copyTextToClipboard(command).then((success) => {
281
+ if (!success) return;
280
282
  setCopied(true);
281
283
  setTimeout(() => setCopied(false), 2000);
282
284
  });
@@ -557,7 +559,8 @@ function SessionContextBar({
557
559
  const userAgent = useMemo(() => getFirstUserAgent(logs), [logs]);
558
560
 
559
561
  const handleCopyLink = useCallback(() => {
560
- void window.navigator.clipboard.writeText(window.location.href).then(() => {
562
+ void copyTextToClipboard(window.location.href).then((success) => {
563
+ if (!success) return;
561
564
  setCopied(true);
562
565
  setTimeout(() => setCopied(false), 2000);
563
566
  });
@@ -24,6 +24,7 @@ import {
24
24
  ChevronRight,
25
25
  } from "lucide-react";
26
26
  import type { ProviderConfig } from "../../proxy/providers";
27
+ import { copyTextToClipboard } from "../../lib/clipboard";
27
28
  import { maskApiKey } from "../../lib/mask";
28
29
  import {
29
30
  findProviderModelMetadata,
@@ -422,9 +423,11 @@ export function ProviderCard({
422
423
  const [showModelResults, setShowModelResults] = useState(false);
423
424
 
424
425
  function handleCopy() {
425
- navigator.clipboard.writeText(provider.apiKey).catch(() => {});
426
- setCopied(true);
427
- setTimeout(() => setCopied(false), 2000);
426
+ void copyTextToClipboard(provider.apiKey).then((success) => {
427
+ if (!success) return;
428
+ setCopied(true);
429
+ setTimeout(() => setCopied(false), 2000);
430
+ });
428
431
  }
429
432
 
430
433
  // Get docs URL: use provider.apiDocsUrl if configured, otherwise check KNOWN_PROVIDER_DOCS
@@ -4,6 +4,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
4
4
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
5
5
  import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
6
6
  import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
7
+ import { copyTextToClipboard } from "../../lib/clipboard";
7
8
  import { maskApiKey } from "../../lib/mask";
8
9
  import { formatContextWindowInput, parseContextWindowTokensInput } from "../../lib/utils";
9
10
 
@@ -316,9 +317,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
316
317
  provider?.modelMetadata?.some((metadata) => metadata.contextWindow !== undefined) ?? false;
317
318
 
318
319
  function handleCopy() {
319
- navigator.clipboard.writeText(apiKey).catch(() => {});
320
- setCopied(true);
321
- setTimeout(() => setCopied(false), 2000);
320
+ void copyTextToClipboard(apiKey).then((success) => {
321
+ if (!success) return;
322
+ setCopied(true);
323
+ setTimeout(() => setCopied(false), 2000);
324
+ });
322
325
  }
323
326
 
324
327
  useEffect(() => {
@@ -8,6 +8,7 @@ import { ConfirmDialog } from "../ui/confirm-dialog";
8
8
  import { ProviderCard } from "./ProviderCard";
9
9
  import { ProviderForm } from "./ProviderForm";
10
10
  import { parseJsonResponse, readApiError } from "../../lib/apiClient";
11
+ import { copyTextToClipboard } from "../../lib/clipboard";
11
12
  import {
12
13
  ProviderConfigSchema,
13
14
  type ProviderConfig,
@@ -614,12 +615,16 @@ export function ProvidersPanel({
614
615
  <button
615
616
  type="button"
616
617
  onClick={() => {
617
- void window.navigator.clipboard.writeText(configPath).then(() => {
618
+ void copyTextToClipboard(configPath).then((success) => {
619
+ if (!success) return;
618
620
  setConfigPathCopied(true);
619
621
  setTimeout(() => setConfigPathCopied(false), 2000);
620
622
  });
621
623
  }}
622
624
  className="shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors"
625
+ aria-label={
626
+ configPathCopied ? "Copied config file path" : "Copy config file path"
627
+ }
623
628
  >
624
629
  {configPathCopied ? (
625
630
  <Check className="size-3 text-green-500" />
@@ -7,6 +7,7 @@ import { Button } from "../ui/button";
7
7
  import { ProvidersPanel } from "./ProvidersPanel";
8
8
  import { useProviders } from "../../lib/useProviders";
9
9
  import { useStripConfig } from "../../lib/useStripConfig";
10
+ import { copyTextToClipboard } from "../../lib/clipboard";
10
11
  import {
11
12
  MAX_PROVIDER_TEST_TIMEOUT_SECONDS,
12
13
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
@@ -171,7 +172,8 @@ function StorageSettingsTab(): JSX.Element {
171
172
  }, [refresh]);
172
173
 
173
174
  const handleCopy = useCallback((id: string, value: string) => {
174
- void window.navigator.clipboard.writeText(value).then(() => {
175
+ void copyTextToClipboard(value).then((success) => {
176
+ if (!success) return;
175
177
  setCopiedId(id);
176
178
  setTimeout(() => setCopiedId(null), 1600);
177
179
  });
@@ -314,7 +316,8 @@ function OnboardingSettingsTab(): JSX.Element {
314
316
  );
315
317
 
316
318
  const handleCopy = useCallback((id: string, value: string) => {
317
- void window.navigator.clipboard.writeText(value).then(() => {
319
+ void copyTextToClipboard(value).then((success) => {
320
+ if (!success) return;
318
321
  setCopiedId(id);
319
322
  setTimeout(() => setCopiedId(null), 1600);
320
323
  });
@@ -13,6 +13,7 @@ import {
13
13
  X,
14
14
  } from "lucide-react";
15
15
  import { cn, formatTokens } from "../../lib/utils";
16
+ import { copyTextToClipboard } from "../../lib/clipboard";
16
17
  import type { CapturedLog } from "../../contracts";
17
18
  import {
18
19
  type DiffOp,
@@ -656,7 +657,8 @@ export function CompareDrawer({ left, right, onClose }: CompareDrawerProps): JSX
656
657
  const [copiedPath, setCopiedPath] = useState<string | null>(null);
657
658
  const copyResetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
658
659
  const onCopyPath = (path: string) => {
659
- void window.navigator.clipboard.writeText(path).then(() => {
660
+ void copyTextToClipboard(path).then((success) => {
661
+ if (!success) return;
660
662
  setCopiedPath(path);
661
663
  if (copyResetTimer.current !== null) clearTimeout(copyResetTimer.current);
662
664
  copyResetTimer.current = setTimeout(() => setCopiedPath(null), 1500);
@@ -1,6 +1,6 @@
1
1
  import { AlertTriangle, GitCompareArrows } from "lucide-react";
2
2
  import { Suspense, type JSX } from "react";
3
- import { useEffect, useMemo, useRef, useState, memo } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
4
4
  import type { CapturedLog } from "../../contracts";
5
5
  import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
6
6
  import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
@@ -308,9 +308,12 @@ export const LogEntry = memo(function ({
308
308
  },
309
309
  });
310
310
 
311
- useEffect(() => {
312
- if (!expanded) return;
313
- setActiveTab(anatomySegments !== null ? "anatomy" : "request");
311
+ const handleToggleExpanded = useCallback(() => {
312
+ const nextExpanded = !expanded;
313
+ if (nextExpanded) {
314
+ setActiveTab(anatomySegments !== null ? "anatomy" : "request");
315
+ }
316
+ setExpanded(nextExpanded);
314
317
  }, [anatomySegments, expanded]);
315
318
 
316
319
  useEffect(() => {
@@ -336,7 +339,7 @@ export const LogEntry = memo(function ({
336
339
  messageCount={requestAnalysis.messageCount}
337
340
  toolCount={requestAnalysis.toolCount}
338
341
  expanded={expanded}
339
- onToggle={() => setExpanded(!expanded)}
342
+ onToggle={handleToggleExpanded}
340
343
  cacheTrend={cacheTrend}
341
344
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
342
345
  timeDisplayFormat={timeDisplayFormat}
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState, type MouseEvent } from "react";
2
+ import { copyTextToClipboard } from "../../lib/clipboard";
2
3
 
3
4
  /**
4
5
  * Clipboard write with a 2s "Copied!" indicator. Returns null-safe
@@ -23,7 +24,8 @@ export function useCopyFeedback(text: string | null): {
23
24
  (event: MouseEvent) => {
24
25
  event.stopPropagation();
25
26
  if (text === null) return;
26
- void window.navigator.clipboard.writeText(text).then(() => {
27
+ void copyTextToClipboard(text).then((success) => {
28
+ if (!success) return;
27
29
  setCopied(true);
28
30
  if (timerRef.current !== null) clearTimeout(timerRef.current);
29
31
  timerRef.current = setTimeout(() => setCopied(false), 2000);
@@ -1,6 +1,7 @@
1
1
  import { Check, ChevronDown, ChevronRight, ChevronsDown, Copy } from "lucide-react";
2
2
  import { type JSX, memo, useEffect, useMemo, useState } from "react";
3
3
  import ReactMarkdown from "react-markdown";
4
+ import { copyTextToClipboard } from "../../lib/clipboard";
4
5
  import { cn } from "../../lib/utils";
5
6
  import { Button } from "./button";
6
7
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
@@ -134,7 +135,8 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
134
135
 
135
136
  function handleCopy(e: React.MouseEvent): void {
136
137
  e.stopPropagation();
137
- void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
138
+ void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
139
+ if (!success) return;
138
140
  setCopied(true);
139
141
  setTimeout(() => {
140
142
  setCopied(false);
@@ -147,6 +149,7 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
147
149
  type="button"
148
150
  onClick={handleCopy}
149
151
  className="inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors"
152
+ aria-label={copied ? "Copied JSON" : "Copy JSON"}
150
153
  title="Copy JSON"
151
154
  >
152
155
  {copied ? <Check className="size-3 text-green-500" /> : <Copy className="size-3" />}
@@ -159,7 +162,8 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
159
162
 
160
163
  function handleCopy(e: React.MouseEvent): void {
161
164
  e.stopPropagation();
162
- void window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
165
+ void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
166
+ if (!success) return;
163
167
  setCopied(true);
164
168
  setTimeout(() => {
165
169
  setCopied(false);
@@ -172,6 +176,7 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
172
176
  type="button"
173
177
  onClick={handleCopy}
174
178
  className="opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0"
179
+ aria-label={copied ? "Copied JSON value" : "Copy JSON value"}
175
180
  title="Copy to clipboard"
176
181
  >
177
182
  {copied ? (
@@ -0,0 +1,67 @@
1
+ function restoreSelection(selection: Selection | null, ranges: readonly Range[]): void {
2
+ if (selection === null) return;
3
+ selection.removeAllRanges();
4
+ for (const range of ranges) {
5
+ selection.addRange(range);
6
+ }
7
+ }
8
+
9
+ function readSelectionRanges(selection: Selection | null): Range[] {
10
+ if (selection === null) return [];
11
+ const ranges: Range[] = [];
12
+ for (let index = 0; index < selection.rangeCount; index++) {
13
+ ranges.push(selection.getRangeAt(index));
14
+ }
15
+ return ranges;
16
+ }
17
+
18
+ function copyWithTextareaFallback(text: string): boolean {
19
+ if (typeof document === "undefined") return false;
20
+
21
+ const activeElement = document.activeElement;
22
+ const selection = document.getSelection();
23
+ const ranges = readSelectionRanges(selection);
24
+ const textarea = document.createElement("textarea");
25
+
26
+ textarea.value = text;
27
+ textarea.setAttribute("readonly", "");
28
+ textarea.style.position = "fixed";
29
+ textarea.style.top = "0";
30
+ textarea.style.left = "-9999px";
31
+ textarea.style.opacity = "0";
32
+
33
+ document.body.appendChild(textarea);
34
+ textarea.focus();
35
+ textarea.select();
36
+ textarea.setSelectionRange(0, text.length);
37
+
38
+ let copied = false;
39
+ try {
40
+ copied = document.execCommand("copy");
41
+ } catch {
42
+ // Keep the false default.
43
+ } finally {
44
+ document.body.removeChild(textarea);
45
+ restoreSelection(selection, ranges);
46
+ if (typeof HTMLElement !== "undefined" && activeElement instanceof HTMLElement) {
47
+ activeElement.focus({ preventScroll: true });
48
+ }
49
+ }
50
+
51
+ return copied;
52
+ }
53
+
54
+ export async function copyTextToClipboard(text: string): Promise<boolean> {
55
+ const clipboard = globalThis.navigator?.clipboard;
56
+ if (clipboard !== undefined) {
57
+ try {
58
+ await clipboard.writeText(text);
59
+ return true;
60
+ } catch {
61
+ // Fall back below. Some embedded browsers expose Clipboard API but reject
62
+ // writes outside a secure context or without an explicit permission grant.
63
+ }
64
+ }
65
+
66
+ return copyWithTextareaFallback(text);
67
+ }
@@ -1 +0,0 @@
1
- import{r as h,j as t}from"./main-CKYe6UUv.js";import{c as X,g as O,r as P,a as q,X as Y,b as x,B as Z,f as $,R as ee,C as te,M as _,d as J,e as M,h as V,J as N,i as re,j as ne}from"./ProxyViewerContainer-BGqeiYKD.js";const se=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],ae=X("equal",se),oe="";function j(e){if(e.length===0)return oe;let r="";for(let n=0;n<e.length;n++){const s=e[n];s!==void 0&&(typeof s=="number"?r+=`[${s}]`:n===0?r+=s:r+=`.${s}`)}return r}function de(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e){if(typeof e=="string")try{return C(JSON.parse(e))}catch{return{kind:"primitive",value:e}}return C(e)}function C(e){if(e===null)return{kind:"primitive",value:null};if(typeof e=="string")return{kind:"primitive",value:e};if(typeof e=="number")return{kind:"primitive",value:e};if(typeof e=="boolean")return{kind:"primitive",value:e};if(Array.isArray(e))return{kind:"array",value:e.map(r=>C(r))};if(de(e)){const r={};for(const n of Object.keys(e).sort())r[n]=C(e[n]);return{kind:"object",value:r}}return{kind:"primitive",value:null}}function ie(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(E(r,n)){s.push({kind:"equal",path:d,value:r});return}if(r.kind!==n.kind){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="primitive"&&n.kind==="primitive"){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="object"&&n.kind==="object"){const o=Object.keys(r.value),a=Object.keys(n.value),m=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!m.has(i))s.push({kind:"removed",path:j([...e,i]),value:f});else{const p=n.value[i];if(p===void 0)continue;R([...e,i],f,p,s)}}for(const i of a){if(o.includes(i))continue;const f=n.value[i];f!==void 0&&s.push({kind:"added",path:j([...e,i]),value:f})}return}if(r.kind==="array"&&n.kind==="array"){const o=Math.min(r.value.length,n.value.length);for(let a=0;a<o;a++){const m=r.value[a],i=n.value[a];m===void 0||i===void 0||R([...e,a],m,i,s)}for(let a=o;a<n.value.length;a++){const m=n.value[a];m!==void 0&&s.push({kind:"added",path:j([...e,a]),value:m})}for(let a=o;a<r.value.length;a++){const m=r.value[a];m!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:m})}}}function E(e,r){if(e.kind!==r.kind)return!1;if(e.kind==="primitive"&&r.kind==="primitive")return e.value===r.value;if(e.kind==="array"&&r.kind==="array"){if(e.value.length!==r.value.length)return!1;for(let n=0;n<e.value.length;n++){const s=e.value[n],d=r.value[n];if(s===void 0||d===void 0||!E(s,d))return!1}return!0}if(e.kind==="object"&&r.kind==="object"){const n=Object.keys(e.value),s=Object.keys(r.value);if(n.length!==s.length)return!1;for(const d of n){const o=e.value[d],a=r.value[d];if(o===void 0||a===void 0||!E(o,a))return!1}return!0}return!1}function v(e,r=80){let n;switch(e.kind){case"primitive":n=e.value===null?"null":JSON.stringify(e.value);break;case"array":n=`[… ${e.value.length} items]`;break;case"object":n=`{… ${Object.keys(e.value).length} keys}`;break}return n.length>r&&(n=`${n.slice(0,r-1)}…`),n}function w(e,r=2){return JSON.stringify(S(e),null,r)}function S(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(S);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=S(s);return r}}}function K(e){if(e==="")return"";for(let r=e.length-1;r>=0;r--){const n=e[r];if(n==="."||n==="[")return e.substring(0,r)}return""}function L(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function le(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&L(s)){const d=K(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!L(a)||K(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let m=n;m<o;m++){const i=e[m];i!==void 0&&i.kind==="equal"&&a.push(i)}r.push({kind:"equal-run",ops:a}),n=o;continue}}s!==void 0&&r.push({kind:"single",op:s}),n++}return r}const B={added:{icon:J,accent:"text-emerald-600 dark:text-emerald-400",bg:"bg-emerald-500/5 hover:bg-emerald-500/10",border:"border-l-emerald-500",label:"ADDED"},removed:{icon:_,accent:"text-rose-600 dark:text-rose-400",bg:"bg-rose-500/5 hover:bg-rose-500/10",border:"border-l-rose-500",label:"REMOVED"},changed:{icon:M,accent:"text-amber-600 dark:text-amber-400",bg:"bg-amber-500/5 hover:bg-amber-500/10",border:"border-l-amber-500",label:"CHANGED"},equal:{icon:ae,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ce({ops:e,expanded:r,onToggle:n}){const s=e[0],d=e[e.length-1];if(s===void 0||d===void 0)return t.jsx("div",{className:"text-muted-foreground/40 text-xs",children:"—"});const o=s.path,a=d.path,m=e.length===1?o:`${o} … ${a}`,i=s.value.kind==="array"?`${e.length} equal arrays`:s.value.kind==="object"?`${e.length} equal objects`:"equal",f=B.equal;return t.jsxs("div",{className:x("border-l-4 rounded-sm",f.border,f.bg),children:[t.jsxs("button",{type:"button",onClick:n,className:"w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",children:[t.jsx(V,{className:x("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:x("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:m}),t.jsx("span",{className:x("text-[10px] uppercase tracking-wider shrink-0",f.accent),children:f.label}),t.jsxs("span",{className:"text-muted-foreground/60 shrink-0",children:["(",i,")"]})]}),r&&t.jsx("div",{className:"ml-5 mt-1 mb-2 space-y-2 pr-2",children:e.map(p=>t.jsxs("div",{className:"border border-border/50 rounded p-2 bg-muted/20",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:p.path}),t.jsx(N,{text:w(p.value),defaultExpandDepth:0})]},p.path))})]})}function ue({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=B[e.kind],m=a.icon,i=e.kind==="added"||e.kind==="removed"?e.value.kind==="object"||e.value.kind==="array":e.kind==="changed"?e.left.kind==="object"||e.left.kind==="array"||e.right.kind==="object"||e.right.kind==="array":!1,f=e.kind==="changed"?[{text:v(e.left,400),tone:"text-rose-700 dark:text-rose-300 line-through"},{text:v(e.right,400),tone:"text-emerald-700 dark:text-emerald-300"}]:e.kind==="removed"?[{text:v(e.value,400),tone:"text-rose-700 dark:text-rose-300 line-through"}]:e.kind==="added"?[{text:v(e.value,400),tone:"text-emerald-700 dark:text-emerald-300"}]:[{text:v(e.value,400),tone:"text-muted-foreground"}],p=n===e.path&&e.path!=="";return t.jsxs("div",{"data-diff-idx":r,"data-diff-kind":e.kind,className:x("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors",a.border,a.bg),children:[t.jsxs("button",{type:"button",onClick:o,disabled:!i,className:x("w-full flex items-center gap-2 text-xs text-left rounded-sm",i?"cursor-pointer":"cursor-default"),"aria-expanded":i?d:void 0,"aria-label":i?d?`Collapse ${e.path||"root"}`:`Expand ${e.path||"root"}`:void 0,children:[i?t.jsx(V,{className:x("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(m,{className:x("size-3.5 shrink-0",a.accent),strokeWidth:2.5}),t.jsx("span",{className:"font-mono truncate flex-1 min-w-0",title:e.path||"(root)",children:e.path===""?"(root)":e.path}),t.jsx("span",{className:x("text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",a.accent,e.kind==="equal"?"bg-muted/40":"bg-background/60"),children:a.label}),e.path!==""&&t.jsx("span",{role:"button",tabIndex:0,onClick:b=>{b.stopPropagation(),s(e.path)},onKeyDown:b=>{(b.key==="Enter"||b.key===" ")&&(b.stopPropagation(),b.preventDefault(),s(e.path))},className:x("shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",p?"text-emerald-500":"text-muted-foreground/50 hover:text-foreground hover:bg-muted"),"aria-label":p?"Copied":"Copy",title:p?"Copied!":"Copy",children:p?t.jsx(re,{className:"size-3"}):t.jsx(ne,{className:"size-3"})})]}),f.map((b,y)=>t.jsx("div",{className:x("font-mono text-xs mt-1 break-all pl-5",b.tone),children:b.text},y)),t.jsx("div",{className:"overflow-hidden transition-all duration-200",style:{maxHeight:d&&i?"2000px":"0"},"aria-hidden":!d,children:d&&i&&e.kind!=="equal"?t.jsx(me,{op:e}):null})]})}function me({op:e}){if(e.kind==="added"||e.kind==="removed")return t.jsx("div",{className:"pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20",children:t.jsx(N,{text:w(e.value),defaultExpandDepth:0})});const r=e.left.kind==="object"||e.left.kind==="array",n=e.right.kind==="object"||e.right.kind==="array";return!r&&!n?t.jsx("div",{className:"pl-5 mt-2 text-xs text-muted-foreground/70 italic",children:"Primitive values are shown inline above."}):t.jsxs("div",{className:"pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2",children:[t.jsxs("div",{className:"border border-rose-500/30 rounded p-2 bg-rose-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-rose-500 mb-1",children:"Old"}),t.jsx(N,{text:w(e.left),defaultExpandDepth:0})]}),t.jsxs("div",{className:"border border-emerald-500/30 rounded p-2 bg-emerald-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-emerald-500 mb-1",children:"New"}),t.jsx(N,{text:w(e.right),defaultExpandDepth:0})]})]})}function xe({counts:e,onJumpTo:r}){const n=e.added+e.removed+e.changed;return t.jsxs("div",{className:"px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap",children:[t.jsxs("span",{className:"text-muted-foreground font-medium",children:[n," ",n===1?"change":"changes"]}),t.jsxs("button",{type:"button",onClick:()=>r("removed"),disabled:e.removed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.removed>0?"border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.removed>0?"Jump to first removed":"No removals",children:[t.jsx(_,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.added>0?"border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.added>0?"Jump to first added":"No additions",children:[t.jsx(J,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.changed>0?"border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.changed>0?"Jump to first changed":"No changes",children:[t.jsx(M,{className:"size-3"}),e.changed," changed"]})]})}function fe({mode:e,onChange:r}){return t.jsxs("div",{className:"inline-flex rounded-md border border-border overflow-hidden",children:[t.jsxs("button",{type:"button",onClick:()=>r("unified"),"aria-pressed":e==="unified",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",e==="unified"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Unified view (single column, emphasized diffs)",children:[t.jsx(ee,{className:"size-3"}),"Unified"]}),t.jsxs("button",{type:"button",onClick:()=>r("split"),"aria-pressed":e==="split",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",e==="split"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Split view (path | left | right)",children:[t.jsx(te,{className:"size-3"}),"Split"]})]})}function A({log:e,side:r}){const n=q(e);return t.jsxs("div",{className:"flex-1 min-w-0 space-y-1 text-xs",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Z,{variant:"outline",className:x("text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",r==="left"?"border-rose-500/40 text-rose-400":"border-emerald-500/40 text-emerald-400"),children:r==="left"?"← Left":"Right →"}),t.jsxs("span",{className:"font-mono text-blue-400/80",children:["#",e.id]}),e.model!==null&&t.jsx("span",{className:"font-mono text-muted-foreground truncate",children:e.model})]}),t.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground font-mono",children:[e.cacheCreationInputTokens!==null&&e.cacheCreationInputTokens>0&&t.jsxs("span",{className:"text-emerald-400",children:["KV Cache +",$(e.cacheCreationInputTokens)]}),e.cacheReadInputTokens!==null&&e.cacheReadInputTokens>0&&t.jsxs("span",{className:"text-purple-400",children:["KV Cache ~",$(e.cacheReadInputTokens)]}),t.jsx("span",{className:"truncate",title:e.timestamp,children:e.timestamp})]}),t.jsxs("div",{className:"text-muted-foreground/70 font-mono truncate",title:n,children:["session: ",n]})]})}function ve({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=O(P(e)).analyzeRequest(e.rawRequestBody),c=O(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return ie(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>le(s),[s]),o=h.useMemo(()=>{let l=0,c=0,u=0;for(const g of d)if(g.kind==="single")switch(g.op.kind){case"added":l++;break;case"removed":c++;break;case"changed":u++;break}return{added:l,removed:c,changed:u}},[d]),[a,m]=h.useState(new Set),i=l=>{m(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})},[f,p]=h.useState(new Set),b=l=>{p(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})};h.useEffect(()=>{p(new Set)},[e.id,r.id]);const[y,F]=h.useState("unified"),T=h.useRef(null),[U,z]=h.useState(null),k=h.useRef(null),H=l=>{window.navigator.clipboard.writeText(l).then(()=>{z(l),k.current!==null&&clearTimeout(k.current),k.current=setTimeout(()=>z(null),1500)})};h.useEffect(()=>()=>{k.current!==null&&clearTimeout(k.current)},[]);const G=l=>{const c=d.findIndex(I=>I.kind==="single"&&I.op.kind===l);if(c===-1)return;const u=T.current;if(u===null)return;const g=u.querySelector(`[data-diff-idx="${c}"]`);g!==null&&g.scrollIntoView({behavior:"smooth",block:"center"})};h.useEffect(()=>{const l=u=>{u.key==="Escape"&&n()};document.addEventListener("keydown",l);const c=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.removeEventListener("keydown",l),document.body.style.overflow=c}},[n]);const Q=q(e)===q(r),W=s.length===1&&s[0]?.kind==="equal";return t.jsxs("div",{className:"fixed inset-0 z-50 flex justify-end",role:"dialog","aria-modal":"true","aria-label":"Compare two log requests",children:[t.jsx("button",{type:"button",onClick:n,"aria-label":"Close compare drawer",className:"absolute inset-0 bg-black/40 cursor-default",tabIndex:-1}),t.jsxs("div",{className:x("relative bg-background border-l border-border shadow-xl","w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"),onClick:l=>l.stopPropagation(),onKeyDown:l=>l.stopPropagation(),children:[t.jsxs("div",{className:"flex items-start gap-4 px-4 py-3 border-b border-border",children:[t.jsxs("div",{className:"flex-1 flex gap-4 min-w-0",children:[t.jsx(A,{log:e,side:"left"}),t.jsx(A,{log:r,side:"right"})]}),t.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[t.jsx(fe,{mode:y,onChange:F}),t.jsx("button",{type:"button",onClick:n,"aria-label":"Close",className:"p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",children:t.jsx(Y,{className:"size-4"})})]})]}),!Q&&t.jsx("div",{className:"px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border",children:"Heads up: the two selected logs are from different sessions."}),W?t.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm",children:"The two Request payloads are identical."}):t.jsxs(t.Fragment,{children:[t.jsx(xe,{counts:o,onJumpTo:G}),t.jsx("div",{ref:T,className:"flex-1 min-h-0 overflow-y-auto",children:y==="unified"?t.jsx("div",{className:"px-3 py-2 space-y-0.5",children:d.map((l,c)=>{if(l.kind==="equal-run")return t.jsx(ce,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(ue,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(pe,{grouped:d,left:e,right:r})})]})]})]})}function pe({grouped:e,left:r,right:n}){return t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs",children:[t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground",children:[t.jsx("span",{children:"Path"}),t.jsxs("span",{children:["Left (Log #",r.id,")"]}),t.jsxs("span",{children:["Right (Log #",n.id,")"]})]}),e.map((s,d)=>{if(s.kind==="equal-run")return t.jsxs("div",{className:"col-span-3 px-2 py-1 text-xs text-muted-foreground/60",children:[s.ops.length," equal siblings collapsed — switch to Unified to expand"]},d);const o=s.op;return o.kind==="equal"?t.jsxs("div",{className:"col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",children:[t.jsx("span",{className:"font-mono text-xs truncate",title:o.path,children:o.path}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)})]},d):o.kind==="added"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-emerald-300/90",children:["+ ",v(o.value,400)]})]},d):o.kind==="removed"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-rose-300/90 line-through",children:["− ",v(o.value,400)]})]},d):t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:o.path}),t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[t.jsx("div",{className:"font-mono text-rose-300/90 break-all line-through",children:v(o.left,400)}),t.jsx("div",{className:"font-mono text-emerald-300/90 break-all",children:v(o.right,400)})]})]},d)})]})}export{ve as CompareDrawer};