@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.
Files changed (48) hide show
  1. package/dist/assets/{main-C4q5_rtN.js → main-4tyUgNqd.js} +3 -3
  2. package/dist/assets/{main-C4q5_rtN.js.map → main-4tyUgNqd.js.map} +1 -1
  3. package/dist/assets/{main-BNtEiK3o.js → main-Bbs84AcL.js} +77 -63
  4. package/dist/assets/main-Bbs84AcL.js.map +1 -0
  5. package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
  6. package/dist/assets/react-vendor-DxnAFk-d.js +611 -0
  7. package/dist/assets/react-vendor-DxnAFk-d.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/package.json +8 -4
  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.map +0 -1
  47. package/dist/assets/react-vendor-pe76PXQl.js +0 -546
  48. 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
+ };