@elizaos/client 1.6.1-alpha.4 → 1.6.1-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/assets/main-BM2lpId8.js +155 -0
  2. package/dist/assets/main-BM2lpId8.js.map +1 -0
  3. package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
  4. package/dist/assets/{main-C4q5_rtN.js → main-CQAV8tyh.js} +4 -4
  5. package/dist/assets/main-CQAV8tyh.js.map +1 -0
  6. package/dist/assets/react-vendor-C1OK-nqm.js +611 -0
  7. package/dist/assets/react-vendor-C1OK-nqm.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/package.json +29 -25
  10. package/src/components/agent-prism/Avatar.tsx +164 -0
  11. package/src/components/agent-prism/Badge.tsx +109 -0
  12. package/src/components/agent-prism/Button.tsx +138 -0
  13. package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
  14. package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
  15. package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
  16. package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
  17. package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
  18. package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
  19. package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
  20. package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
  21. package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
  22. package/src/components/agent-prism/IconButton.tsx +75 -0
  23. package/src/components/agent-prism/PriceBadge.tsx +12 -0
  24. package/src/components/agent-prism/SearchInput.tsx +17 -0
  25. package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
  26. package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
  27. package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
  28. package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
  29. package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
  30. package/src/components/agent-prism/SpanStatus.tsx +79 -0
  31. package/src/components/agent-prism/Tabs.tsx +141 -0
  32. package/src/components/agent-prism/TextInput.tsx +142 -0
  33. package/src/components/agent-prism/TimestampBadge.tsx +28 -0
  34. package/src/components/agent-prism/TokensBadge.tsx +26 -0
  35. package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
  36. package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
  37. package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
  38. package/src/components/agent-prism/TraceViewer.tsx +476 -0
  39. package/src/components/agent-prism/TreeView.tsx +57 -0
  40. package/src/components/agent-prism/shared.ts +210 -0
  41. package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
  42. package/src/components/agent-sidebar.tsx +2 -2
  43. package/src/components/chat.tsx +8 -8
  44. package/src/lib/agent-prism-utils.ts +46 -0
  45. package/src/lib/eliza-span-adapter.ts +487 -0
  46. package/dist/assets/main-BNtEiK3o.js +0 -141
  47. package/dist/assets/main-BNtEiK3o.js.map +0 -1
  48. package/dist/assets/main-C4q5_rtN.js.map +0 -1
  49. package/dist/assets/react-vendor-pe76PXQl.js +0 -546
  50. package/dist/assets/react-vendor-pe76PXQl.js.map +0 -1
@@ -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
+ };
@@ -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
+ };