@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DCg6S2cl.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-DsU6QETm.js +115 -0
- package/.output/public/assets/{ReplayDialog-TTTVCMe9.js → ReplayDialog-ClVgjSDE.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-c8ru0pS-.js → RequestAnatomy-BhqvLQp9.js} +1 -1
- package/.output/public/assets/ResponseView-BZAJoK6B.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-CaORmoKX.js +1 -0
- package/.output/public/assets/_sessionId-BHqywvM3.js +1 -0
- package/.output/public/assets/index-B_LPYuM0.js +1 -0
- package/.output/public/assets/{main-CKYe6UUv.js → main-CI3HFEcV.js} +2 -2
- package/.output/server/{_sessionId-CkwhOzdS.mjs → _sessionId-DQ0ljHQ8.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BHDiuYMw.mjs → CompareDrawer-nkVa9epn.mjs} +4 -3
- package/.output/server/_ssr/{ProxyViewerContainer-0c2_DBMX.mjs → ProxyViewerContainer-CXA7iH3H.mjs} +117 -45
- package/.output/server/_ssr/{ReplayDialog-CTjwzDDH.mjs → ReplayDialog-CK71-ucq.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-D36oujv2.mjs → RequestAnatomy-DDCUJJ3X.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-ClZ3oODE.mjs → ResponseView-C6ZA7V3B.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Ck5l4p1m.mjs → StreamingChunkSequence-DW5v_o6G.mjs} +2 -2
- package/.output/server/_ssr/{index-DKBYH0TC.mjs → index-Ckex98yq.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D4I3ZyVS.mjs → router-ChJIVv7-.mjs} +3 -3
- package/.output/server/{_tanstack-start-manifest_v-D4vSn_wD.mjs → _tanstack-start-manifest_v-CJ4__weU.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +5 -2
- package/src/components/providers/ProviderCard.tsx +6 -3
- package/src/components/providers/ProviderForm.tsx +6 -3
- package/src/components/providers/ProvidersPanel.tsx +6 -1
- package/src/components/providers/SettingsDialog.tsx +5 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
- package/src/components/proxy-viewer/LogEntry.tsx +8 -5
- package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
- package/src/components/ui/json-viewer.tsx +7 -2
- package/src/lib/clipboard.ts +67 -0
- package/.output/public/assets/CompareDrawer-p0uYxW7_.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-BGqeiYKD.js +0 -115
- package/.output/public/assets/ResponseView-D59jTMy4.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-UlAMVF9j.js +0 -1
- package/.output/public/assets/_sessionId-EHPexdPl.js +0 -1
- 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
|
|
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
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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={
|
|
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
|
|
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
|
|
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
|
|
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};
|