@elizaos/client 1.6.1-alpha.3 → 1.6.1-alpha.5
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/dist/assets/{main-C4q5_rtN.js → main-4tyUgNqd.js} +3 -3
- package/dist/assets/{main-C4q5_rtN.js.map → main-4tyUgNqd.js.map} +1 -1
- package/dist/assets/{main-BNtEiK3o.js → main-Bbs84AcL.js} +77 -63
- package/dist/assets/main-Bbs84AcL.js.map +1 -0
- package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
- package/dist/assets/react-vendor-DxnAFk-d.js +611 -0
- package/dist/assets/react-vendor-DxnAFk-d.js.map +1 -0
- package/dist/index.html +1 -1
- package/package.json +8 -4
- package/src/components/agent-prism/Avatar.tsx +164 -0
- package/src/components/agent-prism/Badge.tsx +109 -0
- package/src/components/agent-prism/Button.tsx +138 -0
- package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
- package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
- package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
- package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
- package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
- package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
- package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
- package/src/components/agent-prism/IconButton.tsx +75 -0
- package/src/components/agent-prism/PriceBadge.tsx +12 -0
- package/src/components/agent-prism/SearchInput.tsx +17 -0
- package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
- package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
- package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
- package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
- package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
- package/src/components/agent-prism/SpanStatus.tsx +79 -0
- package/src/components/agent-prism/Tabs.tsx +141 -0
- package/src/components/agent-prism/TextInput.tsx +142 -0
- package/src/components/agent-prism/TimestampBadge.tsx +28 -0
- package/src/components/agent-prism/TokensBadge.tsx +26 -0
- package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
- package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
- package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
- package/src/components/agent-prism/TraceViewer.tsx +476 -0
- package/src/components/agent-prism/TreeView.tsx +57 -0
- package/src/components/agent-prism/shared.ts +210 -0
- package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
- package/src/components/agent-sidebar.tsx +2 -2
- package/src/components/chat.tsx +8 -8
- package/src/lib/agent-prism-utils.ts +46 -0
- package/src/lib/eliza-span-adapter.ts +487 -0
- package/dist/assets/main-BNtEiK3o.js.map +0 -1
- package/dist/assets/react-vendor-pe76PXQl.js +0 -546
- package/dist/assets/react-vendor-pe76PXQl.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { SquareTerminal, Tags, ArrowRightLeft } from "lucide-react";
|
|
5
|
+
import { useState, type ReactElement, type ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
import type { AvatarProps } from "../Avatar";
|
|
8
|
+
|
|
9
|
+
import { Tabs, type TabItem } from "../Tabs";
|
|
10
|
+
import { DetailsViewAttributesTab } from "./DetailsViewAttributesTab";
|
|
11
|
+
import { DetailsViewHeader } from "./DetailsViewHeader";
|
|
12
|
+
import { DetailsViewInputOutputTab } from "./DetailsViewInputOutputTab";
|
|
13
|
+
import { DetailsViewMetrics } from "./DetailsViewMetrics";
|
|
14
|
+
import { DetailsViewRawDataTab } from "./DetailsViewRawDataTab";
|
|
15
|
+
|
|
16
|
+
type DetailsViewTab = "input-output" | "attributes" | "raw";
|
|
17
|
+
|
|
18
|
+
export interface DetailsViewProps {
|
|
19
|
+
/**
|
|
20
|
+
* The span data to display in the details view
|
|
21
|
+
*/
|
|
22
|
+
data: TraceSpan;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional avatar configuration for the header
|
|
26
|
+
*/
|
|
27
|
+
avatar?: AvatarProps;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The initially selected tab
|
|
31
|
+
*/
|
|
32
|
+
defaultTab?: DetailsViewTab;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional className for the root container
|
|
36
|
+
*/
|
|
37
|
+
className?: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for the copy button functionality
|
|
41
|
+
*/
|
|
42
|
+
copyButton?: {
|
|
43
|
+
isEnabled?: boolean;
|
|
44
|
+
onCopy?: (data: TraceSpan) => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Custom header actions to render
|
|
49
|
+
* Can be a ReactNode or a render function that receives the data
|
|
50
|
+
*/
|
|
51
|
+
headerActions?: ReactNode | ((data: TraceSpan) => ReactNode);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Optional custom header component to replace the default
|
|
55
|
+
*/
|
|
56
|
+
customHeader?: ReactNode | ((props: { data: TraceSpan }) => ReactNode);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Callback fired when the active tab changes
|
|
60
|
+
*/
|
|
61
|
+
onTabChange?: (tabValue: DetailsViewTab) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const DetailsView = ({
|
|
65
|
+
data,
|
|
66
|
+
avatar,
|
|
67
|
+
defaultTab,
|
|
68
|
+
className,
|
|
69
|
+
copyButton,
|
|
70
|
+
headerActions,
|
|
71
|
+
customHeader,
|
|
72
|
+
onTabChange,
|
|
73
|
+
}: DetailsViewProps): ReactElement => {
|
|
74
|
+
const [tab, setTab] = useState<DetailsViewTab>(defaultTab || "input-output");
|
|
75
|
+
|
|
76
|
+
const tabItems: TabItem<DetailsViewTab>[] = [
|
|
77
|
+
{
|
|
78
|
+
value: "input-output",
|
|
79
|
+
label: "In/Out",
|
|
80
|
+
icon: <ArrowRightLeft className="size-4" />,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
value: "attributes",
|
|
84
|
+
label: "Attributes",
|
|
85
|
+
icon: <Tags className="size-4" />,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
value: "raw",
|
|
89
|
+
label: "RAW",
|
|
90
|
+
icon: <SquareTerminal className="size-4" />,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
function handleTabChange(tabValue: DetailsViewTab) {
|
|
95
|
+
setTab(tabValue);
|
|
96
|
+
onTabChange?.(tabValue);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resolvedHeaderActions =
|
|
100
|
+
typeof headerActions === "function" ? headerActions(data) : headerActions;
|
|
101
|
+
|
|
102
|
+
const headerContent = customHeader ? (
|
|
103
|
+
typeof customHeader === "function" ? (
|
|
104
|
+
customHeader({ data })
|
|
105
|
+
) : (
|
|
106
|
+
customHeader
|
|
107
|
+
)
|
|
108
|
+
) : (
|
|
109
|
+
<DetailsViewHeader
|
|
110
|
+
data={data}
|
|
111
|
+
avatar={avatar}
|
|
112
|
+
copyButton={copyButton}
|
|
113
|
+
actions={resolvedHeaderActions}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
className={cn(
|
|
120
|
+
"min-w-0 rounded-lg border border-border bg-card p-4 shadow-sm",
|
|
121
|
+
className,
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
{headerContent}
|
|
125
|
+
|
|
126
|
+
<DetailsViewMetrics data={data} />
|
|
127
|
+
|
|
128
|
+
<Tabs
|
|
129
|
+
items={tabItems}
|
|
130
|
+
value={tab}
|
|
131
|
+
onValueChange={handleTabChange}
|
|
132
|
+
theme="underline"
|
|
133
|
+
defaultValue={defaultTab}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
{tab === "input-output" && <DetailsViewInputOutputTab data={data} />}
|
|
137
|
+
{tab === "attributes" && <DetailsViewAttributesTab data={data} />}
|
|
138
|
+
{tab === "raw" && <DetailsViewRawDataTab data={data} />}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { CollapsibleSection } from "../CollapsibleSection";
|
|
4
|
+
import { TextInput } from "../TextInput";
|
|
5
|
+
|
|
6
|
+
interface AttributesTabProps {
|
|
7
|
+
data: TraceSpan;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DetailsViewAttributesTab = ({ data }: AttributesTabProps) => (
|
|
11
|
+
<div className="space-y-6">
|
|
12
|
+
{(!data.attributes || data.attributes.length === 0) && (
|
|
13
|
+
<div className="p-6 text-center">
|
|
14
|
+
<p className="text-muted-foreground ">
|
|
15
|
+
No attributes available for this span.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
)}
|
|
19
|
+
|
|
20
|
+
{(data.attributes || []).map((attribute, index) => {
|
|
21
|
+
const value =
|
|
22
|
+
attribute.value.stringValue ||
|
|
23
|
+
attribute.value.intValue ||
|
|
24
|
+
attribute.value.boolValue?.toString() ||
|
|
25
|
+
"N/A";
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<CollapsibleSection
|
|
29
|
+
key={`${attribute.key}-${index}`}
|
|
30
|
+
title={attribute.key}
|
|
31
|
+
defaultOpen
|
|
32
|
+
>
|
|
33
|
+
<TextInput
|
|
34
|
+
id={`${data.id}-attribute-${index}`}
|
|
35
|
+
value={value}
|
|
36
|
+
disabled
|
|
37
|
+
readOnly
|
|
38
|
+
inputClassName="font-mono text-xs"
|
|
39
|
+
className="w-full"
|
|
40
|
+
/>
|
|
41
|
+
</CollapsibleSection>
|
|
42
|
+
);
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { Check, Copy } from "lucide-react";
|
|
4
|
+
import { useState, type ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
import { Avatar, type AvatarProps } from "../Avatar";
|
|
7
|
+
import { IconButton } from "../IconButton";
|
|
8
|
+
import { SpanStatus } from "../SpanStatus.tsx";
|
|
9
|
+
|
|
10
|
+
export interface DetailsViewHeaderProps {
|
|
11
|
+
data: TraceSpan;
|
|
12
|
+
avatar?: AvatarProps;
|
|
13
|
+
copyButton?: {
|
|
14
|
+
isEnabled?: boolean;
|
|
15
|
+
onCopy?: (data: TraceSpan) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Custom actions to render in the header
|
|
19
|
+
*/
|
|
20
|
+
actions?: ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Optional className for the header container
|
|
23
|
+
*/
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const DetailsViewHeader = ({
|
|
28
|
+
data,
|
|
29
|
+
avatar,
|
|
30
|
+
copyButton,
|
|
31
|
+
actions,
|
|
32
|
+
className = "mb-4 flex flex-wrap items-center gap-4",
|
|
33
|
+
}: DetailsViewHeaderProps) => {
|
|
34
|
+
const [hasCopied, setHasCopied] = useState(false);
|
|
35
|
+
|
|
36
|
+
const handleCopy = () => {
|
|
37
|
+
if (copyButton?.onCopy) {
|
|
38
|
+
copyButton.onCopy(data);
|
|
39
|
+
setHasCopied(true);
|
|
40
|
+
setTimeout(() => setHasCopied(false), 2000);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={className}>
|
|
46
|
+
<div className="flex items-center gap-1.5">
|
|
47
|
+
{avatar && <Avatar size="4" {...avatar} />}
|
|
48
|
+
|
|
49
|
+
<span className="text-base tracking-wide text-foreground ">
|
|
50
|
+
{data.title}
|
|
51
|
+
</span>
|
|
52
|
+
|
|
53
|
+
<div className="flex size-5 items-center justify-center">
|
|
54
|
+
<SpanStatus status={data.status} />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{copyButton && (
|
|
58
|
+
<IconButton
|
|
59
|
+
aria-label={
|
|
60
|
+
copyButton.isEnabled ? "Copy span details" : "Copy disabled"
|
|
61
|
+
}
|
|
62
|
+
variant="ghost"
|
|
63
|
+
onClick={handleCopy}
|
|
64
|
+
>
|
|
65
|
+
{hasCopied ? (
|
|
66
|
+
<Check className="size-3 text-muted-foreground" />
|
|
67
|
+
) : (
|
|
68
|
+
<Copy className="size-3 text-muted-foreground" />
|
|
69
|
+
)}
|
|
70
|
+
</IconButton>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{actions}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface DetailsViewHeaderActionsProps {
|
|
4
|
+
/**
|
|
5
|
+
* Custom actions to render in the header
|
|
6
|
+
*/
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Optional className for the actions container
|
|
10
|
+
*/
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const DetailsViewHeaderActions = ({
|
|
15
|
+
children,
|
|
16
|
+
className = "flex flex-wrap items-center gap-2",
|
|
17
|
+
}: DetailsViewHeaderActionsProps) => {
|
|
18
|
+
if (!children) return null;
|
|
19
|
+
|
|
20
|
+
return <div className={className}>{children}</div>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { Check, Copy } from "lucide-react";
|
|
4
|
+
import { useState, type ReactElement } from "react";
|
|
5
|
+
import JSONPretty from "react-json-pretty";
|
|
6
|
+
import colors from "tailwindcss/colors";
|
|
7
|
+
|
|
8
|
+
import { CollapsibleSection } from "../CollapsibleSection";
|
|
9
|
+
import { IconButton } from "../IconButton";
|
|
10
|
+
import { Tabs, type TabItem } from "../Tabs";
|
|
11
|
+
|
|
12
|
+
interface DetailsViewInputOutputTabProps {
|
|
13
|
+
data: TraceSpan;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type IOTab = "json" | "plain";
|
|
17
|
+
|
|
18
|
+
type IOSection = "Input" | "Output";
|
|
19
|
+
|
|
20
|
+
export const DetailsViewInputOutputTab = ({
|
|
21
|
+
data,
|
|
22
|
+
}: DetailsViewInputOutputTabProps): ReactElement => {
|
|
23
|
+
const hasInput = Boolean(data.input);
|
|
24
|
+
const hasOutput = Boolean(data.output);
|
|
25
|
+
|
|
26
|
+
if (!hasInput && !hasOutput) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="p-6 text-center">
|
|
29
|
+
<p className="text-muted-foreground ">
|
|
30
|
+
No input or output data available for this span.
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let parsedInput: string | null = null;
|
|
37
|
+
let parsedOutput: string | null = null;
|
|
38
|
+
|
|
39
|
+
if (typeof data.input === "string") {
|
|
40
|
+
try {
|
|
41
|
+
parsedInput = JSON.parse(data.input);
|
|
42
|
+
} catch {
|
|
43
|
+
parsedInput = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof data.output === "string") {
|
|
48
|
+
try {
|
|
49
|
+
parsedOutput = JSON.parse(data.output);
|
|
50
|
+
} catch {
|
|
51
|
+
parsedOutput = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="space-y-3">
|
|
57
|
+
{typeof data.input === "string" && (
|
|
58
|
+
<IOSection
|
|
59
|
+
section="Input"
|
|
60
|
+
content={data.input}
|
|
61
|
+
parsedContent={parsedInput}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{typeof data.output === "string" && (
|
|
66
|
+
<IOSection
|
|
67
|
+
section="Output"
|
|
68
|
+
content={data.output}
|
|
69
|
+
parsedContent={parsedOutput}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
interface IOSectionProps {
|
|
77
|
+
section: IOSection;
|
|
78
|
+
content: string;
|
|
79
|
+
parsedContent: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const IOSection = ({
|
|
83
|
+
section,
|
|
84
|
+
content,
|
|
85
|
+
parsedContent,
|
|
86
|
+
}: IOSectionProps): ReactElement => {
|
|
87
|
+
const [tab, setTab] = useState<IOTab>("plain");
|
|
88
|
+
const [open, setOpen] = useState(true);
|
|
89
|
+
|
|
90
|
+
const tabItems: TabItem<IOTab>[] = [
|
|
91
|
+
{
|
|
92
|
+
value: "json",
|
|
93
|
+
label: "JSON",
|
|
94
|
+
disabled: !parsedContent,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "plain",
|
|
98
|
+
label: "Plain",
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<CollapsibleSection
|
|
104
|
+
title={section}
|
|
105
|
+
defaultOpen
|
|
106
|
+
onOpenChange={setOpen}
|
|
107
|
+
rightContent={
|
|
108
|
+
open ? (
|
|
109
|
+
<Tabs<IOTab>
|
|
110
|
+
items={tabItems}
|
|
111
|
+
defaultValue="plain"
|
|
112
|
+
value={tab}
|
|
113
|
+
onValueChange={setTab}
|
|
114
|
+
theme="pill"
|
|
115
|
+
onClick={(event) => event.stopPropagation()}
|
|
116
|
+
/>
|
|
117
|
+
) : null
|
|
118
|
+
}
|
|
119
|
+
triggerClassName="min-h-16"
|
|
120
|
+
>
|
|
121
|
+
<IOContent
|
|
122
|
+
content={content}
|
|
123
|
+
section={section}
|
|
124
|
+
tab={tab}
|
|
125
|
+
parsedContent={parsedContent}
|
|
126
|
+
/>
|
|
127
|
+
</CollapsibleSection>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
interface IOContentProps extends Omit<IOSectionProps, "title"> {
|
|
132
|
+
tab: IOTab;
|
|
133
|
+
parsedContent: string | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const IOContent = ({
|
|
137
|
+
tab,
|
|
138
|
+
content,
|
|
139
|
+
section,
|
|
140
|
+
parsedContent,
|
|
141
|
+
}: IOContentProps): ReactElement => {
|
|
142
|
+
if (!content) {
|
|
143
|
+
return (
|
|
144
|
+
<p className="p-3 text-sm italic text-muted-foreground ">
|
|
145
|
+
No data available
|
|
146
|
+
</p>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="relative rounded-lg border border-border ">
|
|
152
|
+
<CopyButton section={section} content={content} />
|
|
153
|
+
|
|
154
|
+
{tab === "json" && (
|
|
155
|
+
<>
|
|
156
|
+
{parsedContent ? (
|
|
157
|
+
<JSONPretty
|
|
158
|
+
booleanStyle="color: hsl(var(--primary));"
|
|
159
|
+
className="overflow-x-auto rounded-xl p-4 text-left"
|
|
160
|
+
data={parsedContent}
|
|
161
|
+
id={`json-pretty-${section}`}
|
|
162
|
+
keyStyle="color: hsl(var(--primary));"
|
|
163
|
+
mainStyle="color: hsl(var(--muted-foreground)); font-size: 12px;"
|
|
164
|
+
stringStyle="color: hsl(var(--chart-2));"
|
|
165
|
+
valueStyle="color: hsl(var(--chart-1));"
|
|
166
|
+
/>
|
|
167
|
+
) : (
|
|
168
|
+
<div className="p-4 text-sm text-muted-foreground">
|
|
169
|
+
Invalid JSON format
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{tab === "plain" && (
|
|
176
|
+
<div className="rounded-lg bg-muted/50 p-4">
|
|
177
|
+
<pre className="overflow-x-auto whitespace-pre-wrap text-left font-mono text-xs text-foreground">
|
|
178
|
+
{content}
|
|
179
|
+
</pre>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
type CopyButtonProps = {
|
|
187
|
+
section: IOSection;
|
|
188
|
+
content: string;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const CopyButton = ({ section, content }: CopyButtonProps) => {
|
|
192
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
193
|
+
|
|
194
|
+
const onClick = () => {
|
|
195
|
+
navigator.clipboard.writeText(content);
|
|
196
|
+
setIsCopied(true);
|
|
197
|
+
setTimeout(() => setIsCopied(false), 2000);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<IconButton
|
|
202
|
+
onClick={onClick}
|
|
203
|
+
aria-label={isCopied ? `${section} Data Copied` : `Copy ${section} Data`}
|
|
204
|
+
variant="ghost"
|
|
205
|
+
className="absolute right-1.5 top-1.5"
|
|
206
|
+
>
|
|
207
|
+
{isCopied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
|
208
|
+
</IconButton>
|
|
209
|
+
);
|
|
210
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { getDurationMs, formatDuration } from "@evilmartians/agent-prism-data";
|
|
4
|
+
import { Coins } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { Badge } from "../Badge";
|
|
7
|
+
import {
|
|
8
|
+
getSpanCategoryIcon,
|
|
9
|
+
getSpanCategoryLabel,
|
|
10
|
+
getSpanCategoryTheme,
|
|
11
|
+
} from "../shared.ts";
|
|
12
|
+
import { TimestampBadge } from "../TimestampBadge.tsx";
|
|
13
|
+
|
|
14
|
+
interface DetailsViewMetricsProps {
|
|
15
|
+
data: TraceSpan;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const DetailsViewMetrics = ({ data }: DetailsViewMetricsProps) => {
|
|
19
|
+
const Icon = getSpanCategoryIcon(data.type);
|
|
20
|
+
const durationMs = getDurationMs(data);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="mb-4 flex flex-wrap items-center justify-start gap-1">
|
|
24
|
+
<Badge
|
|
25
|
+
iconStart={<Icon className="size-2.5" />}
|
|
26
|
+
theme={getSpanCategoryTheme(data.type)}
|
|
27
|
+
size="4"
|
|
28
|
+
label={getSpanCategoryLabel(data.type)}
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
{typeof data.tokensCount === "number" && (
|
|
32
|
+
<Badge
|
|
33
|
+
iconStart={<Coins className="size-2.5" />}
|
|
34
|
+
theme="gray"
|
|
35
|
+
size="4"
|
|
36
|
+
label={data.tokensCount}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
{typeof data.cost === "number" && (
|
|
41
|
+
<Badge theme="gray" size="4" label={`$ ${data.cost.toFixed(4)}`} />
|
|
42
|
+
)}
|
|
43
|
+
|
|
44
|
+
<span className="text-xs text-muted-foreground">
|
|
45
|
+
LATENCY: {formatDuration(durationMs)}
|
|
46
|
+
</span>
|
|
47
|
+
|
|
48
|
+
{typeof data.startTime === "number" && (
|
|
49
|
+
<TimestampBadge timestamp={data.startTime} />
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import JSONPretty from "react-json-pretty";
|
|
4
|
+
|
|
5
|
+
interface RawDataTabProps {
|
|
6
|
+
data: TraceSpan;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const DetailsViewRawDataTab = ({ data }: RawDataTabProps) => (
|
|
10
|
+
<div className="pt-4">
|
|
11
|
+
<div className="rounded border border-border bg-transparent ">
|
|
12
|
+
<JSONPretty
|
|
13
|
+
booleanStyle="color: hsl(var(--primary));"
|
|
14
|
+
className="overflow-x-auto rounded-xl p-4 text-left"
|
|
15
|
+
data={data.raw}
|
|
16
|
+
id={`json-pretty-${data.id || "span-details"}`}
|
|
17
|
+
keyStyle="color: hsl(var(--primary));"
|
|
18
|
+
mainStyle="color: hsl(var(--muted-foreground)); font-size: 12px;"
|
|
19
|
+
stringStyle="color: hsl(var(--chart-2));"
|
|
20
|
+
valueStyle="color: hsl(var(--chart-1));"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
import type { ComponentSize } from "./shared";
|
|
6
|
+
|
|
7
|
+
type IconButtonSize = Extract<
|
|
8
|
+
ComponentSize,
|
|
9
|
+
"6" | "7" | "8" | "9" | "10" | "11" | "12" | "16"
|
|
10
|
+
>;
|
|
11
|
+
type IconButtonVariant = "default" | "ghost";
|
|
12
|
+
|
|
13
|
+
export type IconButtonProps = ComponentPropsWithRef<"button"> & {
|
|
14
|
+
/**
|
|
15
|
+
* The size of the icon button
|
|
16
|
+
*/
|
|
17
|
+
size?: IconButtonSize;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The visual variant of the icon button
|
|
21
|
+
*/
|
|
22
|
+
variant?: IconButtonVariant;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Accessible label for screen readers
|
|
26
|
+
* Required for accessibility compliance
|
|
27
|
+
*/
|
|
28
|
+
"aria-label": string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const sizeClasses: Record<IconButtonSize, string> = {
|
|
32
|
+
"6": "h-6 min-h-6",
|
|
33
|
+
"7": "h-7 min-h-7",
|
|
34
|
+
"8": "h-8 min-h-8",
|
|
35
|
+
"9": "h-9 min-h-9",
|
|
36
|
+
"10": "h-10 min-h-10",
|
|
37
|
+
"11": "h-11 min-h-11",
|
|
38
|
+
"12": "h-12 min-h-12",
|
|
39
|
+
"16": "h-16 min-h-16",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const variantClasses: Record<IconButtonVariant, string> = {
|
|
43
|
+
default: "border border-border bg-transparent ",
|
|
44
|
+
ghost: "bg-transparent",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// TODO: Remake to call Icon component directly instead of passing children
|
|
48
|
+
export const IconButton = ({
|
|
49
|
+
children,
|
|
50
|
+
className,
|
|
51
|
+
size = "6",
|
|
52
|
+
variant = "default",
|
|
53
|
+
type = "button",
|
|
54
|
+
"aria-label": ariaLabel,
|
|
55
|
+
...rest
|
|
56
|
+
}: IconButtonProps) => {
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
type={type}
|
|
60
|
+
aria-label={ariaLabel}
|
|
61
|
+
className={cn(
|
|
62
|
+
className,
|
|
63
|
+
sizeClasses[size],
|
|
64
|
+
"inline-flex aspect-square shrink-0 items-center justify-center",
|
|
65
|
+
"rounded-md",
|
|
66
|
+
variantClasses[variant],
|
|
67
|
+
"text-muted-foreground ",
|
|
68
|
+
"hover:bg-gray-200 dark:hover:bg-gray-800",
|
|
69
|
+
)}
|
|
70
|
+
{...rest}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Badge, type BadgeProps } from "./Badge";
|
|
4
|
+
|
|
5
|
+
export type PriceBadgeProps = ComponentPropsWithRef<"span"> & {
|
|
6
|
+
cost: number;
|
|
7
|
+
size?: BadgeProps["size"];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const PriceBadge = ({ cost, size, ...rest }: PriceBadgeProps) => {
|
|
11
|
+
return <Badge theme="gray" size={size} {...rest} label={`$ ${cost}`} />;
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Search } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import { TextInput, type TextInputProps } from "./TextInput";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A simple wrapper around the TextInput component.
|
|
7
|
+
* It adds a search icon and a placeholder.
|
|
8
|
+
*/
|
|
9
|
+
export const SearchInput = ({ ...props }: TextInputProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<TextInput
|
|
12
|
+
startIcon={<Search className="size-4" />}
|
|
13
|
+
placeholder="Filter..."
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
};
|