@tangle-network/sandbox-ui 0.2.2 → 0.3.3

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 (67) hide show
  1. package/README.md +201 -10
  2. package/dist/auth.js +2 -2
  3. package/dist/chat-container-C8eHLw8z.d.ts +67 -0
  4. package/dist/chat.d.ts +70 -78
  5. package/dist/chat.js +8 -8
  6. package/dist/chunk-4F2GJRGU.js +756 -0
  7. package/dist/{chunk-HYEAX3DC.js → chunk-5LV6DZZF.js} +445 -114
  8. package/dist/chunk-67C53XVV.js +1106 -0
  9. package/dist/{chunk-QSQBDR3N.js → chunk-BX6AQMUS.js} +5 -2
  10. package/dist/chunk-CCKNIAS7.js +124 -0
  11. package/dist/chunk-CJ2RYVZH.js +128 -0
  12. package/dist/{chunk-KMXV7DDX.js → chunk-CNWVHQFY.js} +1 -1
  13. package/dist/{chunk-OU4TRNQZ.js → chunk-COCSO7FG.js} +3 -3
  14. package/dist/chunk-FJSVPBKY.js +85 -0
  15. package/dist/chunk-FRGMMANX.js +102 -0
  16. package/dist/{chunk-E6FS7R4X.js → chunk-HWLX5NME.js} +1 -1
  17. package/dist/chunk-JF6E2DS5.js +610 -0
  18. package/dist/chunk-MUOL44AE.js +121 -0
  19. package/dist/chunk-MXCSSOGH.js +105 -0
  20. package/dist/{chunk-J4OADEUK.js → chunk-OM6ON27W.js} +24 -9
  21. package/dist/{chunk-NI2EI43H.js → chunk-PDV7W4NY.js} +9 -124
  22. package/dist/chunk-TQN3VR4F.js +92 -0
  23. package/dist/{chunk-SOT2V7TX.js → chunk-TXI4MZAZ.js} +62 -144
  24. package/dist/chunk-WUR652Y3.js +1140 -0
  25. package/dist/chunk-YDBXQQLC.js +336 -0
  26. package/dist/{chunk-4EIWPJMJ.js → chunk-ZP6GSX4D.js} +36 -27
  27. package/dist/dashboard.d.ts +5 -2
  28. package/dist/dashboard.js +5 -4
  29. package/dist/{expanded-tool-detail-OkXGqTHe.d.ts → expanded-tool-detail-BDi_h_dZ.d.ts} +11 -4
  30. package/dist/file-tabs-CmaoDVBI.d.ts +72 -0
  31. package/dist/files.d.ts +25 -44
  32. package/dist/files.js +8 -3
  33. package/{src/styles → dist}/globals.css +16 -67
  34. package/dist/hooks.d.ts +5 -4
  35. package/dist/hooks.js +14 -9
  36. package/dist/index.d.ts +38 -9
  37. package/dist/index.js +100 -126
  38. package/dist/markdown.d.ts +1 -24
  39. package/dist/markdown.js +1 -7
  40. package/dist/openui.d.ts +115 -0
  41. package/dist/openui.js +11 -0
  42. package/dist/pages.d.ts +3 -2
  43. package/dist/pages.js +19 -16
  44. package/dist/primitives.js +25 -19
  45. package/dist/run.d.ts +2 -2
  46. package/dist/run.js +8 -7
  47. package/dist/{use-sidecar-auth-Bb0-w3lX.d.ts → sdk-hooks.d.ts} +61 -72
  48. package/dist/sdk-hooks.js +29 -0
  49. package/dist/styles.css +179 -0
  50. package/dist/tokens.css +165 -0
  51. package/dist/{tool-display-BvsVW_Ur.d.ts → tool-display-Ct9nFAzJ.d.ts} +1 -1
  52. package/dist/types.d.ts +1 -1
  53. package/dist/{usage-chart-DINgSVL5.d.ts → usage-chart-CY9xo3KX.d.ts} +8 -3
  54. package/dist/use-pty-session-DeZSxOCN.d.ts +69 -0
  55. package/dist/utils.d.ts +1 -1
  56. package/dist/utils.js +1 -1
  57. package/dist/workspace.d.ts +171 -33
  58. package/dist/workspace.js +25 -1
  59. package/package.json +10 -3
  60. package/dist/chunk-2UHPE5T7.js +0 -201
  61. package/dist/chunk-6MQIDUPA.js +0 -502
  62. package/dist/chunk-KYY2X6LY.js +0 -318
  63. package/dist/chunk-L6ZDH5F4.js +0 -334
  64. package/dist/chunk-M34OA6PQ.js +0 -233
  65. package/dist/chunk-M6VLC32S.js +0 -219
  66. package/dist/chunk-U62G5TS7.js +0 -472
  67. package/src/styles/tokens.css +0 -73
