@tangle-network/ui 11.6.0 → 11.8.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 (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +43 -0
  3. package/dist/chat.d.ts +18 -8
  4. package/dist/chat.js +9 -7
  5. package/dist/chunk-4CLN43XT.js +45 -0
  6. package/dist/{chunk-AZWDI2JG.js → chunk-565EATZZ.js} +1 -1
  7. package/dist/{chunk-XROQW5J7.js → chunk-BWOUSEHM.js} +1 -1
  8. package/dist/{chunk-J2G7KCYH.js → chunk-KNYSKNZX.js} +113 -69
  9. package/dist/chunk-L57WRIND.js +59 -0
  10. package/dist/{chunk-ULDNFLIM.js → chunk-ND3ZFXQK.js} +11 -58
  11. package/dist/{chunk-AAUNOHVL.js → chunk-NWECTSOH.js} +1 -1
  12. package/dist/{chunk-R5GVQDL3.js → chunk-OXZRCXJK.js} +9 -7
  13. package/dist/{chunk-XPYPZMYT.js → chunk-Q7ZR6LSD.js} +1 -1
  14. package/dist/{chunk-RKC3GXHE.js → chunk-X53RZSX6.js} +99 -104
  15. package/dist/{chunk-PLWRAS5Z.js → chunk-XGOWUDDW.js} +270 -106
  16. package/dist/{chunk-ZRVH3WCA.js → chunk-XU3HB5QL.js} +21 -1
  17. package/dist/chunk-YTO5V6QS.js +19 -0
  18. package/dist/files.d.ts +41 -12
  19. package/dist/files.js +13 -5
  20. package/dist/hooks.js +9 -7
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +37 -24
  23. package/dist/primitives.d.ts +15 -1
  24. package/dist/primitives.js +5 -1
  25. package/dist/run.d.ts +22 -17
  26. package/dist/run.js +11 -8
  27. package/dist/sdk-hooks.js +9 -7
  28. package/dist/tool-previews.js +2 -2
  29. package/dist/utils.d.ts +6 -0
  30. package/dist/utils.js +6 -4
  31. package/package.json +1 -1
  32. package/src/chat/agent-timeline.stories.tsx +163 -10
  33. package/src/chat/agent-timeline.test.tsx +114 -7
  34. package/src/chat/agent-timeline.tsx +120 -158
  35. package/src/chat/chat-container.test.tsx +59 -0
  36. package/src/chat/user-message.test.tsx +33 -0
  37. package/src/chat/user-message.tsx +13 -8
  38. package/src/files/file-artifact-pane.stories.tsx +149 -0
  39. package/src/files/file-artifact-pane.test.tsx +192 -0
  40. package/src/files/file-artifact-pane.tsx +6 -1
  41. package/src/files/file-format.ts +42 -4
  42. package/src/files/file-preview-kind.test.ts +109 -0
  43. package/src/files/file-preview.tsx +315 -132
  44. package/src/files/index.ts +8 -1
  45. package/src/primitives/index.ts +3 -0
  46. package/src/primitives/text-shimmer.test.tsx +34 -0
  47. package/src/primitives/text-shimmer.tsx +82 -0
  48. package/src/run/inline-thinking-item.stories.tsx +2 -2
  49. package/src/run/inline-thinking-item.tsx +2 -2
  50. package/src/run/inline-tool-item.test.tsx +76 -0
  51. package/src/run/inline-tool-item.tsx +9 -7
  52. package/src/run/run-group.tsx +4 -3
  53. package/src/run/run-row-shell.test.tsx +74 -4
  54. package/src/run/run-row-shell.tsx +81 -53
  55. package/src/run/tool-call-step.tsx +9 -7
  56. package/src/utils/tool-display.test.ts +53 -0
  57. package/src/utils/tool-display.ts +16 -15
  58. package/dist/chunk-LQS34IGP.js +0 -0
  59. /package/dist/{chunk-EJ5NBW7V.js → chunk-LDLOJKPC.js} +0 -0
@@ -18,10 +18,6 @@ function extractCommand(input) {
18
18
  function extractFilePath(input) {
19
19
  return extractString(input, "file_path") ?? extractString(input, "path") ?? extractString(input, "filePath") ?? extractString(input, "file");
20
20
  }
21
- function cleanPath(path) {
22
- const parts = path.split("/");
23
- return parts.length > 3 ? ".../" + parts.slice(-2).join("/") : path;
24
- }
25
21
  var TOOL_CATEGORY_ICONS = {
26
22
  command: "terminal",
27
23
  write: "file-plus",
@@ -87,7 +83,7 @@ function getToolDisplayMetadata(part) {
87
83
  case "command":
88
84
  case "execute":
89
85
  return {
90
- title: "Run command",
86
+ title: "Shell",
91
87
  description: command ? truncateCommand(command) : void 0,
92
88
  displayVariant: "command",
93
89
  commandSnippet: command
@@ -96,7 +92,7 @@ function getToolDisplayMetadata(part) {
96
92
  case "write_file":
97
93
  case "create_file":
98
94
  return {
99
- title: filePath ? `Write ${cleanPath(filePath)}` : "Write file",
95
+ title: "Write",
100
96
  description: filePath,
101
97
  displayVariant: "write-file",
102
98
  targetPath: filePath
@@ -104,7 +100,7 @@ function getToolDisplayMetadata(part) {
104
100
  case "edit":
105
101
  case "patch":
106
102
  return {
107
- title: filePath ? `Edit ${cleanPath(filePath)}` : "Edit file",
103
+ title: "Edit",
108
104
  description: filePath,
109
105
  hasDiffOutput: true,
110
106
  diffFilePath: filePath,
@@ -115,7 +111,7 @@ function getToolDisplayMetadata(part) {
115
111
  case "read_file":
116
112
  case "cat":
117
113
  return {
118
- title: filePath ? `Read ${cleanPath(filePath)}` : "Read file",
114
+ title: "Read",
119
115
  description: filePath,
120
116
  displayVariant: "read-file",
121
117
  targetPath: filePath
@@ -125,7 +121,7 @@ function getToolDisplayMetadata(part) {
125
121
  case "rg": {
126
122
  const pattern = extractString(input, "pattern");
127
123
  return {
128
- title: pattern ? `Search: ${pattern}` : "Search",
124
+ title: "Search",
129
125
  description: pattern,
130
126
  displayVariant: "grep"
131
127
  };
@@ -135,7 +131,7 @@ function getToolDisplayMetadata(part) {
135
131
  case "ls": {
136
132
  const pattern = extractString(input, "pattern");
137
133
  return {
138
- title: pattern ? `Find: ${pattern}` : "Find files",
134
+ title: "Find",
139
135
  description: pattern,
140
136
  displayVariant: "glob"
141
137
  };
@@ -145,8 +141,8 @@ function getToolDisplayMetadata(part) {
145
141
  case "fetch": {
146
142
  const query = extractString(input, "query") ?? extractString(input, "url");
147
143
  return {
148
- title: query ? `Web: ${truncateCommand(query)}` : "Web search",
149
- description: query,
144
+ title: name === "web_search" ? "Web search" : "Fetch",
145
+ description: query ? truncateCommand(query) : void 0,
150
146
  displayVariant: "web-search"
151
147
  };
152
148
  }
@@ -155,8 +151,8 @@ function getToolDisplayMetadata(part) {
155
151
  case "spawn": {
156
152
  const desc = extractString(input, "description") ?? extractString(input, "prompt");
157
153
  return {
158
- title: desc ? `Task: ${truncateCommand(desc)}` : "Agent task",
159
- description: desc
154
+ title: "Task",
155
+ description: desc ? truncateCommand(desc) : void 0
160
156
  };
161
157
  }
162
158
  default:
@@ -175,52 +171,9 @@ function getToolErrorText(part, fallback) {
175
171
  return part.state.error || fallback;
176
172
  }
177
173
 
178
- // src/utils/format.ts
179
- function formatDuration(ms) {
180
- if (ms < 1e3) return "<1s";
181
- const seconds = Math.floor(ms / 1e3);
182
- if (seconds < 60) return `${seconds}s`;
183
- const minutes = Math.floor(seconds / 60);
184
- const remaining = seconds % 60;
185
- return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`;
186
- }
187
- function truncateText(text, max) {
188
- const cleaned = text.replace(/\s+/g, " ").trim();
189
- if (cleaned.length <= max) return cleaned;
190
- return cleaned.slice(0, max).trim() + "...";
191
- }
192
- function formatUptime(ms) {
193
- if (!Number.isFinite(ms) || ms < 0) return "\u2014";
194
- const totalSeconds = Math.floor(ms / 1e3);
195
- if (totalSeconds < 60) return `${totalSeconds}s`;
196
- const minutes = Math.floor(totalSeconds / 60);
197
- const seconds = totalSeconds % 60;
198
- if (minutes < 60) return `${minutes}m ${seconds}s`;
199
- const hours = Math.floor(minutes / 60);
200
- const remMinutes = minutes % 60;
201
- if (hours < 24) return `${hours}h ${remMinutes}m`;
202
- const days = Math.floor(hours / 24);
203
- const remHours = hours % 24;
204
- return `${days}d ${remHours}h`;
205
- }
206
- function formatBytes(bytes) {
207
- if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
208
- if (bytes < 1024) return `${Math.round(bytes)} B`;
209
- const kb = bytes / 1024;
210
- if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`;
211
- const mb = kb / 1024;
212
- if (mb < 1024) return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)} MB`;
213
- const gb = mb / 1024;
214
- return `${gb < 10 ? gb.toFixed(2) : gb.toFixed(1)} GB`;
215
- }
216
-
217
174
  export {
218
175
  TOOL_CATEGORY_ICONS,
219
176
  getToolCategory,
220
177
  getToolDisplayMetadata,
221
- getToolErrorText,
222
- formatDuration,
223
- truncateText,
224
- formatUptime,
225
- formatBytes
178
+ getToolErrorText
226
179
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getSyntaxLanguage
3
- } from "./chunk-ZRVH3WCA.js";
3
+ } from "./chunk-XU3HB5QL.js";
4
4
  import {
5
5
  CodeBlock,
6
6
  CopyButton
@@ -2,14 +2,17 @@ import {
2
2
  ExpandableContent,
3
3
  InlineToolItem,
4
4
  RunRowShell
5
- } from "./chunk-J2G7KCYH.js";
5
+ } from "./chunk-KNYSKNZX.js";
6
6
  import {
7
- formatDuration,
8
- truncateText
9
- } from "./chunk-ULDNFLIM.js";
7
+ TextShimmer
8
+ } from "./chunk-L57WRIND.js";
10
9
  import {
11
10
  OpenUIArtifactRenderer
12
11
  } from "./chunk-3M7BPSSW.js";
12
+ import {
13
+ formatDuration,
14
+ truncateText
15
+ } from "./chunk-4CLN43XT.js";
13
16
  import {
14
17
  Markdown
15
18
  } from "./chunk-P5F43A65.js";
@@ -54,10 +57,9 @@ var InlineThinkingItem = memo(
54
57
  RunRowShell,
55
58
  {
56
59
  icon: /* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5" }),
57
- title: isActive ? "Thinking\u2026" : "Reasoning",
60
+ title: isActive ? /* @__PURE__ */ jsx(TextShimmer, { children: "Thinking\u2026" }) : "Reasoning",
58
61
  description: preview,
59
62
  status: isActive ? "running" : "idle",
60
- startTime,
61
63
  durationMs,
62
64
  open,
63
65
  onOpenChange: setOpen,
@@ -107,7 +109,7 @@ function SpineRow({
107
109
  "span",
108
110
  {
109
111
  className: cn(
110
- "relative mt-1.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
112
+ "relative mt-2.5 h-[var(--timeline-dot-size,0.5rem)] w-[var(--timeline-dot-size,0.5rem)] rounded-full ring-4 ring-[var(--bg-root)]",
111
113
  accentClassName
112
114
  )
113
115
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ToolCallGroup,
3
3
  ToolCallStep
4
- } from "./chunk-J2G7KCYH.js";
4
+ } from "./chunk-KNYSKNZX.js";
5
5
  import {
6
6
  Markdown
7
7
  } from "./chunk-P5F43A65.js";
@@ -2,18 +2,18 @@ import {
2
2
  useAutoScroll,
3
3
  useRunCollapseState,
4
4
  useRunGroups
5
- } from "./chunk-AZWDI2JG.js";
5
+ } from "./chunk-565EATZZ.js";
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-R5GVQDL3.js";
9
+ } from "./chunk-OXZRCXJK.js";
10
10
  import {
11
11
  ToolCallGroup,
12
12
  ToolCallStep
13
- } from "./chunk-J2G7KCYH.js";
13
+ } from "./chunk-KNYSKNZX.js";
14
14
  import {
15
15
  getToolDisplayMetadata
16
- } from "./chunk-ULDNFLIM.js";
16
+ } from "./chunk-ND3ZFXQK.js";
17
17
  import {
18
18
  OpenUIArtifactRenderer
19
19
  } from "./chunk-3M7BPSSW.js";
@@ -45,10 +45,17 @@ var UserMessage = memo(
45
45
  ({ message: _message, parts, content, timestamp, actions }) => {
46
46
  const text = content ?? (parts ?? []).filter((p) => p.type === "text").map((p) => p.text).join("\n");
47
47
  if (!text.trim()) return null;
48
- return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs("div", { className: "flex max-w-[78%] flex-col items-end gap-2", children: [
49
- /* @__PURE__ */ jsxs("div", { className: "w-full rounded-2xl border border-border bg-muted/50 px-4 py-3", children: [
50
- timestamp ? /* @__PURE__ */ jsx("div", { className: "mb-1.5 text-right text-[var(--font-size-xs)] text-muted-foreground", children: formatTime(timestamp) }) : null,
51
- /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground", children: text })
48
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs("div", { className: "group flex max-w-[78%] flex-col items-end gap-2", children: [
49
+ /* @__PURE__ */ jsxs("div", { className: "relative w-full rounded-2xl bg-muted/50 px-4 py-3", children: [
50
+ timestamp ? /* @__PURE__ */ jsx(
51
+ "span",
52
+ {
53
+ "data-user-message-time": "",
54
+ className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
55
+ children: formatTime(timestamp)
56
+ }
57
+ ) : null,
58
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[1.5] text-foreground", children: text })
52
59
  ] }),
53
60
  actions ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-end gap-1.5 text-xs text-muted-foreground", children: actions }) : null
54
61
  ] }) });
@@ -146,55 +153,56 @@ function ThinkingIndicator({ className }) {
146
153
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
147
154
  var TONE_STYLES = {
148
155
  default: {
149
- dot: "bg-[var(--border-hover)]",
150
156
  card: "border-border bg-card",
151
157
  text: "text-foreground",
152
158
  icon: CircleDot
153
159
  },
154
160
  info: {
155
- dot: "bg-[var(--surface-info-text)]",
156
161
  card: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)]",
157
162
  text: "text-[var(--surface-info-text)]",
158
163
  icon: Info
159
164
  },
160
165
  success: {
161
- dot: "bg-[var(--surface-success-text)]",
162
166
  card: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]",
163
167
  text: "text-[var(--surface-success-text)]",
164
168
  icon: CheckCircle2
165
169
  },
166
170
  warning: {
167
- dot: "bg-[var(--surface-warning-text)]",
168
171
  card: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]",
169
172
  text: "text-[var(--surface-warning-text)]",
170
173
  icon: AlertTriangle
171
174
  },
172
175
  error: {
173
- dot: "bg-[var(--surface-danger-text)]",
174
176
  card: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)]",
175
177
  text: "text-[var(--surface-danger-text)]",
176
178
  icon: AlertTriangle
177
179
  }
178
180
  };
179
- function formatTime2(date) {
180
- return date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
181
+ function stepKind(item) {
182
+ if (item.kind === "message") return item.role === "user" ? "user" : "prose";
183
+ if (item.kind === "status" || item.kind === "artifact") return "card";
184
+ return "tool";
181
185
  }
182
- var CARD_ROW_DOT = "mt-[calc((2.5rem-var(--timeline-dot-size))/2)]";
183
- function AgentTimelineRow({ isLast, accentClassName, dotClassName = "mt-2", children }) {
184
- return /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-[1.25rem_minmax(0,1fr)] gap-x-4", children: [
185
- /* @__PURE__ */ jsxs3("div", { className: "relative flex justify-center", children: [
186
- !isLast && /* @__PURE__ */ jsx4("span", { className: "absolute top-4 bottom-[-0.75rem] left-1/2 w-px -translate-x-1/2 bg-border" }),
187
- /* @__PURE__ */ jsx4("span", { className: cn("relative h-[var(--timeline-dot-size)] w-[var(--timeline-dot-size)] rounded-full ring-4 ring-[var(--bg-root)]", dotClassName, accentClassName) })
188
- ] }),
189
- /* @__PURE__ */ jsx4("div", { className: "min-w-0 pb-3", children })
190
- ] });
186
+ function spacingBefore(prev, next) {
187
+ if (prev === void 0) return "";
188
+ if (prev === "user" || next === "user") return "mt-4";
189
+ if (prev === "card" || next === "card") return "mt-2";
190
+ if (prev === "tool" || next === "tool") return "mt-1";
191
+ return "mt-3";
191
192
  }
193
+ var ROW_BLEED = "-mx-3";
192
194
  function AssistantMessage({ item }) {
193
- return /* @__PURE__ */ jsxs3("div", { className: "-mt-0.5", children: [
194
- item.timestamp && /* @__PURE__ */ jsx4("div", { className: "mb-2 text-[var(--font-size-xs)] text-muted-foreground", children: formatTime2(item.timestamp) }),
195
- item.content && /* @__PURE__ */ jsx4(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]", children: item.content }),
196
- item.isStreaming && /* @__PURE__ */ jsx4("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-primary align-text-bottom" }),
197
- item.toolCalls && /* @__PURE__ */ jsx4("div", { className: "mt-3", children: item.toolCalls }),
195
+ return /* @__PURE__ */ jsxs3("div", { children: [
196
+ item.content && /* @__PURE__ */ jsx4(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[1.5]", children: item.content }),
197
+ item.isStreaming && /* @__PURE__ */ jsx4(
198
+ "span",
199
+ {
200
+ "aria-hidden": true,
201
+ "data-streaming-caret": "",
202
+ className: "ml-0.5 inline-block h-[1em] w-0.5 animate-pulse rounded-full bg-muted-foreground align-text-bottom"
203
+ }
204
+ ),
205
+ item.toolCalls && /* @__PURE__ */ jsx4("div", { className: "mt-1", children: item.toolCalls }),
198
206
  item.after && /* @__PURE__ */ jsx4("div", { className: "mt-3 border-t border-border pt-3", children: item.after })
199
207
  ] });
200
208
  }
@@ -252,101 +260,88 @@ function AgentTimeline({
252
260
  }
253
261
  const renderedItems = isThinking ? [...items, { id: "__thinking__", kind: "custom", content: /* @__PURE__ */ jsx4(ThinkingIndicator, {}) }] : items;
254
262
  const isUserMessage = (item) => item.kind === "message" && item.role === "user";
255
- const timelineItems = renderedItems.filter((item) => !isUserMessage(item));
263
+ const stepItems = renderedItems.filter((item) => !isUserMessage(item));
256
264
  const limit = collapseAfter ?? Infinity;
257
- const collapsible = timelineItems.length > limit;
265
+ const collapsible = stepItems.length > limit;
258
266
  const collapsed = collapsible && !expanded;
259
267
  let renderList = renderedItems;
260
268
  let hiddenCount = 0;
261
269
  if (collapsed) {
262
270
  const visible = [];
263
- let spineCount = 0;
271
+ let stepCount = 0;
264
272
  for (const item of renderedItems) {
265
- if (spineCount >= limit) break;
273
+ if (stepCount >= limit) break;
266
274
  visible.push(item);
267
- if (!isUserMessage(item)) spineCount += 1;
275
+ if (!isUserMessage(item)) stepCount += 1;
268
276
  }
269
277
  renderList = visible;
270
- hiddenCount = timelineItems.length - limit;
278
+ hiddenCount = stepItems.length - limit;
271
279
  }
272
- const visibleSpine = renderList.filter((item) => !isUserMessage(item));
273
- const lastSpineItem = collapsible ? void 0 : visibleSpine[visibleSpine.length - 1];
274
- return /* @__PURE__ */ jsxs3("div", { className: cn("mx-auto w-full max-w-5xl px-4 py-4", className), children: [
275
- renderList.map((item) => {
276
- if (item.kind === "message" && item.role === "user") {
277
- return /* @__PURE__ */ jsx4("div", { className: "mt-6 mb-4 first:mt-0", children: /* @__PURE__ */ jsx4(UserMessage, { content: item.content, timestamp: item.timestamp }) }, item.id);
278
- }
279
- const isLast = item === lastSpineItem;
280
- if (item.kind === "message") {
281
- return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--brand-glow)]", children: /* @__PURE__ */ jsx4(AssistantMessage, { item }) }, item.id);
282
- }
283
- if (item.kind === "tool") {
284
- return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", dotClassName: CARD_ROW_DOT, children: /* @__PURE__ */ jsx4(
285
- ToolCallStep,
286
- {
287
- type: item.call.type,
288
- label: item.call.label,
289
- status: item.call.status,
290
- detail: item.call.detail,
291
- output: item.call.output,
292
- duration: item.call.duration,
293
- part: item.part,
294
- actions: item.part ? renderToolActions?.(item.part) : void 0
295
- }
296
- ) }, item.id);
297
- }
298
- if (item.kind === "tool_group") {
299
- return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", dotClassName: CARD_ROW_DOT, children: /* @__PURE__ */ jsx4(ToolCallGroup, { title: item.title, children: item.calls.map((call, callIndex) => {
300
- const part = item.parts?.[callIndex];
301
- return /* @__PURE__ */ jsx4(
302
- ToolCallStep,
303
- {
304
- type: call.type,
305
- label: call.label,
306
- status: call.status,
307
- detail: call.detail,
308
- output: call.output,
309
- duration: call.duration,
310
- part,
311
- actions: part ? renderToolActions?.(part) : void 0
312
- },
313
- call.id
314
- );
315
- }) }) }, item.id);
316
- }
317
- if (item.kind === "status") {
318
- return /* @__PURE__ */ jsx4(
319
- AgentTimelineRow,
320
- {
321
- isLast,
322
- accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
323
- children: /* @__PURE__ */ jsx4(StatusCard, { item })
324
- },
325
- item.id
326
- );
327
- }
328
- if (item.kind === "artifact") {
280
+ const renderItem = (item) => {
281
+ if (item.kind === "message") {
282
+ return item.role === "user" ? /* @__PURE__ */ jsx4(UserMessage, { content: item.content, timestamp: item.timestamp }) : /* @__PURE__ */ jsx4(AssistantMessage, { item });
283
+ }
284
+ if (item.kind === "tool") {
285
+ return /* @__PURE__ */ jsx4(
286
+ ToolCallStep,
287
+ {
288
+ type: item.call.type,
289
+ label: item.call.label,
290
+ status: item.call.status,
291
+ detail: item.call.detail,
292
+ output: item.call.output,
293
+ duration: item.call.duration,
294
+ part: item.part,
295
+ actions: item.part ? renderToolActions?.(item.part) : void 0
296
+ }
297
+ );
298
+ }
299
+ if (item.kind === "tool_group") {
300
+ return /* @__PURE__ */ jsx4(ToolCallGroup, { title: item.title, children: item.calls.map((call, callIndex) => {
301
+ const part = item.parts?.[callIndex];
329
302
  return /* @__PURE__ */ jsx4(
330
- AgentTimelineRow,
303
+ ToolCallStep,
331
304
  {
332
- isLast,
333
- accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
334
- children: /* @__PURE__ */ jsx4(ArtifactCard, { item })
305
+ type: call.type,
306
+ label: call.label,
307
+ status: call.status,
308
+ detail: call.detail,
309
+ output: call.output,
310
+ duration: call.duration,
311
+ part,
312
+ actions: part ? renderToolActions?.(part) : void 0
335
313
  },
336
- item.id
314
+ call.id
337
315
  );
338
- }
339
- return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", dotClassName: CARD_ROW_DOT, children: item.content }, item.id);
316
+ }) });
317
+ }
318
+ if (item.kind === "status") return /* @__PURE__ */ jsx4(StatusCard, { item });
319
+ if (item.kind === "artifact") return /* @__PURE__ */ jsx4(ArtifactCard, { item });
320
+ return item.content;
321
+ };
322
+ return /* @__PURE__ */ jsxs3("div", { className: cn("mx-auto flex w-full max-w-5xl flex-col px-4 py-4", className), children: [
323
+ renderList.map((item, index) => {
324
+ const kind = stepKind(item);
325
+ const prev = index > 0 ? stepKind(renderList[index - 1]) : void 0;
326
+ return /* @__PURE__ */ jsx4(
327
+ "div",
328
+ {
329
+ "data-timeline-step": kind,
330
+ className: cn(spacingBefore(prev, kind), kind === "tool" && ROW_BLEED),
331
+ children: renderItem(item)
332
+ },
333
+ item.id
334
+ );
340
335
  }),
341
- collapsible ? /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast: true, accentClassName: "bg-[var(--border-hover)]", children: /* @__PURE__ */ jsx4(
336
+ collapsible ? /* @__PURE__ */ jsx4(
342
337
  "button",
343
338
  {
344
339
  type: "button",
345
340
  onClick: () => setExpanded((value) => !value),
346
- className: "text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
341
+ className: "mt-2 self-start text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
347
342
  children: collapsed ? `Show ${hiddenCount} more step${hiddenCount === 1 ? "" : "s"}` : "Show less"
348
343
  }
349
- ) }) : null
344
+ ) : null
350
345
  ] });
351
346
  }
352
347
 
@@ -693,7 +688,7 @@ function ChatMessage({
693
688
  children: [
694
689
  !hideRoleLabel && /* @__PURE__ */ jsxs5("div", { className: cn("flex items-center gap-2 px-1", isUser && "flex-row-reverse"), children: [
695
690
  /* @__PURE__ */ jsx6("span", { className: "font-medium text-foreground text-xs", children: isUser ? userLabel : assistantLabel }),
696
- timestamp && /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground text-xs", children: formatTime3(timestamp) })
691
+ timestamp && /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground text-xs", children: formatTime2(timestamp) })
697
692
  ] }),
698
693
  /* @__PURE__ */ jsxs5(
699
694
  "div",
@@ -716,7 +711,7 @@ function ChatMessage({
716
711
  }
717
712
  );
718
713
  }
719
- function formatTime3(date) {
714
+ function formatTime2(date) {
720
715
  return date.toLocaleTimeString(void 0, {
721
716
  hour: "numeric",
722
717
  minute: "2-digit"