@tangle-network/ui 1.0.0

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 (220) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
  5. package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
  6. package/dist/auth.d.ts +74 -0
  7. package/dist/auth.js +15 -0
  8. package/dist/button-CMQuQEW_.d.ts +17 -0
  9. package/dist/chat.d.ts +232 -0
  10. package/dist/chat.js +30 -0
  11. package/dist/chunk-2NFQRQOD.js +1009 -0
  12. package/dist/chunk-2VH6PUXD.js +186 -0
  13. package/dist/chunk-34A66VBG.js +214 -0
  14. package/dist/chunk-3OI2QKFD.js +0 -0
  15. package/dist/chunk-4CLN43XT.js +45 -0
  16. package/dist/chunk-54SQQMMM.js +156 -0
  17. package/dist/chunk-5Z5ZYMOJ.js +0 -0
  18. package/dist/chunk-66BNMOVT.js +167 -0
  19. package/dist/chunk-6BGQA4BQ.js +0 -0
  20. package/dist/chunk-7UO2ZMRQ.js +133 -0
  21. package/dist/chunk-BX6AQMUS.js +183 -0
  22. package/dist/chunk-CD53GZOM.js +59 -0
  23. package/dist/chunk-CSAIKY36.js +54 -0
  24. package/dist/chunk-EEE55AVS.js +1201 -0
  25. package/dist/chunk-GYPQXTJU.js +230 -0
  26. package/dist/chunk-HFL6R6IF.js +37 -0
  27. package/dist/chunk-HJKCSXCH.js +737 -0
  28. package/dist/chunk-LISXUB4D.js +1222 -0
  29. package/dist/chunk-LQS34IGP.js +0 -0
  30. package/dist/chunk-MKTSMWVD.js +109 -0
  31. package/dist/chunk-NKDZ7GZE.js +192 -0
  32. package/dist/chunk-OEX7NZE3.js +321 -0
  33. package/dist/chunk-Q56BYXQF.js +61 -0
  34. package/dist/chunk-Q7EIIWTC.js +0 -0
  35. package/dist/chunk-REJESC5U.js +117 -0
  36. package/dist/chunk-RQGKSCEZ.js +0 -0
  37. package/dist/chunk-RQHJBTEU.js +10 -0
  38. package/dist/chunk-TMFOPHHN.js +299 -0
  39. package/dist/chunk-XGKULLYE.js +40 -0
  40. package/dist/chunk-XIHMJ7ZQ.js +614 -0
  41. package/dist/chunk-YJ2G3XO5.js +1048 -0
  42. package/dist/chunk-YNN4O57I.js +754 -0
  43. package/dist/code-block-DjXf8eOG.d.ts +19 -0
  44. package/dist/document-editor-pane-A5LT5H4N.js +12 -0
  45. package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
  46. package/dist/editor.d.ts +120 -0
  47. package/dist/editor.js +34 -0
  48. package/dist/files.d.ts +175 -0
  49. package/dist/files.js +20 -0
  50. package/dist/hooks.d.ts +56 -0
  51. package/dist/hooks.js +41 -0
  52. package/dist/index.d.ts +43 -0
  53. package/dist/index.js +446 -0
  54. package/dist/markdown.d.ts +15 -0
  55. package/dist/markdown.js +14 -0
  56. package/dist/message-BHWbxBtT.d.ts +15 -0
  57. package/dist/openui.d.ts +115 -0
  58. package/dist/openui.js +12 -0
  59. package/dist/parts-dj7AcUg0.d.ts +36 -0
  60. package/dist/primitives.d.ts +332 -0
  61. package/dist/primitives.js +191 -0
  62. package/dist/run-PfLmDAox.d.ts +41 -0
  63. package/dist/run.d.ts +69 -0
  64. package/dist/run.js +36 -0
  65. package/dist/sdk-hooks.d.ts +285 -0
  66. package/dist/sdk-hooks.js +31 -0
  67. package/dist/stores.d.ts +17 -0
  68. package/dist/stores.js +76 -0
  69. package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
  70. package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
  71. package/dist/tool-previews.d.ts +48 -0
  72. package/dist/tool-previews.js +21 -0
  73. package/dist/types.d.ts +19 -0
  74. package/dist/types.js +1 -0
  75. package/dist/utils.d.ts +45 -0
  76. package/dist/utils.js +32 -0
  77. package/package.json +193 -0
  78. package/src/auth/auth.tsx +228 -0
  79. package/src/auth/index.ts +13 -0
  80. package/src/auth/login-layout.tsx +46 -0
  81. package/src/chat/agent-timeline.stories.tsx +429 -0
  82. package/src/chat/agent-timeline.tsx +360 -0
  83. package/src/chat/chat-container.tsx +486 -0
  84. package/src/chat/chat-input.stories.tsx +142 -0
  85. package/src/chat/chat-input.tsx +389 -0
  86. package/src/chat/chat-message.stories.tsx +237 -0
  87. package/src/chat/chat-message.tsx +129 -0
  88. package/src/chat/index.ts +18 -0
  89. package/src/chat/message-list.stories.tsx +336 -0
  90. package/src/chat/message-list.tsx +79 -0
  91. package/src/chat/thinking-indicator.stories.tsx +56 -0
  92. package/src/chat/thinking-indicator.tsx +30 -0
  93. package/src/chat/user-message.stories.tsx +92 -0
  94. package/src/chat/user-message.tsx +43 -0
  95. package/src/editor/document-editor-pane.tsx +351 -0
  96. package/src/editor/editor-provider.tsx +428 -0
  97. package/src/editor/editor-toolbar.tsx +130 -0
  98. package/src/editor/index.ts +31 -0
  99. package/src/editor/markdown-conversion.ts +21 -0
  100. package/src/editor/markdown-document-editor.tsx +137 -0
  101. package/src/editor/tiptap-editor.tsx +331 -0
  102. package/src/editor/use-editor.ts +221 -0
  103. package/src/files/file-artifact-pane.tsx +183 -0
  104. package/src/files/file-preview.tsx +342 -0
  105. package/src/files/file-tabs.tsx +71 -0
  106. package/src/files/file-tree.tsx +258 -0
  107. package/src/files/index.ts +17 -0
  108. package/src/files/rich-file-tree.stories.tsx +104 -0
  109. package/src/files/rich-file-tree.test.tsx +42 -0
  110. package/src/files/rich-file-tree.tsx +232 -0
  111. package/src/hooks/index.ts +10 -0
  112. package/src/hooks/use-auth.ts +153 -0
  113. package/src/hooks/use-auto-scroll.ts +59 -0
  114. package/src/hooks/use-dropdown-menu.ts +40 -0
  115. package/src/hooks/use-live-time.test.tsx +40 -0
  116. package/src/hooks/use-live-time.ts +27 -0
  117. package/src/hooks/use-realtime-session.ts +319 -0
  118. package/src/hooks/use-run-collapse-state.ts +25 -0
  119. package/src/hooks/use-run-groups.ts +111 -0
  120. package/src/hooks/use-sdk-session.ts +575 -0
  121. package/src/hooks/use-sse-stream.ts +475 -0
  122. package/src/hooks/use-tool-call-stream.ts +96 -0
  123. package/src/index.ts +14 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/markdown/code-block.tsx +198 -0
  126. package/src/markdown/index.ts +2 -0
  127. package/src/markdown/markdown.stories.tsx +190 -0
  128. package/src/markdown/markdown.tsx +62 -0
  129. package/src/openui/index.ts +20 -0
  130. package/src/openui/openui-artifact-renderer.tsx +542 -0
  131. package/src/primitives/artifact-pane.tsx +91 -0
  132. package/src/primitives/avatar.stories.tsx +95 -0
  133. package/src/primitives/avatar.tsx +47 -0
  134. package/src/primitives/badge.stories.tsx +57 -0
  135. package/src/primitives/badge.tsx +97 -0
  136. package/src/primitives/button.stories.tsx +48 -0
  137. package/src/primitives/button.tsx +115 -0
  138. package/src/primitives/card.stories.tsx +53 -0
  139. package/src/primitives/card.tsx +98 -0
  140. package/src/primitives/code-block.stories.tsx +115 -0
  141. package/src/primitives/code-block.tsx +22 -0
  142. package/src/primitives/design-tokens.stories.tsx +162 -0
  143. package/src/primitives/dialog.stories.tsx +176 -0
  144. package/src/primitives/dialog.tsx +137 -0
  145. package/src/primitives/drop-zone.stories.tsx +123 -0
  146. package/src/primitives/drop-zone.tsx +131 -0
  147. package/src/primitives/dropdown-menu.stories.tsx +122 -0
  148. package/src/primitives/dropdown-menu.tsx +214 -0
  149. package/src/primitives/empty-state.stories.tsx +81 -0
  150. package/src/primitives/empty-state.tsx +40 -0
  151. package/src/primitives/index.ts +118 -0
  152. package/src/primitives/input.stories.tsx +113 -0
  153. package/src/primitives/input.tsx +136 -0
  154. package/src/primitives/label.stories.tsx +84 -0
  155. package/src/primitives/label.tsx +24 -0
  156. package/src/primitives/progress.stories.tsx +93 -0
  157. package/src/primitives/progress.tsx +50 -0
  158. package/src/primitives/segmented-control.test.tsx +328 -0
  159. package/src/primitives/segmented-control.tsx +154 -0
  160. package/src/primitives/select.stories.tsx +164 -0
  161. package/src/primitives/select.tsx +158 -0
  162. package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
  163. package/src/primitives/sidebar-drop-zone.tsx +149 -0
  164. package/src/primitives/skeleton.stories.tsx +79 -0
  165. package/src/primitives/skeleton.tsx +55 -0
  166. package/src/primitives/stat-card.stories.tsx +137 -0
  167. package/src/primitives/stat-card.tsx +97 -0
  168. package/src/primitives/switch.stories.tsx +85 -0
  169. package/src/primitives/switch.tsx +28 -0
  170. package/src/primitives/table.stories.tsx +170 -0
  171. package/src/primitives/table.tsx +116 -0
  172. package/src/primitives/tabs.stories.tsx +180 -0
  173. package/src/primitives/tabs.tsx +71 -0
  174. package/src/primitives/terminal-display.stories.tsx +191 -0
  175. package/src/primitives/terminal-display.tsx +189 -0
  176. package/src/primitives/theme-toggle.stories.tsx +32 -0
  177. package/src/primitives/theme-toggle.tsx +96 -0
  178. package/src/primitives/toast.stories.tsx +155 -0
  179. package/src/primitives/toast.tsx +190 -0
  180. package/src/primitives/upload-progress.stories.tsx +120 -0
  181. package/src/primitives/upload-progress.tsx +110 -0
  182. package/src/run/expanded-tool-detail.stories.tsx +182 -0
  183. package/src/run/expanded-tool-detail.tsx +186 -0
  184. package/src/run/index.ts +13 -0
  185. package/src/run/inline-thinking-item.stories.tsx +136 -0
  186. package/src/run/inline-thinking-item.tsx +120 -0
  187. package/src/run/inline-tool-item.stories.tsx +222 -0
  188. package/src/run/inline-tool-item.tsx +190 -0
  189. package/src/run/run-group.stories.tsx +322 -0
  190. package/src/run/run-group.tsx +569 -0
  191. package/src/run/run-item-primitives.tsx +17 -0
  192. package/src/run/tool-call-feed.stories.tsx +294 -0
  193. package/src/run/tool-call-feed.tsx +192 -0
  194. package/src/run/tool-call-step.stories.tsx +198 -0
  195. package/src/run/tool-call-step.tsx +240 -0
  196. package/src/sdk-hooks.ts +38 -0
  197. package/src/stores/active-sessions-store.ts +455 -0
  198. package/src/stores/chat-store.ts +43 -0
  199. package/src/stores/index.ts +2 -0
  200. package/src/tool-previews/command-preview.tsx +116 -0
  201. package/src/tool-previews/diff-preview.tsx +85 -0
  202. package/src/tool-previews/glob-results-preview.tsx +98 -0
  203. package/src/tool-previews/grep-results-preview.tsx +157 -0
  204. package/src/tool-previews/index.ts +22 -0
  205. package/src/tool-previews/preview-primitives.tsx +84 -0
  206. package/src/tool-previews/question-preview.tsx +101 -0
  207. package/src/tool-previews/web-search-preview.tsx +117 -0
  208. package/src/tool-previews/write-file-preview.tsx +80 -0
  209. package/src/types/branding.ts +11 -0
  210. package/src/types/index.ts +5 -0
  211. package/src/types/message.ts +13 -0
  212. package/src/types/parts.ts +51 -0
  213. package/src/types/run.ts +56 -0
  214. package/src/types/tool-display.ts +41 -0
  215. package/src/utils/copy-text.ts +30 -0
  216. package/src/utils/format.test.ts +43 -0
  217. package/src/utils/format.ts +56 -0
  218. package/src/utils/index.ts +10 -0
  219. package/src/utils/time-ago.ts +9 -0
  220. package/src/utils/tool-display.ts +238 -0
