@slipstream-ui/ai 0.0.0-stage → 0.3.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 (111) hide show
  1. package/LICENSE +25 -0
  2. package/dist/index.d.mts +579 -0
  3. package/dist/index.d.ts +579 -0
  4. package/dist/index.js +1778 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +1706 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/manifests/attachment.manifest.json +33 -0
  9. package/dist/manifests/branch.manifest.json +65 -0
  10. package/dist/manifests/chain-thought.manifest.json +39 -0
  11. package/dist/manifests/code-block.manifest.json +50 -0
  12. package/dist/manifests/command-palette.manifest.json +80 -0
  13. package/dist/manifests/confirmation.manifest.json +143 -0
  14. package/dist/manifests/conversation.manifest.json +290 -0
  15. package/dist/manifests/inline-citation.manifest.json +46 -0
  16. package/dist/manifests/message-actions.manifest.json +35 -0
  17. package/dist/manifests/message.manifest.json +235 -0
  18. package/dist/manifests/prompt-input.manifest.json +334 -0
  19. package/dist/manifests/reasoning.manifest.json +177 -0
  20. package/dist/manifests/response.manifest.json +51 -0
  21. package/dist/manifests/source.manifest.json +78 -0
  22. package/dist/manifests/streaming-indicator.manifest.json +39 -0
  23. package/dist/manifests/suggestions.manifest.json +45 -0
  24. package/dist/manifests/tool-call.manifest.json +158 -0
  25. package/package.json +61 -3
  26. package/src/adapter/adapter.test.tsx +83 -0
  27. package/src/adapter/hooks.ts +82 -0
  28. package/src/adapter/index.ts +3 -0
  29. package/src/adapter/messages.ts +49 -0
  30. package/src/adapter/types.ts +24 -0
  31. package/src/components/attachment/Attachment.test.tsx +72 -0
  32. package/src/components/attachment/Attachment.tsx +84 -0
  33. package/src/components/attachment/AttachmentList.tsx +30 -0
  34. package/src/components/attachment/index.ts +5 -0
  35. package/src/components/branch/Branch.test.tsx +52 -0
  36. package/src/components/branch/Branch.tsx +77 -0
  37. package/src/components/branch/index.ts +2 -0
  38. package/src/components/chainthought/ChainThought.test.tsx +34 -0
  39. package/src/components/chainthought/ChainThought.tsx +45 -0
  40. package/src/components/chainthought/index.ts +2 -0
  41. package/src/components/code-block/CodeBlock.test.tsx +46 -0
  42. package/src/components/code-block/CodeBlock.tsx +95 -0
  43. package/src/components/code-block/index.ts +2 -0
  44. package/src/components/command-palette/CommandPalette.test.tsx +74 -0
  45. package/src/components/command-palette/CommandPalette.tsx +156 -0
  46. package/src/components/command-palette/index.ts +2 -0
  47. package/src/components/confirmation/Confirmation.test.tsx +97 -0
  48. package/src/components/confirmation/Confirmation.tsx +146 -0
  49. package/src/components/confirmation/index.ts +2 -0
  50. package/src/components/conversation/Conversation.test.tsx +49 -0
  51. package/src/components/conversation/Conversation.tsx +301 -0
  52. package/src/components/conversation/Conversation.welcome.test.tsx +73 -0
  53. package/src/components/conversation/index.ts +2 -0
  54. package/src/components/inline-citation/InlineCitation.test.tsx +42 -0
  55. package/src/components/inline-citation/InlineCitation.tsx +56 -0
  56. package/src/components/inline-citation/index.ts +2 -0
  57. package/src/components/message/Message.error-edit.test.tsx +60 -0
  58. package/src/components/message/Message.test.tsx +80 -0
  59. package/src/components/message/Message.tsx +205 -0
  60. package/src/components/message/index.ts +2 -0
  61. package/src/components/message-actions/MessageActions.test.tsx +77 -0
  62. package/src/components/message-actions/MessageActions.tsx +134 -0
  63. package/src/components/message-actions/index.ts +2 -0
  64. package/src/components/prompt-input/PromptInput.compound.test.tsx +150 -0
  65. package/src/components/prompt-input/PromptInput.test.tsx +74 -0
  66. package/src/components/prompt-input/PromptInput.tsx +584 -0
  67. package/src/components/prompt-input/index.ts +21 -0
  68. package/src/components/reasoning/Reasoning.duration.test.tsx +35 -0
  69. package/src/components/reasoning/Reasoning.test.tsx +58 -0
  70. package/src/components/reasoning/Reasoning.tsx +85 -0
  71. package/src/components/reasoning/index.ts +2 -0
  72. package/src/components/response/Response.test.tsx +69 -0
  73. package/src/components/response/Response.tsx +75 -0
  74. package/src/components/response/close-incomplete-markdown.ts +47 -0
  75. package/src/components/response/index.ts +3 -0
  76. package/src/components/source/Source.tsx +51 -0
  77. package/src/components/source/Sources.tsx +42 -0
  78. package/src/components/source/index.ts +5 -0
  79. package/src/components/source/source.test.tsx +72 -0
  80. package/src/components/streaming/StreamingIndicator.test.tsx +26 -0
  81. package/src/components/streaming/StreamingIndicator.tsx +46 -0
  82. package/src/components/streaming/index.ts +2 -0
  83. package/src/components/suggestion/Suggestion.test.tsx +43 -0
  84. package/src/components/suggestion/Suggestion.tsx +47 -0
  85. package/src/components/suggestion/index.ts +2 -0
  86. package/src/components/tool-call/ToolCall.test.tsx +45 -0
  87. package/src/components/tool-call/ToolCall.tsx +101 -0
  88. package/src/components/tool-call/index.ts +2 -0
  89. package/src/index.ts +29 -0
  90. package/src/manifests/attachment.manifest.json +33 -0
  91. package/src/manifests/branch.manifest.json +65 -0
  92. package/src/manifests/chain-thought.manifest.json +39 -0
  93. package/src/manifests/code-block.manifest.json +50 -0
  94. package/src/manifests/command-palette.manifest.json +80 -0
  95. package/src/manifests/confirmation.manifest.json +143 -0
  96. package/src/manifests/conversation.manifest.json +290 -0
  97. package/src/manifests/inline-citation.manifest.json +46 -0
  98. package/src/manifests/message-actions.manifest.json +35 -0
  99. package/src/manifests/message.manifest.json +235 -0
  100. package/src/manifests/prompt-input.manifest.json +334 -0
  101. package/src/manifests/reasoning.manifest.json +177 -0
  102. package/src/manifests/response.manifest.json +51 -0
  103. package/src/manifests/source.manifest.json +78 -0
  104. package/src/manifests/streaming-indicator.manifest.json +39 -0
  105. package/src/manifests/suggestions.manifest.json +45 -0
  106. package/src/manifests/tool-call.manifest.json +158 -0
  107. package/src/test/jest-axe.d.ts +16 -0
  108. package/src/test/setup.ts +18 -0
  109. package/src/test/vitest-matchers.d.ts +10 -0
  110. package/src/types/index.ts +40 -0
  111. package/README.md +0 -3
