@elizaos/client 1.6.1-alpha.4 → 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 { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import * as RadixTabs from "@radix-ui/react-tabs";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
export interface TabItem<T extends string = string> {
|
|
8
|
+
value: T;
|
|
9
|
+
label: string;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TabTheme = "underline" | "pill";
|
|
15
|
+
|
|
16
|
+
const BASE_TRIGGER =
|
|
17
|
+
"text-sm font-medium transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
18
|
+
|
|
19
|
+
const THEMES = {
|
|
20
|
+
underline: {
|
|
21
|
+
list: "h-9 flex border-b border-border ",
|
|
22
|
+
trigger: `w-full justify-center px-3 ${BASE_TRIGGER}
|
|
23
|
+
text-muted-foreground hover:text-foreground data-[state=active]:text-foreground
|
|
24
|
+
dark:hover:text-gray-200 dark:data-[state=active]:text-gray-200
|
|
25
|
+
border-b-2 border-transparent data-[state=active]:border-primary
|
|
26
|
+
dark:data-[state=active]:border-muted -mb-[2px]
|
|
27
|
+
hover:border-muted `,
|
|
28
|
+
},
|
|
29
|
+
pill: {
|
|
30
|
+
list: "h-9 inline-flex gap-1 p-1 bg-muted rounded-lg",
|
|
31
|
+
trigger: `px-3 ${BASE_TRIGGER} rounded-md
|
|
32
|
+
text-muted-foreground hover:text-foreground data-[state=active]:text-foreground
|
|
33
|
+
dark:hover:text-gray-200 dark:data-[state=active]:text-gray-200
|
|
34
|
+
hover:bg-accent data-[state=active]:bg-card data-[state=active]:shadow-sm
|
|
35
|
+
dark:hover:bg-gray-700 dark:data-[state=active]:bg-gray-600 dark:data-[state=active]:shadow-none`,
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
export type TabsProps<T extends string = string> = Omit<
|
|
40
|
+
ComponentPropsWithRef<"div">,
|
|
41
|
+
"dir"
|
|
42
|
+
> & {
|
|
43
|
+
/**
|
|
44
|
+
* Array of tab items to display
|
|
45
|
+
*/
|
|
46
|
+
items: TabItem<T>[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The initially selected tab value (uncontrolled)
|
|
50
|
+
*/
|
|
51
|
+
defaultValue?: T;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The currently selected tab value (controlled)
|
|
55
|
+
*/
|
|
56
|
+
value?: T;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Callback fired when the selected tab changes
|
|
60
|
+
*/
|
|
61
|
+
onValueChange?: (value: T) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Visual theme variant for the tabs
|
|
65
|
+
* @default "underline"
|
|
66
|
+
*/
|
|
67
|
+
theme?: TabTheme;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Optional className for the root container
|
|
71
|
+
*/
|
|
72
|
+
className?: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional className for the tabs list container
|
|
76
|
+
*/
|
|
77
|
+
tabsListClassName?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Optional className for individual tab triggers
|
|
81
|
+
*/
|
|
82
|
+
triggerClassName?: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The direction of the content of the tabs
|
|
86
|
+
*/
|
|
87
|
+
dir?: "ltr" | "rtl";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Tabs = <T extends string = string>({
|
|
91
|
+
items,
|
|
92
|
+
defaultValue,
|
|
93
|
+
value,
|
|
94
|
+
onValueChange,
|
|
95
|
+
theme = "underline",
|
|
96
|
+
className = "",
|
|
97
|
+
tabsListClassName = "",
|
|
98
|
+
triggerClassName = "",
|
|
99
|
+
dir,
|
|
100
|
+
...rest
|
|
101
|
+
}: TabsProps<T>) => {
|
|
102
|
+
const defaultTab = defaultValue || items[0]?.value;
|
|
103
|
+
|
|
104
|
+
const currentTheme = THEMES[theme];
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<RadixTabs.Root
|
|
108
|
+
className={className}
|
|
109
|
+
defaultValue={!value ? defaultTab : undefined}
|
|
110
|
+
value={value}
|
|
111
|
+
onValueChange={onValueChange as (value: string) => void}
|
|
112
|
+
dir={dir}
|
|
113
|
+
{...rest}
|
|
114
|
+
>
|
|
115
|
+
<RadixTabs.List
|
|
116
|
+
className={cn(currentTheme.list, tabsListClassName)}
|
|
117
|
+
aria-label="Navigation tabs"
|
|
118
|
+
>
|
|
119
|
+
{items.map((item: TabItem) => (
|
|
120
|
+
<RadixTabs.Trigger
|
|
121
|
+
key={item.value}
|
|
122
|
+
value={item.value}
|
|
123
|
+
disabled={item.disabled}
|
|
124
|
+
className={cn(
|
|
125
|
+
"flex items-center overflow-hidden",
|
|
126
|
+
currentTheme.trigger,
|
|
127
|
+
triggerClassName,
|
|
128
|
+
)}
|
|
129
|
+
>
|
|
130
|
+
{item.icon && (
|
|
131
|
+
<span className="mr-2 text-muted-foreground group-data-[state=active]:text-current ">
|
|
132
|
+
{item.icon}
|
|
133
|
+
</span>
|
|
134
|
+
)}
|
|
135
|
+
<span className="truncate">{item.label}</span>
|
|
136
|
+
</RadixTabs.Trigger>
|
|
137
|
+
))}
|
|
138
|
+
</RadixTabs.List>
|
|
139
|
+
</RadixTabs.Root>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import {
|
|
4
|
+
useRef,
|
|
5
|
+
type ChangeEvent,
|
|
6
|
+
type ComponentPropsWithRef,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
type RefObject,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
export type TextInputProps = ComponentPropsWithRef<"input"> & {
|
|
12
|
+
/**
|
|
13
|
+
* Callback fired when the input value changes
|
|
14
|
+
*/
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icon to display at the start of the input
|
|
19
|
+
*/
|
|
20
|
+
startIcon?: ReactNode;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Callback fired when the clear button is clicked. If this callback is provided,
|
|
24
|
+
* the clear button will be shown.
|
|
25
|
+
*/
|
|
26
|
+
onClear?: () => void;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ref to the input element
|
|
30
|
+
*/
|
|
31
|
+
ref?: RefObject<HTMLInputElement | null>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional className for the input element
|
|
35
|
+
*/
|
|
36
|
+
inputClassName?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Unique identifier for the input (required)
|
|
40
|
+
*/
|
|
41
|
+
id: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Label text for the input
|
|
45
|
+
*/
|
|
46
|
+
label?: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether to visually hide the label while keeping it for screen readers
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
hideLabel?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const iconBaseClassName =
|
|
56
|
+
"absolute top-1/2 -translate-y-1/2 flex items-center justify-center text-foreground";
|
|
57
|
+
|
|
58
|
+
export const TextInput = ({
|
|
59
|
+
className,
|
|
60
|
+
onChange,
|
|
61
|
+
onValueChange,
|
|
62
|
+
startIcon,
|
|
63
|
+
onClear,
|
|
64
|
+
ref,
|
|
65
|
+
inputClassName,
|
|
66
|
+
label,
|
|
67
|
+
hideLabel = false,
|
|
68
|
+
id,
|
|
69
|
+
...rest
|
|
70
|
+
}: TextInputProps) => {
|
|
71
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
72
|
+
|
|
73
|
+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
74
|
+
onChange?.(e);
|
|
75
|
+
onValueChange?.(e.target.value);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleClear = () => {
|
|
79
|
+
onClear?.();
|
|
80
|
+
|
|
81
|
+
if (ref) {
|
|
82
|
+
ref.current?.focus();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
inputRef.current?.focus();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className={cn("w-full", className)}>
|
|
91
|
+
{label && (
|
|
92
|
+
<label
|
|
93
|
+
htmlFor={id}
|
|
94
|
+
className={cn(
|
|
95
|
+
"block text-sm font-medium text-foreground ",
|
|
96
|
+
hideLabel && "sr-only",
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
{label}
|
|
100
|
+
</label>
|
|
101
|
+
)}
|
|
102
|
+
<div
|
|
103
|
+
className={cn(
|
|
104
|
+
"relative flex w-full items-center justify-center",
|
|
105
|
+
label && !hideLabel && "mt-1",
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<input
|
|
109
|
+
id={id}
|
|
110
|
+
ref={ref || inputRef}
|
|
111
|
+
onChange={handleChange}
|
|
112
|
+
className={cn(
|
|
113
|
+
inputClassName,
|
|
114
|
+
"flex h-7 items-center truncate",
|
|
115
|
+
"w-full px-2",
|
|
116
|
+
!!startIcon && "pl-8",
|
|
117
|
+
!!onClear && "pr-8",
|
|
118
|
+
"rounded border border-border bg-transparent ",
|
|
119
|
+
"text-foreground placeholder:text-muted-foreground ",
|
|
120
|
+
"hover:border-border dark:hover:border-gray-700",
|
|
121
|
+
)}
|
|
122
|
+
{...rest}
|
|
123
|
+
/>
|
|
124
|
+
{startIcon && (
|
|
125
|
+
<div className={cn(iconBaseClassName, "left-2")} aria-hidden>
|
|
126
|
+
{startIcon}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
{onClear && rest.value && (
|
|
130
|
+
<button
|
|
131
|
+
className={cn(iconBaseClassName, "right-2")}
|
|
132
|
+
aria-label="Clear input value"
|
|
133
|
+
onClick={handleClear}
|
|
134
|
+
type="button"
|
|
135
|
+
>
|
|
136
|
+
<X className="size-4" />
|
|
137
|
+
</button>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Badge, type BadgeProps } from "./Badge";
|
|
4
|
+
|
|
5
|
+
export type TimestampBadgeProps = ComponentPropsWithRef<"span"> & {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
size?: BadgeProps["size"];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const TimestampBadge = ({
|
|
11
|
+
timestamp,
|
|
12
|
+
size,
|
|
13
|
+
...rest
|
|
14
|
+
}: TimestampBadgeProps) => {
|
|
15
|
+
return (
|
|
16
|
+
<Badge
|
|
17
|
+
variant="outline"
|
|
18
|
+
theme="gray"
|
|
19
|
+
size={size}
|
|
20
|
+
{...rest}
|
|
21
|
+
label={formatTimestamp(timestamp)}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function formatTimestamp(timestamp: number): string {
|
|
27
|
+
return new Date(timestamp).toLocaleString();
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { Coins } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { Badge, type BadgeProps } from "./Badge";
|
|
6
|
+
|
|
7
|
+
export type TokensBadgeProps = ComponentPropsWithRef<"span"> & {
|
|
8
|
+
tokensCount: number;
|
|
9
|
+
size?: BadgeProps["size"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const TokensBadge = ({
|
|
13
|
+
tokensCount,
|
|
14
|
+
size,
|
|
15
|
+
...rest
|
|
16
|
+
}: TokensBadgeProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<Badge
|
|
19
|
+
iconStart={<Coins className="size-2.5" />}
|
|
20
|
+
theme="gray"
|
|
21
|
+
size={size}
|
|
22
|
+
{...rest}
|
|
23
|
+
label={tokensCount}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { TraceRecord } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { ArrowLeft } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { Badge, type BadgeProps } from "../Badge.tsx";
|
|
7
|
+
import { IconButton } from "../IconButton.tsx";
|
|
8
|
+
import { TraceListItem } from "./TraceListItem.tsx";
|
|
9
|
+
|
|
10
|
+
type TraceRecordWithBadges = TraceRecord & {
|
|
11
|
+
badges?: Array<BadgeProps>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type TraceListProps = {
|
|
15
|
+
traces: TraceRecordWithBadges[];
|
|
16
|
+
expanded: boolean;
|
|
17
|
+
onExpandStateChange: (expanded: boolean) => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
onTraceSelect?: (trace: TraceRecord) => void;
|
|
20
|
+
selectedTrace?: TraceRecord;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const TraceList = ({
|
|
24
|
+
traces,
|
|
25
|
+
expanded,
|
|
26
|
+
onExpandStateChange,
|
|
27
|
+
className,
|
|
28
|
+
onTraceSelect,
|
|
29
|
+
selectedTrace,
|
|
30
|
+
}: TraceListProps) => {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={cn(
|
|
34
|
+
"w-full min-w-0",
|
|
35
|
+
"flex flex-col gap-3",
|
|
36
|
+
expanded ? "w-full" : "w-fit",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
>
|
|
40
|
+
<header className="flex min-h-6 items-center justify-between gap-2">
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<h2
|
|
43
|
+
className={cn(
|
|
44
|
+
"font-semibold text-lg text-foreground",
|
|
45
|
+
!expanded && "hidden",
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
Traces
|
|
49
|
+
</h2>
|
|
50
|
+
|
|
51
|
+
<Badge
|
|
52
|
+
size="5"
|
|
53
|
+
theme="teal"
|
|
54
|
+
aria-label={`Total number of traces: ${traces.length}`}
|
|
55
|
+
label={traces.length}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
</header>
|
|
60
|
+
|
|
61
|
+
{expanded && (
|
|
62
|
+
<ul className="flex flex-col items-center overflow-hidden rounded border border-border ">
|
|
63
|
+
{traces.map((trace) => (
|
|
64
|
+
<li
|
|
65
|
+
className="w-full list-none border-b-border [&:not(:last-child)]:border-b"
|
|
66
|
+
key={trace.id}
|
|
67
|
+
>
|
|
68
|
+
<TraceListItem
|
|
69
|
+
trace={trace}
|
|
70
|
+
onClick={() => onTraceSelect?.(trace)}
|
|
71
|
+
isSelected={selectedTrace?.id === trace.id}
|
|
72
|
+
badges={trace.badges}
|
|
73
|
+
/>
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ul>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { TraceRecord } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { useCallback, type KeyboardEvent } from "react";
|
|
5
|
+
|
|
6
|
+
import { type AvatarProps } from "../Avatar.tsx";
|
|
7
|
+
import { Badge, type BadgeProps } from "../Badge.tsx";
|
|
8
|
+
import { PriceBadge } from "../PriceBadge.tsx";
|
|
9
|
+
import { TimestampBadge } from "../TimestampBadge.tsx";
|
|
10
|
+
import { TokensBadge } from "../TokensBadge.tsx";
|
|
11
|
+
import { TraceListItemHeader } from "./TraceListItemHeader.tsx";
|
|
12
|
+
|
|
13
|
+
interface TraceListItemProps {
|
|
14
|
+
trace: TraceRecord;
|
|
15
|
+
badges?: Array<BadgeProps>;
|
|
16
|
+
avatar?: AvatarProps;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
isSelected?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TraceListItem = ({
|
|
22
|
+
trace,
|
|
23
|
+
avatar,
|
|
24
|
+
onClick,
|
|
25
|
+
badges,
|
|
26
|
+
isSelected,
|
|
27
|
+
}: TraceListItemProps) => {
|
|
28
|
+
const handleKeyDown = useCallback(
|
|
29
|
+
(e: KeyboardEvent): void => {
|
|
30
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
onClick?.();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
[onClick],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const { name, agentDescription, totalCost, totalTokens, startTime } = trace;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={cn(
|
|
43
|
+
"group w-full",
|
|
44
|
+
"flex flex-col gap-2.5 p-4",
|
|
45
|
+
"cursor-pointer",
|
|
46
|
+
isSelected
|
|
47
|
+
? "bg-muted"
|
|
48
|
+
: "bg-card",
|
|
49
|
+
)}
|
|
50
|
+
role="button"
|
|
51
|
+
tabIndex={0}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
onKeyDown={handleKeyDown}
|
|
54
|
+
aria-label={`Select trace ${name}`}
|
|
55
|
+
>
|
|
56
|
+
<TraceListItemHeader trace={trace} avatar={avatar} />
|
|
57
|
+
|
|
58
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
59
|
+
<span className="mr-4 max-w-full truncate text-sm text-muted-foreground ">
|
|
60
|
+
{agentDescription}
|
|
61
|
+
</span>
|
|
62
|
+
|
|
63
|
+
{typeof totalCost === "number" && <PriceBadge cost={totalCost} />}
|
|
64
|
+
|
|
65
|
+
{typeof totalTokens === "number" && (
|
|
66
|
+
<TokensBadge tokensCount={totalTokens} />
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{badges?.map((badge, index) => (
|
|
70
|
+
<Badge key={index} theme={badge.theme} size="4" label={badge.label} />
|
|
71
|
+
))}
|
|
72
|
+
|
|
73
|
+
{typeof startTime === "number" && (
|
|
74
|
+
<TimestampBadge timestamp={startTime} />
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TraceRecord } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { formatDuration } from "@evilmartians/agent-prism-data";
|
|
4
|
+
|
|
5
|
+
import { Avatar, type AvatarProps } from "../Avatar.tsx";
|
|
6
|
+
import { Badge } from "../Badge.tsx";
|
|
7
|
+
|
|
8
|
+
interface TraceListItemHeaderProps {
|
|
9
|
+
trace: TraceRecord;
|
|
10
|
+
avatar?: AvatarProps;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const TraceListItemHeader = ({
|
|
14
|
+
trace,
|
|
15
|
+
avatar,
|
|
16
|
+
}: TraceListItemHeaderProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<header className="flex min-w-0 flex-wrap items-center justify-between gap-2">
|
|
19
|
+
<div className="flex min-w-0 items-center gap-1.5 overflow-hidden">
|
|
20
|
+
{avatar && <Avatar size="4" {...avatar} />}
|
|
21
|
+
|
|
22
|
+
<h3 className="font-medium max-w-full truncate text-sm text-foreground">
|
|
23
|
+
{trace.name}
|
|
24
|
+
</h3>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="flex items-center gap-2">
|
|
28
|
+
<Badge
|
|
29
|
+
size="5"
|
|
30
|
+
theme="gray"
|
|
31
|
+
variant="outline"
|
|
32
|
+
label={
|
|
33
|
+
trace.spansCount === 1 ? "1 span" : `${trace.spansCount} spans`
|
|
34
|
+
}
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<Badge
|
|
38
|
+
size="5"
|
|
39
|
+
theme="gray"
|
|
40
|
+
variant="outline"
|
|
41
|
+
label={formatDuration(trace.durationMs)}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</header>
|
|
45
|
+
);
|
|
46
|
+
};
|