@@ -0,0 +1,360 @@
1
+ import { type KeyboardEvent, type ReactNode } from "react";
2
+ import {
3
+ AlertTriangle,
4
+ CheckCircle2,
5
+ CircleDot,
6
+ FileText,
7
+ Info,
8
+ } from "lucide-react";
9
+ import { cn } from "../lib/utils";
10
+ import { type MessageRole } from "./chat-message";
11
+ import { Markdown } from "../markdown/markdown";
12
+ import { ThinkingIndicator } from "./thinking-indicator";
13
+ import { type ToolCallData } from "../run/tool-call-feed";
14
+ import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step";
15
+
16
+ export type AgentTimelineTone = "default" | "info" | "success" | "warning" | "error";
17
+
18
+ export interface AgentTimelineMessageItem {
19
+ id: string;
20
+ kind: "message";
21
+ role: MessageRole;
22
+ content: string;
23
+ toolCalls?: ReactNode;
24
+ isStreaming?: boolean;
25
+ timestamp?: Date;
26
+ after?: ReactNode;
27
+ }
28
+
29
+ export interface AgentTimelineToolItem {
30
+ id: string;
31
+ kind: "tool";
32
+ call: ToolCallData;
33
+ }
34
+
35
+ export interface AgentTimelineToolGroupItem {
36
+ id: string;
37
+ kind: "tool_group";
38
+ title?: string;
39
+ calls: ToolCallData[];
40
+ }
41
+
42
+ export interface AgentTimelineStatusItem {
43
+ id: string;
44
+ kind: "status";
45
+ label: string;
46
+ detail?: string;
47
+ tone?: AgentTimelineTone;
48
+ }
49
+
50
+ export interface AgentTimelineArtifactItem {
51
+ id: string;
52
+ kind: "artifact";
53
+ title: string;
54
+ description?: string;
55
+ meta?: ReactNode;
56
+ icon?: ReactNode;
57
+ tone?: AgentTimelineTone;
58
+ action?: ReactNode;
59
+ onClick?: () => void;
60
+ }
61
+
62
+ export interface AgentTimelineCustomItem {
63
+ id: string;
64
+ kind: "custom";
65
+ content: ReactNode;
66
+ }
67
+
68
+ export type AgentTimelineItem =
69
+ | AgentTimelineMessageItem
70
+ | AgentTimelineToolItem
71
+ | AgentTimelineToolGroupItem
72
+ | AgentTimelineStatusItem
73
+ | AgentTimelineArtifactItem
74
+ | AgentTimelineCustomItem;
75
+
76
+ export interface AgentTimelineProps {
77
+ items: AgentTimelineItem[];
78
+ isThinking?: boolean;
79
+ emptyState?: ReactNode;
80
+ className?: string;
81
+ }
82
+
83
+ const TONE_STYLES: Record<AgentTimelineTone, { dot: string; card: string; text: string; icon: typeof Info }> = {
84
+ default: {
85
+ dot: "bg-[var(--border-hover)]",
86
+ card: "border-border bg-card",
87
+ text: "text-foreground",
88
+ icon: CircleDot,
89
+ },
90
+ info: {
91
+ dot: "bg-[var(--surface-info-text)]",
92
+ card: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)]",
93
+ text: "text-[var(--surface-info-text)]",
94
+ icon: Info,
95
+ },
96
+ success: {
97
+ dot: "bg-[var(--surface-success-text)]",
98
+ card: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]",
99
+ text: "text-[var(--surface-success-text)]",
100
+ icon: CheckCircle2,
101
+ },
102
+ warning: {
103
+ dot: "bg-[var(--surface-warning-text)]",
104
+ card: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]",
105
+ text: "text-[var(--surface-warning-text)]",
106
+ icon: AlertTriangle,
107
+ },
108
+ error: {
109
+ dot: "bg-[var(--surface-danger-text)]",
110
+ card: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)]",
111
+ text: "text-[var(--surface-danger-text)]",
112
+ icon: AlertTriangle,
113
+ },
114
+ };
115
+
116
+ function formatTime(date: Date): string {
117
+ return date.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
118
+ }
119
+
120
+ interface AgentTimelineRowProps {
121
+ isLast: boolean;
122
+ accentClassName: string;
123
+ children: ReactNode;
124
+ }
125
+
126
+ function AgentTimelineRow({ isLast, accentClassName, children }: AgentTimelineRowProps) {
127
+ return (
128
+ <div className="grid grid-cols-[1.25rem_minmax(0,1fr)] gap-x-4">
129
+ <div className="relative flex justify-center">
130
+ {!isLast && (
131
+ <span className="absolute top-4 bottom-[-0.75rem] left-1/2 w-px -translate-x-1/2 bg-border" />
132
+ )}
133
+ <span className={cn("relative mt-2 h-[var(--timeline-dot-size)] w-[var(--timeline-dot-size)] rounded-full ring-4 ring-[var(--bg-root)]", accentClassName)} />
134
+ </div>
135
+ <div className="min-w-0 pb-3">{children}</div>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ function UserMessage({ item }: { item: AgentTimelineMessageItem }) {
141
+ return (
142
+ <div className="mb-3 flex justify-end">
143
+ <div className="max-w-[72%]">
144
+ <div className="rounded-2xl border border-border bg-muted/50 px-4 py-3">
145
+ {item.timestamp && (
146
+ <div className="mb-1.5 text-right text-[var(--font-size-xs)] text-muted-foreground">
147
+ {formatTime(item.timestamp)}
148
+ </div>
149
+ )}
150
+ <div className="whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground">
151
+ {item.content}
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ function AssistantMessage({ item }: { item: AgentTimelineMessageItem }) {
160
+ return (
161
+ <div className="-mt-0.5">
162
+ {item.timestamp && (
163
+ <div className="mb-2 text-[var(--font-size-xs)] text-muted-foreground">
164
+ {formatTime(item.timestamp)}
165
+ </div>
166
+ )}
167
+ {item.content && (
168
+ <Markdown className="tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]">{item.content}</Markdown>
169
+ )}
170
+ {item.isStreaming && (
171
+ <span className="ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-primary align-text-bottom" />
172
+ )}
173
+ {item.toolCalls && <div className="mt-3">{item.toolCalls}</div>}
174
+ {item.after && (
175
+ <div className="mt-3 border-t border-border pt-3">
176
+ {item.after}
177
+ </div>
178
+ )}
179
+ </div>
180
+ );
181
+ }
182
+
183
+ function StatusCard({ item }: { item: AgentTimelineStatusItem }) {
184
+ const tone = TONE_STYLES[item.tone ?? "default"];
185
+ const Icon = tone.icon;
186
+
187
+ return (
188
+ <div className={cn("rounded-[var(--radius-lg)] border px-4 py-3", tone.card)}>
189
+ <div className="flex items-start gap-3">
190
+ <Icon className={cn("mt-0.5 h-4 w-4 shrink-0", tone.text)} />
191
+ <div className="min-w-0">
192
+ <div className={cn("text-sm font-medium", tone.text)}>{item.label}</div>
193
+ {item.detail && (
194
+ <div className="mt-0.5 text-sm text-muted-foreground">{item.detail}</div>
195
+ )}
196
+ </div>
197
+ </div>
198
+ </div>
199
+ );
200
+ }
201
+
202
+ function ArtifactCard({ item }: { item: AgentTimelineArtifactItem }) {
203
+ const tone = TONE_STYLES[item.tone ?? "default"];
204
+ const content = (
205
+ <div className={cn("rounded-[var(--radius-lg)] border px-4 py-3", tone.card)}>
206
+ <div className="flex items-start gap-3">
207
+ <div className="mt-0.5 flex h-[var(--avatar-size)] w-[var(--avatar-size)] shrink-0 items-center justify-center rounded-[var(--radius-md)] bg-muted/50 text-foreground">
208
+ {item.icon ?? <FileText className="h-4 w-4" />}
209
+ </div>
210
+ <div className="min-w-0 flex-1">
211
+ <div className="text-sm font-medium text-foreground">{item.title}</div>
212
+ {item.description && (
213
+ <div className="mt-1 text-sm text-muted-foreground">{item.description}</div>
214
+ )}
215
+ {item.meta && (
216
+ <div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
217
+ {item.meta}
218
+ </div>
219
+ )}
220
+ </div>
221
+ {item.action && <div className="shrink-0">{item.action}</div>}
222
+ </div>
223
+ </div>
224
+ );
225
+
226
+ if (!item.onClick) return content;
227
+
228
+ return (
229
+ <div
230
+ role="button"
231
+ tabIndex={0}
232
+ onClick={item.onClick}
233
+ onKeyDown={(event: KeyboardEvent<HTMLDivElement>) => {
234
+ if (event.key === "Enter" || event.key === " ") {
235
+ event.preventDefault();
236
+ item.onClick?.();
237
+ }
238
+ }}
239
+ className="block w-full text-left transition-transform hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
240
+ >
241
+ {content}
242
+ </div>
243
+ );
244
+ }
245
+
246
+ /**
247
+ * AgentTimeline — unified mixed-content timeline for agent-backed sandbox
248
+ * sessions. Renders messages, tool steps, status cards, and artifact handoffs in
249
+ * a single execution narrative.
250
+ */
251
+ export function AgentTimeline({
252
+ items,
253
+ isThinking,
254
+ emptyState,
255
+ className,
256
+ }: AgentTimelineProps) {
257
+ if (items.length === 0 && !isThinking) {
258
+ return emptyState ? (
259
+ <div className={cn("flex h-full items-center justify-center p-4", className)}>
260
+ {emptyState}
261
+ </div>
262
+ ) : null;
263
+ }
264
+
265
+ const renderedItems: AgentTimelineItem[] = isThinking
266
+ ? [...items, { id: "__thinking__", kind: "custom", content: <ThinkingIndicator /> }]
267
+ : items;
268
+
269
+ // Determine which items participate in the timeline connector (non-user-message items)
270
+ // User messages are rendered outside the timeline grid
271
+ const timelineItems = renderedItems.filter((item) => !(item.kind === "message" && item.role === "user"));
272
+
273
+ return (
274
+ <div className={cn("mx-auto w-full max-w-5xl px-4 py-4", className)}>
275
+ {renderedItems.map((item, index) => {
276
+ // User messages: right-aligned bubble, no connector
277
+ if (item.kind === "message" && item.role === "user") {
278
+ return <UserMessage key={item.id} item={item} />;
279
+ }
280
+
281
+ const timelineIndex = timelineItems.indexOf(item);
282
+ const isLast = timelineIndex === timelineItems.length - 1;
283
+
284
+ if (item.kind === "message") {
285
+ return (
286
+ <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--brand-glow)]">
287
+ <AssistantMessage item={item} />
288
+ </AgentTimelineRow>
289
+ );
290
+ }
291
+
292
+ if (item.kind === "tool") {
293
+ return (
294
+ <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
295
+ <ToolCallStep
296
+ type={item.call.type}
297
+ label={item.call.label}
298
+ status={item.call.status}
299
+ detail={item.call.detail}
300
+ output={item.call.output}
301
+ duration={item.call.duration}
302
+ />
303
+ </AgentTimelineRow>
304
+ );
305
+ }
306
+
307
+ if (item.kind === "tool_group") {
308
+ return (
309
+ <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
310
+ <ToolCallGroup title={item.title}>
311
+ {item.calls.map((call) => (
312
+ <ToolCallStep
313
+ key={call.id}
314
+ type={call.type}
315
+ label={call.label}
316
+ status={call.status}
317
+ detail={call.detail}
318
+ output={call.output}
319
+ duration={call.duration}
320
+ />
321
+ ))}
322
+ </ToolCallGroup>
323
+ </AgentTimelineRow>
324
+ );
325
+ }
326
+
327
+ if (item.kind === "status") {
328
+ return (
329
+ <AgentTimelineRow
330
+ key={item.id}
331
+ isLast={isLast}
332
+ accentClassName={TONE_STYLES[item.tone ?? "default"].dot}
333
+ >
334
+ <StatusCard item={item} />
335
+ </AgentTimelineRow>
336
+ );
337
+ }
338
+
339
+ if (item.kind === "artifact") {
340
+ return (
341
+ <AgentTimelineRow
342
+ key={item.id}
343
+ isLast={isLast}
344
+ accentClassName={TONE_STYLES[item.tone ?? "default"].dot}
345
+ >
346
+ <ArtifactCard item={item} />
347
+ </AgentTimelineRow>
348
+ );
349
+ }
350
+
351
+ // custom
352
+ return (
353
+ <AgentTimelineRow key={item.id} isLast={isLast} accentClassName="bg-[var(--border-hover)]">
354
+ {(item as AgentTimelineCustomItem).content}
355
+ </AgentTimelineRow>
356
+ );
357
+ })}
358
+ </div>
359
+ );
360
+ }