package/dist/index.js ADDED
@@ -0,0 +1,1778 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ Attachment: () => Attachment,
35
+ AttachmentList: () => AttachmentList,
36
+ Branch: () => Branch,
37
+ ChainThought: () => ChainThought,
38
+ CodeBlock: () => CodeBlock,
39
+ CommandPalette: () => CommandPalette,
40
+ Confirmation: () => Confirmation,
41
+ Conversation: () => Conversation,
42
+ InlineCitation: () => InlineCitation,
43
+ Message: () => MemoizedMessage,
44
+ MessageActions: () => MessageActions,
45
+ PromptInput: () => PromptInputRoot,
46
+ PromptInputActionAddAttachments: () => PromptInputActionAddAttachments,
47
+ PromptInputActionDictate: () => PromptInputActionDictate,
48
+ PromptInputBody: () => PromptInputBody,
49
+ PromptInputButton: () => PromptInputButton,
50
+ PromptInputFooter: () => PromptInputFooter,
51
+ PromptInputHeader: () => PromptInputHeader,
52
+ PromptInputSubmit: () => PromptInputSubmit,
53
+ PromptInputTextarea: () => PromptInputTextarea,
54
+ PromptInputTools: () => PromptInputTools,
55
+ Reasoning: () => Reasoning,
56
+ Response: () => Response,
57
+ Source: () => Source,
58
+ Sources: () => Sources,
59
+ StreamingIndicator: () => StreamingIndicator,
60
+ Suggestions: () => Suggestions,
61
+ ToolCall: () => ToolCall,
62
+ closeIncompleteMarkdown: () => closeIncompleteMarkdown,
63
+ toSlipstreamMessage: () => toSlipstreamMessage,
64
+ toSlipstreamMessages: () => toSlipstreamMessages,
65
+ toSlipstreamToolCall: () => toSlipstreamToolCall,
66
+ toSlipstreamToolCalls: () => toSlipstreamToolCalls,
67
+ usePromptInputAttachments: () => usePromptInputAttachments,
68
+ useSlipstreamMessages: () => useSlipstreamMessages,
69
+ useSlipstreamStream: () => useSlipstreamStream,
70
+ useSpeechRecognition: () => useSpeechRecognition
71
+ });
72
+ module.exports = __toCommonJS(index_exports);
73
+
74
+ // src/components/message/Message.tsx
75
+ var React = __toESM(require("react"));
76
+ var import_core = require("@slipstream-ui/core");
77
+ var import_core2 = require("@slipstream-ui/core");
78
+ var import_jsx_runtime = require("react/jsx-runtime");
79
+ var Message = React.forwardRef(
80
+ ({
81
+ message,
82
+ className,
83
+ showAvatar = true,
84
+ avatarSrc,
85
+ avatarFallback,
86
+ userLabel = "You",
87
+ assistantLabel = "Assistant",
88
+ renderContent,
89
+ isStreaming = false,
90
+ error,
91
+ onRetry,
92
+ onEditContent,
93
+ labels,
94
+ ...props
95
+ }, ref) => {
96
+ const [isEditing, setIsEditing] = React.useState(false);
97
+ const [draft, setDraft] = React.useState(message.content);
98
+ const t = {
99
+ retry: labels?.retry ?? "Retry",
100
+ edit: labels?.edit ?? "Edit",
101
+ save: labels?.save ?? "Save & resend",
102
+ cancel: labels?.cancel ?? "Cancel"
103
+ };
104
+ const isUser = message.role === "user";
105
+ const isSystem = message.role === "system";
106
+ const displayLabel = isUser ? userLabel : message.role === "assistant" ? assistantLabel : message.role;
107
+ const startEdit = () => {
108
+ setDraft(message.content);
109
+ setIsEditing(true);
110
+ };
111
+ const commitEdit = () => {
112
+ const trimmed = draft.trim();
113
+ if (!trimmed) return;
114
+ setIsEditing(false);
115
+ onEditContent?.(trimmed);
116
+ };
117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
118
+ "div",
119
+ {
120
+ ref,
121
+ "aria-busy": isStreaming || void 0,
122
+ className: (0, import_core2.cn)(
123
+ "flex gap-3 p-4 rounded-lg",
124
+ isUser ? "bg-primary/10" : "bg-muted",
125
+ isSystem && "border-l-4 border-muted-foreground",
126
+ error && "border border-destructive/40",
127
+ className
128
+ ),
129
+ ...props,
130
+ children: [
131
+ showAvatar && !isSystem && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_core.Avatar, { className: "h-8 w-8 flex-shrink-0", children: [
132
+ avatarSrc && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.AvatarImage, { src: avatarSrc, alt: displayLabel }),
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.AvatarFallback, { className: (0, import_core2.cn)(
134
+ "text-xs font-medium",
135
+ isUser ? "bg-primary text-primary-foreground" : "bg-secondary text-secondary-foreground"
136
+ ), children: avatarFallback || (isUser ? "U" : "A") })
137
+ ] }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 min-w-0", children: [
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 mb-1", children: [
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium", children: displayLabel }),
141
+ message.timestamp && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: new Date(message.timestamp).toLocaleTimeString() })
142
+ ] }),
143
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", className: "text-sm", children: [
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-destructive mb-2", children: error }),
145
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_core.Button, { size: "sm", variant: "outline", onClick: onRetry, children: [
146
+ "\u21BB ",
147
+ t.retry
148
+ ] })
149
+ ] }) : isEditing ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ "textarea",
152
+ {
153
+ value: draft,
154
+ onChange: (event) => setDraft(event.target.value),
155
+ onKeyDown: (event) => {
156
+ if (event.key === "Enter" && !event.shiftKey) {
157
+ event.preventDefault();
158
+ commitEdit();
159
+ }
160
+ if (event.key === "Escape") setIsEditing(false);
161
+ },
162
+ "aria-label": "Edit message",
163
+ autoFocus: true,
164
+ rows: Math.min(6, draft.split("\n").length + 1),
165
+ className: "w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
166
+ }
167
+ ),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2", children: [
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Button, { size: "sm", onClick: commitEdit, children: t.save }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Button, { size: "sm", variant: "ghost", onClick: () => setIsEditing(false), children: t.cancel })
171
+ ] })
172
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-sm whitespace-pre-wrap break-words", children: [
173
+ renderContent ? renderContent(message.content) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
174
+ message.content,
175
+ isStreaming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
176
+ "span",
177
+ {
178
+ className: "inline-block w-[2px] h-[1em] ml-0.5 align-text-bottom bg-current animate-pulse motion-reduce:animate-none",
179
+ "data-testid": "streaming-cursor"
180
+ }
181
+ )
182
+ ] }),
183
+ onEditContent && !isStreaming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
184
+ "button",
185
+ {
186
+ type: "button",
187
+ onClick: startEdit,
188
+ "aria-label": t.edit,
189
+ className: "mt-1 rounded px-1 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
190
+ children: [
191
+ "\u270E ",
192
+ t.edit
193
+ ]
194
+ }
195
+ ) })
196
+ ] })
197
+ ] })
198
+ ]
199
+ }
200
+ );
201
+ }
202
+ );
203
+ Message.displayName = "Message";
204
+ var MemoizedMessage = React.memo(Message);
205
+ MemoizedMessage.displayName = "Message";
206
+
207
+ // src/components/message-actions/MessageActions.tsx
208
+ var React2 = __toESM(require("react"));
209
+ var import_core3 = require("@slipstream-ui/core");
210
+ var import_jsx_runtime2 = require("react/jsx-runtime");
211
+ var MessageActions = React2.forwardRef(
212
+ ({ content, onCopy, onFeedback, onRegenerate, autohide = "always", labels, className, ...props }, ref) => {
213
+ const [copied, setCopied] = React2.useState(false);
214
+ const [rating, setRating] = React2.useState();
215
+ const t = {
216
+ copy: labels?.copy ?? "Copy",
217
+ copied: labels?.copied ?? "Copied",
218
+ thumbUp: labels?.thumbUp ?? "Good response",
219
+ thumbDown: labels?.thumbDown ?? "Bad response",
220
+ regenerate: labels?.regenerate ?? "Regenerate"
221
+ };
222
+ const handleCopy = async () => {
223
+ if (!content) return;
224
+ try {
225
+ await navigator.clipboard.writeText(content);
226
+ } catch {
227
+ }
228
+ setCopied(true);
229
+ window.setTimeout(() => setCopied(false), 2e3);
230
+ onCopy?.(content);
231
+ };
232
+ const handleRating = (next) => {
233
+ const updated = rating === next ? void 0 : next;
234
+ setRating(updated);
235
+ onFeedback?.(updated);
236
+ };
237
+ const actionButton = "rounded px-1.5 py-0.5 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
238
+ const actionButtonIdle = "text-muted-foreground hover:bg-accent hover:text-accent-foreground";
239
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
240
+ "div",
241
+ {
242
+ ref,
243
+ role: "toolbar",
244
+ "aria-label": "Message actions",
245
+ className: (0, import_core3.cn)(
246
+ "flex items-center gap-1 mt-2",
247
+ autohide !== "always" && "opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100 motion-reduce:opacity-100",
248
+ className
249
+ ),
250
+ ...props,
251
+ children: [
252
+ content !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: handleCopy, className: (0, import_core3.cn)(actionButton, actionButtonIdle), children: copied ? t.copied : t.copy }),
253
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
254
+ "button",
255
+ {
256
+ type: "button",
257
+ onClick: () => handleRating("up"),
258
+ "aria-label": t.thumbUp,
259
+ "aria-pressed": rating === "up",
260
+ className: (0, import_core3.cn)(
261
+ actionButton,
262
+ rating === "up" ? "text-primary" : actionButtonIdle
263
+ ),
264
+ children: "\u{1F44D}"
265
+ }
266
+ ),
267
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
268
+ "button",
269
+ {
270
+ type: "button",
271
+ onClick: () => handleRating("down"),
272
+ "aria-label": t.thumbDown,
273
+ "aria-pressed": rating === "down",
274
+ className: (0, import_core3.cn)(
275
+ actionButton,
276
+ rating === "down" ? "text-destructive" : actionButtonIdle
277
+ ),
278
+ children: "\u{1F44E}"
279
+ }
280
+ ),
281
+ onRegenerate && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: onRegenerate, className: (0, import_core3.cn)(actionButton, actionButtonIdle), children: t.regenerate })
282
+ ]
283
+ }
284
+ );
285
+ }
286
+ );
287
+ MessageActions.displayName = "MessageActions";
288
+
289
+ // src/components/attachment/Attachment.tsx
290
+ var React3 = __toESM(require("react"));
291
+ var import_core4 = require("@slipstream-ui/core");
292
+ var import_core5 = require("@slipstream-ui/core");
293
+ var import_jsx_runtime3 = require("react/jsx-runtime");
294
+ var Attachment = React3.forwardRef(
295
+ ({ attachment, onRemove, className, ...props }, ref) => {
296
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
297
+ "div",
298
+ {
299
+ ref,
300
+ className: (0, import_core5.cn)(
301
+ "inline-flex items-center gap-2 rounded-md border border-border bg-muted/50 px-2 py-1.5 text-sm max-w-xs",
302
+ className
303
+ ),
304
+ ...props,
305
+ children: [
306
+ attachment.previewUrl ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
307
+ "img",
308
+ {
309
+ src: attachment.previewUrl,
310
+ alt: "",
311
+ "aria-hidden": "true",
312
+ className: "h-8 w-8 rounded object-cover flex-shrink-0"
313
+ }
314
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
315
+ "span",
316
+ {
317
+ "aria-hidden": "true",
318
+ className: "flex h-8 w-8 items-center justify-center rounded bg-secondary text-[10px] font-semibold uppercase text-secondary-foreground flex-shrink-0",
319
+ children: (attachment.type ?? attachment.name.split(".").pop() ?? "?").slice(0, 4)
320
+ }
321
+ ),
322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "min-w-0 flex-1", children: [
323
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "truncate font-medium text-foreground", title: attachment.name, children: attachment.name }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-muted-foreground", children: attachment.status === "uploading" ? "Uploading..." : [attachment.size, attachment.type].filter(Boolean).join(" \xB7 ") || null })
325
+ ] }),
326
+ attachment.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_core4.Spinner, { size: "sm", label: "Uploading", "aria-label": "Uploading", className: "flex-shrink-0" }),
327
+ onRemove && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
328
+ "button",
329
+ {
330
+ type: "button",
331
+ onClick: () => onRemove(attachment),
332
+ "aria-label": `Remove ${attachment.name}`,
333
+ className: "flex-shrink-0 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
334
+ children: "\u2715"
335
+ }
336
+ )
337
+ ]
338
+ }
339
+ );
340
+ }
341
+ );
342
+ Attachment.displayName = "Attachment";
343
+
344
+ // src/components/attachment/AttachmentList.tsx
345
+ var React4 = __toESM(require("react"));
346
+ var import_core6 = require("@slipstream-ui/core");
347
+ var import_jsx_runtime4 = require("react/jsx-runtime");
348
+ var AttachmentList = React4.forwardRef(
349
+ ({ attachments, onRemove, className, ...props }, ref) => {
350
+ if (attachments.length === 0) {
351
+ return null;
352
+ }
353
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: (0, import_core6.cn)("flex flex-wrap gap-2", className), ...props, children: attachments.map((attachment) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Attachment, { attachment, onRemove }, attachment.id)) });
354
+ }
355
+ );
356
+ AttachmentList.displayName = "AttachmentList";
357
+
358
+ // src/components/response/Response.tsx
359
+ var React6 = __toESM(require("react"));
360
+ var import_react_markdown = __toESM(require("react-markdown"));
361
+ var import_remark_gfm = __toESM(require("remark-gfm"));
362
+ var import_core8 = require("@slipstream-ui/core");
363
+
364
+ // src/components/code-block/CodeBlock.tsx
365
+ var React5 = __toESM(require("react"));
366
+ var import_prism_react_renderer = require("prism-react-renderer");
367
+ var import_core7 = require("@slipstream-ui/core");
368
+ var import_jsx_runtime5 = require("react/jsx-runtime");
369
+ function useIsDark() {
370
+ const [dark, setDark] = React5.useState(false);
371
+ React5.useEffect(() => {
372
+ const el = document.documentElement;
373
+ setDark(el.classList.contains("dark"));
374
+ const observer = new MutationObserver(() => setDark(el.classList.contains("dark")));
375
+ observer.observe(el, { attributes: true, attributeFilter: ["class"] });
376
+ return () => observer.disconnect();
377
+ }, []);
378
+ return dark;
379
+ }
380
+ var CodeBlock = React5.forwardRef(
381
+ ({ code, language = "tsx", showCopyButton = true, className, ...props }, ref) => {
382
+ const [copied, setCopied] = React5.useState(false);
383
+ const isDark = useIsDark();
384
+ const trimmedCode = code.replace(/\n$/, "");
385
+ const handleCopy = async () => {
386
+ try {
387
+ await navigator.clipboard.writeText(trimmedCode);
388
+ } catch {
389
+ }
390
+ setCopied(true);
391
+ window.setTimeout(() => setCopied(false), 2e3);
392
+ };
393
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
394
+ "div",
395
+ {
396
+ ref,
397
+ className: (0, import_core7.cn)(
398
+ // bg-background (not muted): Prism light themes assume a white
399
+ // surface — on gray the comment/keyword tokens fall below AA.
400
+ "relative group rounded-md border border-border bg-background overflow-hidden",
401
+ className
402
+ ),
403
+ ...props,
404
+ children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border bg-muted/60", children: [
406
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs font-mono text-muted-foreground uppercase", children: language }),
407
+ showCopyButton && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
408
+ "button",
409
+ {
410
+ type: "button",
411
+ onClick: handleCopy,
412
+ "aria-label": copied ? "Copied" : "Copy code",
413
+ className: "text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded px-1",
414
+ children: copied ? "Copied!" : "Copy"
415
+ }
416
+ )
417
+ ] }),
418
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_prism_react_renderer.Highlight, { theme: isDark ? import_prism_react_renderer.themes.vsDark : import_prism_react_renderer.themes.github, code: trimmedCode, language, children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
419
+ "pre",
420
+ {
421
+ className: "overflow-x-auto p-4 text-xs leading-relaxed m-0",
422
+ style: { ...style, background: "transparent" },
423
+ children: tokens.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ...getLineProps({ line }), children: line.map((token, key) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { ...getTokenProps({ token }) }, key)) }, i))
424
+ }
425
+ ) })
426
+ ]
427
+ }
428
+ );
429
+ }
430
+ );
431
+ CodeBlock.displayName = "CodeBlock";
432
+
433
+ // src/components/response/close-incomplete-markdown.ts
434
+ function closeIncompleteMarkdown(markdown) {
435
+ let text = markdown;
436
+ const fenceMatches = text.match(/^`{3,}/gm);
437
+ if (fenceMatches && fenceMatches.length % 2 === 1) {
438
+ text = `${text}
439
+ \`\`\``;
440
+ }
441
+ const firstFence = text.indexOf("```");
442
+ const hasUnclosedFence = fenceMatches !== null && fenceMatches.length % 2 === 1;
443
+ const prose = hasUnclosedFence && firstFence !== -1 ? text.slice(0, firstFence) : text;
444
+ const rest = hasUnclosedFence && firstFence !== -1 ? text.slice(firstFence) : "";
445
+ let repaired = prose;
446
+ for (const marker of ["**", "__", "~~"]) {
447
+ const occurrences = prose.split(marker).length - 1;
448
+ if (occurrences % 2 === 1) {
449
+ repaired += marker;
450
+ }
451
+ }
452
+ for (const marker of ["*", "_"]) {
453
+ const singles = repaired.replace(/\*\*/g, "").replace(/__/g, "").split(marker).length - 1;
454
+ if (singles % 2 === 1) {
455
+ repaired += marker;
456
+ }
457
+ }
458
+ const backtickCount = repaired.replace(/```/g, "").split("`").length - 1;
459
+ if (backtickCount % 2 === 1) {
460
+ repaired += "`";
461
+ }
462
+ return repaired + rest;
463
+ }
464
+
465
+ // src/components/response/Response.tsx
466
+ var import_jsx_runtime6 = require("react/jsx-runtime");
467
+ var Response = React6.forwardRef(
468
+ ({ children, parseIncompleteMarkdown = true, className, ...props }, ref) => {
469
+ const content = React6.useMemo(
470
+ () => parseIncompleteMarkdown ? closeIncompleteMarkdown(children) : children,
471
+ [children, parseIncompleteMarkdown]
472
+ );
473
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
474
+ "div",
475
+ {
476
+ ref,
477
+ className: (0, import_core8.cn)(
478
+ "text-sm space-y-3 [&_a]:text-primary [&_a]:underline underline-offset-2",
479
+ "[&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5",
480
+ "[&_h1]:text-lg [&_h1]:font-semibold [&_h2]:text-base [&_h2]:font-semibold",
481
+ "[&_h3]:text-sm [&_h3]:font-semibold",
482
+ "[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground",
483
+ "[&_table]:w-full [&_table]:text-xs [&_th]:border [&_th]:border-border [&_th]:p-1.5 [&_td]:border [&_td]:border-border [&_td]:p-1.5",
484
+ className
485
+ ),
486
+ ...props,
487
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
488
+ import_react_markdown.default,
489
+ {
490
+ remarkPlugins: [import_remark_gfm.default],
491
+ components: {
492
+ pre: ({ children: children2 }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children2 }),
493
+ code: ({ className: codeClassName, children: codeChildren }) => {
494
+ const raw = String(codeChildren).replace(/\n$/, "");
495
+ const match = /language-(\w+)/.exec(codeClassName ?? "");
496
+ const isInline = !match && !raw.includes("\n");
497
+ if (isInline) {
498
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "bg-muted px-1 py-0.5 rounded text-xs font-mono before:content-none after:content-none", children: raw });
499
+ }
500
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CodeBlock, { code: raw, language: match?.[1] ?? "text" });
501
+ }
502
+ },
503
+ children: content
504
+ }
505
+ )
506
+ }
507
+ );
508
+ }
509
+ );
510
+ Response.displayName = "Response";
511
+
512
+ // src/components/confirmation/Confirmation.tsx
513
+ var React7 = __toESM(require("react"));
514
+ var import_core9 = require("@slipstream-ui/core");
515
+ var import_core10 = require("@slipstream-ui/core");
516
+ var import_jsx_runtime7 = require("react/jsx-runtime");
517
+ var statusChipClasses = {
518
+ pending: "bg-warning/10 text-warning border-warning/20",
519
+ approved: "bg-success/10 text-success border-success/20",
520
+ rejected: "bg-destructive/10 text-destructive border-destructive/20"
521
+ };
522
+ var pendingVariantClasses = {
523
+ default: "border-warning/40",
524
+ warning: "border-warning/40",
525
+ destructive: "border-destructive/40 bg-destructive/5",
526
+ info: "border-info/40",
527
+ success: "border-success/40"
528
+ };
529
+ var Confirmation = React7.forwardRef(
530
+ ({
531
+ title,
532
+ description,
533
+ toolName,
534
+ status: controlledStatus,
535
+ variant = "default",
536
+ onApprove,
537
+ onReject,
538
+ approveVariant = "default",
539
+ rejectVariant = "outline",
540
+ showActions = true,
541
+ labels,
542
+ className,
543
+ ...props
544
+ }, ref) => {
545
+ const [internalStatus, setInternalStatus] = React7.useState("pending");
546
+ const status = controlledStatus ?? internalStatus;
547
+ const t = {
548
+ approve: labels?.approve ?? "Approve",
549
+ reject: labels?.reject ?? "Reject",
550
+ approved: labels?.approved ?? "Approved",
551
+ rejected: labels?.rejected ?? "Rejected"
552
+ };
553
+ const decide = (next) => {
554
+ setInternalStatus(next);
555
+ (next === "approved" ? onApprove : onReject)?.();
556
+ };
557
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
558
+ "div",
559
+ {
560
+ ref,
561
+ role: status === "pending" ? "alertdialog" : void 0,
562
+ "aria-label": status === "pending" ? title : void 0,
563
+ className: (0, import_core10.cn)(
564
+ "rounded-lg border p-4 bg-muted/30 max-w-md",
565
+ status === "pending" && pendingVariantClasses[variant],
566
+ className
567
+ ),
568
+ ...props,
569
+ children: [
570
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between gap-2 mb-2", children: [
571
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-sm font-medium text-foreground", children: title }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
573
+ "span",
574
+ {
575
+ className: (0, import_core10.cn)(
576
+ "text-xs px-2 py-0.5 rounded-full border whitespace-nowrap",
577
+ statusChipClasses[status]
578
+ ),
579
+ children: status === "approved" ? t.approved : status === "rejected" ? t.rejected : "Pending"
580
+ }
581
+ )
582
+ ] }),
583
+ description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: description }),
584
+ toolName && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-xs font-mono text-muted-foreground mb-3", children: toolName }),
585
+ status === "pending" && showActions && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex gap-2 mt-2", children: [
586
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core9.Button, { size: "sm", variant: approveVariant, onClick: () => decide("approved"), children: t.approve }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_core9.Button, { size: "sm", variant: rejectVariant, onClick: () => decide("rejected"), children: t.reject })
588
+ ] })
589
+ ]
590
+ }
591
+ );
592
+ }
593
+ );
594
+ Confirmation.displayName = "Confirmation";
595
+
596
+ // src/components/branch/Branch.tsx
597
+ var React8 = __toESM(require("react"));
598
+ var import_core11 = require("@slipstream-ui/core");
599
+ var import_jsx_runtime8 = require("react/jsx-runtime");
600
+ var Branch = React8.forwardRef(
601
+ ({ count, selected, defaultSelected = 0, onSelect, labels, className, ...props }, ref) => {
602
+ const [internalIndex, setInternalIndex] = React8.useState(defaultSelected);
603
+ const isControlled = selected !== void 0;
604
+ const index = Math.min(Math.max(isControlled ? selected : internalIndex, 0), count - 1);
605
+ const t = {
606
+ previous: labels?.previous ?? "Previous response",
607
+ next: labels?.next ?? "Next response"
608
+ };
609
+ if (count < 2) return null;
610
+ const goTo = (next) => {
611
+ const clamped = Math.min(Math.max(next, 0), count - 1);
612
+ if (!isControlled) setInternalIndex(clamped);
613
+ onSelect?.(clamped);
614
+ };
615
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
616
+ "div",
617
+ {
618
+ ref,
619
+ className: (0, import_core11.cn)("flex items-center gap-1 text-xs text-muted-foreground", className),
620
+ ...props,
621
+ children: [
622
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
623
+ "button",
624
+ {
625
+ type: "button",
626
+ onClick: () => goTo(index - 1),
627
+ disabled: index === 0,
628
+ "aria-label": t.previous,
629
+ className: "rounded px-1 hover:bg-accent hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
630
+ children: "\u2039"
631
+ }
632
+ ),
633
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { "aria-live": "polite", className: "tabular-nums", children: [
634
+ index + 1,
635
+ " / ",
636
+ count
637
+ ] }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
639
+ "button",
640
+ {
641
+ type: "button",
642
+ onClick: () => goTo(index + 1),
643
+ disabled: index === count - 1,
644
+ "aria-label": t.next,
645
+ className: "rounded px-1 hover:bg-accent hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40",
646
+ children: "\u203A"
647
+ }
648
+ )
649
+ ]
650
+ }
651
+ );
652
+ }
653
+ );
654
+ Branch.displayName = "Branch";
655
+
656
+ // src/components/inline-citation/InlineCitation.tsx
657
+ var React9 = __toESM(require("react"));
658
+ var import_core12 = require("@slipstream-ui/core");
659
+ var import_jsx_runtime9 = require("react/jsx-runtime");
660
+ var InlineCitation = React9.forwardRef(
661
+ ({ source, children, className, ...props }, ref) => {
662
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
663
+ "span",
664
+ {
665
+ ref,
666
+ className: (0, import_core12.cn)("group relative inline focus-within:outline-none", className),
667
+ ...props,
668
+ children: [
669
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ "aria-describedby": `citation-${source.id}-card`,
674
+ className: "inline-flex items-baseline rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
675
+ children: [
676
+ children ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: source.title }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
678
+ "span",
679
+ {
680
+ "aria-hidden": "true",
681
+ className: "inline-flex items-center justify-center align-super ml-0.5 h-4 min-w-4 px-1 rounded-full border border-border bg-muted text-[10px] font-medium text-muted-foreground group-hover:text-foreground transition-colors",
682
+ children: source.title.charAt(0).toUpperCase()
683
+ }
684
+ )
685
+ ]
686
+ }
687
+ ),
688
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
689
+ "span",
690
+ {
691
+ id: `citation-${source.id}-card`,
692
+ role: "tooltip",
693
+ className: "pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 -translate-x-1/2 w-64 rounded-md border border-border bg-popover p-2.5 text-xs text-popover-foreground shadow-md opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity",
694
+ children: [
695
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "block font-medium text-foreground truncate", children: source.title }),
696
+ source.snippet && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "mt-0.5 block text-muted-foreground line-clamp-3", children: source.snippet })
697
+ ]
698
+ }
699
+ )
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ );
705
+ InlineCitation.displayName = "InlineCitation";
706
+
707
+ // src/components/conversation/Conversation.tsx
708
+ var React10 = __toESM(require("react"));
709
+ var import_core13 = require("@slipstream-ui/core");
710
+ var import_jsx_runtime10 = require("react/jsx-runtime");
711
+ var PINNED_THRESHOLD = 80;
712
+ var DEFAULT_WELCOME_TITLE = "Start the conversation";
713
+ var DEFAULT_WELCOME_DESCRIPTION = "Ask anything \u2014 responses stream in real time.";
714
+ var DEFAULT_WELCOME_SUGGESTIONS = [
715
+ "Explain what you can do",
716
+ "Summarize a topic for me"
717
+ ];
718
+ function transcriptToMarkdown(messages) {
719
+ return messages.map((message) => {
720
+ const speaker = message.role === "user" ? "You" : message.role === "assistant" ? "Assistant" : message.role;
721
+ const time = message.timestamp ? ` (${new Date(message.timestamp).toISOString()})` : "";
722
+ return `**${speaker}${time}:**
723
+
724
+ ${message.content}`;
725
+ }).join("\n\n---\n\n");
726
+ }
727
+ var Conversation = React10.forwardRef(
728
+ ({
729
+ messages,
730
+ className,
731
+ showAvatars = true,
732
+ userAvatarSrc,
733
+ userAvatarFallback,
734
+ assistantAvatarSrc,
735
+ assistantAvatarFallback,
736
+ isLoading = false,
737
+ loadingMessage = "Thinking...",
738
+ renderContent,
739
+ streamingMessageId,
740
+ showScrollButton = true,
741
+ showDownload = false,
742
+ messageErrors,
743
+ onRetryMessage,
744
+ welcomeTitle = DEFAULT_WELCOME_TITLE,
745
+ welcomeDescription = DEFAULT_WELCOME_DESCRIPTION,
746
+ welcomeSuggestions = DEFAULT_WELCOME_SUGGESTIONS,
747
+ onWelcomeSelect,
748
+ hasMoreHistory = false,
749
+ isLoadingMore = false,
750
+ onLoadMore,
751
+ ...props
752
+ }, ref) => {
753
+ const scrollRef = React10.useRef(null);
754
+ const messagesEndRef = React10.useRef(null);
755
+ const [isUnpinned, setIsUnpinned] = React10.useState(false);
756
+ const prevScrollMetrics = React10.useRef(null);
757
+ const [announcement, setAnnouncement] = React10.useState("");
758
+ const prevStreamingRef = React10.useRef(void 0);
759
+ React10.useEffect(() => {
760
+ if (prevStreamingRef.current && !streamingMessageId) {
761
+ setAnnouncement("Response complete.");
762
+ }
763
+ prevStreamingRef.current = streamingMessageId;
764
+ }, [streamingMessageId]);
765
+ React10.useEffect(() => {
766
+ const container = scrollRef.current;
767
+ if (!container) return;
768
+ const updatePinned = () => {
769
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
770
+ setIsUnpinned(distanceFromBottom > PINNED_THRESHOLD);
771
+ };
772
+ updatePinned();
773
+ container.addEventListener("scroll", updatePinned, { passive: true });
774
+ return () => container.removeEventListener("scroll", updatePinned);
775
+ }, []);
776
+ React10.useLayoutEffect(() => {
777
+ const container = scrollRef.current;
778
+ if (!container || prevScrollMetrics.current === null) return;
779
+ const { height, top } = prevScrollMetrics.current;
780
+ container.scrollTop = container.scrollHeight - height + top;
781
+ prevScrollMetrics.current = null;
782
+ }, [messages]);
783
+ const captureAnchor = () => {
784
+ const container = scrollRef.current;
785
+ if (!container) return;
786
+ prevScrollMetrics.current = { height: container.scrollHeight, top: container.scrollTop };
787
+ };
788
+ const handleLoadMore = () => {
789
+ captureAnchor();
790
+ onLoadMore?.();
791
+ };
792
+ React10.useEffect(() => {
793
+ const container = scrollRef.current;
794
+ if (!container) return;
795
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
796
+ if (distanceFromBottom > PINNED_THRESHOLD) return;
797
+ container.scrollTo?.({
798
+ top: container.scrollHeight,
799
+ behavior: streamingMessageId ? "auto" : "smooth"
800
+ });
801
+ }, [messages, streamingMessageId]);
802
+ const scrollToLatest = () => {
803
+ const container = scrollRef.current;
804
+ if (!container) return;
805
+ container.scrollTo?.({ top: container.scrollHeight, behavior: "smooth" });
806
+ };
807
+ const handleDownload = () => {
808
+ const blob = new Blob([transcriptToMarkdown(messages)], { type: "text/markdown" });
809
+ const url = URL.createObjectURL(blob);
810
+ const anchor = document.createElement("a");
811
+ anchor.href = url;
812
+ anchor.download = "conversation.md";
813
+ anchor.click();
814
+ URL.revokeObjectURL(url);
815
+ };
816
+ const isEmpty = messages.length === 0 && !isLoading;
817
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: (0, import_core13.cn)("relative h-full w-full", className), ...props, children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: scrollRef, className: "flex flex-col gap-4 p-4 overflow-y-auto h-full", children: [
819
+ (hasMoreHistory || isLoadingMore) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
820
+ "button",
821
+ {
822
+ type: "button",
823
+ onClick: handleLoadMore,
824
+ disabled: isLoadingMore,
825
+ className: "mx-auto rounded-full border border-border bg-background px-3 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50",
826
+ children: isLoadingMore ? "Loading\u2026" : "Load earlier messages"
827
+ }
828
+ ),
829
+ isEmpty ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 text-center py-16", children: [
830
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-lg font-semibold", children: welcomeTitle }),
831
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground max-w-sm", children: welcomeDescription }),
832
+ welcomeSuggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2 flex flex-col gap-2 w-full max-w-sm", children: welcomeSuggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
833
+ "button",
834
+ {
835
+ type: "button",
836
+ onClick: () => onWelcomeSelect?.(suggestion),
837
+ className: "rounded-md border border-border bg-background px-4 py-2.5 text-sm text-left text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
838
+ children: suggestion
839
+ },
840
+ suggestion
841
+ )) })
842
+ ] }) : messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
843
+ MemoizedMessage,
844
+ {
845
+ message,
846
+ showAvatar: showAvatars,
847
+ avatarSrc: message.role === "user" ? userAvatarSrc : assistantAvatarSrc,
848
+ avatarFallback: message.role === "user" ? userAvatarFallback : assistantAvatarFallback,
849
+ renderContent,
850
+ isStreaming: message.id === streamingMessageId,
851
+ error: messageErrors?.[message.id],
852
+ onRetry: onRetryMessage && messageErrors?.[message.id] ? () => onRetryMessage(message) : void 0
853
+ },
854
+ message.id
855
+ )),
856
+ isLoading && !isEmpty && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
857
+ MemoizedMessage,
858
+ {
859
+ message: {
860
+ id: "__loading__",
861
+ role: "assistant",
862
+ content: loadingMessage
863
+ },
864
+ showAvatar: showAvatars,
865
+ avatarSrc: assistantAvatarSrc,
866
+ avatarFallback: assistantAvatarFallback,
867
+ className: "bg-muted/50",
868
+ renderContent: (content) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground animate-pulse motion-reduce:animate-none", children: content })
869
+ }
870
+ ),
871
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
873
+ ] }),
874
+ showDownload && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
875
+ "button",
876
+ {
877
+ type: "button",
878
+ onClick: handleDownload,
879
+ "aria-label": "Download transcript",
880
+ className: "absolute top-2 right-2 z-10 rounded-md border border-border bg-background/90 px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
881
+ children: "\u2193 .md"
882
+ }
883
+ ),
884
+ showScrollButton && isUnpinned && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
885
+ "button",
886
+ {
887
+ type: "button",
888
+ onClick: scrollToLatest,
889
+ "aria-label": "Scroll to latest message",
890
+ className: "absolute bottom-3 left-1/2 z-10 -translate-x-1/2 rounded-full border border-border bg-background/95 px-3 py-1.5 text-xs font-medium shadow-sm hover:bg-accent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
891
+ children: "\u2193 Latest"
892
+ }
893
+ )
894
+ ] });
895
+ }
896
+ );
897
+ Conversation.displayName = "Conversation";
898
+
899
+ // src/components/prompt-input/PromptInput.tsx
900
+ var React11 = __toESM(require("react"));
901
+ var import_core14 = require("@slipstream-ui/core");
902
+ var import_core15 = require("@slipstream-ui/core");
903
+ var import_jsx_runtime11 = require("react/jsx-runtime");
904
+ function usePromptInputAttachments(options = {}) {
905
+ const { accept, maxFiles, maxFileSize, multiple = true, onError } = options;
906
+ const [files, setFiles] = React11.useState([]);
907
+ const inputRef = React11.useRef(null);
908
+ const add = React11.useCallback(
909
+ (fileList) => {
910
+ const incoming = Array.from(fileList);
911
+ setFiles((prev) => {
912
+ const next = [...prev];
913
+ for (const file of incoming) {
914
+ if (accept && !file.type.match(accept.replace(/\*/g, ".*"))) {
915
+ onError?.({ code: "accept", message: `"${file.name}" is not an accepted file type.` });
916
+ continue;
917
+ }
918
+ if (maxFileSize && file.size > maxFileSize) {
919
+ onError?.({
920
+ code: "max_file_size",
921
+ message: `"${file.name}" exceeds the ${Math.round(maxFileSize / 1024)} KB limit.`
922
+ });
923
+ continue;
924
+ }
925
+ if (maxFiles !== void 0 && next.length >= maxFiles) {
926
+ onError?.({ code: "max_files", message: `At most ${maxFiles} files can be attached.` });
927
+ break;
928
+ }
929
+ if (!multiple && next.length >= 1) {
930
+ onError?.({ code: "max_files", message: "Only one file can be attached." });
931
+ break;
932
+ }
933
+ next.push({
934
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
935
+ name: file.name,
936
+ size: `${Math.max(1, Math.round(file.size / 1024))} KB`,
937
+ type: file.type.split("/")[1]?.toUpperCase() ?? file.name.split(".").pop()?.toUpperCase()
938
+ });
939
+ }
940
+ return next;
941
+ });
942
+ },
943
+ [accept, maxFileSize, maxFiles, multiple, onError]
944
+ );
945
+ const remove = React11.useCallback((id) => {
946
+ setFiles((prev) => prev.filter((file) => file.id !== id));
947
+ }, []);
948
+ const clear = React11.useCallback(() => setFiles([]), []);
949
+ const openFileDialog = React11.useCallback(() => inputRef.current?.click(), []);
950
+ const input = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
951
+ "input",
952
+ {
953
+ ref: inputRef,
954
+ type: "file",
955
+ hidden: true,
956
+ accept,
957
+ multiple,
958
+ onChange: (event) => {
959
+ if (event.target.files) add(event.target.files);
960
+ event.target.value = "";
961
+ }
962
+ }
963
+ );
964
+ return { files, add, remove, clear, openFileDialog, input };
965
+ }
966
+ function useSpeechRecognition(onTranscript) {
967
+ const [isListening, setIsListening] = React11.useState(false);
968
+ const Ctor = typeof window !== "undefined" ? window.SpeechRecognition ?? window.webkitSpeechRecognition : void 0;
969
+ const recognitionRef = React11.useRef(null);
970
+ const toggle = React11.useCallback(() => {
971
+ if (!Ctor) return;
972
+ if (isListening) {
973
+ recognitionRef.current?.stop();
974
+ setIsListening(false);
975
+ return;
976
+ }
977
+ const recognition = new Ctor();
978
+ recognition.lang = navigator.language || "en-US";
979
+ recognition.continuous = false;
980
+ recognition.interimResults = false;
981
+ recognition.onresult = (event) => {
982
+ const transcript = Array.from({ length: event.results.length }, (_, i) => event.results[i][0].transcript).join(" ");
983
+ onTranscript(transcript.trim());
984
+ };
985
+ recognition.onend = () => setIsListening(false);
986
+ recognitionRef.current = recognition;
987
+ recognition.start();
988
+ setIsListening(true);
989
+ }, [Ctor, isListening, onTranscript]);
990
+ return { supported: Boolean(Ctor), isListening, toggle };
991
+ }
992
+ var PromptInputContext = React11.createContext(null);
993
+ function usePromptInputContext() {
994
+ const context = React11.useContext(PromptInputContext);
995
+ if (!context) throw new Error("PromptInput primitives must be rendered inside <PromptInput>.");
996
+ return context;
997
+ }
998
+ var PromptInputRoot = React11.forwardRef(
999
+ ({
1000
+ onSubmit,
1001
+ onStop,
1002
+ attachments: controlledAttachments,
1003
+ onRemoveAttachment,
1004
+ onAttachmentsChange,
1005
+ hasError = false,
1006
+ accept,
1007
+ multiple = true,
1008
+ maxFiles,
1009
+ maxFileSize,
1010
+ onAttachmentError,
1011
+ globalDrop = false,
1012
+ placeholder = "Type your message...",
1013
+ disabled = false,
1014
+ isLoading = false,
1015
+ className,
1016
+ autoFocus = false,
1017
+ maxLength,
1018
+ showSendButton = true,
1019
+ sendButtonText = "Send",
1020
+ stopButtonText = "Stop generating",
1021
+ children,
1022
+ ...props
1023
+ }, ref) => {
1024
+ const [value, setValue] = React11.useState("");
1025
+ const textareaRef = React11.useRef(null);
1026
+ const managed = usePromptInputAttachments({ accept, maxFiles, maxFileSize, multiple, onError: onAttachmentError });
1027
+ const prevManagedCount = React11.useRef(0);
1028
+ React11.useEffect(() => {
1029
+ if (!controlledAttachments && managed.files.length !== prevManagedCount.current) {
1030
+ prevManagedCount.current = managed.files.length;
1031
+ onAttachmentsChange?.(managed.files);
1032
+ }
1033
+ }, [managed.files, controlledAttachments, onAttachmentsChange]);
1034
+ const activeAttachments = controlledAttachments ?? managed.files;
1035
+ React11.useEffect(() => {
1036
+ if (autoFocus && textareaRef.current) textareaRef.current.focus();
1037
+ }, [autoFocus]);
1038
+ React11.useImperativeHandle(ref, () => textareaRef.current);
1039
+ React11.useEffect(() => {
1040
+ if (textareaRef.current) {
1041
+ textareaRef.current.style.height = "auto";
1042
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
1043
+ }
1044
+ }, [value]);
1045
+ React11.useEffect(() => {
1046
+ if (!globalDrop || controlledAttachments) return;
1047
+ const prevent = (event) => event.preventDefault();
1048
+ const drop = (event) => {
1049
+ event.preventDefault();
1050
+ if (event.dataTransfer?.files.length) managed.add(event.dataTransfer.files);
1051
+ };
1052
+ document.addEventListener("dragover", prevent);
1053
+ document.addEventListener("drop", drop);
1054
+ return () => {
1055
+ document.removeEventListener("dragover", prevent);
1056
+ document.removeEventListener("drop", drop);
1057
+ };
1058
+ }, [globalDrop, controlledAttachments, managed]);
1059
+ const canSubmit = Boolean(value.trim()) && !disabled && !isLoading;
1060
+ const handleSubmit = () => {
1061
+ const trimmedValue = value.trim();
1062
+ if (!trimmedValue || disabled || isLoading) return;
1063
+ onSubmit(trimmedValue);
1064
+ setValue("");
1065
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
1066
+ if (!controlledAttachments) managed.clear();
1067
+ };
1068
+ const handleStop = () => onStop?.();
1069
+ const handleKeyDown = (event) => {
1070
+ if (event.key === "Enter" && !event.shiftKey) {
1071
+ event.preventDefault();
1072
+ handleSubmit();
1073
+ }
1074
+ };
1075
+ const dictate = useSpeechRecognition(
1076
+ (transcript) => setValue((current) => current ? `${current} ${transcript}` : transcript)
1077
+ );
1078
+ const contextValue = {
1079
+ value,
1080
+ setValue,
1081
+ disabled,
1082
+ isLoading,
1083
+ canSubmit,
1084
+ submit: handleSubmit,
1085
+ stop: handleStop,
1086
+ hasStop: Boolean(onStop),
1087
+ attachments: activeAttachments,
1088
+ removeAttachment: (id) => controlledAttachments ? onRemoveAttachment?.({ id }) : managed.remove(id),
1089
+ addFiles: (files) => !controlledAttachments && managed.add(files),
1090
+ openFilePicker: managed.openFileDialog,
1091
+ hiddenInput: managed.input,
1092
+ dictate
1093
+ };
1094
+ const showStop = isLoading && Boolean(onStop);
1095
+ const submitIcon = showStop ? "\u25A0" : hasError ? "\u26A0" : "\u2192";
1096
+ const submitLabel = showStop ? stopButtonText : hasError ? "Failed \u2014 check message" : sendButtonText;
1097
+ const textarea = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1098
+ "textarea",
1099
+ {
1100
+ ref: textareaRef,
1101
+ value,
1102
+ onChange: (event) => setValue(event.target.value),
1103
+ onKeyDown: handleKeyDown,
1104
+ placeholder,
1105
+ disabled: disabled || isLoading,
1106
+ maxLength,
1107
+ rows: 1,
1108
+ className: (0, import_core15.cn)(
1109
+ "flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm",
1110
+ "placeholder:text-muted-foreground",
1111
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
1112
+ "disabled:cursor-not-allowed disabled:opacity-50",
1113
+ "min-h-[40px] max-h-[200px] overflow-y-auto"
1114
+ )
1115
+ }
1116
+ );
1117
+ const sendButton = showSendButton ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1118
+ import_core14.Button,
1119
+ {
1120
+ onClick: showStop ? handleStop : handleSubmit,
1121
+ disabled: showStop ? false : !canSubmit || disabled,
1122
+ variant: showStop ? "outline" : "default",
1123
+ size: "icon",
1124
+ type: "button",
1125
+ "aria-label": submitLabel,
1126
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: submitIcon })
1127
+ }
1128
+ ) : null;
1129
+ const dropHandlers = controlledAttachments ? {} : {
1130
+ onDrop: (event) => {
1131
+ event.preventDefault();
1132
+ if (event.dataTransfer.files.length) managed.add(event.dataTransfer.files);
1133
+ },
1134
+ onPaste: (event) => {
1135
+ const files = event.clipboardData?.files;
1136
+ if (files && files.length > 0) {
1137
+ event.preventDefault();
1138
+ managed.add(files);
1139
+ }
1140
+ }
1141
+ };
1142
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PromptInputContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: (0, import_core15.cn)("flex flex-col gap-2", className), ...dropHandlers, ...props, children: [
1143
+ activeAttachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1144
+ AttachmentList,
1145
+ {
1146
+ attachments: activeAttachments,
1147
+ onRemove: controlledAttachments ? onRemoveAttachment : (a) => managed.remove(a.id)
1148
+ }
1149
+ ),
1150
+ children ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1151
+ children,
1152
+ managed.input
1153
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1154
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2 items-end", children: [
1155
+ textarea,
1156
+ sendButton
1157
+ ] }),
1158
+ managed.input
1159
+ ] })
1160
+ ] }) });
1161
+ }
1162
+ );
1163
+ PromptInputRoot.displayName = "PromptInput";
1164
+ var PromptInputTextarea = React11.forwardRef(({ className, onKeyDown, ...props }, ref) => {
1165
+ const { value, setValue, disabled, isLoading, submit } = usePromptInputContext();
1166
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1167
+ "textarea",
1168
+ {
1169
+ ref,
1170
+ value,
1171
+ onChange: (event) => setValue(event.target.value),
1172
+ onKeyDown: (event) => {
1173
+ if (event.key === "Enter" && !event.shiftKey) {
1174
+ event.preventDefault();
1175
+ submit();
1176
+ }
1177
+ onKeyDown?.(event);
1178
+ },
1179
+ disabled: disabled || isLoading,
1180
+ rows: 1,
1181
+ className: (0, import_core15.cn)(
1182
+ "flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm",
1183
+ "placeholder:text-muted-foreground",
1184
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
1185
+ "min-h-[40px]",
1186
+ className
1187
+ ),
1188
+ ...props
1189
+ }
1190
+ );
1191
+ });
1192
+ PromptInputTextarea.displayName = "PromptInputTextarea";
1193
+ var PromptInputSubmit = React11.forwardRef(({ className }, ref) => {
1194
+ const { canSubmit, isLoading, hasStop, submit, stop, disabled } = usePromptInputContext();
1195
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1196
+ import_core14.Button,
1197
+ {
1198
+ ref,
1199
+ onClick: isLoading && hasStop ? stop : submit,
1200
+ disabled: isLoading && hasStop ? false : !canSubmit || disabled,
1201
+ variant: isLoading && hasStop ? "outline" : "default",
1202
+ size: "icon",
1203
+ type: "button",
1204
+ "aria-label": isLoading && hasStop ? "Stop generating" : "Send",
1205
+ className,
1206
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: isLoading && hasStop ? "\u25A0" : "\u2192" })
1207
+ }
1208
+ );
1209
+ });
1210
+ PromptInputSubmit.displayName = "PromptInputSubmit";
1211
+ function PromptInputFooter({ className, ...props }) {
1212
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_core15.cn)("flex items-center justify-between gap-2 pt-1", className), ...props });
1213
+ }
1214
+ function PromptInputTools({ className, ...props }) {
1215
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_core15.cn)("flex items-center gap-1", className), ...props });
1216
+ }
1217
+ function PromptInputHeader({ className, ...props }) {
1218
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_core15.cn)("mb-1", className), ...props });
1219
+ }
1220
+ function PromptInputBody({ className, ...props }) {
1221
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: (0, import_core15.cn)("flex gap-2 items-end", className), ...props });
1222
+ }
1223
+ var PromptInputButton = React11.forwardRef(
1224
+ ({ tooltip, active = false, className, ...props }, ref) => {
1225
+ const tip = typeof tooltip === "string" ? { content: tooltip } : tooltip;
1226
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "relative inline-flex group/tip", children: [
1227
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1228
+ "button",
1229
+ {
1230
+ ref,
1231
+ type: "button",
1232
+ "aria-pressed": active || void 0,
1233
+ className: (0, import_core15.cn)(
1234
+ "inline-flex h-7 items-center gap-1 rounded-md border border-transparent px-2 text-xs text-muted-foreground transition-colors",
1235
+ "hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1236
+ active && "border-border bg-muted text-foreground",
1237
+ className
1238
+ ),
1239
+ ...props
1240
+ }
1241
+ ),
1242
+ tip && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1243
+ "span",
1244
+ {
1245
+ role: "tooltip",
1246
+ className: "pointer-events-none absolute bottom-full left-0 mb-1 z-10 flex items-center gap-1 whitespace-nowrap rounded border border-border bg-popover px-2 py-0.5 text-[11px] text-popover-foreground opacity-0 shadow-sm transition-opacity group-hover/tip:opacity-100",
1247
+ children: [
1248
+ tip.content,
1249
+ tip.shortcut && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("kbd", { className: "rounded bg-muted px-1 font-mono", children: tip.shortcut })
1250
+ ]
1251
+ }
1252
+ )
1253
+ ] });
1254
+ }
1255
+ );
1256
+ PromptInputButton.displayName = "PromptInputButton";
1257
+ function PromptInputActionDictate({ labels }) {
1258
+ const { dictate } = usePromptInputContext();
1259
+ if (!dictate.supported) return null;
1260
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1261
+ PromptInputButton,
1262
+ {
1263
+ onClick: dictate.toggle,
1264
+ active: dictate.isListening,
1265
+ tooltip: { content: dictate.isListening ? labels?.stop ?? "Stop dictating" : labels?.start ?? "Dictate" },
1266
+ "aria-label": dictate.isListening ? labels?.stop ?? "Stop dictation" : labels?.start ?? "Start dictation",
1267
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: "\u{1F399}" })
1268
+ }
1269
+ );
1270
+ }
1271
+ function PromptInputActionAddAttachments({ label = "Add photos or files" }) {
1272
+ const { openFilePicker } = usePromptInputContext();
1273
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(PromptInputButton, { onClick: openFilePicker, children: [
1274
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": "true", children: "\uFF0B" }),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: label })
1276
+ ] });
1277
+ }
1278
+
1279
+ // src/components/reasoning/Reasoning.tsx
1280
+ var React12 = __toESM(require("react"));
1281
+ var import_core16 = require("@slipstream-ui/core");
1282
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1283
+ var Reasoning = React12.forwardRef(
1284
+ ({ steps, className, collapsible = true, defaultExpanded = false, duration, isStreaming = false, ...props }, ref) => {
1285
+ const [isExpanded, setIsExpanded] = React12.useState(defaultExpanded);
1286
+ const contentId = React12.useId();
1287
+ if (steps.length === 0) {
1288
+ return null;
1289
+ }
1290
+ const headerText = [
1291
+ "Reasoning",
1292
+ `(${steps.length} ${steps.length === 1 ? "step" : "steps"})`,
1293
+ typeof duration === "number" ? `\xB7 ${duration}s` : duration ? `\xB7 ${duration}` : "",
1294
+ isStreaming ? "\xB7 thinking\u2026" : ""
1295
+ ].filter(Boolean).join(" ");
1296
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { ref, className: (0, import_core16.cn)("border-l-2 border-muted-foreground/30 pl-4", className), ...props, children: [
1297
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1298
+ "button",
1299
+ {
1300
+ onClick: () => setIsExpanded(!isExpanded),
1301
+ className: "flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors",
1302
+ "aria-expanded": isExpanded,
1303
+ "aria-controls": contentId,
1304
+ children: [
1305
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: (0, import_core16.cn)("transition-transform", isExpanded ? "rotate-90" : ""), children: "\u25B6" }),
1306
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: headerText }),
1307
+ isStreaming && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "flex gap-0.5", "aria-hidden": "true", children: [0, 150, 300].map((delay) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1308
+ "span",
1309
+ {
1310
+ className: "h-1 w-1 rounded-full bg-muted-foreground animate-pulse motion-reduce:animate-none",
1311
+ style: { animationDelay: `${delay}ms` }
1312
+ },
1313
+ delay
1314
+ )) })
1315
+ ]
1316
+ }
1317
+ ) : null,
1318
+ (isExpanded || !collapsible) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { id: contentId, className: "mt-3 space-y-3", children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-3", children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-3 h-3 rounded-full bg-primary/20 border-2 border-primary flex-shrink-0 mt-1" }),
1320
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "text-sm flex-1", children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "font-medium text-foreground mb-1", children: [
1322
+ "Step ",
1323
+ index + 1
1324
+ ] }),
1325
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-muted-foreground", children: step.content }),
1326
+ step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-xs text-muted-foreground mt-1", children: new Date(step.timestamp).toLocaleTimeString() })
1327
+ ] })
1328
+ ] }, step.id)) })
1329
+ ] });
1330
+ }
1331
+ );
1332
+ Reasoning.displayName = "Reasoning";
1333
+
1334
+ // src/components/tool-call/ToolCall.tsx
1335
+ var React13 = __toESM(require("react"));
1336
+ var import_core17 = require("@slipstream-ui/core");
1337
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1338
+ var statusChipClasses2 = {
1339
+ pending: "bg-warning/10 text-warning border-warning/20",
1340
+ running: "bg-info/10 text-info border-info/20",
1341
+ completed: "bg-success/10 text-success border-success/20",
1342
+ failed: "bg-destructive/10 text-destructive border-destructive/20"
1343
+ };
1344
+ var statusLabels = {
1345
+ pending: "Pending",
1346
+ running: "Running",
1347
+ completed: "Completed",
1348
+ failed: "Failed"
1349
+ };
1350
+ var ToolCall = React13.forwardRef(
1351
+ ({ toolCall, className, showResult = true, ...props }, ref) => {
1352
+ const serializedArguments = React13.useMemo(
1353
+ () => JSON.stringify(toolCall.arguments, null, 2),
1354
+ [toolCall.arguments]
1355
+ );
1356
+ const serializedResult = React13.useMemo(() => {
1357
+ if (toolCall.result === void 0) return void 0;
1358
+ return typeof toolCall.result === "string" ? toolCall.result : JSON.stringify(toolCall.result, null, 2);
1359
+ }, [toolCall.result]);
1360
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1361
+ "div",
1362
+ {
1363
+ ref,
1364
+ className: (0, import_core17.cn)(
1365
+ "border rounded-lg p-4 bg-muted/30",
1366
+ className
1367
+ ),
1368
+ ...props,
1369
+ children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between mb-3", children: [
1371
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", children: [
1372
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "font-medium text-sm", children: toolCall.name }),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1374
+ "span",
1375
+ {
1376
+ className: (0, import_core17.cn)(
1377
+ "text-xs px-2 py-0.5 rounded-full border",
1378
+ statusChipClasses2[toolCall.status]
1379
+ ),
1380
+ children: statusLabels[toolCall.status]
1381
+ }
1382
+ )
1383
+ ] }),
1384
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-xs text-muted-foreground", children: toolCall.id })
1385
+ ] }),
1386
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-2", children: [
1387
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1388
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Arguments" }),
1389
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: serializedArguments }) })
1390
+ ] }),
1391
+ showResult && serializedResult !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
1392
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Result" }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-muted p-2 rounded overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: serializedResult }) })
1394
+ ] }),
1395
+ toolCall.status === "failed" && toolCall.error && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { role: "alert", children: [
1396
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-xs font-medium text-destructive mb-1", children: "Error" }),
1397
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { className: "text-xs bg-destructive/10 text-destructive p-2 rounded overflow-x-auto whitespace-pre-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("code", { children: toolCall.error }) })
1398
+ ] })
1399
+ ] })
1400
+ ]
1401
+ }
1402
+ );
1403
+ }
1404
+ );
1405
+ ToolCall.displayName = "ToolCall";
1406
+
1407
+ // src/components/source/Source.tsx
1408
+ var React14 = __toESM(require("react"));
1409
+ var import_core18 = require("@slipstream-ui/core");
1410
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1411
+ var Source = React14.forwardRef(
1412
+ ({ source, index, className, ...props }, ref) => {
1413
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { ref, className: (0, import_core18.cn)("flex gap-2 items-start text-sm", className), ...props, children: [
1414
+ index !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-muted-foreground select-none", "aria-hidden": "true", children: [
1415
+ "[",
1416
+ index,
1417
+ "]"
1418
+ ] }),
1419
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex-1 min-w-0", children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "font-medium text-foreground", children: source.url ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1421
+ "a",
1422
+ {
1423
+ href: source.url,
1424
+ target: "_blank",
1425
+ rel: "noopener noreferrer",
1426
+ className: "hover:text-primary transition-colors",
1427
+ children: source.title
1428
+ }
1429
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: source.title }) }),
1430
+ source.snippet && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-muted-foreground mt-1 line-clamp-2", children: source.snippet })
1431
+ ] })
1432
+ ] });
1433
+ }
1434
+ );
1435
+ Source.displayName = "Source";
1436
+
1437
+ // src/components/source/Sources.tsx
1438
+ var React15 = __toESM(require("react"));
1439
+ var import_core19 = require("@slipstream-ui/core");
1440
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1441
+ var Sources = React15.forwardRef(
1442
+ ({ sources, className, maxDisplay = 3, label = "Sources", ...props }, ref) => {
1443
+ const displaySources = maxDisplay ? sources.slice(0, maxDisplay) : sources;
1444
+ const hasMore = maxDisplay && sources.length > maxDisplay;
1445
+ if (sources.length === 0) {
1446
+ return null;
1447
+ }
1448
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { ref, className: (0, import_core19.cn)("mt-4 pt-4 border-t border-border", className), ...props, children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "text-xs font-medium text-muted-foreground mb-2", children: label }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "space-y-2", children: [
1451
+ displaySources.map((source, index) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Source, { source, index: index + 1 }, source.id)),
1452
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "text-xs text-muted-foreground", children: [
1453
+ "+",
1454
+ sources.length - (maxDisplay ?? 0),
1455
+ " more sources"
1456
+ ] })
1457
+ ] })
1458
+ ] });
1459
+ }
1460
+ );
1461
+ Sources.displayName = "Sources";
1462
+
1463
+ // src/components/suggestion/Suggestion.tsx
1464
+ var React16 = __toESM(require("react"));
1465
+ var import_core20 = require("@slipstream-ui/core");
1466
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1467
+ var Suggestions = React16.forwardRef(
1468
+ ({ suggestions, onSelect, className, maxDisplay = 4, ...props }, ref) => {
1469
+ const displaySuggestions = maxDisplay ? suggestions.slice(0, maxDisplay) : suggestions;
1470
+ if (suggestions.length === 0) {
1471
+ return null;
1472
+ }
1473
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref, className: (0, import_core20.cn)("mt-4 space-y-2", className), ...props, children: displaySuggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1474
+ "button",
1475
+ {
1476
+ className: (0, import_core20.cn)(
1477
+ "w-full text-left h-auto py-2 px-3 text-sm rounded-md",
1478
+ "hover:bg-accent hover:text-accent-foreground",
1479
+ "transition-colors",
1480
+ "text-muted-foreground",
1481
+ "focus:outline-none focus:ring-2 focus:ring-ring"
1482
+ ),
1483
+ onClick: () => onSelect?.(suggestion),
1484
+ children: suggestion.text
1485
+ },
1486
+ suggestion.id
1487
+ )) });
1488
+ }
1489
+ );
1490
+ Suggestions.displayName = "Suggestions";
1491
+
1492
+ // src/components/chainthought/ChainThought.tsx
1493
+ var React17 = __toESM(require("react"));
1494
+ var import_core21 = require("@slipstream-ui/core");
1495
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1496
+ var ChainThought = React17.forwardRef(
1497
+ ({ steps, className, compact = false, ...props }, ref) => {
1498
+ if (steps.length === 0) {
1499
+ return null;
1500
+ }
1501
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { ref, className: (0, import_core21.cn)("space-y-2", className), ...props, children: steps.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-3 items-start", children: [
1502
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: (0, import_core21.cn)(
1503
+ "w-2 h-2 rounded-full mt-1.5 flex-shrink-0",
1504
+ step.status === "completed" ? "bg-success" : step.status === "failed" ? "bg-destructive" : step.status === "pending" ? "bg-warning" : "bg-primary"
1505
+ ) }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: (0, import_core21.cn)("flex-1", compact && "text-sm"), children: [
1507
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "text-sm text-muted-foreground", children: [
1508
+ "Step ",
1509
+ index + 1
1510
+ ] }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "text-foreground", children: step.content })
1512
+ ] })
1513
+ ] }, step.id)) });
1514
+ }
1515
+ );
1516
+ ChainThought.displayName = "ChainThought";
1517
+
1518
+ // src/components/streaming/StreamingIndicator.tsx
1519
+ var React18 = __toESM(require("react"));
1520
+ var import_core22 = require("@slipstream-ui/core");
1521
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1522
+ var StreamingIndicator = React18.forwardRef(
1523
+ ({ isStreaming, className, text = "Generating...", ...props }, ref) => {
1524
+ if (!isStreaming) {
1525
+ return null;
1526
+ }
1527
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1528
+ "div",
1529
+ {
1530
+ ref,
1531
+ role: "status",
1532
+ "aria-live": "polite",
1533
+ "aria-label": text,
1534
+ className: (0, import_core22.cn)("flex items-center gap-2 text-sm text-muted-foreground", className),
1535
+ ...props,
1536
+ children: [
1537
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex gap-1", "aria-hidden": "true", children: [0, 150, 300].map((delay) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1538
+ "span",
1539
+ {
1540
+ className: "w-2 h-2 bg-primary rounded-full animate-pulse motion-reduce:animate-none",
1541
+ style: { animationDelay: `${delay}ms` }
1542
+ },
1543
+ delay
1544
+ )) }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: text })
1546
+ ]
1547
+ }
1548
+ );
1549
+ }
1550
+ );
1551
+ StreamingIndicator.displayName = "StreamingIndicator";
1552
+
1553
+ // src/components/command-palette/CommandPalette.tsx
1554
+ var React19 = __toESM(require("react"));
1555
+ var import_core23 = require("@slipstream-ui/core");
1556
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1557
+ function CommandPalette({
1558
+ open,
1559
+ onOpenChange,
1560
+ commands,
1561
+ onSelect,
1562
+ placeholder = "Type a command\u2026"
1563
+ }) {
1564
+ const [query, setQuery] = React19.useState("");
1565
+ const [activeIndex, setActiveIndex] = React19.useState(0);
1566
+ const inputRef = React19.useRef(null);
1567
+ const listboxId = React19.useId();
1568
+ const optionId = (index) => `${listboxId}-option-${index}`;
1569
+ const filtered = React19.useMemo(() => {
1570
+ const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
1571
+ if (terms.length === 0) return commands;
1572
+ return commands.filter((command) => {
1573
+ const haystack = `${command.label} ${command.section ?? ""} ${(command.keywords ?? []).join(" ")}`.toLowerCase();
1574
+ return terms.every((term) => haystack.includes(term));
1575
+ });
1576
+ }, [commands, query]);
1577
+ React19.useEffect(() => {
1578
+ if (open) {
1579
+ setQuery("");
1580
+ setActiveIndex(0);
1581
+ const timer = setTimeout(() => inputRef.current?.focus(), 0);
1582
+ return () => clearTimeout(timer);
1583
+ }
1584
+ return void 0;
1585
+ }, [open]);
1586
+ React19.useEffect(() => {
1587
+ setActiveIndex((index) => Math.min(index, Math.max(0, filtered.length - 1)));
1588
+ }, [filtered.length]);
1589
+ if (!open) return null;
1590
+ const onKeyDown = (event) => {
1591
+ if (event.key === "Escape") {
1592
+ event.preventDefault();
1593
+ onOpenChange(false);
1594
+ } else if (event.key === "ArrowDown") {
1595
+ event.preventDefault();
1596
+ setActiveIndex((index) => filtered.length === 0 ? 0 : (index + 1) % filtered.length);
1597
+ } else if (event.key === "ArrowUp") {
1598
+ event.preventDefault();
1599
+ setActiveIndex((index) => filtered.length === 0 ? 0 : (index - 1 + filtered.length) % filtered.length);
1600
+ } else if (event.key === "Enter") {
1601
+ event.preventDefault();
1602
+ const command = filtered[activeIndex];
1603
+ if (command) {
1604
+ onSelect(command);
1605
+ onOpenChange(false);
1606
+ }
1607
+ }
1608
+ };
1609
+ let lastSection;
1610
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1611
+ "div",
1612
+ {
1613
+ className: "fixed inset-0 z-50 flex items-start justify-center bg-background/80 p-4 pt-[15vh]",
1614
+ onMouseDown: (event) => {
1615
+ if (event.target === event.currentTarget) onOpenChange(false);
1616
+ },
1617
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "w-full max-w-lg overflow-hidden rounded-xl border border-border bg-card shadow-2xl", children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1619
+ "input",
1620
+ {
1621
+ ref: inputRef,
1622
+ type: "text",
1623
+ role: "combobox",
1624
+ "aria-expanded": "true",
1625
+ "aria-controls": listboxId,
1626
+ "aria-activedescendant": filtered.length > 0 ? optionId(activeIndex) : void 0,
1627
+ "aria-autocomplete": "list",
1628
+ "aria-label": "Command palette",
1629
+ placeholder,
1630
+ value: query,
1631
+ onChange: (event) => {
1632
+ setQuery(event.target.value);
1633
+ setActiveIndex(0);
1634
+ },
1635
+ onKeyDown,
1636
+ className: "w-full border-b border-border bg-transparent px-4 py-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring placeholder:text-muted-foreground"
1637
+ }
1638
+ ),
1639
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("ul", { id: listboxId, role: "listbox", "aria-label": "Commands", className: "max-h-72 overflow-y-auto py-1", children: [
1640
+ filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 py-3 text-sm text-muted-foreground", children: "No matching commands." }),
1641
+ filtered.map((command, index) => {
1642
+ const showSection = command.section && command.section !== lastSection;
1643
+ lastSection = command.section ?? lastSection;
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(React19.Fragment, { children: [
1645
+ showSection && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { role: "presentation", className: "px-4 pb-1 pt-3 text-xs font-medium uppercase text-muted-foreground", children: command.section }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1647
+ "li",
1648
+ {
1649
+ id: optionId(index),
1650
+ role: "option",
1651
+ "aria-selected": index === activeIndex,
1652
+ onMouseEnter: () => setActiveIndex(index),
1653
+ onClick: () => {
1654
+ onSelect(command);
1655
+ onOpenChange(false);
1656
+ },
1657
+ className: (0, import_core23.cn)(
1658
+ "flex cursor-pointer items-center justify-between px-4 py-2 text-sm",
1659
+ index === activeIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground"
1660
+ ),
1661
+ children: [
1662
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: command.label }),
1663
+ command.shortcut && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "font-mono text-xs", children: command.shortcut })
1664
+ ]
1665
+ }
1666
+ )
1667
+ ] }, command.id);
1668
+ })
1669
+ ] })
1670
+ ] })
1671
+ }
1672
+ );
1673
+ }
1674
+
1675
+ // src/adapter/messages.ts
1676
+ function toSlipstreamToolCall(invocation) {
1677
+ const status = invocation.state === "result" ? "completed" : invocation.state === "error" ? "failed" : invocation.state === "call" ? "pending" : "pending";
1678
+ return {
1679
+ id: invocation.toolCallId,
1680
+ name: invocation.toolName,
1681
+ arguments: invocation.args ?? {},
1682
+ status,
1683
+ ...status === "completed" && invocation.result !== void 0 ? { result: invocation.result } : {},
1684
+ ...status === "failed" ? { error: invocation.error instanceof Error ? invocation.error.message : String(invocation.error ?? "Tool call failed") } : {}
1685
+ };
1686
+ }
1687
+ function toSlipstreamToolCalls(message) {
1688
+ return (message.toolInvocations ?? []).map(toSlipstreamToolCall);
1689
+ }
1690
+ function toSlipstreamMessage(message) {
1691
+ return {
1692
+ id: message.id,
1693
+ role: message.role,
1694
+ content: message.content,
1695
+ ...message.toolInvocations?.length ? { metadata: { toolCalls: toSlipstreamToolCalls(message) } } : {}
1696
+ };
1697
+ }
1698
+ function toSlipstreamMessages(messages) {
1699
+ return messages.map(toSlipstreamMessage);
1700
+ }
1701
+
1702
+ // src/adapter/hooks.ts
1703
+ var import_react = require("react");
1704
+ function useSlipstreamMessages({ messages }) {
1705
+ const mapped = (0, import_react.useMemo)(() => toSlipstreamMessages(messages), [messages]);
1706
+ return { messages: mapped };
1707
+ }
1708
+ function useSlipstreamStream({
1709
+ status,
1710
+ error,
1711
+ onRetry,
1712
+ messages = []
1713
+ }) {
1714
+ const isLoading = status === "submitted" || status === "streaming";
1715
+ const streamingMessageId = (0, import_react.useMemo)(() => {
1716
+ if (status !== "streaming") return void 0;
1717
+ for (let index = messages.length - 1; index >= 0; index--) {
1718
+ const message = messages[index];
1719
+ if (message?.role === "assistant") return message.id;
1720
+ }
1721
+ return messages.at(-1)?.id;
1722
+ }, [status, messages]);
1723
+ const messageErrors = (0, import_react.useMemo)(() => {
1724
+ if (status !== "error" || !error) return void 0;
1725
+ const last = messages.at(-1);
1726
+ if (!last) return void 0;
1727
+ return {
1728
+ [last.id]: error instanceof Error ? error.message : String(error)
1729
+ };
1730
+ }, [status, error, messages]);
1731
+ return {
1732
+ isLoading,
1733
+ ...streamingMessageId ? { streamingMessageId } : {},
1734
+ ...messageErrors ? { messageErrors } : {},
1735
+ ...onRetry ? { onRetryMessage: onRetry } : {}
1736
+ };
1737
+ }
1738
+ // Annotate the CommonJS export names for ESM import in node:
1739
+ 0 && (module.exports = {
1740
+ Attachment,
1741
+ AttachmentList,
1742
+ Branch,
1743
+ ChainThought,
1744
+ CodeBlock,
1745
+ CommandPalette,
1746
+ Confirmation,
1747
+ Conversation,
1748
+ InlineCitation,
1749
+ Message,
1750
+ MessageActions,
1751
+ PromptInput,
1752
+ PromptInputActionAddAttachments,
1753
+ PromptInputActionDictate,
1754
+ PromptInputBody,
1755
+ PromptInputButton,
1756
+ PromptInputFooter,
1757
+ PromptInputHeader,
1758
+ PromptInputSubmit,
1759
+ PromptInputTextarea,
1760
+ PromptInputTools,
1761
+ Reasoning,
1762
+ Response,
1763
+ Source,
1764
+ Sources,
1765
+ StreamingIndicator,
1766
+ Suggestions,
1767
+ ToolCall,
1768
+ closeIncompleteMarkdown,
1769
+ toSlipstreamMessage,
1770
+ toSlipstreamMessages,
1771
+ toSlipstreamToolCall,
1772
+ toSlipstreamToolCalls,
1773
+ usePromptInputAttachments,
1774
+ useSlipstreamMessages,
1775
+ useSlipstreamStream,
1776
+ useSpeechRecognition
1777
+ });
1778
+ //# sourceMappingURL=index.js.map