@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,1009 @@
1
+ import {
2
+ useAutoScroll,
3
+ useRunCollapseState,
4
+ useRunGroups
5
+ } from "./chunk-54SQQMMM.js";
6
+ import {
7
+ InlineThinkingItem,
8
+ RunGroup
9
+ } from "./chunk-YNN4O57I.js";
10
+ import {
11
+ ToolCallGroup,
12
+ ToolCallStep
13
+ } from "./chunk-2VH6PUXD.js";
14
+ import {
15
+ getToolDisplayMetadata
16
+ } from "./chunk-BX6AQMUS.js";
17
+ import {
18
+ OpenUIArtifactRenderer
19
+ } from "./chunk-TMFOPHHN.js";
20
+ import {
21
+ Markdown
22
+ } from "./chunk-CD53GZOM.js";
23
+ import {
24
+ cn
25
+ } from "./chunk-RQHJBTEU.js";
26
+
27
+ // src/chat/chat-container.tsx
28
+ import {
29
+ memo as memo3,
30
+ useCallback as useCallback2,
31
+ useMemo,
32
+ useRef as useRef2
33
+ } from "react";
34
+ import { ArrowDown } from "lucide-react";
35
+
36
+ // src/chat/message-list.tsx
37
+ import { memo as memo2 } from "react";
38
+
39
+ // src/chat/user-message.tsx
40
+ import { memo } from "react";
41
+ import { jsx, jsxs } from "react/jsx-runtime";
42
+ var UserMessage = memo(({ message, parts, actions }) => {
43
+ const textContent = parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
44
+ if (!textContent.trim()) return null;
45
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs("div", { className: "flex max-w-[78%] flex-col items-end gap-2", children: [
46
+ /* @__PURE__ */ jsxs("div", { className: "w-full rounded-[26px] rounded-br-[12px] bg-[var(--brand-primary)] px-4 py-3 text-white shadow-[0_8px_20px_rgba(15,23,42,0.12)]", children: [
47
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-white/60", children: "You" }),
48
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap text-[15px] leading-6.5 text-white", children: textContent })
49
+ ] }),
50
+ actions ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-end gap-1.5 text-xs text-muted-foreground", children: actions }) : null
51
+ ] }) });
52
+ });
53
+ UserMessage.displayName = "UserMessage";
54
+
55
+ // src/chat/message-list.tsx
56
+ import { jsx as jsx2 } from "react/jsx-runtime";
57
+ var MessageList = memo2(
58
+ ({
59
+ groups,
60
+ partMap,
61
+ isCollapsed,
62
+ onToggleCollapse,
63
+ branding,
64
+ renderToolDetail,
65
+ renderRunActions,
66
+ renderUserMessageActions,
67
+ renderToolActions
68
+ }) => {
69
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-4", children: groups.map((group) => {
70
+ if (group.type === "user") {
71
+ const messageParts = partMap[group.message.id] ?? [];
72
+ return /* @__PURE__ */ jsx2(
73
+ UserMessage,
74
+ {
75
+ message: group.message,
76
+ parts: messageParts,
77
+ actions: renderUserMessageActions?.(group.message, messageParts)
78
+ },
79
+ group.message.id
80
+ );
81
+ }
82
+ return /* @__PURE__ */ jsx2(
83
+ RunGroup,
84
+ {
85
+ run: group.run,
86
+ partMap,
87
+ collapsed: isCollapsed(group.run.id),
88
+ onToggle: () => onToggleCollapse(group.run.id),
89
+ branding,
90
+ renderToolDetail,
91
+ headerActions: renderRunActions?.(group.run),
92
+ renderToolActions
93
+ },
94
+ group.run.id
95
+ );
96
+ }) });
97
+ }
98
+ );
99
+ MessageList.displayName = "MessageList";
100
+
101
+ // src/chat/agent-timeline.tsx
102
+ import {
103
+ AlertTriangle,
104
+ CheckCircle2,
105
+ CircleDot,
106
+ FileText,
107
+ Info
108
+ } from "lucide-react";
109
+
110
+ // src/chat/thinking-indicator.tsx
111
+ import { useEffect, useState } from "react";
112
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
113
+ function ThinkingIndicator({ className }) {
114
+ const [elapsed, setElapsed] = useState(0);
115
+ useEffect(() => {
116
+ const interval = window.setInterval(() => setElapsed((current) => current + 1), 1e3);
117
+ return () => window.clearInterval(interval);
118
+ }, []);
119
+ return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-2 px-3 py-1.5", className), children: [
120
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-[3px]", children: [
121
+ /* @__PURE__ */ jsx3("span", { className: "h-[var(--indicator-dot-size)] w-[var(--indicator-dot-size)] animate-bounce rounded-full bg-[var(--brand-glow)]", style: { animationDelay: "0ms" } }),
122
+ /* @__PURE__ */ jsx3("span", { className: "h-[var(--indicator-dot-size)] w-[var(--indicator-dot-size)] animate-bounce rounded-full bg-[var(--brand-glow)]", style: { animationDelay: "150ms" } }),
123
+ /* @__PURE__ */ jsx3("span", { className: "h-[var(--indicator-dot-size)] w-[var(--indicator-dot-size)] animate-bounce rounded-full bg-[var(--brand-glow)]", style: { animationDelay: "300ms" } })
124
+ ] }),
125
+ elapsed > 3 && /* @__PURE__ */ jsxs2("span", { className: "text-[var(--font-size-xs)] tabular-nums text-[var(--text-dim)]", children: [
126
+ elapsed,
127
+ "s"
128
+ ] })
129
+ ] });
130
+ }
131
+
132
+ // src/chat/agent-timeline.tsx
133
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
134
+ var TONE_STYLES = {
135
+ default: {
136
+ dot: "bg-[var(--border-hover)]",
137
+ card: "border-border bg-card",
138
+ text: "text-foreground",
139
+ icon: CircleDot
140
+ },
141
+ info: {
142
+ dot: "bg-[var(--surface-info-text)]",
143
+ card: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)]",
144
+ text: "text-[var(--surface-info-text)]",
145
+ icon: Info
146
+ },
147
+ success: {
148
+ dot: "bg-[var(--surface-success-text)]",
149
+ card: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]",
150
+ text: "text-[var(--surface-success-text)]",
151
+ icon: CheckCircle2
152
+ },
153
+ warning: {
154
+ dot: "bg-[var(--surface-warning-text)]",
155
+ card: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]",
156
+ text: "text-[var(--surface-warning-text)]",
157
+ icon: AlertTriangle
158
+ },
159
+ error: {
160
+ dot: "bg-[var(--surface-danger-text)]",
161
+ card: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)]",
162
+ text: "text-[var(--surface-danger-text)]",
163
+ icon: AlertTriangle
164
+ }
165
+ };
166
+ function formatTime(date) {
167
+ return date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
168
+ }
169
+ function AgentTimelineRow({ isLast, accentClassName, children }) {
170
+ return /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-[1.25rem_minmax(0,1fr)] gap-x-4", children: [
171
+ /* @__PURE__ */ jsxs3("div", { className: "relative flex justify-center", children: [
172
+ !isLast && /* @__PURE__ */ jsx4("span", { className: "absolute top-4 bottom-[-0.75rem] left-1/2 w-px -translate-x-1/2 bg-border" }),
173
+ /* @__PURE__ */ jsx4("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) })
174
+ ] }),
175
+ /* @__PURE__ */ jsx4("div", { className: "min-w-0 pb-3", children })
176
+ ] });
177
+ }
178
+ function UserMessage2({ item }) {
179
+ return /* @__PURE__ */ jsx4("div", { className: "mb-3 flex justify-end", children: /* @__PURE__ */ jsx4("div", { className: "max-w-[72%]", children: /* @__PURE__ */ jsxs3("div", { className: "rounded-2xl border border-border bg-muted/50 px-4 py-3", children: [
180
+ item.timestamp && /* @__PURE__ */ jsx4("div", { className: "mb-1.5 text-right text-[var(--font-size-xs)] text-muted-foreground", children: formatTime(item.timestamp) }),
181
+ /* @__PURE__ */ jsx4("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground", children: item.content })
182
+ ] }) }) });
183
+ }
184
+ function AssistantMessage({ item }) {
185
+ return /* @__PURE__ */ jsxs3("div", { className: "-mt-0.5", children: [
186
+ item.timestamp && /* @__PURE__ */ jsx4("div", { className: "mb-2 text-[var(--font-size-xs)] text-muted-foreground", children: formatTime(item.timestamp) }),
187
+ item.content && /* @__PURE__ */ jsx4(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]", children: item.content }),
188
+ item.isStreaming && /* @__PURE__ */ jsx4("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-primary align-text-bottom" }),
189
+ item.toolCalls && /* @__PURE__ */ jsx4("div", { className: "mt-3", children: item.toolCalls }),
190
+ item.after && /* @__PURE__ */ jsx4("div", { className: "mt-3 border-t border-border pt-3", children: item.after })
191
+ ] });
192
+ }
193
+ function StatusCard({ item }) {
194
+ const tone = TONE_STYLES[item.tone ?? "default"];
195
+ const Icon = tone.icon;
196
+ return /* @__PURE__ */ jsx4("div", { className: cn("rounded-[var(--radius-lg)] border px-4 py-3", tone.card), children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-3", children: [
197
+ /* @__PURE__ */ jsx4(Icon, { className: cn("mt-0.5 h-4 w-4 shrink-0", tone.text) }),
198
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
199
+ /* @__PURE__ */ jsx4("div", { className: cn("text-sm font-medium", tone.text), children: item.label }),
200
+ item.detail && /* @__PURE__ */ jsx4("div", { className: "mt-0.5 text-sm text-muted-foreground", children: item.detail })
201
+ ] })
202
+ ] }) });
203
+ }
204
+ function ArtifactCard({ item }) {
205
+ const tone = TONE_STYLES[item.tone ?? "default"];
206
+ const content = /* @__PURE__ */ jsx4("div", { className: cn("rounded-[var(--radius-lg)] border px-4 py-3", tone.card), children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-3", children: [
207
+ /* @__PURE__ */ jsx4("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", children: item.icon ?? /* @__PURE__ */ jsx4(FileText, { className: "h-4 w-4" }) }),
208
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
209
+ /* @__PURE__ */ jsx4("div", { className: "text-sm font-medium text-foreground", children: item.title }),
210
+ item.description && /* @__PURE__ */ jsx4("div", { className: "mt-1 text-sm text-muted-foreground", children: item.description }),
211
+ item.meta && /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: item.meta })
212
+ ] }),
213
+ item.action && /* @__PURE__ */ jsx4("div", { className: "shrink-0", children: item.action })
214
+ ] }) });
215
+ if (!item.onClick) return content;
216
+ return /* @__PURE__ */ jsx4(
217
+ "div",
218
+ {
219
+ role: "button",
220
+ tabIndex: 0,
221
+ onClick: item.onClick,
222
+ onKeyDown: (event) => {
223
+ if (event.key === "Enter" || event.key === " ") {
224
+ event.preventDefault();
225
+ item.onClick?.();
226
+ }
227
+ },
228
+ 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",
229
+ children: content
230
+ }
231
+ );
232
+ }
233
+ function AgentTimeline({
234
+ items,
235
+ isThinking,
236
+ emptyState,
237
+ className
238
+ }) {
239
+ if (items.length === 0 && !isThinking) {
240
+ return emptyState ? /* @__PURE__ */ jsx4("div", { className: cn("flex h-full items-center justify-center p-4", className), children: emptyState }) : null;
241
+ }
242
+ const renderedItems = isThinking ? [...items, { id: "__thinking__", kind: "custom", content: /* @__PURE__ */ jsx4(ThinkingIndicator, {}) }] : items;
243
+ const timelineItems = renderedItems.filter((item) => !(item.kind === "message" && item.role === "user"));
244
+ return /* @__PURE__ */ jsx4("div", { className: cn("mx-auto w-full max-w-5xl px-4 py-4", className), children: renderedItems.map((item, index) => {
245
+ if (item.kind === "message" && item.role === "user") {
246
+ return /* @__PURE__ */ jsx4(UserMessage2, { item }, item.id);
247
+ }
248
+ const timelineIndex = timelineItems.indexOf(item);
249
+ const isLast = timelineIndex === timelineItems.length - 1;
250
+ if (item.kind === "message") {
251
+ return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--brand-glow)]", children: /* @__PURE__ */ jsx4(AssistantMessage, { item }) }, item.id);
252
+ }
253
+ if (item.kind === "tool") {
254
+ return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", children: /* @__PURE__ */ jsx4(
255
+ ToolCallStep,
256
+ {
257
+ type: item.call.type,
258
+ label: item.call.label,
259
+ status: item.call.status,
260
+ detail: item.call.detail,
261
+ output: item.call.output,
262
+ duration: item.call.duration
263
+ }
264
+ ) }, item.id);
265
+ }
266
+ if (item.kind === "tool_group") {
267
+ return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", children: /* @__PURE__ */ jsx4(ToolCallGroup, { title: item.title, children: item.calls.map((call) => /* @__PURE__ */ jsx4(
268
+ ToolCallStep,
269
+ {
270
+ type: call.type,
271
+ label: call.label,
272
+ status: call.status,
273
+ detail: call.detail,
274
+ output: call.output,
275
+ duration: call.duration
276
+ },
277
+ call.id
278
+ )) }) }, item.id);
279
+ }
280
+ if (item.kind === "status") {
281
+ return /* @__PURE__ */ jsx4(
282
+ AgentTimelineRow,
283
+ {
284
+ isLast,
285
+ accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
286
+ children: /* @__PURE__ */ jsx4(StatusCard, { item })
287
+ },
288
+ item.id
289
+ );
290
+ }
291
+ if (item.kind === "artifact") {
292
+ return /* @__PURE__ */ jsx4(
293
+ AgentTimelineRow,
294
+ {
295
+ isLast,
296
+ accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
297
+ children: /* @__PURE__ */ jsx4(ArtifactCard, { item })
298
+ },
299
+ item.id
300
+ );
301
+ }
302
+ return /* @__PURE__ */ jsx4(AgentTimelineRow, { isLast, accentClassName: "bg-[var(--border-hover)]", children: item.content }, item.id);
303
+ }) });
304
+ }
305
+
306
+ // src/chat/chat-input.tsx
307
+ import { useState as useState2, useRef, useCallback } from "react";
308
+ import { Send, Square, Paperclip, FolderUp, X, Upload } from "lucide-react";
309
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
310
+ function ChatInput({
311
+ onSend,
312
+ onCancel,
313
+ isStreaming,
314
+ disabled,
315
+ placeholder = "Ask the agent to inspect files, run commands, or explain results\u2026",
316
+ modelLabel,
317
+ onModelClick,
318
+ pendingFiles = [],
319
+ onRemoveFile,
320
+ onAttach,
321
+ onAttachFolder,
322
+ accept,
323
+ dropTitle = "Drop files to add context",
324
+ dropDescription = "Files will be attached to your next message.",
325
+ className,
326
+ inputLabel = "Agent Command Deck",
327
+ idleStatus = "Ready for next instruction",
328
+ streamingStatus = "Streaming response",
329
+ hideShortcutHint
330
+ }) {
331
+ const [value, setValue] = useState2("");
332
+ const [dragOver, setDragOver] = useState2(false);
333
+ const dragCounter = useRef(0);
334
+ const textareaRef = useRef(null);
335
+ const fileInputRef = useRef(null);
336
+ const folderInputRef = useRef(null);
337
+ const handleSend = useCallback(() => {
338
+ const trimmed = value.trim();
339
+ if (!trimmed || isStreaming || disabled) return;
340
+ onSend(trimmed);
341
+ setValue("");
342
+ if (textareaRef.current) {
343
+ textareaRef.current.style.height = "auto";
344
+ }
345
+ }, [value, isStreaming, disabled, onSend]);
346
+ const handleKeyDown = (e) => {
347
+ if (e.key === "Enter" && !e.shiftKey) {
348
+ e.preventDefault();
349
+ handleSend();
350
+ }
351
+ };
352
+ const handleChange = (e) => {
353
+ setValue(e.target.value);
354
+ const el = e.target;
355
+ el.style.height = "auto";
356
+ el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
357
+ };
358
+ const handleAttachClick = () => {
359
+ fileInputRef.current?.click();
360
+ };
361
+ const handleFolderClick = () => {
362
+ folderInputRef.current?.click();
363
+ };
364
+ const handleFileChange = (e) => {
365
+ if (e.target.files?.length) {
366
+ onAttach?.(e.target.files);
367
+ e.target.value = "";
368
+ }
369
+ };
370
+ const handleFolderChange = (e) => {
371
+ if (e.target.files?.length) {
372
+ (onAttachFolder ?? onAttach)?.(e.target.files);
373
+ e.target.value = "";
374
+ }
375
+ };
376
+ const handleDragEnter = useCallback((e) => {
377
+ e.preventDefault();
378
+ e.stopPropagation();
379
+ dragCounter.current++;
380
+ if (e.dataTransfer?.types.includes("Files")) {
381
+ setDragOver(true);
382
+ }
383
+ }, []);
384
+ const handleDragLeave = useCallback((e) => {
385
+ e.preventDefault();
386
+ e.stopPropagation();
387
+ dragCounter.current--;
388
+ if (dragCounter.current === 0) {
389
+ setDragOver(false);
390
+ }
391
+ }, []);
392
+ const handleDragOver = useCallback((e) => {
393
+ e.preventDefault();
394
+ e.stopPropagation();
395
+ e.dataTransfer.dropEffect = "copy";
396
+ }, []);
397
+ const handleDrop = useCallback((e) => {
398
+ e.preventDefault();
399
+ e.stopPropagation();
400
+ dragCounter.current = 0;
401
+ setDragOver(false);
402
+ const files = e.dataTransfer?.files;
403
+ if (files?.length && onAttach) {
404
+ onAttach(files);
405
+ }
406
+ }, [onAttach]);
407
+ const fileChips = pendingFiles.filter((f) => f.type === "file" || !f.type);
408
+ const folderChips = pendingFiles.filter((f) => f.type === "folder");
409
+ return /* @__PURE__ */ jsxs4(
410
+ "div",
411
+ {
412
+ className: cn("relative", className),
413
+ onDragEnter: onAttach ? handleDragEnter : void 0,
414
+ onDragLeave: onAttach ? handleDragLeave : void 0,
415
+ onDragOver: onAttach ? handleDragOver : void 0,
416
+ onDrop: onAttach ? handleDrop : void 0,
417
+ children: [
418
+ dragOver && /* @__PURE__ */ jsx5("div", { className: "absolute inset-0 z-10 flex items-center justify-center rounded-[var(--radius-xl)] border-2 border-dashed border-border bg-card pointer-events-none", children: /* @__PURE__ */ jsxs4("div", { className: "text-center", children: [
419
+ /* @__PURE__ */ jsx5("div", { className: "mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-[var(--accent-surface-soft)]", children: /* @__PURE__ */ jsx5(Upload, { className: "h-6 w-6 text-primary" }) }),
420
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-semibold text-foreground", children: dropTitle }),
421
+ /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-muted-foreground", children: dropDescription })
422
+ ] }) }),
423
+ pendingFiles.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "mb-3 flex flex-wrap gap-2", children: [
424
+ folderChips.map((f) => /* @__PURE__ */ jsxs4(
425
+ "span",
426
+ {
427
+ className: cn(
428
+ "inline-flex items-center gap-1.5 rounded-[var(--radius-full)] border px-3 py-1.5 text-xs",
429
+ "border-border bg-muted/50",
430
+ f.status === "error" && "border-[var(--code-error)]/30 text-[var(--code-error)]",
431
+ f.status !== "error" && "text-foreground"
432
+ ),
433
+ children: [
434
+ /* @__PURE__ */ jsx5(FolderUp, { className: "h-3 w-3 shrink-0" }),
435
+ /* @__PURE__ */ jsx5("span", { className: "truncate max-w-[150px]", children: f.name }),
436
+ f.fileCount !== void 0 && /* @__PURE__ */ jsxs4("span", { className: "text-muted-foreground", children: [
437
+ "(",
438
+ f.fileCount,
439
+ ")"
440
+ ] }),
441
+ f.status === "uploading" && /* @__PURE__ */ jsx5("span", { className: "w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin" }),
442
+ onRemoveFile && /* @__PURE__ */ jsx5(
443
+ "button",
444
+ {
445
+ type: "button",
446
+ "aria-label": `Remove ${f.name}`,
447
+ onClick: () => onRemoveFile(f.id),
448
+ className: "rounded p-0.5 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
449
+ children: /* @__PURE__ */ jsx5(X, { className: "h-3 w-3" })
450
+ }
451
+ )
452
+ ]
453
+ },
454
+ f.id
455
+ )),
456
+ fileChips.map((f) => /* @__PURE__ */ jsxs4(
457
+ "span",
458
+ {
459
+ className: cn(
460
+ "inline-flex items-center gap-1.5 rounded-[var(--radius-full)] border px-3 py-1.5 text-xs",
461
+ "border-border bg-muted/50",
462
+ f.status === "error" && "border-[var(--code-error)]/30 text-[var(--code-error)]",
463
+ f.status !== "error" && "text-foreground"
464
+ ),
465
+ children: [
466
+ /* @__PURE__ */ jsx5(Paperclip, { className: "h-3 w-3 shrink-0" }),
467
+ /* @__PURE__ */ jsx5("span", { className: "truncate max-w-[150px]", children: f.name }),
468
+ f.status === "uploading" && /* @__PURE__ */ jsx5("span", { className: "w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin" }),
469
+ onRemoveFile && /* @__PURE__ */ jsx5(
470
+ "button",
471
+ {
472
+ type: "button",
473
+ "aria-label": `Remove ${f.name}`,
474
+ onClick: () => onRemoveFile(f.id),
475
+ className: "rounded p-0.5 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
476
+ children: /* @__PURE__ */ jsx5(X, { className: "h-3 w-3" })
477
+ }
478
+ )
479
+ ]
480
+ },
481
+ f.id
482
+ ))
483
+ ] }),
484
+ /* @__PURE__ */ jsx5("div", { className: "rounded-[24px] border border-[var(--chat-input-border,var(--border-default))] [background:var(--chat-input-bg,var(--bg-card))] shadow-[var(--chat-input-shadow,0_1px_2px_rgba(15,23,42,0.05))] transition-all focus-within:border-[var(--chat-input-focus-border,var(--border-accent))] focus-within:shadow-[var(--chat-input-focus-shadow,0_10px_30px_rgba(15,23,42,0.08))]", children: /* @__PURE__ */ jsxs4("div", { className: "rounded-[24px] px-4 py-[var(--chat-input-py)]", children: [
485
+ (inputLabel !== null || idleStatus !== null || streamingStatus !== null) && /* @__PURE__ */ jsxs4("div", { className: "mb-1.5 flex items-center justify-between gap-3 px-1", children: [
486
+ inputLabel !== null && /* @__PURE__ */ jsx5("div", { className: "text-[var(--chat-label-size,11px)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.16em)] text-[var(--text-muted)]", children: inputLabel }),
487
+ (idleStatus !== null || streamingStatus !== null) && /* @__PURE__ */ jsx5("div", { className: "text-[var(--chat-label-size,11px)] text-[var(--text-muted)]", children: isStreaming ? streamingStatus ?? "" : idleStatus ?? "" })
488
+ ] }),
489
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2.5", children: [
490
+ onAttach && /* @__PURE__ */ jsxs4(Fragment, { children: [
491
+ /* @__PURE__ */ jsx5(
492
+ "button",
493
+ {
494
+ type: "button",
495
+ onClick: handleAttachClick,
496
+ disabled: isStreaming,
497
+ "aria-label": "Attach files",
498
+ title: "Attach files",
499
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-md)] border border-transparent p-2 text-muted-foreground transition-colors hover:border-border hover:bg-accent hover:text-foreground disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
500
+ children: /* @__PURE__ */ jsx5(Paperclip, { className: "h-4 w-4" })
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx5(
504
+ "input",
505
+ {
506
+ ref: fileInputRef,
507
+ type: "file",
508
+ multiple: true,
509
+ className: "hidden",
510
+ onChange: handleFileChange,
511
+ accept: accept ?? ".pdf,.csv,.xlsx,.xls,.jpg,.jpeg,.png,.gif,.txt,.json,.yaml,.yml"
512
+ }
513
+ )
514
+ ] }),
515
+ (onAttachFolder ?? onAttach) && /* @__PURE__ */ jsxs4(Fragment, { children: [
516
+ /* @__PURE__ */ jsx5(
517
+ "button",
518
+ {
519
+ type: "button",
520
+ onClick: handleFolderClick,
521
+ disabled: isStreaming,
522
+ "aria-label": "Attach folder",
523
+ title: "Attach folder",
524
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-md)] border border-transparent p-2 text-muted-foreground transition-colors hover:border-border hover:bg-accent hover:text-foreground disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
525
+ children: /* @__PURE__ */ jsx5(FolderUp, { className: "h-4 w-4" })
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsx5(
529
+ "input",
530
+ {
531
+ ref: folderInputRef,
532
+ type: "file",
533
+ multiple: true,
534
+ className: "hidden",
535
+ onChange: handleFolderChange,
536
+ webkitdirectory: ""
537
+ }
538
+ )
539
+ ] }),
540
+ /* @__PURE__ */ jsx5(
541
+ "textarea",
542
+ {
543
+ ref: textareaRef,
544
+ value,
545
+ onChange: handleChange,
546
+ onKeyDown: handleKeyDown,
547
+ placeholder,
548
+ disabled: isStreaming || disabled,
549
+ rows: 1,
550
+ "aria-label": "Message input",
551
+ className: "min-h-[42px] max-h-[160px] flex-1 resize-none bg-transparent py-2 text-[15px] leading-6 text-foreground placeholder:text-muted-foreground disabled:opacity-50 focus-visible:outline-none"
552
+ }
553
+ ),
554
+ isStreaming ? /* @__PURE__ */ jsx5(
555
+ "button",
556
+ {
557
+ type: "button",
558
+ onClick: onCancel,
559
+ "aria-label": "Stop response",
560
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-lg)] border border-[var(--code-error)]/20 bg-[var(--code-error)]/14 p-2.5 text-[var(--code-error)] transition-colors hover:bg-[var(--code-error)]/24 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--code-error)]/50",
561
+ children: /* @__PURE__ */ jsx5(Square, { className: "h-4 w-4" })
562
+ }
563
+ ) : /* @__PURE__ */ jsxs4(
564
+ "button",
565
+ {
566
+ type: "button",
567
+ onClick: handleSend,
568
+ disabled: !value.trim() || disabled,
569
+ "aria-label": "Send message",
570
+ className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full border border-[var(--chat-send-border,var(--border-accent))] [background:var(--chat-send-bg,var(--brand-primary))] px-3.5 py-2.5 text-sm font-medium text-[var(--chat-send-color,white)] shadow-[var(--chat-send-shadow,0_6px_16px_rgba(15,23,42,0.12))] transition-all hover:translate-y-[-1px] hover:[background:var(--chat-send-hover-bg,var(--brand-strong))] disabled:opacity-30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--chat-send-ring,var(--border-accent))]",
571
+ children: [
572
+ /* @__PURE__ */ jsx5(Send, { className: "h-4 w-4" }),
573
+ /* @__PURE__ */ jsx5("span", { children: "Send" })
574
+ ]
575
+ }
576
+ )
577
+ ] })
578
+ ] }) }),
579
+ (modelLabel || !hideShortcutHint) && /* @__PURE__ */ jsxs4("div", { className: "mt-2 flex items-center justify-between px-1", children: [
580
+ /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-2", children: modelLabel && /* @__PURE__ */ jsxs4(
581
+ "button",
582
+ {
583
+ type: "button",
584
+ onClick: onModelClick,
585
+ "aria-label": `Select model, current model ${modelLabel}`,
586
+ className: "inline-flex items-center gap-1.5 rounded-[var(--radius-full)] border border-border bg-[linear-gradient(180deg,rgba(255,255,255,0.04),transparent)] px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:border-primary/20 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
587
+ children: [
588
+ /* @__PURE__ */ jsx5("span", { className: "w-1.5 h-1.5 rounded-full bg-[var(--code-success)]" }),
589
+ modelLabel
590
+ ]
591
+ }
592
+ ) }),
593
+ !hideShortcutHint && /* @__PURE__ */ jsxs4("span", { className: "text-xs text-muted-foreground", children: [
594
+ /* @__PURE__ */ jsx5("kbd", { className: "px-1 py-0.5 bg-background rounded border border-border text-[10px]", children: "Cmd" }),
595
+ /* @__PURE__ */ jsx5("kbd", { className: "px-1 py-0.5 bg-background rounded border border-border text-[10px] ml-0.5", children: "L" }),
596
+ /* @__PURE__ */ jsx5("span", { className: "ml-1", children: "to focus" })
597
+ ] })
598
+ ] })
599
+ ]
600
+ }
601
+ );
602
+ }
603
+
604
+ // src/chat/chat-container.tsx
605
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
606
+ var OPENUI_NODE_TYPES = /* @__PURE__ */ new Set([
607
+ "heading",
608
+ "text",
609
+ "badge",
610
+ "stat",
611
+ "key_value",
612
+ "code",
613
+ "markdown",
614
+ "table",
615
+ "actions",
616
+ "separator",
617
+ "stack",
618
+ "grid",
619
+ "card"
620
+ ]);
621
+ function isOpenUINode(value) {
622
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && OPENUI_NODE_TYPES.has(value.type);
623
+ }
624
+ function extractOpenUISchema(output) {
625
+ if (output == null) return null;
626
+ if (isOpenUINode(output)) return [output];
627
+ if (Array.isArray(output) && output.length > 0 && output.every(isOpenUINode)) {
628
+ return output;
629
+ }
630
+ if (typeof output === "object" && !Array.isArray(output)) {
631
+ const obj = output;
632
+ for (const key of ["openui", "schema", "ui"]) {
633
+ if (obj[key]) {
634
+ const inner = obj[key];
635
+ if (isOpenUINode(inner)) return [inner];
636
+ if (Array.isArray(inner) && inner.length > 0 && inner.every(isOpenUINode)) {
637
+ return inner;
638
+ }
639
+ }
640
+ }
641
+ }
642
+ if (typeof output === "string") {
643
+ try {
644
+ const parsed = JSON.parse(output);
645
+ return extractOpenUISchema(parsed);
646
+ } catch {
647
+ return null;
648
+ }
649
+ }
650
+ return null;
651
+ }
652
+ function formatUnknown(value) {
653
+ if (value == null) return void 0;
654
+ if (typeof value === "string") return value;
655
+ try {
656
+ return JSON.stringify(value, null, 2);
657
+ } catch {
658
+ return String(value);
659
+ }
660
+ }
661
+ function createdAtFromMessage(message) {
662
+ return message.time?.created ? new Date(message.time.created) : void 0;
663
+ }
664
+ function mapToolPartToTimelineType(part) {
665
+ const name = part.tool.toLowerCase().replace(/^tool:/, "");
666
+ switch (name) {
667
+ case "bash":
668
+ case "shell":
669
+ case "command":
670
+ case "execute":
671
+ return "bash";
672
+ case "write":
673
+ case "write_file":
674
+ case "create_file":
675
+ return "write";
676
+ case "read":
677
+ case "read_file":
678
+ case "cat":
679
+ return "read";
680
+ case "edit":
681
+ case "patch":
682
+ case "sed":
683
+ return "edit";
684
+ case "glob":
685
+ case "find":
686
+ return "glob";
687
+ case "ls":
688
+ return "list";
689
+ case "grep":
690
+ case "search":
691
+ case "rg":
692
+ return "grep";
693
+ case "inspect":
694
+ return "inspect";
695
+ default:
696
+ return "unknown";
697
+ }
698
+ }
699
+ function buildTimelineItems(messages, partMap, isStreaming, onOpenUIAction, enableOpenUI = true) {
700
+ const items = [];
701
+ const lastAssistantMessage = [...messages].reverse().find((message) => message.role === "assistant");
702
+ const toToolCall = (part) => {
703
+ const meta = getToolDisplayMetadata(part);
704
+ const start = part.state.time?.start;
705
+ const end = part.state.time?.end;
706
+ return {
707
+ id: part.id,
708
+ type: mapToolPartToTimelineType(part),
709
+ label: meta.description ? `${meta.title}: ${meta.description}` : meta.title,
710
+ status: part.state.status === "completed" ? "success" : part.state.status === "error" ? "error" : "running",
711
+ detail: formatUnknown(part.state.input),
712
+ output: formatUnknown(part.state.output),
713
+ duration: start && end ? end - start : void 0
714
+ };
715
+ };
716
+ for (const message of messages) {
717
+ const parts = partMap[message.id] ?? [];
718
+ if (message.role === "user") {
719
+ const content = parts.filter((part) => part.type === "text").map((part) => part.text).join("\n").trim();
720
+ if (!content) continue;
721
+ items.push({
722
+ id: message.id,
723
+ kind: "message",
724
+ role: "user",
725
+ content,
726
+ timestamp: createdAtFromMessage(message)
727
+ });
728
+ continue;
729
+ }
730
+ const toolBuffer = [];
731
+ const flushToolBuffer = (index) => {
732
+ if (toolBuffer.length === 0) return;
733
+ if (toolBuffer.length === 1) {
734
+ items.push({
735
+ id: `${message.id}-tool-${toolBuffer[0].id}`,
736
+ kind: "tool",
737
+ call: toToolCall(toolBuffer[0])
738
+ });
739
+ } else {
740
+ items.push({
741
+ id: `${message.id}-tool-group-${index}`,
742
+ kind: "tool_group",
743
+ title: "Tool activity",
744
+ calls: toolBuffer.map((part) => toToolCall(part))
745
+ });
746
+ }
747
+ if (enableOpenUI) {
748
+ for (const part of toolBuffer) {
749
+ if (part.state.status !== "completed" || !part.state.output) continue;
750
+ const schema = extractOpenUISchema(part.state.output);
751
+ if (!schema) continue;
752
+ items.push({
753
+ id: `${message.id}-openui-${part.id}`,
754
+ kind: "custom",
755
+ content: /* @__PURE__ */ jsx6("div", { className: "my-2 rounded-[var(--radius-lg)] border border-border bg-card p-4 shadow-[var(--shadow-card)]", children: /* @__PURE__ */ jsx6(OpenUIArtifactRenderer, { schema, onAction: onOpenUIAction }) })
756
+ });
757
+ }
758
+ }
759
+ toolBuffer.length = 0;
760
+ };
761
+ parts.forEach((part, index) => {
762
+ const itemId = `${message.id}-${index}`;
763
+ if (part.type === "tool") {
764
+ toolBuffer.push(part);
765
+ return;
766
+ }
767
+ flushToolBuffer(index);
768
+ if (part.type === "text" && !part.synthetic && part.text.trim()) {
769
+ if (enableOpenUI) {
770
+ const jsonMatch = part.text.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
771
+ if (jsonMatch) {
772
+ const schema = extractOpenUISchema(jsonMatch[1]);
773
+ if (schema) {
774
+ const beforeJson = part.text.slice(0, part.text.indexOf("```")).trim();
775
+ if (beforeJson) {
776
+ items.push({
777
+ id: `${itemId}-text`,
778
+ kind: "message",
779
+ role: "assistant",
780
+ content: beforeJson,
781
+ timestamp: createdAtFromMessage(message)
782
+ });
783
+ }
784
+ items.push({
785
+ id: `${itemId}-openui`,
786
+ kind: "custom",
787
+ content: /* @__PURE__ */ jsx6("div", { className: "my-2 rounded-[var(--radius-lg)] border border-border bg-card p-4 shadow-[var(--shadow-card)]", children: /* @__PURE__ */ jsx6(OpenUIArtifactRenderer, { schema, onAction: onOpenUIAction }) })
788
+ });
789
+ const afterJson = part.text.slice(part.text.lastIndexOf("```") + 3).trim();
790
+ if (afterJson) {
791
+ items.push({
792
+ id: `${itemId}-after`,
793
+ kind: "message",
794
+ role: "assistant",
795
+ content: afterJson,
796
+ timestamp: createdAtFromMessage(message)
797
+ });
798
+ }
799
+ return;
800
+ }
801
+ }
802
+ }
803
+ items.push({
804
+ id: itemId,
805
+ kind: "message",
806
+ role: "assistant",
807
+ content: part.text,
808
+ timestamp: createdAtFromMessage(message),
809
+ isStreaming: isStreaming && lastAssistantMessage?.id === message.id && index === parts.length - 1
810
+ });
811
+ return;
812
+ }
813
+ if (part.type === "reasoning") {
814
+ items.push({
815
+ id: itemId,
816
+ kind: "custom",
817
+ content: /* @__PURE__ */ jsx6(InlineThinkingItem, { part, defaultOpen: isStreaming && lastAssistantMessage?.id === message.id })
818
+ });
819
+ return;
820
+ }
821
+ });
822
+ flushToolBuffer(parts.length);
823
+ }
824
+ const showThinking = isStreaming && lastAssistantMessage != null && !items.some(
825
+ (item) => item.kind === "message" && item.role === "assistant" && item.id.startsWith(lastAssistantMessage.id)
826
+ );
827
+ return { items, showThinking };
828
+ }
829
+ var ChatContainer = memo3(
830
+ ({
831
+ messages,
832
+ partMap,
833
+ isStreaming,
834
+ onSend,
835
+ onCancel,
836
+ branding,
837
+ placeholder = "Type a message...",
838
+ className,
839
+ hideInput = false,
840
+ renderToolDetail,
841
+ presentation = "runs",
842
+ modelLabel,
843
+ onModelClick,
844
+ pendingFiles,
845
+ onRemoveFile,
846
+ onAttach,
847
+ disabled = false,
848
+ onOpenUIAction,
849
+ enableOpenUI = true,
850
+ renderRunActions,
851
+ renderUserMessageActions,
852
+ renderToolActions
853
+ }) => {
854
+ const scrollRef = useRef2(null);
855
+ const groups = useRunGroups({ messages, partMap, isStreaming });
856
+ const runs = groups.filter((g) => g.type === "run").map((g) => g.run);
857
+ const { isCollapsed, toggleCollapse } = useRunCollapseState(runs);
858
+ const { isAtBottom, scrollToBottom } = useAutoScroll(scrollRef, [
859
+ messages,
860
+ partMap,
861
+ isStreaming
862
+ ]);
863
+ const timeline = useMemo(
864
+ () => buildTimelineItems(messages, partMap, isStreaming, onOpenUIAction, enableOpenUI),
865
+ [messages, partMap, isStreaming, onOpenUIAction, enableOpenUI]
866
+ );
867
+ const handleSend = useCallback2(
868
+ (text) => {
869
+ onSend?.(text);
870
+ },
871
+ [onSend]
872
+ );
873
+ return /* @__PURE__ */ jsxs5("div", { className: cn("flex h-full flex-col", className), children: [
874
+ /* @__PURE__ */ jsx6(
875
+ "div",
876
+ {
877
+ ref: scrollRef,
878
+ className: "flex-1 overflow-y-auto [scrollbar-gutter:stable]",
879
+ children: messages.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx6("div", { className: "max-w-md text-center", children: /* @__PURE__ */ jsx6("div", { className: "text-sm font-medium text-muted-foreground", children: "Start a conversation." }) }) }) : presentation === "timeline" ? /* @__PURE__ */ jsx6("div", { className: "mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end", children: /* @__PURE__ */ jsx6(AgentTimeline, { items: timeline.items, isThinking: timeline.showThinking }) }) : /* @__PURE__ */ jsx6("div", { className: "mx-auto flex min-h-full w-full max-w-3xl flex-col justify-end", children: /* @__PURE__ */ jsx6(
880
+ MessageList,
881
+ {
882
+ groups,
883
+ partMap,
884
+ isCollapsed,
885
+ onToggleCollapse: toggleCollapse,
886
+ branding,
887
+ renderToolDetail,
888
+ renderRunActions,
889
+ renderUserMessageActions,
890
+ renderToolActions
891
+ }
892
+ ) })
893
+ }
894
+ ),
895
+ !isAtBottom && /* @__PURE__ */ jsx6("div", { className: "relative z-10 -mt-10 flex justify-center", children: /* @__PURE__ */ jsxs5(
896
+ "button",
897
+ {
898
+ onClick: scrollToBottom,
899
+ className: cn(
900
+ "flex items-center gap-1.5 px-3 py-1.5 rounded-full",
901
+ "border border-border bg-card shadow-[0_6px_16px_rgba(15,23,42,0.08)]",
902
+ "text-xs text-foreground transition-colors hover:bg-accent",
903
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
904
+ ),
905
+ children: [
906
+ /* @__PURE__ */ jsx6(ArrowDown, { className: "w-3 h-3" }),
907
+ "Scroll to bottom"
908
+ ]
909
+ }
910
+ ) }),
911
+ !hideInput && onSend && /* @__PURE__ */ jsx6(
912
+ ChatInput,
913
+ {
914
+ onSend: handleSend,
915
+ onCancel,
916
+ isStreaming,
917
+ placeholder,
918
+ modelLabel,
919
+ onModelClick,
920
+ pendingFiles,
921
+ onRemoveFile,
922
+ onAttach,
923
+ disabled,
924
+ className: "shrink-0 border-t border-border bg-background"
925
+ }
926
+ )
927
+ ] });
928
+ }
929
+ );
930
+ ChatContainer.displayName = "ChatContainer";
931
+
932
+ // src/chat/chat-message.tsx
933
+ import { User, Bot } from "lucide-react";
934
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
935
+ function ChatMessage({
936
+ role,
937
+ content,
938
+ toolCalls,
939
+ isStreaming,
940
+ timestamp,
941
+ className,
942
+ userLabel = "You",
943
+ assistantLabel = "Agent",
944
+ hideRoleLabel,
945
+ hideAvatar,
946
+ avatar
947
+ }) {
948
+ const isUser = role === "user";
949
+ return /* @__PURE__ */ jsxs6(
950
+ "div",
951
+ {
952
+ className: cn(
953
+ "flex gap-3",
954
+ isUser ? "flex-row-reverse" : "flex-row",
955
+ className
956
+ ),
957
+ children: [
958
+ !hideAvatar && (avatar ? /* @__PURE__ */ jsx7("div", { className: "mt-0.5 shrink-0", children: avatar }) : /* @__PURE__ */ jsx7(
959
+ "div",
960
+ {
961
+ className: cn(
962
+ "mt-0.5 flex shrink-0 items-center justify-center rounded-[calc(var(--radius-md)+2px)] border",
963
+ "h-[var(--avatar-size)] w-[var(--avatar-size)]",
964
+ isUser ? "border-border bg-[var(--accent-surface-soft)] text-[var(--accent-text)]" : "border-border bg-muted text-[var(--brand-cool)]"
965
+ ),
966
+ children: isUser ? /* @__PURE__ */ jsx7(User, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(Bot, { className: "h-3.5 w-3.5" })
967
+ }
968
+ )),
969
+ /* @__PURE__ */ jsxs6(
970
+ "div",
971
+ {
972
+ className: cn(
973
+ "min-w-0 max-w-[85%] space-y-1 rounded-[var(--radius-lg)] border",
974
+ "px-[var(--chat-message-px)] py-[var(--chat-message-py)]",
975
+ isUser ? "border-border bg-muted/50" : "border-border bg-card"
976
+ ),
977
+ children: [
978
+ !hideRoleLabel && /* @__PURE__ */ jsxs6("div", { className: cn("flex items-center gap-2", isUser && "flex-row-reverse"), children: [
979
+ /* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] font-[var(--chat-label-weight,600)] uppercase tracking-[var(--chat-label-tracking,0.14em)] text-foreground", children: isUser ? userLabel : assistantLabel }),
980
+ timestamp && /* @__PURE__ */ jsx7("span", { className: "text-[var(--font-size-xs)] text-muted-foreground", children: formatTime2(timestamp) })
981
+ ] }),
982
+ isUser ? /* @__PURE__ */ jsx7("div", { className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)] text-foreground", children: content }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
983
+ content && /* @__PURE__ */ jsx7(Markdown, { className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]", children: content }),
984
+ isStreaming && /* @__PURE__ */ jsx7("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" })
985
+ ] }),
986
+ toolCalls
987
+ ]
988
+ }
989
+ )
990
+ ]
991
+ }
992
+ );
993
+ }
994
+ function formatTime2(date) {
995
+ return date.toLocaleTimeString(void 0, {
996
+ hour: "numeric",
997
+ minute: "2-digit"
998
+ });
999
+ }
1000
+
1001
+ export {
1002
+ UserMessage,
1003
+ MessageList,
1004
+ ThinkingIndicator,
1005
+ AgentTimeline,
1006
+ ChatInput,
1007
+ ChatContainer,
1008
+ ChatMessage
1009
+ };