@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,467 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatDuration,
|
|
5
|
+
getTimelineData,
|
|
6
|
+
} from "@evilmartians/agent-prism-data";
|
|
7
|
+
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
import {
|
|
10
|
+
type FC,
|
|
11
|
+
useCallback,
|
|
12
|
+
type KeyboardEvent,
|
|
13
|
+
type MouseEvent,
|
|
14
|
+
} from "react";
|
|
15
|
+
|
|
16
|
+
import { Avatar, type AvatarProps } from "../Avatar.tsx";
|
|
17
|
+
import { getSpanCategoryTheme } from "../shared.ts";
|
|
18
|
+
import { SpanStatus } from "../SpanStatus.tsx";
|
|
19
|
+
import { SpanCardBadges } from "./SpanCardBadges.tsx";
|
|
20
|
+
import {
|
|
21
|
+
type SpanCardConnectorType,
|
|
22
|
+
SpanCardConnector,
|
|
23
|
+
} from "./SpanCardConnector.tsx";
|
|
24
|
+
import { SpanCardTimeline } from "./SpanCardTimeline.tsx";
|
|
25
|
+
import { SpanCardToggle } from "./SpanCardToggle.tsx";
|
|
26
|
+
|
|
27
|
+
const LAYOUT_CONSTANTS = {
|
|
28
|
+
CONNECTOR_WIDTH: 20,
|
|
29
|
+
CONTENT_BASE_WIDTH: 320,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
type ExpandButtonPlacement = "inside" | "outside";
|
|
33
|
+
|
|
34
|
+
export type SpanCardViewOptions = {
|
|
35
|
+
withStatus?: boolean;
|
|
36
|
+
expandButton?: ExpandButtonPlacement;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const DEFAULT_VIEW_OPTIONS: Required<SpanCardViewOptions> = {
|
|
40
|
+
withStatus: true,
|
|
41
|
+
expandButton: "inside",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface SpanCardProps {
|
|
45
|
+
data: TraceSpan;
|
|
46
|
+
level?: number;
|
|
47
|
+
selectedSpan?: TraceSpan;
|
|
48
|
+
avatar?: AvatarProps;
|
|
49
|
+
onSpanSelect?: (span: TraceSpan) => void;
|
|
50
|
+
minStart: number;
|
|
51
|
+
maxEnd: number;
|
|
52
|
+
isLastChild: boolean;
|
|
53
|
+
prevLevelConnectors?: SpanCardConnectorType[];
|
|
54
|
+
expandedSpansIds: string[];
|
|
55
|
+
onExpandSpansIdsChange: (ids: string[]) => void;
|
|
56
|
+
viewOptions?: SpanCardViewOptions;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SpanCardState {
|
|
60
|
+
isExpanded: boolean;
|
|
61
|
+
hasChildren: boolean;
|
|
62
|
+
isSelected: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const getContentWidth = ({
|
|
66
|
+
level,
|
|
67
|
+
hasExpandButton,
|
|
68
|
+
contentPadding,
|
|
69
|
+
expandButton,
|
|
70
|
+
}: {
|
|
71
|
+
level: number;
|
|
72
|
+
hasExpandButton: boolean;
|
|
73
|
+
contentPadding: number;
|
|
74
|
+
expandButton: ExpandButtonPlacement;
|
|
75
|
+
}) => {
|
|
76
|
+
let width =
|
|
77
|
+
LAYOUT_CONSTANTS.CONTENT_BASE_WIDTH -
|
|
78
|
+
level * LAYOUT_CONSTANTS.CONNECTOR_WIDTH;
|
|
79
|
+
|
|
80
|
+
if (hasExpandButton && expandButton === "inside") {
|
|
81
|
+
width -= LAYOUT_CONSTANTS.CONNECTOR_WIDTH;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (expandButton === "outside" && level === 0) {
|
|
85
|
+
width -= LAYOUT_CONSTANTS.CONNECTOR_WIDTH;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return width - contentPadding;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const getGridTemplateColumns = ({
|
|
92
|
+
connectorsColumnWidth,
|
|
93
|
+
expandButton,
|
|
94
|
+
}: {
|
|
95
|
+
connectorsColumnWidth: number;
|
|
96
|
+
expandButton: ExpandButtonPlacement;
|
|
97
|
+
}) => {
|
|
98
|
+
if (expandButton === "inside") {
|
|
99
|
+
return `${connectorsColumnWidth}px 1fr`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return `${connectorsColumnWidth}px 1fr ${LAYOUT_CONSTANTS.CONNECTOR_WIDTH}px`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const getContentPadding = ({
|
|
106
|
+
level,
|
|
107
|
+
hasExpandButton,
|
|
108
|
+
}: {
|
|
109
|
+
level: number;
|
|
110
|
+
hasExpandButton: boolean;
|
|
111
|
+
}) => {
|
|
112
|
+
if (level === 0) return 0;
|
|
113
|
+
|
|
114
|
+
if (hasExpandButton) return 4;
|
|
115
|
+
|
|
116
|
+
return 8;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const getConnectorsLayout = ({
|
|
120
|
+
level,
|
|
121
|
+
hasExpandButton,
|
|
122
|
+
isLastChild,
|
|
123
|
+
prevConnectors,
|
|
124
|
+
expandButton,
|
|
125
|
+
}: {
|
|
126
|
+
hasExpandButton: boolean;
|
|
127
|
+
isLastChild: boolean;
|
|
128
|
+
level: number;
|
|
129
|
+
prevConnectors: SpanCardConnectorType[];
|
|
130
|
+
expandButton: ExpandButtonPlacement;
|
|
131
|
+
}): {
|
|
132
|
+
connectors: SpanCardConnectorType[];
|
|
133
|
+
connectorsColumnWidth: number;
|
|
134
|
+
} => {
|
|
135
|
+
const connectors: SpanCardConnectorType[] = [];
|
|
136
|
+
|
|
137
|
+
if (level === 0) {
|
|
138
|
+
return {
|
|
139
|
+
connectors: expandButton === "inside" ? [] : ["vertical"],
|
|
140
|
+
connectorsColumnWidth: 20,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < level - 1; i++) {
|
|
145
|
+
connectors.push("vertical");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!isLastChild) {
|
|
149
|
+
connectors.push("t-right");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isLastChild) {
|
|
153
|
+
connectors.push("corner-top-right");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let connectorsColumnWidth =
|
|
157
|
+
connectors.length * LAYOUT_CONSTANTS.CONNECTOR_WIDTH;
|
|
158
|
+
|
|
159
|
+
if (hasExpandButton) {
|
|
160
|
+
connectorsColumnWidth += LAYOUT_CONSTANTS.CONNECTOR_WIDTH;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < prevConnectors.length; i++) {
|
|
164
|
+
if (
|
|
165
|
+
prevConnectors[i] === "empty" ||
|
|
166
|
+
prevConnectors[i] === "corner-top-right"
|
|
167
|
+
) {
|
|
168
|
+
connectors[i] = "empty";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
connectors,
|
|
174
|
+
connectorsColumnWidth,
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const useSpanCardEventHandlers = (
|
|
179
|
+
data: TraceSpan,
|
|
180
|
+
onSpanSelect?: (span: TraceSpan) => void,
|
|
181
|
+
) => {
|
|
182
|
+
const handleCardClick = useCallback((): void => {
|
|
183
|
+
onSpanSelect?.(data);
|
|
184
|
+
}, [data, onSpanSelect]);
|
|
185
|
+
|
|
186
|
+
const handleKeyDown = useCallback(
|
|
187
|
+
(e: KeyboardEvent): void => {
|
|
188
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
handleCardClick();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
[handleCardClick],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const handleToggleClick = useCallback(
|
|
197
|
+
(e: MouseEvent | KeyboardEvent): void => {
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
},
|
|
200
|
+
[],
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
handleCardClick,
|
|
205
|
+
handleKeyDown,
|
|
206
|
+
handleToggleClick,
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const SpanCardChildren: FC<{
|
|
211
|
+
data: TraceSpan;
|
|
212
|
+
level: number;
|
|
213
|
+
selectedSpan?: TraceSpan;
|
|
214
|
+
onSpanSelect?: (span: TraceSpan) => void;
|
|
215
|
+
minStart: number;
|
|
216
|
+
maxEnd: number;
|
|
217
|
+
prevLevelConnectors: SpanCardConnectorType[];
|
|
218
|
+
expandedSpansIds: string[];
|
|
219
|
+
onExpandSpansIdsChange: (ids: string[]) => void;
|
|
220
|
+
viewOptions?: SpanCardViewOptions;
|
|
221
|
+
}> = ({
|
|
222
|
+
data,
|
|
223
|
+
level,
|
|
224
|
+
selectedSpan,
|
|
225
|
+
onSpanSelect,
|
|
226
|
+
minStart,
|
|
227
|
+
maxEnd,
|
|
228
|
+
prevLevelConnectors,
|
|
229
|
+
expandedSpansIds,
|
|
230
|
+
onExpandSpansIdsChange,
|
|
231
|
+
viewOptions = DEFAULT_VIEW_OPTIONS,
|
|
232
|
+
}) => {
|
|
233
|
+
if (!data.children?.length) return null;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div className="relative">
|
|
237
|
+
<Collapsible.Content>
|
|
238
|
+
<ul role="group">
|
|
239
|
+
{data.children.map((child, idx) => (
|
|
240
|
+
<SpanCard
|
|
241
|
+
viewOptions={viewOptions}
|
|
242
|
+
key={child.id}
|
|
243
|
+
data={child}
|
|
244
|
+
minStart={minStart}
|
|
245
|
+
maxEnd={maxEnd}
|
|
246
|
+
level={level + 1}
|
|
247
|
+
selectedSpan={selectedSpan}
|
|
248
|
+
onSpanSelect={onSpanSelect}
|
|
249
|
+
isLastChild={idx === (data.children || []).length - 1}
|
|
250
|
+
prevLevelConnectors={prevLevelConnectors}
|
|
251
|
+
expandedSpansIds={expandedSpansIds}
|
|
252
|
+
onExpandSpansIdsChange={onExpandSpansIdsChange}
|
|
253
|
+
/>
|
|
254
|
+
))}
|
|
255
|
+
</ul>
|
|
256
|
+
</Collapsible.Content>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const SpanCard: FC<SpanCardProps> = ({
|
|
262
|
+
data,
|
|
263
|
+
level = 0,
|
|
264
|
+
selectedSpan,
|
|
265
|
+
onSpanSelect,
|
|
266
|
+
viewOptions = DEFAULT_VIEW_OPTIONS,
|
|
267
|
+
avatar,
|
|
268
|
+
minStart,
|
|
269
|
+
maxEnd,
|
|
270
|
+
isLastChild,
|
|
271
|
+
prevLevelConnectors = [],
|
|
272
|
+
expandedSpansIds,
|
|
273
|
+
onExpandSpansIdsChange,
|
|
274
|
+
}) => {
|
|
275
|
+
const isExpanded = expandedSpansIds.includes(data.id);
|
|
276
|
+
|
|
277
|
+
const withStatus = viewOptions.withStatus ?? DEFAULT_VIEW_OPTIONS.withStatus;
|
|
278
|
+
const expandButton =
|
|
279
|
+
viewOptions.expandButton || DEFAULT_VIEW_OPTIONS.expandButton;
|
|
280
|
+
|
|
281
|
+
const handleToggleClick = useCallback(
|
|
282
|
+
(expanded: boolean) => {
|
|
283
|
+
const alreadyExpanded = expandedSpansIds.includes(data.id);
|
|
284
|
+
|
|
285
|
+
if (alreadyExpanded && !expanded) {
|
|
286
|
+
onExpandSpansIdsChange(expandedSpansIds.filter((id) => id !== data.id));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!alreadyExpanded && expanded) {
|
|
290
|
+
onExpandSpansIdsChange([...expandedSpansIds, data.id]);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
[isExpanded, expandedSpansIds, data.id, onExpandSpansIdsChange],
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const state: SpanCardState = {
|
|
297
|
+
isExpanded,
|
|
298
|
+
hasChildren: Boolean(data.children?.length),
|
|
299
|
+
isSelected: selectedSpan?.id === data.id,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const eventHandlers = useSpanCardEventHandlers(data, onSpanSelect);
|
|
303
|
+
|
|
304
|
+
const { durationMs } = getTimelineData({
|
|
305
|
+
spanCard: data,
|
|
306
|
+
minStart,
|
|
307
|
+
maxEnd,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const hasExpandButtonAsFirstChild =
|
|
311
|
+
expandButton === "inside" && state.hasChildren;
|
|
312
|
+
|
|
313
|
+
const contentPadding = getContentPadding({
|
|
314
|
+
level,
|
|
315
|
+
hasExpandButton: hasExpandButtonAsFirstChild,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const contentWidth = getContentWidth({
|
|
319
|
+
level,
|
|
320
|
+
hasExpandButton: hasExpandButtonAsFirstChild,
|
|
321
|
+
contentPadding,
|
|
322
|
+
expandButton,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const { connectors, connectorsColumnWidth } = getConnectorsLayout({
|
|
326
|
+
level,
|
|
327
|
+
hasExpandButton: hasExpandButtonAsFirstChild,
|
|
328
|
+
isLastChild,
|
|
329
|
+
prevConnectors: prevLevelConnectors,
|
|
330
|
+
expandButton,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const gridTemplateColumns = getGridTemplateColumns({
|
|
334
|
+
connectorsColumnWidth,
|
|
335
|
+
expandButton,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<li
|
|
340
|
+
role="treeitem"
|
|
341
|
+
aria-expanded={state.hasChildren ? state.isExpanded : undefined}
|
|
342
|
+
className="list-none"
|
|
343
|
+
>
|
|
344
|
+
<Collapsible.Root
|
|
345
|
+
open={state.isExpanded}
|
|
346
|
+
onOpenChange={handleToggleClick}
|
|
347
|
+
>
|
|
348
|
+
<div
|
|
349
|
+
className={cn(
|
|
350
|
+
"relative grid w-full rounded-md transition-colors",
|
|
351
|
+
state.isSelected
|
|
352
|
+
? "bg-accent/50 border border-accent"
|
|
353
|
+
: "hover:bg-muted/30",
|
|
354
|
+
)}
|
|
355
|
+
style={{
|
|
356
|
+
gridTemplateColumns,
|
|
357
|
+
}}
|
|
358
|
+
onClick={eventHandlers.handleCardClick}
|
|
359
|
+
onKeyDown={eventHandlers.handleKeyDown}
|
|
360
|
+
tabIndex={0}
|
|
361
|
+
role="button"
|
|
362
|
+
aria-pressed={state.isSelected}
|
|
363
|
+
aria-describedby={`span-card-desc-${data.id}`}
|
|
364
|
+
aria-expanded={state.hasChildren ? state.isExpanded : undefined}
|
|
365
|
+
aria-label={`${state.isSelected ? "Selected" : "Not selected"} span card for ${data.title} at level ${level}`}
|
|
366
|
+
>
|
|
367
|
+
<div className="flex flex-nowrap">
|
|
368
|
+
{connectors.map((connector, idx) => (
|
|
369
|
+
<SpanCardConnector key={`${connector}-${idx}`} type={connector} />
|
|
370
|
+
))}
|
|
371
|
+
|
|
372
|
+
{hasExpandButtonAsFirstChild && (
|
|
373
|
+
<div className="flex w-5 flex-col items-center">
|
|
374
|
+
<SpanCardToggle
|
|
375
|
+
isExpanded={state.isExpanded}
|
|
376
|
+
title={data.title}
|
|
377
|
+
onToggleClick={eventHandlers.handleToggleClick}
|
|
378
|
+
/>
|
|
379
|
+
|
|
380
|
+
{state.isExpanded && <SpanCardConnector type="vertical" />}
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
<div
|
|
385
|
+
className={cn(
|
|
386
|
+
"flex flex-nowrap items-center align-middle gap-x-3 gap py-3 px-2",
|
|
387
|
+
"min-h-5 w-full cursor-pointer",
|
|
388
|
+
level !== 0 && !hasExpandButtonAsFirstChild && "pl-2",
|
|
389
|
+
level !== 0 && hasExpandButtonAsFirstChild && "pl-1",
|
|
390
|
+
)}
|
|
391
|
+
>
|
|
392
|
+
<div
|
|
393
|
+
className="relative flex min-h-4 flex-shrink-0 flex-grow-0 flex-wrap items-start gap-1"
|
|
394
|
+
style={{
|
|
395
|
+
width: `min(${contentWidth}px, 100%)`,
|
|
396
|
+
minWidth: 140,
|
|
397
|
+
}}
|
|
398
|
+
>
|
|
399
|
+
{avatar && <Avatar size="4" {...avatar} />}
|
|
400
|
+
|
|
401
|
+
<h3
|
|
402
|
+
className="mr-1 h-4 max-w-32 truncate text-sm leading-[14px] text-foreground "
|
|
403
|
+
title={data.title}
|
|
404
|
+
>
|
|
405
|
+
{data.title}
|
|
406
|
+
</h3>
|
|
407
|
+
|
|
408
|
+
<SpanCardBadges data={data} />
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<div className="flex flex-shrink flex-grow flex-nowrap items-center justify-end gap-1 min-w-0">
|
|
412
|
+
{expandButton === "outside" && withStatus && (
|
|
413
|
+
<div className="flex-shrink-0">
|
|
414
|
+
<SpanStatus status={data.status} />
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
|
|
418
|
+
<SpanCardTimeline
|
|
419
|
+
theme={getSpanCategoryTheme(data.type)}
|
|
420
|
+
minStart={minStart}
|
|
421
|
+
maxEnd={maxEnd}
|
|
422
|
+
spanCard={data}
|
|
423
|
+
className="max-w-48 flex-shrink"
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
427
|
+
<span className="inline-block w-14 whitespace-nowrap px-1 text-right text-xs text-foreground">
|
|
428
|
+
{formatDuration(durationMs)}
|
|
429
|
+
</span>
|
|
430
|
+
|
|
431
|
+
{expandButton === "inside" && withStatus && (
|
|
432
|
+
<div>
|
|
433
|
+
<SpanStatus status={data.status} />
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
{expandButton === "outside" &&
|
|
441
|
+
(state.hasChildren ? (
|
|
442
|
+
<SpanCardToggle
|
|
443
|
+
isExpanded={state.isExpanded}
|
|
444
|
+
title={data.title}
|
|
445
|
+
onToggleClick={eventHandlers.handleToggleClick}
|
|
446
|
+
/>
|
|
447
|
+
) : (
|
|
448
|
+
<div />
|
|
449
|
+
))}
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<SpanCardChildren
|
|
453
|
+
minStart={minStart}
|
|
454
|
+
maxEnd={maxEnd}
|
|
455
|
+
viewOptions={viewOptions}
|
|
456
|
+
data={data}
|
|
457
|
+
level={level}
|
|
458
|
+
selectedSpan={selectedSpan}
|
|
459
|
+
onSpanSelect={onSpanSelect}
|
|
460
|
+
prevLevelConnectors={connectors}
|
|
461
|
+
expandedSpansIds={expandedSpansIds}
|
|
462
|
+
onExpandSpansIdsChange={onExpandSpansIdsChange}
|
|
463
|
+
/>
|
|
464
|
+
</Collapsible.Root>
|
|
465
|
+
</li>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "../Badge.tsx";
|
|
4
|
+
import { PriceBadge } from "../PriceBadge.tsx";
|
|
5
|
+
import {
|
|
6
|
+
getSpanCategoryIcon,
|
|
7
|
+
getSpanCategoryLabel,
|
|
8
|
+
getSpanCategoryTheme,
|
|
9
|
+
} from "../shared.ts";
|
|
10
|
+
import { TokensBadge } from "../TokensBadge.tsx";
|
|
11
|
+
|
|
12
|
+
interface SpanCardBagdesProps {
|
|
13
|
+
data: TraceSpan;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SpanCardBadges = ({ data }: SpanCardBagdesProps) => {
|
|
17
|
+
const Icon = getSpanCategoryIcon(data.type);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex flex-wrap items-center justify-start gap-1">
|
|
21
|
+
<Badge
|
|
22
|
+
iconStart={<Icon className="size-2.5" />}
|
|
23
|
+
theme={getSpanCategoryTheme(data.type)}
|
|
24
|
+
size="4"
|
|
25
|
+
label={getSpanCategoryLabel(data.type)}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
{typeof data.tokensCount === "number" && (
|
|
29
|
+
<TokensBadge tokensCount={data.tokensCount} />
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
{typeof data.cost === "number" && <PriceBadge cost={data.cost} />}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type SpanCardConnectorType =
|
|
2
|
+
| "horizontal"
|
|
3
|
+
| "vertical"
|
|
4
|
+
| "t-right"
|
|
5
|
+
| "corner-top-right"
|
|
6
|
+
| "empty";
|
|
7
|
+
|
|
8
|
+
interface SpanCardConnectorProps {
|
|
9
|
+
type: SpanCardConnectorType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const SpanCardConnector = ({ type }: SpanCardConnectorProps) => {
|
|
13
|
+
if (type === "empty") return <div className="w-5 shrink-0 grow" />;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="relative w-5 shrink-0 grow">
|
|
17
|
+
{(type === "vertical" || type === "t-right") && (
|
|
18
|
+
<div className="absolute bottom-0 left-1/2 top-0 w-0.5 -translate-x-1/2 bg-muted" />
|
|
19
|
+
)}
|
|
20
|
+
|
|
21
|
+
{type === "t-right" && (
|
|
22
|
+
<div className="absolute left-2.5 top-2.5 h-0.5 w-2.5 -translate-y-[3px] bg-muted" />
|
|
23
|
+
)}
|
|
24
|
+
|
|
25
|
+
{type === "corner-top-right" && (
|
|
26
|
+
<>
|
|
27
|
+
<div className="absolute left-1/2 top-2 size-0.5 -translate-x-1/2 -translate-y-px bg-muted" />
|
|
28
|
+
|
|
29
|
+
<div className="absolute left-1/2 top-2.5 h-0.5 w-2.5 -translate-y-[3px] bg-muted" />
|
|
30
|
+
|
|
31
|
+
<div className="absolute left-1/2 top-0 h-[7px] w-0.5 -translate-x-px bg-muted" />
|
|
32
|
+
</>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TraceSpan } from "@evilmartians/agent-prism-types";
|
|
2
|
+
|
|
3
|
+
import { getTimelineData } from "@evilmartians/agent-prism-data";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
import type { ColorVariant } from "../shared.ts";
|
|
7
|
+
|
|
8
|
+
interface SpanCardTimelineProps {
|
|
9
|
+
spanCard: TraceSpan;
|
|
10
|
+
theme: ColorVariant;
|
|
11
|
+
minStart: number;
|
|
12
|
+
maxEnd: number;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const timelineBgColors: Record<ColorVariant, string> = {
|
|
17
|
+
purple: "bg-primary",
|
|
18
|
+
indigo: "bg-primary",
|
|
19
|
+
orange: "bg-chart-1",
|
|
20
|
+
teal: "bg-chart-2",
|
|
21
|
+
cyan: "bg-chart-3",
|
|
22
|
+
sky: "bg-chart-4",
|
|
23
|
+
yellow: "bg-chart-5",
|
|
24
|
+
emerald: "bg-accent",
|
|
25
|
+
red: "bg-destructive",
|
|
26
|
+
gray: "bg-muted-foreground",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const SpanCardTimeline = ({
|
|
30
|
+
spanCard,
|
|
31
|
+
theme,
|
|
32
|
+
minStart,
|
|
33
|
+
maxEnd,
|
|
34
|
+
className,
|
|
35
|
+
}: SpanCardTimelineProps) => {
|
|
36
|
+
const { startPercent, widthPercent } = getTimelineData({
|
|
37
|
+
spanCard,
|
|
38
|
+
minStart,
|
|
39
|
+
maxEnd,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<span
|
|
44
|
+
className={cn(
|
|
45
|
+
"relative flex h-4 min-w-20 flex-1 rounded bg-muted",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<span className="pointer-events-none absolute inset-x-1 top-1/2 h-1.5 -translate-y-1/2">
|
|
50
|
+
<span
|
|
51
|
+
className={`absolute h-full rounded-sm ${timelineBgColors[theme]}`}
|
|
52
|
+
style={{
|
|
53
|
+
left: `${startPercent}%`,
|
|
54
|
+
width: `${widthPercent}%`,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</span>
|
|
58
|
+
</span>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
2
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
3
|
+
import { type KeyboardEvent, type MouseEvent } from "react";
|
|
4
|
+
|
|
5
|
+
interface SpanCardToggleProps {
|
|
6
|
+
isExpanded: boolean;
|
|
7
|
+
title: string;
|
|
8
|
+
onToggleClick: (e: MouseEvent | KeyboardEvent) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SpanCardToggle = ({
|
|
12
|
+
isExpanded,
|
|
13
|
+
title,
|
|
14
|
+
onToggleClick,
|
|
15
|
+
}: SpanCardToggleProps) => (
|
|
16
|
+
<Collapsible.Trigger asChild>
|
|
17
|
+
<button
|
|
18
|
+
className="flex h-4 w-5 shrink-0 items-center justify-center"
|
|
19
|
+
onClick={onToggleClick}
|
|
20
|
+
onKeyDown={onToggleClick}
|
|
21
|
+
aria-label={`${isExpanded ? "Collapse" : "Expand"} ${title} children`}
|
|
22
|
+
aria-expanded={isExpanded}
|
|
23
|
+
type="button"
|
|
24
|
+
>
|
|
25
|
+
{isExpanded ? (
|
|
26
|
+
<ChevronDown aria-hidden="true" className="size-3 text-muted-foreground" />
|
|
27
|
+
) : (
|
|
28
|
+
<ChevronRight aria-hidden="true" className="size-3 text-muted-foreground" />
|
|
29
|
+
)}
|
|
30
|
+
</button>
|
|
31
|
+
</Collapsible.Trigger>
|
|
32
|
+
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { TraceSpanStatus } from "@evilmartians/agent-prism-types";
|
|
2
|
+
import type { ComponentPropsWithRef } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import { Check, Ellipsis, Info, TriangleAlert } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
type StatusVariant = "dot" | "badge";
|
|
8
|
+
|
|
9
|
+
export type StatusProps = ComponentPropsWithRef<"div"> & {
|
|
10
|
+
status: TraceSpanStatus;
|
|
11
|
+
variant?: StatusVariant;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const STATUS_COLORS_DOT: Record<TraceSpanStatus, string> = {
|
|
15
|
+
success: "bg-green-500 dark:bg-green-500",
|
|
16
|
+
error: "bg-red-500 dark:bg-red-500",
|
|
17
|
+
pending: "bg-violet-500 dark:bg-violet-500",
|
|
18
|
+
warning: "bg-yellow-500 dark:bg-yellow-500",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const STATUS_COLORS_BADGE: Record<TraceSpanStatus, string> = {
|
|
22
|
+
success: "bg-green-100 dark:bg-green-950 text-green-600 dark:text-green-400",
|
|
23
|
+
error: "bg-red-100 dark:bg-red-950 text-red-600 dark:text-red-400",
|
|
24
|
+
pending:
|
|
25
|
+
"bg-violet-100 dark:bg-violet-950 text-violet-600 dark:text-violet-400",
|
|
26
|
+
warning:
|
|
27
|
+
"bg-yellow-100 dark:bg-yellow-950 text-yellow-600 dark:text-yellow-400",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const SpanStatus = ({
|
|
31
|
+
status,
|
|
32
|
+
variant = "dot",
|
|
33
|
+
...rest
|
|
34
|
+
}: StatusProps) => {
|
|
35
|
+
const title = `Status: ${status}`;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex size-4 items-center justify-center" {...rest}>
|
|
39
|
+
{variant === "dot" ? (
|
|
40
|
+
<SpanStatusDot status={status} title={title} />
|
|
41
|
+
) : (
|
|
42
|
+
<SpanStatusBadge status={status} title={title} />
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
interface StatusWithTitleProps extends StatusProps {
|
|
49
|
+
title: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SpanStatusDot = ({ status, title }: StatusWithTitleProps) => {
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
className={cn("block size-1.5 rounded-full", STATUS_COLORS_DOT[status])}
|
|
56
|
+
aria-label={title}
|
|
57
|
+
title={title}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const SpanStatusBadge = ({ status, title }: StatusWithTitleProps) => {
|
|
63
|
+
return (
|
|
64
|
+
<span
|
|
65
|
+
className={cn(
|
|
66
|
+
"inline-flex items-center justify-center",
|
|
67
|
+
"h-3.5 w-4 rounded",
|
|
68
|
+
STATUS_COLORS_BADGE[status],
|
|
69
|
+
)}
|
|
70
|
+
aria-label={title}
|
|
71
|
+
title={title}
|
|
72
|
+
>
|
|
73
|
+
{status === "success" && <Check className="size-2.5" aria-hidden />}
|
|
74
|
+
{status === "error" && <TriangleAlert className="size-2.5" aria-hidden />}
|
|
75
|
+
{status === "warning" && <Info className="size-2.5" aria-hidden />}
|
|
76
|
+
{status === "pending" && <Ellipsis className="size-2.5" aria-hidden />}
|
|
77
|
+
</span>
|
|
78
|
+
);
|
|
79
|
+
};
|