@@ -0,0 +1,756 @@
1
+ import {
2
+ useAutoScroll,
3
+ useRunCollapseState,
4
+ useRunGroups
5
+ } from "./chunk-CNWVHQFY.js";
6
+ import {
7
+ InlineThinkingItem,
8
+ RunGroup
9
+ } from "./chunk-WUR652Y3.js";
10
+ import {
11
+ ToolCallGroup,
12
+ ToolCallStep
13
+ } from "./chunk-CJ2RYVZH.js";
14
+ import {
15
+ getToolDisplayMetadata
16
+ } from "./chunk-BX6AQMUS.js";
17
+ import {
18
+ Markdown
19
+ } from "./chunk-LTFK464G.js";
20
+ import {
21
+ cn
22
+ } from "./chunk-RQHJBTEU.js";
23
+
24
+ // src/chat/user-message.tsx
25
+ import { memo } from "react";
26
+ import { jsx, jsxs } from "react/jsx-runtime";
27
+ var UserMessage = memo(({ message, parts }) => {
28
+ const textContent = parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
29
+ if (!textContent.trim()) return null;
30
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs("div", { className: "max-w-[85%] rounded-[var(--radius-xl)] rounded-br-[var(--radius-sm)] border border-[var(--border-accent)] bg-[linear-gradient(135deg,rgba(98,114,243,0.16),rgba(98,114,243,0.06))] px-4 py-3 shadow-[var(--shadow-card)]", children: [
31
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-[var(--brand-cool)]", children: "You" }),
32
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-[var(--text-primary)]", children: /* @__PURE__ */ jsx(Markdown, { children: textContent }) })
33
+ ] }) });
34
+ });
35
+ UserMessage.displayName = "UserMessage";
36
+
37
+ // src/chat/message-list.tsx
38
+ import { memo as memo2 } from "react";
39
+ import { jsx as jsx2 } from "react/jsx-runtime";
40
+ var MessageList = memo2(
41
+ ({
42
+ groups,
43
+ partMap,
44
+ isCollapsed,
45
+ onToggleCollapse,
46
+ branding,
47
+ renderToolDetail
48
+ }) => {
49
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-3", children: groups.map((group) => {
50
+ if (group.type === "user") {
51
+ return /* @__PURE__ */ jsx2(
52
+ UserMessage,
53
+ {
54
+ message: group.message,
55
+ parts: partMap[group.message.id] ?? []
56
+ },
57
+ group.message.id
58
+ );
59
+ }
60
+ return /* @__PURE__ */ jsx2(
61
+ RunGroup,
62
+ {
63
+ run: group.run,
64
+ partMap,
65
+ collapsed: isCollapsed(group.run.id),
66
+ onToggle: () => onToggleCollapse(group.run.id),
67
+ branding,
68
+ renderToolDetail
69
+ },
70
+ group.run.id
71
+ );
72
+ }) });
73
+ }
74
+ );
75
+ MessageList.displayName = "MessageList";
76
+
77
+ // src/chat/chat-message.tsx
78
+ import { User, Bot } from "lucide-react";
79
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
80
+ function ChatMessage({
81
+ role,
82
+ content,
83
+ toolCalls,
84
+ isStreaming,
85
+ timestamp,
86
+ className
87
+ }) {
88
+ const isUser = role === "user";
89
+ return /* @__PURE__ */ jsxs2(
90
+ "div",
91
+ {
92
+ className: cn(
93
+ "flex gap-3 rounded-[var(--radius-xl)] border px-4 py-4 shadow-[var(--shadow-card)]",
94
+ isUser ? "border-[var(--border-accent)] bg-[linear-gradient(135deg,rgba(98,114,243,0.14),rgba(98,114,243,0.05))]" : "border-[var(--border-subtle)] bg-[linear-gradient(180deg,rgba(255,255,255,0.02),transparent_32%),var(--bg-card)]",
95
+ className
96
+ ),
97
+ children: [
98
+ /* @__PURE__ */ jsx3(
99
+ "div",
100
+ {
101
+ className: cn(
102
+ "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-[var(--radius-md)] border",
103
+ isUser ? "border-[var(--border-accent)] bg-[var(--brand-cool)]/15 text-[var(--brand-cool)]" : "border-[var(--border-subtle)] bg-[var(--brand-glow)]/12 text-[var(--brand-glow)]"
104
+ ),
105
+ children: isUser ? /* @__PURE__ */ jsx3(User, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx3(Bot, { className: "h-4 w-4" })
106
+ }
107
+ ),
108
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 min-w-0 space-y-1", children: [
109
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
110
+ /* @__PURE__ */ jsx3("span", { className: "text-xs font-semibold text-[var(--text-secondary)]", children: isUser ? "You" : "Agent" }),
111
+ timestamp && /* @__PURE__ */ jsx3("span", { className: "text-xs text-[var(--text-muted)]", children: formatTime(timestamp) })
112
+ ] }),
113
+ isUser ? /* @__PURE__ */ jsx3("div", { className: "text-sm leading-relaxed text-[var(--text-primary)] whitespace-pre-wrap", children: content }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
114
+ content && /* @__PURE__ */ jsx3(Markdown, { className: "tangle-prose", children: content }),
115
+ isStreaming && /* @__PURE__ */ jsx3("span", { className: "inline-block w-2 h-4 bg-[var(--brand-cool)] animate-pulse rounded-sm ml-0.5 align-text-bottom" })
116
+ ] }),
117
+ toolCalls
118
+ ] })
119
+ ]
120
+ }
121
+ );
122
+ }
123
+ function formatTime(date) {
124
+ return date.toLocaleTimeString(void 0, {
125
+ hour: "numeric",
126
+ minute: "2-digit"
127
+ });
128
+ }
129
+
130
+ // src/chat/thinking-indicator.tsx
131
+ import { useEffect, useState } from "react";
132
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
133
+ function ThinkingIndicator({ className }) {
134
+ const [elapsed, setElapsed] = useState(0);
135
+ useEffect(() => {
136
+ const interval = window.setInterval(() => setElapsed((current) => current + 1), 1e3);
137
+ return () => window.clearInterval(interval);
138
+ }, []);
139
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex gap-3 px-4 py-3", className), children: [
140
+ /* @__PURE__ */ jsx4("div", { className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-[var(--brand-glow)]/15 text-[var(--brand-glow)]", children: /* @__PURE__ */ jsxs3("div", { className: "flex gap-0.5", children: [
141
+ /* @__PURE__ */ jsx4("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full bg-current", style: { animationDelay: "0ms" } }),
142
+ /* @__PURE__ */ jsx4("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full bg-current", style: { animationDelay: "150ms" } }),
143
+ /* @__PURE__ */ jsx4("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full bg-current", style: { animationDelay: "300ms" } })
144
+ ] }) }),
145
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
146
+ /* @__PURE__ */ jsx4("span", { className: "text-sm text-[var(--text-muted)]", children: elapsed < 10 ? "Thinking..." : elapsed < 60 ? "Thinking deeply..." : "Still working..." }),
147
+ elapsed > 5 && /* @__PURE__ */ jsxs3("span", { className: "text-xs tabular-nums text-[var(--text-muted)]", children: [
148
+ elapsed,
149
+ "s"
150
+ ] })
151
+ ] })
152
+ ] });
153
+ }
154
+
155
+ // src/chat/agent-timeline.tsx
156
+ import {
157
+ AlertTriangle,
158
+ CheckCircle2,
159
+ CircleDot,
160
+ FileText,
161
+ Info
162
+ } from "lucide-react";
163
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
164
+ var TONE_STYLES = {
165
+ default: {
166
+ dot: "bg-[var(--border-hover)]",
167
+ card: "border-[var(--border-subtle)] bg-[var(--bg-card)]",
168
+ text: "text-[var(--text-secondary)]",
169
+ icon: CircleDot
170
+ },
171
+ info: {
172
+ dot: "bg-sky-400",
173
+ card: "border-sky-500/20 bg-sky-500/5",
174
+ text: "text-sky-200",
175
+ icon: Info
176
+ },
177
+ success: {
178
+ dot: "bg-[var(--code-success)]",
179
+ card: "border-emerald-500/20 bg-emerald-500/5",
180
+ text: "text-emerald-200",
181
+ icon: CheckCircle2
182
+ },
183
+ warning: {
184
+ dot: "bg-amber-400",
185
+ card: "border-amber-500/20 bg-amber-500/5",
186
+ text: "text-amber-100",
187
+ icon: AlertTriangle
188
+ },
189
+ error: {
190
+ dot: "bg-[var(--code-error)]",
191
+ card: "border-red-500/20 bg-red-500/5",
192
+ text: "text-red-200",
193
+ icon: AlertTriangle
194
+ }
195
+ };
196
+ function AgentTimelineRow({ isLast, accentClassName, children }) {
197
+ return /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-[1.25rem_minmax(0,1fr)] gap-4", children: [
198
+ /* @__PURE__ */ jsxs4("div", { className: "relative flex justify-center", children: [
199
+ !isLast && /* @__PURE__ */ jsx5("span", { className: "absolute top-4 bottom-[-1rem] left-1/2 w-px -translate-x-1/2 bg-[var(--border-subtle)]" }),
200
+ /* @__PURE__ */ jsx5("span", { className: cn("relative mt-2 h-2.5 w-2.5 rounded-full ring-4 ring-[var(--bg-root)]", accentClassName) })
201
+ ] }),
202
+ /* @__PURE__ */ jsx5("div", { className: "min-w-0 pb-4", children })
203
+ ] });
204
+ }
205
+ function StatusCard({ item }) {
206
+ const tone = TONE_STYLES[item.tone ?? "default"];
207
+ const Icon = tone.icon;
208
+ return /* @__PURE__ */ jsx5("div", { className: cn("rounded-[var(--radius-lg)] border px-4 py-3 shadow-[var(--shadow-card)]", tone.card), children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
209
+ /* @__PURE__ */ jsx5(Icon, { className: cn("mt-0.5 h-4 w-4 shrink-0", tone.text) }),
210
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0", children: [
211
+ /* @__PURE__ */ jsx5("div", { className: cn("text-sm font-medium", tone.text), children: item.label }),
212
+ item.detail && /* @__PURE__ */ jsx5("div", { className: "mt-1 text-sm text-[var(--text-muted)]", children: item.detail })
213
+ ] })
214
+ ] }) });
215
+ }
216
+ function ArtifactCard({ item }) {
217
+ const tone = TONE_STYLES[item.tone ?? "default"];
218
+ const content = /* @__PURE__ */ jsx5("div", { className: cn("rounded-[var(--radius-lg)] border px-4 py-3 shadow-[var(--shadow-card)]", tone.card), children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
219
+ /* @__PURE__ */ jsx5("div", { className: cn("mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-[var(--radius-md)] bg-[var(--bg-elevated)] text-[var(--text-secondary)]"), children: item.icon ?? /* @__PURE__ */ jsx5(FileText, { className: "h-4 w-4" }) }),
220
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
221
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium text-[var(--text-primary)]", children: item.title }),
222
+ item.description && /* @__PURE__ */ jsx5("div", { className: "mt-1 text-sm text-[var(--text-muted)]", children: item.description }),
223
+ item.meta && /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-wrap items-center gap-2 text-xs text-[var(--text-muted)]", children: item.meta })
224
+ ] }),
225
+ item.action && /* @__PURE__ */ jsx5("div", { className: "shrink-0", children: item.action })
226
+ ] }) });
227
+ if (!item.onClick) return content;
228
+ return /* @__PURE__ */ jsx5(
229
+ "div",
230
+ {
231
+ role: "button",
232
+ tabIndex: 0,
233
+ onClick: item.onClick,
234
+ onKeyDown: (event) => {
235
+ if (event.key === "Enter" || event.key === " ") {
236
+ event.preventDefault();
237
+ item.onClick?.();
238
+ }
239
+ },
240
+ className: "block w-full text-left transition-transform hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60",
241
+ children: content
242
+ }
243
+ );
244
+ }
245
+ function AgentTimeline({
246
+ items,
247
+ isThinking,
248
+ emptyState,
249
+ className
250
+ }) {
251
+ if (items.length === 0 && !isThinking) {
252
+ return emptyState ? /* @__PURE__ */ jsx5("div", { className: cn("flex h-full items-center justify-center p-8", className), children: emptyState }) : null;
253
+ }
254
+ const renderedItems = isThinking ? [...items, { id: "__thinking__", kind: "custom", content: /* @__PURE__ */ jsx5(ThinkingIndicator, {}) }] : items;
255
+ return /* @__PURE__ */ jsx5("div", { className: cn("mx-auto w-full max-w-5xl px-4 py-4", className), children: renderedItems.map((item, index) => {
256
+ const isLast = index === renderedItems.length - 1;
257
+ if (item.kind === "message") {
258
+ const accentClassName = item.role === "user" ? "bg-[var(--brand-cool)]" : "bg-[var(--brand-glow)]";
259
+ return /* @__PURE__ */ jsx5(AgentTimelineRow, { isLast, accentClassName, children: /* @__PURE__ */ jsxs4("div", { className: "rounded-[var(--radius-xl)] border border-[var(--border-subtle)] bg-[var(--bg-card)] shadow-[var(--shadow-card)]", children: [
260
+ /* @__PURE__ */ jsx5(
261
+ ChatMessage,
262
+ {
263
+ role: item.role,
264
+ content: item.content,
265
+ toolCalls: item.toolCalls,
266
+ isStreaming: item.isStreaming,
267
+ timestamp: item.timestamp
268
+ }
269
+ ),
270
+ item.after && /* @__PURE__ */ jsx5("div", { className: "border-t border-[var(--border-subtle)] px-4 py-3", children: item.after })
271
+ ] }) }, item.id);
272
+ }
273
+ if (item.kind === "tool") {
274
+ return /* @__PURE__ */ jsx5(
275
+ AgentTimelineRow,
276
+ {
277
+ isLast,
278
+ accentClassName: "bg-[var(--brand-cool)]/80",
279
+ children: /* @__PURE__ */ jsx5(
280
+ ToolCallStep,
281
+ {
282
+ type: item.call.type,
283
+ label: item.call.label,
284
+ status: item.call.status,
285
+ detail: item.call.detail,
286
+ output: item.call.output,
287
+ duration: item.call.duration
288
+ }
289
+ )
290
+ },
291
+ item.id
292
+ );
293
+ }
294
+ if (item.kind === "tool_group") {
295
+ return /* @__PURE__ */ jsx5(
296
+ AgentTimelineRow,
297
+ {
298
+ isLast,
299
+ accentClassName: "bg-[var(--brand-cool)]/80",
300
+ children: /* @__PURE__ */ jsx5(ToolCallGroup, { title: item.title, children: item.calls.map((call) => /* @__PURE__ */ jsx5(
301
+ ToolCallStep,
302
+ {
303
+ type: call.type,
304
+ label: call.label,
305
+ status: call.status,
306
+ detail: call.detail,
307
+ output: call.output,
308
+ duration: call.duration
309
+ },
310
+ call.id
311
+ )) })
312
+ },
313
+ item.id
314
+ );
315
+ }
316
+ if (item.kind === "status") {
317
+ return /* @__PURE__ */ jsx5(
318
+ AgentTimelineRow,
319
+ {
320
+ isLast,
321
+ accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
322
+ children: /* @__PURE__ */ jsx5(StatusCard, { item })
323
+ },
324
+ item.id
325
+ );
326
+ }
327
+ if (item.kind === "artifact") {
328
+ return /* @__PURE__ */ jsx5(
329
+ AgentTimelineRow,
330
+ {
331
+ isLast,
332
+ accentClassName: TONE_STYLES[item.tone ?? "default"].dot,
333
+ children: /* @__PURE__ */ jsx5(ArtifactCard, { item })
334
+ },
335
+ item.id
336
+ );
337
+ }
338
+ return /* @__PURE__ */ jsx5(
339
+ AgentTimelineRow,
340
+ {
341
+ isLast,
342
+ accentClassName: "bg-[var(--border-hover)]",
343
+ children: item.content
344
+ },
345
+ item.id
346
+ );
347
+ }) });
348
+ }
349
+
350
+ // src/chat/chat-input.tsx
351
+ import { useState as useState2, useRef, useCallback } from "react";
352
+ import { Send, Square, Paperclip, X } from "lucide-react";
353
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
354
+ function ChatInput({
355
+ onSend,
356
+ onCancel,
357
+ isStreaming,
358
+ disabled,
359
+ placeholder = "Ask the agent to inspect files, run commands, or explain results\u2026",
360
+ modelLabel,
361
+ onModelClick,
362
+ pendingFiles = [],
363
+ onRemoveFile,
364
+ onAttach,
365
+ className
366
+ }) {
367
+ const [value, setValue] = useState2("");
368
+ const textareaRef = useRef(null);
369
+ const fileInputRef = useRef(null);
370
+ const handleSend = useCallback(() => {
371
+ const trimmed = value.trim();
372
+ if (!trimmed || isStreaming || disabled) return;
373
+ onSend(trimmed);
374
+ setValue("");
375
+ if (textareaRef.current) {
376
+ textareaRef.current.style.height = "auto";
377
+ }
378
+ }, [value, isStreaming, disabled, onSend]);
379
+ const handleKeyDown = (e) => {
380
+ if (e.key === "Enter" && !e.shiftKey) {
381
+ e.preventDefault();
382
+ handleSend();
383
+ }
384
+ };
385
+ const handleChange = (e) => {
386
+ setValue(e.target.value);
387
+ const el = e.target;
388
+ el.style.height = "auto";
389
+ el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
390
+ };
391
+ const handleAttachClick = () => {
392
+ fileInputRef.current?.click();
393
+ };
394
+ const handleFileChange = (e) => {
395
+ if (e.target.files?.length) {
396
+ onAttach?.(e.target.files);
397
+ e.target.value = "";
398
+ }
399
+ };
400
+ return /* @__PURE__ */ jsxs5("div", { className: cn("px-4 py-3", className), children: [
401
+ pendingFiles.length > 0 && /* @__PURE__ */ jsx6("div", { className: "mb-3 flex flex-wrap gap-2", children: pendingFiles.map((f) => /* @__PURE__ */ jsxs5(
402
+ "span",
403
+ {
404
+ className: cn(
405
+ "inline-flex items-center gap-1.5 rounded-[var(--radius-full)] border px-3 py-1.5 text-xs shadow-[var(--shadow-card)]",
406
+ "border-[var(--border-subtle)] bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent)]",
407
+ f.status === "error" && "border-[var(--code-error)]/30 text-[var(--code-error)]",
408
+ f.status !== "error" && "text-[var(--text-secondary)]"
409
+ ),
410
+ children: [
411
+ /* @__PURE__ */ jsx6("span", { className: "truncate max-w-[150px]", children: f.name }),
412
+ f.status === "uploading" && /* @__PURE__ */ jsx6("span", { className: "w-3 h-3 border-2 border-[var(--brand-cool)] border-t-transparent rounded-full animate-spin" }),
413
+ onRemoveFile && /* @__PURE__ */ jsx6(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ "aria-label": `Remove ${f.name}`,
418
+ onClick: () => onRemoveFile(f.id),
419
+ className: "rounded p-0.5 transition-colors hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60",
420
+ children: /* @__PURE__ */ jsx6(X, { className: "h-3 w-3" })
421
+ }
422
+ )
423
+ ]
424
+ },
425
+ f.id
426
+ )) }),
427
+ /* @__PURE__ */ jsx6("div", { className: "rounded-[calc(var(--radius-xl)+2px)] border border-[var(--border-subtle)] bg-[linear-gradient(135deg,rgba(98,114,243,0.08),rgba(255,255,255,0.02)_40%,transparent)] p-[1px] shadow-[var(--shadow-card)]", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2 rounded-[var(--radius-xl)] border border-white/4 bg-[var(--bg-card)] px-3 py-3 transition-colors focus-within:border-[var(--border-accent)]", children: [
428
+ onAttach && /* @__PURE__ */ jsxs5(Fragment2, { children: [
429
+ /* @__PURE__ */ jsx6(
430
+ "button",
431
+ {
432
+ type: "button",
433
+ onClick: handleAttachClick,
434
+ disabled: isStreaming,
435
+ "aria-label": "Attach files",
436
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-md)] p-2 text-[var(--text-muted)] transition-colors hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)] disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60",
437
+ children: /* @__PURE__ */ jsx6(Paperclip, { className: "h-4 w-4" })
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx6(
441
+ "input",
442
+ {
443
+ ref: fileInputRef,
444
+ type: "file",
445
+ multiple: true,
446
+ className: "hidden",
447
+ onChange: handleFileChange,
448
+ accept: ".pdf,.csv,.xlsx,.xls,.jpg,.jpeg,.png,.gif,.txt,.json,.yaml,.yml"
449
+ }
450
+ )
451
+ ] }),
452
+ /* @__PURE__ */ jsx6(
453
+ "textarea",
454
+ {
455
+ ref: textareaRef,
456
+ value,
457
+ onChange: handleChange,
458
+ onKeyDown: handleKeyDown,
459
+ placeholder,
460
+ disabled: isStreaming || disabled,
461
+ rows: 1,
462
+ "aria-label": "Message input",
463
+ className: "min-h-[28px] max-h-[160px] flex-1 resize-none bg-transparent text-sm leading-relaxed text-[var(--text-primary)] placeholder:text-[var(--text-muted)] disabled:opacity-50 focus-visible:outline-none"
464
+ }
465
+ ),
466
+ isStreaming ? /* @__PURE__ */ jsx6(
467
+ "button",
468
+ {
469
+ type: "button",
470
+ onClick: onCancel,
471
+ "aria-label": "Stop response",
472
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-md)] bg-[var(--code-error)]/15 p-2 text-[var(--code-error)] transition-colors hover:bg-[var(--code-error)]/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--code-error)]/50",
473
+ children: /* @__PURE__ */ jsx6(Square, { className: "h-4 w-4" })
474
+ }
475
+ ) : /* @__PURE__ */ jsx6(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ onClick: handleSend,
480
+ disabled: !value.trim() || disabled,
481
+ "aria-label": "Send message",
482
+ className: "mb-0.5 shrink-0 rounded-[var(--radius-md)] bg-[var(--brand-cool)]/15 p-2 text-[var(--brand-cool)] transition-colors hover:bg-[var(--brand-cool)]/25 disabled:opacity-30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60",
483
+ children: /* @__PURE__ */ jsx6(Send, { className: "h-4 w-4" })
484
+ }
485
+ )
486
+ ] }) }),
487
+ /* @__PURE__ */ jsxs5("div", { className: "mt-2 flex items-center justify-between px-1", children: [
488
+ /* @__PURE__ */ jsx6("div", { className: "flex items-center gap-2", children: modelLabel && /* @__PURE__ */ jsxs5(
489
+ "button",
490
+ {
491
+ type: "button",
492
+ onClick: onModelClick,
493
+ "aria-label": `Select model, current model ${modelLabel}`,
494
+ className: "inline-flex items-center gap-1 rounded-[var(--radius-full)] border border-[var(--border-subtle)] bg-[var(--bg-section)]/55 px-2.5 py-1 text-xs text-[var(--text-muted)] transition-colors hover:border-[var(--border-accent)] hover:text-[var(--text-secondary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60",
495
+ children: [
496
+ /* @__PURE__ */ jsx6("span", { className: "w-1.5 h-1.5 rounded-full bg-[var(--code-success)]" }),
497
+ modelLabel
498
+ ]
499
+ }
500
+ ) }),
501
+ /* @__PURE__ */ jsxs5("span", { className: "text-xs text-[var(--text-muted)]", children: [
502
+ /* @__PURE__ */ jsx6("kbd", { className: "px-1 py-0.5 bg-[var(--bg-input)] rounded border border-[var(--border-subtle)] text-[10px]", children: "Cmd" }),
503
+ /* @__PURE__ */ jsx6("kbd", { className: "px-1 py-0.5 bg-[var(--bg-input)] rounded border border-[var(--border-subtle)] text-[10px] ml-0.5", children: "L" }),
504
+ /* @__PURE__ */ jsx6("span", { className: "ml-1", children: "to focus" })
505
+ ] })
506
+ ] })
507
+ ] });
508
+ }
509
+
510
+ // src/chat/chat-container.tsx
511
+ import {
512
+ memo as memo3,
513
+ useCallback as useCallback2,
514
+ useMemo,
515
+ useRef as useRef2
516
+ } from "react";
517
+ import { ArrowDown } from "lucide-react";
518
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
519
+ function formatUnknown(value) {
520
+ if (value == null) return void 0;
521
+ if (typeof value === "string") return value;
522
+ try {
523
+ return JSON.stringify(value, null, 2);
524
+ } catch {
525
+ return String(value);
526
+ }
527
+ }
528
+ function createdAtFromMessage(message) {
529
+ return message.time?.created ? new Date(message.time.created) : void 0;
530
+ }
531
+ function mapToolPartToTimelineType(part) {
532
+ const name = part.tool.toLowerCase().replace(/^tool:/, "");
533
+ switch (name) {
534
+ case "bash":
535
+ case "shell":
536
+ case "command":
537
+ case "execute":
538
+ return "bash";
539
+ case "write":
540
+ case "write_file":
541
+ case "create_file":
542
+ return "write";
543
+ case "read":
544
+ case "read_file":
545
+ case "cat":
546
+ return "read";
547
+ case "edit":
548
+ case "patch":
549
+ case "sed":
550
+ return "edit";
551
+ case "glob":
552
+ case "find":
553
+ return "glob";
554
+ case "ls":
555
+ return "list";
556
+ case "grep":
557
+ case "search":
558
+ case "rg":
559
+ return "grep";
560
+ case "inspect":
561
+ return "inspect";
562
+ default:
563
+ return "unknown";
564
+ }
565
+ }
566
+ function buildTimelineItems(messages, partMap, isStreaming) {
567
+ const items = [];
568
+ const lastAssistantMessage = [...messages].reverse().find((message) => message.role === "assistant");
569
+ const toToolCall = (part) => {
570
+ const meta = getToolDisplayMetadata(part);
571
+ const start = part.state.time?.start;
572
+ const end = part.state.time?.end;
573
+ return {
574
+ id: part.id,
575
+ type: mapToolPartToTimelineType(part),
576
+ label: meta.description ? `${meta.title}: ${meta.description}` : meta.title,
577
+ status: part.state.status === "completed" ? "success" : part.state.status === "error" ? "error" : "running",
578
+ detail: formatUnknown(part.state.input),
579
+ output: formatUnknown(part.state.output),
580
+ duration: start && end ? end - start : void 0
581
+ };
582
+ };
583
+ for (const message of messages) {
584
+ const parts = partMap[message.id] ?? [];
585
+ if (message.role === "user") {
586
+ const content = parts.filter((part) => part.type === "text").map((part) => part.text).join("\n").trim();
587
+ if (!content) continue;
588
+ items.push({
589
+ id: message.id,
590
+ kind: "message",
591
+ role: "user",
592
+ content,
593
+ timestamp: createdAtFromMessage(message)
594
+ });
595
+ continue;
596
+ }
597
+ const toolBuffer = [];
598
+ const flushToolBuffer = (index) => {
599
+ if (toolBuffer.length === 0) return;
600
+ if (toolBuffer.length === 1) {
601
+ items.push({
602
+ id: `${message.id}-tool-${toolBuffer[0].id}`,
603
+ kind: "tool",
604
+ call: toToolCall(toolBuffer[0])
605
+ });
606
+ } else {
607
+ items.push({
608
+ id: `${message.id}-tool-group-${index}`,
609
+ kind: "tool_group",
610
+ title: "Tool activity",
611
+ calls: toolBuffer.map((part) => toToolCall(part))
612
+ });
613
+ }
614
+ toolBuffer.length = 0;
615
+ };
616
+ parts.forEach((part, index) => {
617
+ const itemId = `${message.id}-${index}`;
618
+ if (part.type === "tool") {
619
+ toolBuffer.push(part);
620
+ return;
621
+ }
622
+ flushToolBuffer(index);
623
+ if (part.type === "text" && !part.synthetic && part.text.trim()) {
624
+ items.push({
625
+ id: itemId,
626
+ kind: "message",
627
+ role: "assistant",
628
+ content: part.text,
629
+ timestamp: createdAtFromMessage(message),
630
+ isStreaming: isStreaming && lastAssistantMessage?.id === message.id && index === parts.length - 1
631
+ });
632
+ return;
633
+ }
634
+ if (part.type === "reasoning") {
635
+ items.push({
636
+ id: itemId,
637
+ kind: "custom",
638
+ content: /* @__PURE__ */ jsx7(InlineThinkingItem, { part, defaultOpen: isStreaming && lastAssistantMessage?.id === message.id })
639
+ });
640
+ return;
641
+ }
642
+ });
643
+ flushToolBuffer(parts.length);
644
+ }
645
+ const showThinking = isStreaming && lastAssistantMessage != null && !items.some(
646
+ (item) => item.kind === "message" && item.role === "assistant" && item.id.startsWith(lastAssistantMessage.id)
647
+ );
648
+ return { items, showThinking };
649
+ }
650
+ var ChatContainer = memo3(
651
+ ({
652
+ messages,
653
+ partMap,
654
+ isStreaming,
655
+ onSend,
656
+ onCancel,
657
+ branding,
658
+ placeholder = "Type a message...",
659
+ className,
660
+ hideInput = false,
661
+ renderToolDetail,
662
+ presentation = "runs",
663
+ modelLabel,
664
+ onModelClick,
665
+ pendingFiles,
666
+ onRemoveFile,
667
+ onAttach,
668
+ disabled = false
669
+ }) => {
670
+ const scrollRef = useRef2(null);
671
+ const groups = useRunGroups({ messages, partMap, isStreaming });
672
+ const runs = groups.filter((g) => g.type === "run").map((g) => g.run);
673
+ const { isCollapsed, toggleCollapse } = useRunCollapseState(runs);
674
+ const { isAtBottom, scrollToBottom } = useAutoScroll(scrollRef, [
675
+ messages,
676
+ partMap,
677
+ isStreaming
678
+ ]);
679
+ const timeline = useMemo(
680
+ () => buildTimelineItems(messages, partMap, isStreaming),
681
+ [messages, partMap, isStreaming]
682
+ );
683
+ const handleSend = useCallback2(
684
+ (text) => {
685
+ onSend?.(text);
686
+ },
687
+ [onSend]
688
+ );
689
+ return /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col h-full", className), children: [
690
+ /* @__PURE__ */ jsx7(
691
+ "div",
692
+ {
693
+ ref: scrollRef,
694
+ className: "flex-1 overflow-y-auto px-4 py-4 [scrollbar-gutter:stable]",
695
+ children: messages.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs6("div", { className: "max-w-md rounded-[var(--radius-xl)] border border-[var(--border-subtle)] bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent)] px-6 py-8 text-center shadow-[var(--shadow-card)]", children: [
696
+ /* @__PURE__ */ jsx7("div", { className: "text-sm font-semibold text-[var(--text-primary)]", children: "Start the filing workflow" }),
697
+ /* @__PURE__ */ jsx7("div", { className: "mt-2 text-sm leading-relaxed text-[var(--text-muted)]", children: "Ask the agent to analyze documents, generate forms, explain a calculation, or review the current filing package." })
698
+ ] }) }) : presentation === "timeline" ? /* @__PURE__ */ jsx7(AgentTimeline, { items: timeline.items, isThinking: timeline.showThinking }) : /* @__PURE__ */ jsx7("div", { className: "mx-auto flex w-full max-w-5xl flex-col gap-3", children: /* @__PURE__ */ jsx7(
699
+ MessageList,
700
+ {
701
+ groups,
702
+ partMap,
703
+ isCollapsed,
704
+ onToggleCollapse: toggleCollapse,
705
+ branding,
706
+ renderToolDetail
707
+ }
708
+ ) })
709
+ }
710
+ ),
711
+ !isAtBottom && /* @__PURE__ */ jsx7("div", { className: "flex justify-center -mt-10 relative z-10", children: /* @__PURE__ */ jsxs6(
712
+ "button",
713
+ {
714
+ onClick: scrollToBottom,
715
+ className: cn(
716
+ "flex items-center gap-1.5 px-3 py-1.5 rounded-full",
717
+ "border border-[var(--border-subtle)] bg-[var(--bg-card)] shadow-[var(--shadow-card)]",
718
+ "text-xs text-[var(--text-secondary)] transition-colors hover:bg-[var(--bg-hover)]",
719
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand-cool)]/60"
720
+ ),
721
+ children: [
722
+ /* @__PURE__ */ jsx7(ArrowDown, { className: "w-3 h-3" }),
723
+ "Scroll to bottom"
724
+ ]
725
+ }
726
+ ) }),
727
+ !hideInput && onSend && /* @__PURE__ */ jsx7(
728
+ ChatInput,
729
+ {
730
+ onSend: handleSend,
731
+ onCancel,
732
+ isStreaming,
733
+ placeholder,
734
+ modelLabel,
735
+ onModelClick,
736
+ pendingFiles,
737
+ onRemoveFile,
738
+ onAttach,
739
+ disabled,
740
+ className: "shrink-0 border-t border-[var(--border-subtle)] bg-[var(--bg-dark)]"
741
+ }
742
+ )
743
+ ] });
744
+ }
745
+ );
746
+ ChatContainer.displayName = "ChatContainer";
747
+
748
+ export {
749
+ UserMessage,
750
+ MessageList,
751
+ ChatMessage,
752
+ ThinkingIndicator,
753
+ AgentTimeline,
754
+ ChatInput,
755
+ ChatContainer
756
+ };