@miiflow/assistant-ui 0.13.0 → 0.15.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 (51) hide show
  1. package/README.md +209 -46
  2. package/dist/{WelcomeScreen-EGO8Qt2y.d.ts → WelcomeScreen-CSzlU0Yx.d.ts} +41 -9
  3. package/dist/{avatar-CD685KQV.d.ts → avatar-B_AvYfE8.d.ts} +4 -1
  4. package/dist/chunk-7QNDCHY7.js +2 -0
  5. package/dist/chunk-7QNDCHY7.js.map +1 -0
  6. package/dist/chunk-BINTGT2Q.js +5 -0
  7. package/dist/chunk-BINTGT2Q.js.map +1 -0
  8. package/dist/chunk-HNHLLH6W.js +2 -0
  9. package/dist/chunk-HNHLLH6W.js.map +1 -0
  10. package/dist/{chunk-WG77GQR3.js → chunk-ISMTV5BU.js} +2 -2
  11. package/dist/{chunk-WG77GQR3.js.map → chunk-ISMTV5BU.js.map} +1 -1
  12. package/dist/chunk-LDTAZ4GT.js +2 -0
  13. package/dist/chunk-LDTAZ4GT.js.map +1 -0
  14. package/dist/chunk-OU5ITGKX.js +2 -0
  15. package/dist/chunk-OU5ITGKX.js.map +1 -0
  16. package/dist/chunk-W7HQXA2Z.js +146 -0
  17. package/dist/chunk-W7HQXA2Z.js.map +1 -0
  18. package/dist/client/index.d.ts +66 -5
  19. package/dist/client/index.js +6 -6
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/composer/index.d.ts +37 -3
  22. package/dist/composer/index.js +1 -1
  23. package/dist/context/index.d.ts +10 -3
  24. package/dist/context/index.js +1 -1
  25. package/dist/hooks/index.d.ts +11 -141
  26. package/dist/hooks/index.js +1 -1
  27. package/dist/index.d.ts +5 -5
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/{message-D0oMw3tR.d.ts → message-DTNTKSQr.d.ts} +1 -1
  31. package/dist/primitives/index.d.ts +3 -3
  32. package/dist/primitives/index.js +1 -1
  33. package/dist/{streaming-CmQo_OOA.d.ts → streaming-BfLEgW5u.d.ts} +81 -3
  34. package/dist/styled/index.d.ts +410 -11
  35. package/dist/styled/index.js +1 -1
  36. package/dist/styles-no-preflight.css +1 -1
  37. package/dist/styles.css +3 -1
  38. package/dist/use-branding-css-vars-CR2tjSV8.d.ts +155 -0
  39. package/package.json +2 -1
  40. package/dist/chunk-4WWJTYYA.js +0 -2
  41. package/dist/chunk-4WWJTYYA.js.map +0 -1
  42. package/dist/chunk-6RN7SUWT.js +0 -2
  43. package/dist/chunk-6RN7SUWT.js.map +0 -1
  44. package/dist/chunk-D2PFIJNZ.js +0 -2
  45. package/dist/chunk-D2PFIJNZ.js.map +0 -1
  46. package/dist/chunk-HYFMOOLH.js +0 -133
  47. package/dist/chunk-HYFMOOLH.js.map +0 -1
  48. package/dist/chunk-NKLA5PPB.js +0 -2
  49. package/dist/chunk-NKLA5PPB.js.map +0 -1
  50. package/dist/chunk-ZP4FGAAH.js +0 -2
  51. package/dist/chunk-ZP4FGAAH.js.map +0 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ React components and hooks for building custom Miiflow chat interfaces. Install
8
8
  npm install @miiflow/assistant-ui
9
9
  ```
10
10
 
11
- **Peer dependencies:** `react >= 18`, `react-dom >= 18`
11
+ **Peer dependencies:** `react >= 19`, `react-dom >= 19`, `zod >= 3`. Optionally `lexical >= 0.20` and `@lexical/react >= 0.20` — required only if you use the Lexical-based `MessageComposer` / `WelcomeScreen` input or the `/composer` entry. React 18 projects must stay on `0.10.x`.
12
12
 
13
13
  The styled components use [TailwindCSS](https://tailwindcss.com/). If your project doesn't use Tailwind, import the pre-built CSS instead:
14
14
 
@@ -44,6 +44,7 @@ function Chat() {
44
44
  streamingMessageId,
45
45
  sendMessage,
46
46
  uploadFile,
47
+ stopStreaming,
47
48
  startNewThread,
48
49
  branding,
49
50
  brandingCSSVars,
@@ -63,7 +64,8 @@ function Chat() {
63
64
  messages={messages}
64
65
  isStreaming={isStreaming}
65
66
  streamingMessageId={streamingMessageId}
66
- onSendMessage={sendMessage}
67
+ onSendMessage={(content) => sendMessage(content)}
68
+ onStopStreaming={stopStreaming}
67
69
  >
68
70
  <div style={{ height: "100vh", ...brandingCSSVars }}>
69
71
  <ChatLayout
@@ -82,8 +84,9 @@ function Chat() {
82
84
  welcomeText={branding?.welcomeMessage}
83
85
  placeholders={branding?.rotatingPlaceholders}
84
86
  suggestions={branding?.presetQuestions}
85
- onSubmit={sendMessage}
86
- onSuggestionClick={sendMessage}
87
+ onSubmit={(message) => sendMessage(message)}
88
+ onSuggestionClick={(s) => sendMessage(s)}
89
+ disabled={isStreaming}
87
90
  />
88
91
  }
89
92
  messageList={
@@ -101,9 +104,12 @@ function Chat() {
101
104
  }
102
105
  composer={
103
106
  <MessageComposer
104
- onSubmit={sendMessage}
107
+ onSubmit={(content, _files, attachmentIds) =>
108
+ sendMessage(content, attachmentIds)
109
+ }
105
110
  onUploadFile={uploadFile}
106
- disabled={isStreaming}
111
+ isStreaming={isStreaming}
112
+ onStopStreaming={stopStreaming}
107
113
  placeholder={branding?.chatboxPlaceholder}
108
114
  />
109
115
  }
@@ -114,6 +120,8 @@ function Chat() {
114
120
  }
115
121
  ```
116
122
 
123
+ Note the small signature adapters: the hook's `sendMessage` takes `(content, attachmentIds?)`, while `ChatProvider.onSendMessage` passes `(content, attachments?: File[])` and `MessageComposer.onSubmit` passes `(content, attachments?, attachmentIds?)` — wrap them as shown rather than passing `sendMessage` directly.
124
+
117
125
  ## Configuration Reference
118
126
 
119
127
  Pass a `MiiflowChatConfig` object to `useMiiflowChat`:
@@ -126,11 +134,70 @@ Pass a `MiiflowChatConfig` object to `useMiiflowChat`:
126
134
  | `userName` | `string` | No | User display name |
127
135
  | `userEmail` | `string` | No | User email |
128
136
  | `userMetadata` | `string` | No | JSON string of custom user metadata |
129
- | `hmac` | `string` | No | HMAC for identity verification |
130
- | `timestamp` | `string` | No | Timestamp for HMAC verification |
131
- | `baseUrl` | `string` | No | Override API endpoint (default: `https://api.miiflow.ai/api`) |
137
+ | `userData` | `string` | No | The exact JSON string your server signed, sent verbatim. See [Verified identity](#verified-identity-hmac) |
138
+ | `hmac` | `string` | No | Signature over `userData` + `timestamp`. See [Verified identity](#verified-identity-hmac) |
139
+ | `timestamp` | `string` | No | Unix seconds (string) used in the signature |
140
+ | `baseUrl` | `string` | No | API origin override (default: `https://api.miiflow.ai`). A trailing `/api` is stripped; the client appends `/api/...` per request |
132
141
  | `webSocketUrl` | `string` | No | WebSocket URL for tool invocations (auto-derived from `baseUrl` if not set) |
133
- | `responseTimeout` | `number` | No | SSE stream timeout in ms (default: `60000`) |
142
+ | `initialBranding` | `BrandingData` | No | Branding rendered before session init so the shell paints instantly (SSR-safe). Server branding overrides it once init resolves |
143
+ | `tools` | `ClientToolDefinition[]` | No | Client tools folded into the init round-trip instead of a separate `registerTools()` call |
144
+ | `onToolInvocationFallback` | `(invocation: ToolInvocationRequest) => Promise<boolean>` | No | Handles tool invocations with no local handler (multi-widget routing); return `true` if handled |
145
+ | `onUserMessageCreated` | `(message: { id: string; content: string }) => void` | No | Fired when a user message is created |
146
+ | `onAssistantMessageComplete` | `(message: { id: string; content: string }) => void` | No | Fired when an assistant stream completes |
147
+
148
+ ## Verified identity (HMAC)
149
+
150
+ By default a session is anonymous: the widget keys it on a random id in
151
+ `localStorage`, and `userId`/`userName`/`userEmail` are untrusted hints the
152
+ browser could set to anything. That is fine for a public help widget.
153
+
154
+ If the assistant should act **as a signed-in person** — see their data, or use
155
+ a connected integration on their behalf — the browser cannot be the one making
156
+ that claim. Sign the identity on your server instead:
157
+
158
+ ```js
159
+ // SERVER-SIDE ONLY. The private key must never reach the browser: anyone
160
+ // holding it can impersonate any of your users to the assistant.
161
+ import crypto from "node:crypto";
162
+
163
+ const userData = JSON.stringify({
164
+ user_id: "your-internal-user-id", // required — the session is keyed on this
165
+ name: "Ada Lovelace", // optional
166
+ email: "ada@example.com", // optional
167
+ tenant_scope: "acme-hvac", // optional — see below
168
+ });
169
+ const timestamp = String(Math.floor(Date.now() / 1000));
170
+ const hmac = crypto
171
+ .createHmac("sha256", process.env.MIIFLOW_EMBED_PRIVATE_KEY)
172
+ .update(`${userData}|${timestamp}`)
173
+ .digest("hex");
174
+ ```
175
+
176
+ Pass all three to the hook and send nothing else:
177
+
178
+ ```tsx
179
+ useMiiflowChat({ publicKey, assistantId, userData, hmac, timestamp });
180
+ ```
181
+
182
+ Details that matter:
183
+
184
+ - **The signature covers the exact bytes of `userData`.** Pass the same string
185
+ you signed — re-serializing it, even to equivalent JSON, invalidates the
186
+ signature. This is why `userData` is a string you supply rather than
187
+ something the SDK assembles for you.
188
+ - **All three fields travel together.** Send a partial set and the session
189
+ falls back to anonymous rather than failing loudly.
190
+ - **`timestamp` must be within 5 minutes** of server time. Sign per page load,
191
+ not once at build time.
192
+ - **`user_id` is the session identity.** Two different signed-in users on the
193
+ same browser get separate sessions and separate chat histories, and neither
194
+ can reach the other's — an unsigned request cannot claim a signed identity.
195
+ - **`tenant_scope`** names which of *your* tenants (workspace, shop, account)
196
+ the session is acting for, when one deployment serves many. It scopes the
197
+ credentials the assistant uses, so a session can only reach the tenant it was
198
+ signed for. Omit it if your users belong to exactly one.
199
+
200
+ Rotate the private key from the Miiflow dashboard if it is ever exposed.
134
201
 
135
202
  ## Connecting to a Custom Backend
136
203
 
@@ -140,13 +207,13 @@ By default, the hook connects to `https://api.miiflow.ai`. To point to your own
140
207
  useMiiflowChat({
141
208
  publicKey: "pk_live_...",
142
209
  assistantId: "ast_...",
143
- baseUrl: "https://your-server.example.com/api",
210
+ baseUrl: "https://your-server.example.com",
144
211
  // webSocketUrl is auto-derived from baseUrl; override if needed:
145
212
  // webSocketUrl: "wss://your-server.example.com/ws",
146
213
  });
147
214
  ```
148
215
 
149
- Your backend must implement the same API contract as the Miiflow platform (session init, SSE streaming, file upload, and tool-result endpoints).
216
+ Pass the bare origin — the client appends `/api/...` to each request itself (a trailing `/api` on `baseUrl` is stripped). Your backend must implement the same API contract as the Miiflow platform (session init, SSE streaming, file upload, and tool-result endpoints).
150
217
 
151
218
  ## Hook API — `useMiiflowChat`
152
219
 
@@ -173,12 +240,17 @@ const result = useMiiflowChat(config);
173
240
 
174
241
  | Method | Signature | Description |
175
242
  |--------|-----------|-------------|
176
- | `sendMessage` | `(content: string, attachmentIds?: string[]) => Promise<void>` | Send a message to the assistant |
243
+ | `sendMessage` | `(content: string, attachmentIds?: string[]) => Promise<void>` | Send a message; queued if init hasn't resolved yet |
177
244
  | `uploadFile` | `(file: File) => Promise<string>` | Upload a file and get an attachment ID |
178
- | `startNewThread` | `() => Promise<string>` | Start a new conversation thread |
245
+ | `removeUploadedAttachment` | `(attachmentId: string) => void` | Drop uploaded-attachment metadata when the user removes it pre-send |
246
+ | `stopStreaming` | `() => void` | Abort the in-flight stream, preserving partial content |
247
+ | `startNewThread` | `() => Promise<string>` | Start a new conversation thread; re-registers client tools |
179
248
  | `registerTool` | `(tool: ClientToolDefinition) => Promise<void>` | Register a client-side tool |
180
249
  | `registerTools` | `(tools: ClientToolDefinition[]) => Promise<void>` | Register multiple tools |
181
- | `sendSystemEvent` | `(event: SystemEvent) => Promise<void>` | Send an invisible system event |
250
+ | `sendSystemEvent` | `(event: SystemEvent) => Promise<void>` | Send an invisible system event (triggers a reply) |
251
+ | `sendPageContext` | `(context: PageContext) => Promise<void>` | Append hidden page context to the thread (no reply) |
252
+ | `handleToolInvocation` | `(invocation: ToolInvocationRequest) => Promise<boolean>` | Execute a tool invocation; `true` if handled locally |
253
+ | `updateSession` | `(session: EmbedSession) => void` | Replace the session externally (e.g. after token refresh) |
182
254
 
183
255
  ## Components Reference
184
256
 
@@ -188,14 +260,18 @@ Wraps children and provides chat context via React context.
188
260
 
189
261
  | Prop | Type | Default | Description |
190
262
  |------|------|---------|-------------|
191
- | `messages` | `ChatMessage[]` | — | Messages to display |
263
+ | `children` | `ReactNode` | — | **Required.** Subtree |
264
+ | `messages` | `ChatMessage[]` | — | **Required.** Messages to display |
265
+ | `onSendMessage` | `(content: string, attachments?: File[]) => Promise<void>` | — | **Required.** Message send handler |
192
266
  | `isStreaming` | `boolean` | `false` | Whether a response is streaming |
193
267
  | `streamingMessageId` | `string \| null` | `null` | ID of the streaming message |
194
268
  | `viewerRole` | `ParticipantRole` | `"user"` | Viewer's role (determines message alignment) |
195
- | `onSendMessage` | `(content: string, attachments?: File[]) => Promise<void>` | — | Message send handler |
196
- | `onStopStreaming` | `() => void` | — | Stop streaming handler |
197
- | `onRetryLastMessage` | `() => Promise<void>` | — | Retry last message handler |
198
- | `onVisualizationAction` | `(event: VisualizationActionEvent) => void` | — | Callback for form/card interactions |
269
+ | `onStopStreaming` | `() => void` | — | Stop streaming handler (wire to the hook's `stopStreaming`) |
270
+ | `onRetryLastMessage` | `() => Promise<void>` | — | Retry last message handler (host-implemented) |
271
+ | `customData` | `Record<string, unknown>` | — | Arbitrary data passed through context |
272
+ | `onVisualizationAction` | `(event: VisualizationActionEvent) => void` | — | Callback for form/card/auth interactions |
273
+ | `resolveCommandToken` | `(id: string, kind: string) => { label?: string; tag?: ReactNode } \| undefined` | — | Customize inline command-token chip rendering |
274
+ | `isDarkSurface` | `boolean` | `false` | Tells the package the host surface is dark — drives choices CSS variables can't express, currently the code-block syntax theme |
199
275
 
200
276
  ### `ChatLayout`
201
277
 
@@ -203,13 +279,12 @@ Handles the empty-to-active state transition with crossfade animation. Accepts r
203
279
 
204
280
  | Prop | Type | Default | Description |
205
281
  |------|------|---------|-------------|
206
- | `isEmpty` | `boolean` | — | Whether the chat has no messages |
282
+ | `isEmpty` | `boolean` | — | **Required.** Whether the chat has no messages |
207
283
  | `header` | `ReactNode` | — | Header slot (rendered in both states) |
208
284
  | `welcomeScreen` | `ReactNode` | — | Content for empty state |
209
285
  | `messageList` | `ReactNode` | — | Message list for active state |
210
286
  | `composer` | `ReactNode` | — | Composer for active state |
211
287
  | `footer` | `ReactNode` | — | Extra content between list and composer |
212
- | `variant` | `"standalone" \| "embedded" \| "widget"` | `"standalone"` | Layout variant |
213
288
  | `className` | `string` | — | Additional CSS classes |
214
289
 
215
290
  ### `WelcomeScreen`
@@ -220,38 +295,64 @@ Empty state with rotating placeholder text and suggestion cards.
220
295
  |------|------|---------|-------------|
221
296
  | `placeholders` | `string[]` | `[]` | Rotating placeholder strings |
222
297
  | `suggestions` | `string[]` | `[]` | Preset suggestion cards |
223
- | `onSubmit` | `(message: string) => void` | — | Submit handler for built-in input |
298
+ | `onSubmit` | `(message: string, files?: File[]) => void` | — | Submit handler for built-in input |
224
299
  | `onSuggestionClick` | `(suggestion: string) => void` | — | Suggestion card click handler |
225
300
  | `welcomeText` | `string` | `"How can I help you today?"` | Heading text |
301
+ | `supportsAttachments` | `boolean` | — | Show the attach button on the built-in input |
302
+ | `disabled` | `boolean` | — | Block the built-in input (e.g. while streaming). Ignored when `composerSlot` is set |
226
303
  | `composerSlot` | `ReactNode` | — | Override default input with custom composer |
304
+ | `commandProvider` | `CommandProvider \| null` | — | Slash-command typeahead provider |
305
+ | `commandProviders` | `CommandProvider[]` | — | Multiple trigger providers; takes precedence over `commandProvider` |
306
+ | `assistantAvatar` | `string` | — | Avatar URL; when set, welcome text renders in message format |
307
+ | `assistantName` | `string` | — | Name shown alongside the avatar |
227
308
  | `className` | `string` | — | Additional CSS classes |
228
309
 
229
310
  ### `MessageList`
230
311
 
231
- Scrollable message container with auto-scroll.
312
+ Scrollable message transcript built on a scroll engine that follows streamed output only while the reader is pinned to the live edge, and preserves the reader's position when earlier content changes height. Each direct child is wrapped in a scroll-anchored item.
232
313
 
233
314
  | Prop | Type | Default | Description |
234
315
  |------|------|---------|-------------|
235
- | `children` | `ReactNode` | — | Message elements |
236
- | `autoScroll` | `boolean` | `true` | Auto-scroll to bottom on new messages |
237
- | `className` | `string` | | Additional CSS classes |
316
+ | `children` | `ReactNode` | — | **Required.** Message elements |
317
+ | `autoScroll` | `boolean` | `true` | Follow streamed output while at the live edge (not "always jump to bottom") |
318
+ | `showScrollToBottom` | `boolean` | `true` | Render the floating scroll-to-bottom button |
319
+ | `className` | `string` | — | Classes applied to the inner transcript content container |
238
320
 
239
321
  ### `Message`
240
322
 
241
- Individual message with markdown rendering, reasoning panel, citations, and visualizations.
323
+ Individual message with markdown rendering, reasoning panel, citations, visualizations, media, artifacts, and interactive panels (clarification, tool approval).
242
324
 
243
325
  | Prop | Type | Default | Description |
244
326
  |------|------|---------|-------------|
245
- | `message` | `MessageData` | — | Message data object |
327
+ | `message` | `MessageData` | — | **Required.** Message data object |
246
328
  | `viewerRole` | `ParticipantRole` | `"user"` | Viewer's role (determines alignment) |
247
329
  | `showAvatar` | `boolean` | `true` | Show participant avatar |
248
330
  | `showTimestamp` | `boolean` | `true` | Show message timestamp |
249
331
  | `renderMarkdown` | `boolean` | `true` | Render content as markdown |
250
332
  | `reasoning` | `StreamingChunk[]` | — | Reasoning/thinking chunks for collapsible panel |
333
+ | `reasoningExpanded` | `boolean` | — | Controlled expansion of the reasoning panel |
334
+ | `onReasoningExpandedChange` | `(expanded: boolean) => void` | — | Reasoning panel expansion callback |
335
+ | `executionPlan` | `unknown` | — | Execution plan for completed agent messages |
336
+ | `executionTimeline` | `unknown[]` | — | Execution timeline for completed messages |
337
+ | `executionTime` | `number` | — | Total execution time in seconds (persisted) |
338
+ | `streamStartedAt` | `number` | — | Epoch ms the in-progress run started, so the live elapsed counter survives remounts |
251
339
  | `suggestedActions` | `SuggestedAction[]` | — | Suggested follow-up actions |
252
340
  | `onSuggestedAction` | `(action: SuggestedAction) => void` | — | Suggested action click handler |
341
+ | `renderInlineSuggestedAction` | `(id: string) => ReactNode` | — | Renderer for inline `[SA:id]` markers |
253
342
  | `citations` | `SourceReference[]` | — | Citation sources to display |
254
- | `visualizations` | `VisualizationChunkData[]` | — | Inline visualizations |
343
+ | `visualizations` | `VisualizationChunkData[]` | — | Inline visualizations (`[VIZ:id]` markers) |
344
+ | `medias` | `MediaChunkData[]` | — | Inline images/videos |
345
+ | `artifacts` | `ArtifactChunkData[]` | — | Inline downloadable artifacts (PDF, HTML, …) |
346
+ | `onArtifactOpen` | `(artifact: ArtifactChunkData) => void` | — | Artifact inline-card click handler |
347
+ | `baselineFontSize` | `number` | — | Base font size multiplier for markdown |
348
+ | `pendingClarification` | `ClarificationData` | — | Agent needs user input |
349
+ | `onClarificationSubmit` | `(response: string) => void` | — | Clarification response handler |
350
+ | `pendingToolApproval` | `ToolApprovalData` | — | Tool awaiting approval |
351
+ | `onToolApprove` | `(modifiedInputs: Record<string, unknown>) => void` | — | Tool approval handler |
352
+ | `onToolReject` | `(reason?: string) => void` | — | Tool rejection handler |
353
+ | `onReportIncorrect` | `(reason?: string) => void` | — | Report the response as incorrect |
354
+ | `onConfirmCorrect` | `() => void` | — | Confirm the response was helpful |
355
+ | `onEditSubmit` | `(newText: string) => void` | — | Edit-and-resubmit for the viewer's own messages; enables the edit action in the hover bar |
255
356
  | `className` | `string` | — | Additional CSS classes |
256
357
 
257
358
  ### `MessageComposer`
@@ -260,15 +361,35 @@ Rich text editor (Lexical) with file upload, drag-and-drop, and Enter-to-send.
260
361
 
261
362
  | Prop | Type | Default | Description |
262
363
  |------|------|---------|-------------|
263
- | `onSubmit` | `(content: string, attachments?: File[]) => Promise<void>` | — | Submit handler |
364
+ | `onSubmit` | `(content: string, attachments?: File[], attachmentIds?: string[]) => Promise<void>` | — | **Required.** Submit handler; `attachmentIds` carries server-uploaded IDs |
264
365
  | `onUploadFile` | `(file: File) => Promise<string>` | — | File upload handler (returns attachment ID) |
265
366
  | `onAttach` | `(files: File[]) => void` | — | Called when files are attached |
266
- | `disabled` | `boolean` | `false` | Disable the composer |
367
+ | `onRemoveUploadedAttachment` | `(attachmentId: string) => void` | | Called when an uploaded attachment is removed pre-send |
368
+ | `disabled` | `boolean` | `false` | Disable the composer entirely |
267
369
  | `supportsAttachments` | `boolean` | `true` | Enable file attachments |
268
370
  | `allowedFileTypes` | `string[]` | images, docs, videos | Allowed MIME types |
269
371
  | `maxFileSize` | `number` | `104857600` (100MB) | Max file size in bytes |
270
372
  | `placeholder` | `string` | `"Type a message..."` | Placeholder text |
271
- | `isSubmitting` | `boolean` | `false` | Show loading state on send button |
373
+ | `isSubmitting` | `boolean` | `false` | Guards the submit handshake only (loading state on send) |
374
+ | `isStreaming` | `boolean` | `false` | Per-conversation gate while a response streams: blocks Enter and swaps Send for Stop |
375
+ | `onStopStreaming` | `() => void` | — | Stop-button handler |
376
+ | `centered` | `boolean` | `false` | Welcome-screen mode: bigger radius, more padding, larger shadow |
377
+ | `commandProvider` | `CommandProvider \| null` | — | Slash-command typeahead provider |
378
+ | `commandProviders` | `CommandProvider[]` | — | Multiple trigger providers; takes precedence |
379
+ | `className` | `string` | — | Additional CSS classes |
380
+
381
+ Use `isStreaming` (not `disabled`) while a response is streaming — it keeps the composer editable, blocks submission, and shows the Stop button.
382
+
383
+ ### `MessageAttachments`
384
+
385
+ Attachment strip rendered inside messages. Image attachments render as inline thumbnails that open a shared lightbox (Esc to close, arrow-key paging, body scroll-lock); non-image files render as downloadable chips. Images whose URL fails to load fall back to the file chip.
386
+
387
+ | Prop | Type | Default | Description |
388
+ |------|------|---------|-------------|
389
+ | `attachments` | `Attachment[]` | — | **Required.** Attachments to display |
390
+ | `onDownload` | `(attachment: Attachment) => void` | — | Custom download handler |
391
+ | `onPreview` | `(attachment: Attachment) => void` | — | Custom preview handler |
392
+ | `align` | `"start" \| "end"` | `"start"` | Edge to align against; use `"end"` for right-aligned viewer messages |
272
393
  | `className` | `string` | — | Additional CSS classes |
273
394
 
274
395
  ### `ChatHeader`
@@ -307,14 +428,31 @@ The `brandingCSSVars` object from `useMiiflowChat` contains CSS custom propertie
307
428
  </div>
308
429
  ```
309
430
 
310
- Available CSS variables:
431
+ Variables emitted by `brandingCSSVars`:
311
432
 
312
433
  | Variable | Source | Description |
313
434
  |----------|--------|-------------|
314
435
  | `--chat-primary` | `backgroundBubbleColor` | Primary accent color |
315
436
  | `--chat-user-message-bg` | `backgroundBubbleColor` | User message bubble background |
437
+ | `--chat-user-message-text` | derived | Auto-computed contrast color for user bubbles |
316
438
  | `--chat-header-bg` | `headerBackgroundColor` | Header background color |
317
439
  | `--chat-message-font-size` | `messageFontSize` | Base message font size |
440
+ | `--chat-font-family` | `fontFamily` | Base font stack |
441
+ | `--chat-approval-accent` / `--chat-approval-accent-soft` | `approvalAccentColor` | Tool-approval panel accent |
442
+ | `--chat-approve-bg` / `--chat-approve-bg-hover` | `approveButtonColor` | Approve button colors |
443
+ | `--chat-reject-bg-hover` | `rejectButtonHoverColor` | Reject button hover |
444
+ | `--chat-clarification-accent` / `--chat-clarification-accent-soft` | `clarificationAccentColor` | Clarification panel accent |
445
+ | `--chat-activity` | `activityAccentColor` | In-progress indicators (falls back to `--chat-primary`) |
446
+
447
+ Beyond these, the stylesheet declares many more host-overridable `--chat-*` tokens (surfaces, borders, text, radii, composer chrome, `--chat-font-mono` for code, and more) — see `styles.css` for the full set. Set them on any ancestor element to theme the components.
448
+
449
+ ### Dark surfaces
450
+
451
+ CSS variables handle colors, but some choices can't be expressed in CSS — currently the syntax-highlighting theme for code blocks. If your app renders the chat on a dark surface, pass `isDarkSurface` to `ChatProvider` (the package does not infer dark mode from the OS `prefers-color-scheme`):
452
+
453
+ ```tsx
454
+ <ChatProvider isDarkSurface {...rest}>
455
+ ```
318
456
 
319
457
  ### TailwindCSS Customization
320
458
 
@@ -332,13 +470,13 @@ Pass `onUploadFile={uploadFile}` to `MessageComposer` to enable server-side file
332
470
  const { sendMessage, uploadFile } = useMiiflowChat(config);
333
471
 
334
472
  <MessageComposer
335
- onSubmit={sendMessage}
473
+ onSubmit={(content, _files, attachmentIds) => sendMessage(content, attachmentIds)}
336
474
  onUploadFile={uploadFile}
337
475
  supportsAttachments={true}
338
476
  />
339
477
  ```
340
478
 
341
- The composer handles file picking, validation, drag-and-drop, and preview thumbnails. Files are uploaded via `uploadFile()` which returns an attachment ID. The IDs are passed along when `sendMessage()` is called.
479
+ The composer handles file picking, validation, drag-and-drop, and preview thumbnails. Files are uploaded via `uploadFile()` which returns an attachment ID; the IDs arrive as `onSubmit`'s third argument. A message with attachments and no text is valid — the composer allows attachment-only sends.
342
480
 
343
481
  ## Client-Side Tools
344
482
 
@@ -364,9 +502,9 @@ await registerTool({
364
502
  });
365
503
  ```
366
504
 
367
- Tools are automatically re-registered when starting a new thread via `startNewThread()`.
505
+ Tools known at mount time can instead be passed via `config.tools` — they're folded into the session-init round-trip, saving a registration call. Tools are automatically re-registered when starting a new thread via `startNewThread()`.
368
506
 
369
- The `handler` function receives the parameters as a `Record<string, unknown>` and must return a `Promise`. Results are sent back to the assistant automatically. A 30-second timeout is enforced per invocation.
507
+ The `handler` function receives the parameters as a `Record<string, unknown>` and must return a `Promise`. Results are sent back to the assistant automatically. A 30-second timeout is enforced per invocation. Invocations with no locally registered handler are offered to `config.onToolInvocationFallback` (useful when several widgets share one session).
370
508
 
371
509
  ## System Events
372
510
 
@@ -389,6 +527,8 @@ await sendSystemEvent({
389
527
  | `followUpInstruction` | `string` | Yes | Instruction for the assistant |
390
528
  | `metadata` | `Record<string, unknown>` | No | Additional structured data |
391
529
 
530
+ A system event triggers an assistant reply. To attach silent context that the assistant only uses on the *next* user message, use `sendPageContext(context)` instead — it appends hidden context to the thread without generating a response.
531
+
392
532
  ## Identity Verification (HMAC)
393
533
 
394
534
  For secure identity verification, compute an HMAC on your server and pass it to the config:
@@ -421,6 +561,7 @@ Assistant messages can contain rich visualizations (charts, tables, forms, etc.)
421
561
  | `kpi` | `KpiVisualization` | Key performance indicator metrics with trends |
422
562
  | `code_preview` | `CodePreviewVisualization` | Syntax-highlighted code blocks |
423
563
  | `form` | `FormVisualization` | Interactive forms with validation |
564
+ | `auth_prompt` | `AuthPromptVisualization` | "Connect this integration" card shown when the assistant needs an authorized provider; emits an `auth_connect` action via `onAction` (no button renders without it) |
424
565
 
425
566
  ### Visualization Registry
426
567
 
@@ -441,7 +582,7 @@ registerVisualization("my_widget", {
441
582
 
442
583
  // Check what's registered
443
584
  console.log(getRegisteredTypes());
444
- // ["chart", "table", "card", "kpi", "code_preview", "form", "my_widget"]
585
+ // ["chart", "table", "card", "kpi", "code_preview", "form", "auth_prompt", "my_widget"]
445
586
  ```
446
587
 
447
588
  Your component receives these props:
@@ -452,6 +593,7 @@ interface VisualizationComponentProps {
452
593
  config?: VisualizationConfig;
453
594
  isStreaming?: boolean;
454
595
  onAction?: (event: VisualizationActionEvent) => void;
596
+ medias?: MediaChunkData[]; // for resolving media_ref:<id> values
455
597
  }
456
598
  ```
457
599
 
@@ -471,6 +613,7 @@ import {
471
613
  kpiVisualizationSchema,
472
614
  codePreviewVisualizationSchema,
473
615
  formVisualizationSchema,
616
+ authPromptVisualizationSchema,
474
617
  } from "@miiflow/assistant-ui/styled";
475
618
 
476
619
  const result = chartVisualizationSchema.safeParse(data);
@@ -499,7 +642,7 @@ registerVisualization("my_widget", {
499
642
 
500
643
  ### Interaction Callbacks
501
644
 
502
- Forms and cards can trigger user interactions (submit, cancel, button click). Instead of listening for global `CustomEvent`s, pass a callback through `ChatProvider`:
645
+ Forms, cards, and auth prompts can trigger user interactions. Instead of listening for global `CustomEvent`s, pass a callback through `ChatProvider`:
503
646
 
504
647
  ```tsx
505
648
  function handleVisualizationAction(event: VisualizationActionEvent) {
@@ -514,12 +657,16 @@ function handleVisualizationAction(event: VisualizationActionEvent) {
514
657
  case "card_action":
515
658
  console.log("Card action clicked:", event.action);
516
659
  break;
660
+ case "auth_connect":
661
+ // Kick off your OAuth/connect flow for event.providerName
662
+ console.log("Connect requested:", event.providerName);
663
+ break;
517
664
  }
518
665
  }
519
666
 
520
667
  <ChatProvider
521
668
  messages={messages}
522
- onSendMessage={sendMessage}
669
+ onSendMessage={(content) => sendMessage(content)}
523
670
  onVisualizationAction={handleVisualizationAction}
524
671
  >
525
672
  ...
@@ -532,9 +679,17 @@ The `VisualizationActionEvent` type is a discriminated union:
532
679
  type VisualizationActionEvent =
533
680
  | { type: "form_submit"; action: string; data: Record<string, unknown> }
534
681
  | { type: "form_cancel"; action: string }
535
- | { type: "card_action"; action: string };
682
+ | { type: "card_action"; action: string }
683
+ | {
684
+ type: "auth_connect";
685
+ providerName: string;
686
+ mcpServerId?: string;
687
+ serviceProviderId?: string;
688
+ };
536
689
  ```
537
690
 
691
+ Note the `auth_connect` variant has no `action` field — narrow on `event.type` before reading variant-specific fields.
692
+
538
693
  **Backward compatibility:** If no `onVisualizationAction` callback is provided, components fall back to dispatching `CustomEvent`s on `window` (`visualization-form-submit`, `visualization-form-cancel`, `visualization-action`).
539
694
 
540
695
  ### Using `VisualizationRenderer` Standalone
@@ -562,10 +717,11 @@ import { VisualizationRenderer } from "@miiflow/assistant-ui/styled";
562
717
 
563
718
  | Import | Description |
564
719
  |--------|-------------|
565
- | `@miiflow/assistant-ui` | Core types, context, hooks, primitives |
566
- | `@miiflow/assistant-ui/styled` | TailwindCSS-styled components, visualization registry, schemas |
567
- | `@miiflow/assistant-ui/client` | `useMiiflowChat` hook, session utilities, types |
720
+ | `@miiflow/assistant-ui` | Core types, context, hooks, primitives — plus re-exports of the styled components and shared utils (`cn`, format/color helpers, `chatTokens`) |
721
+ | `@miiflow/assistant-ui/styled` | TailwindCSS-styled components, visualization + artifact registries, schemas |
722
+ | `@miiflow/assistant-ui/client` | `useMiiflowChat` hook, session utilities, tool validation, SSE helpers, types |
568
723
  | `@miiflow/assistant-ui/primitives` | Headless unstyled component primitives |
724
+ | `@miiflow/assistant-ui/composer` | Lexical composer internals: `LexicalChatInput`, command-token node/plugin/view, `CommandProvider` types |
569
725
  | `@miiflow/assistant-ui/styles.css` | Full CSS (includes Tailwind preflight) |
570
726
  | `@miiflow/assistant-ui/styles-no-preflight.css` | CSS without preflight (for embedding in existing pages) |
571
727
 
@@ -575,7 +731,14 @@ import { VisualizationRenderer } from "@miiflow/assistant-ui/styled";
575
731
  `registerVisualization`, `getVisualization`, `getRegisteredTypes`, `VisualizationEntry`
576
732
 
577
733
  **Visualization Schemas:**
578
- `chartVisualizationSchema`, `tableVisualizationSchema`, `cardVisualizationSchema`, `kpiVisualizationSchema`, `codePreviewVisualizationSchema`, `formVisualizationSchema`
734
+ `chartVisualizationSchema`, `tableVisualizationSchema`, `cardVisualizationSchema`, `kpiVisualizationSchema`, `codePreviewVisualizationSchema`, `formVisualizationSchema`, `authPromptVisualizationSchema`
735
+
736
+ **Artifact Registry:**
737
+ `registerArtifact`, `getArtifact`, `getRegisteredArtifactTypes`, `ArtifactInlineCard`, `ArtifactList`
579
738
 
580
739
  **Types:**
581
740
  `VisualizationActionEvent`, `VisualizationChunkData`, `VisualizationConfig`, `VisualizationType`
741
+
742
+ ### Key Exports from `@miiflow/assistant-ui/client`
743
+
744
+ Beyond `useMiiflowChat`: session helpers (`initSession`, `createThread`, `uploadFile`, `sendSystemEvent`, `sendPageContext`, `sendToolResult`, `getBackendBaseUrl`), tool validation (`validateToolDefinition`, `serializeToolDefinition`, `ToolValidationError`), and SSE-reducer helpers for hosts with their own stream parsing (`findToolChunkIndex`, `MatchableToolChunk`, `ToolFrame`).
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode } from 'react';
3
- import { A as Attachment, M as MessageData, P as ParticipantRole, d as SuggestedAction, S as SourceReference } from './message-D0oMw3tR.js';
4
- import { h as AvatarProps$1 } from './avatar-CD685KQV.js';
5
- import { S as StreamingChunk, V as VisualizationChunkData, M as MediaChunkData, A as ArtifactChunkData, C as ClarificationData, T as ToolApprovalData } from './streaming-CmQo_OOA.js';
3
+ import { A as Attachment, M as MessageData, P as ParticipantRole, d as SuggestedAction, S as SourceReference } from './message-DTNTKSQr.js';
4
+ import { h as AvatarProps$1 } from './avatar-B_AvYfE8.js';
5
+ import { S as StreamingChunk, V as VisualizationChunkData, M as MediaChunkData, A as ArtifactChunkData, C as ClarificationData, T as ToolApprovalData } from './streaming-BfLEgW5u.js';
6
6
  import { C as CommandProvider } from './types-Du00UBst.js';
7
7
 
8
8
  interface AttachmentPreviewProps {
@@ -46,12 +46,19 @@ interface MarkdownContentProps {
46
46
  className?: string;
47
47
  /** Base font size multiplier for responsive scaling */
48
48
  baselineFontSize?: number;
49
- /** Use dark theme for code blocks */
49
+ /** Use dark theme for code blocks. Falls back to the host surface's
50
+ * `isDarkSurface` from ChatProvider. */
50
51
  darkCodeTheme?: boolean;
51
52
  }
52
53
  /**
53
- * Styled markdown renderer with syntax highlighting, copy-to-clipboard,
54
- * and heading anchor links.
54
+ * Markdown renderer for chat messages.
55
+ *
56
+ * Appearance lives entirely in the `.chat-prose` rules in
57
+ * `src/styles/prose.css`; the overrides below carry only behaviour (heading
58
+ * anchors, the code-block copy button, inline command-token chips, and the
59
+ * image-URL swap). Keep it that way — the two used to be duplicated, and
60
+ * because react-markdown v9 dropped the `className` prop the CSS half was
61
+ * silently dead for the entire time both existed.
55
62
  */
56
63
  declare function MarkdownContent({ children, className, baselineFontSize, darkCodeTheme, }: MarkdownContentProps): react.JSX.Element;
57
64
 
@@ -70,9 +77,14 @@ interface MessageProps {
70
77
  renderMarkdown?: boolean;
71
78
  /** Streaming chunks for reasoning panel */
72
79
  reasoning?: StreamingChunk[];
73
- /** Execution plan for completed Plan & Execute messages */
80
+ /**
81
+ * @deprecated No longer read. Host adapters already reconstruct these into
82
+ * `reasoning` chunks, so passing them separately made the same run
83
+ * describable two ways. Kept on the interface so existing callers compile;
84
+ * remove in the next major.
85
+ */
74
86
  executionPlan?: unknown;
75
- /** Execution timeline for completed messages (all orchestrator modes) */
87
+ /** @deprecated No longer read see `executionPlan`. */
76
88
  executionTimeline?: unknown[];
77
89
  /** Suggested actions */
78
90
  suggestedActions?: SuggestedAction[];
@@ -96,6 +108,15 @@ interface MessageProps {
96
108
  baselineFontSize?: number;
97
109
  /** Total execution time in seconds (persisted from streaming wall-clock) */
98
110
  executionTime?: number;
111
+ /** Epoch ms the in-progress run started. Supply the run's durable start
112
+ * (e.g. from a server snapshot) so the streaming elapsed figure stays
113
+ * correct across remounts; omit to time from when this component mounted. */
114
+ streamStartedAt?: number;
115
+ /** This turn finished moments ago. The streaming message and the completed
116
+ * message are different elements, so this component cannot see that edge
117
+ * itself — the host reports it, and it is what animates the reasoning
118
+ * steps folding into the "Thought for …" line instead of snapping shut. */
119
+ justCompleted?: boolean;
99
120
  /** Pending clarification data (agent needs user input) */
100
121
  pendingClarification?: ClarificationData;
101
122
  /** Callback when user responds to a clarification */
@@ -229,9 +250,17 @@ declare const SuggestedActions: react.ForwardRefExoticComponent<SuggestedActions
229
250
  interface TypingIndicatorProps {
230
251
  /** Additional CSS classes */
231
252
  className?: string;
253
+ /** Optional status line rendered beside the mark (e.g. "Getting started…") */
254
+ label?: string | null;
255
+ /** Brand mark for the waiting state, supplied by the host. */
256
+ mark?: ReactNode;
232
257
  }
233
258
  /**
234
- * Styled TypingIndicator with animated dots.
259
+ * The pre-first-token waiting state.
260
+ *
261
+ * Kept as a thin wrapper over `ThinkingIndicator` so the primitive's semantics
262
+ * (and every existing `<TypingIndicator label=… />` call site) survive the
263
+ * change from three bouncing dots to a decoding line.
235
264
  */
236
265
  declare const TypingIndicator: react.ForwardRefExoticComponent<TypingIndicatorProps & react.RefAttributes<HTMLDivElement>>;
237
266
 
@@ -328,6 +357,9 @@ interface WelcomeScreenProps {
328
357
  welcomeText?: string;
329
358
  /** Whether to show the attachment (paperclip) button */
330
359
  supportsAttachments?: boolean;
360
+ /** Blocks the built-in input (e.g. while a response is already streaming).
361
+ * Ignored when `composerSlot` is provided — that composer owns its own gating. */
362
+ disabled?: boolean;
331
363
  /** Override the default plain-text input with a custom composer (e.g. chat-ui MessageComposer) */
332
364
  composerSlot?: ReactNode;
333
365
  /** Optional slash-command typeahead provider (e.g. for skill picker). */
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode, TextareaHTMLAttributes, ButtonHTMLAttributes } from 'react';
3
- import { M as MessageData, P as ParticipantRole } from './message-D0oMw3tR.js';
3
+ import { M as MessageData, P as ParticipantRole } from './message-DTNTKSQr.js';
4
4
 
5
5
  interface MessageContextValue {
6
6
  message: MessageData;
@@ -65,6 +65,9 @@ interface MessageComposerProps {
65
65
  onSubmit: (content: string, attachments?: File[]) => Promise<void>;
66
66
  /** Whether the composer is disabled */
67
67
  disabled?: boolean;
68
+ /** Whether the current conversation is mid-response. Blocks submitting a
69
+ * second, concurrent turn into the same conversation. */
70
+ isStreaming?: boolean;
68
71
  /** Children to render inside the composer */
69
72
  children: ReactNode;
70
73
  /** Additional CSS classes */
@@ -0,0 +1,2 @@
1
+ import {a,b as b$1}from'./chunk-LDTAZ4GT.js';import {createContext,forwardRef,useContext}from'react';import {jsx,Fragment,jsxs}from'react/jsx-runtime';var g=createContext(null);function f(){let e=useContext(g);if(!e)throw new Error("useMessage must be used within a Message component");return e}var L=forwardRef(({message:e,viewerRole:t="user",children:r,...o},s)=>{let n=(e.participant?.role||"").toLowerCase()===(t||"").toLowerCase(),a=e.isStreaming??false;return jsx(g.Provider,{value:{message:e,isViewer:n,isStreaming:a},children:jsx("div",{ref:s,"data-role":e.participant?.role,"data-viewer":n,"data-streaming":a,...o,children:r})})});L.displayName="Message";var h=forwardRef(({children:e,...t},r)=>{let{message:o}=f();return jsx("div",{ref:r,...t,children:e??o.textContent})});h.displayName="MessageContent";var R=forwardRef(({format:e,...t},r)=>{let{message:o}=f(),s=typeof o.createdAt=="string"?new Date(o.createdAt):o.createdAt,n=e?e(s):s.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:true});return jsx("span",{ref:r,...t,children:n})});R.displayName="MessageTimestamp";var P=forwardRef(({children:e,autoScroll:t=true,onIsAtBottomChange:r,onScrollToBottomRef:o,...s},n)=>{let{containerRef:a$1,scrollToBottom:i,isAtBottom:p}=a({enabled:t});return {current:p}.current!==p&&r?.(p),o?.(i),jsx("div",{ref:m=>{a$1.current=m,typeof n=="function"?n(m):n&&(n.current=m);},...s,children:e})});P.displayName="MessageList";var x=createContext(null);function b(){let e=useContext(x);if(!e)throw new Error("useComposer must be used within a MessageComposer component");return e}var H=forwardRef(({onSubmit:e,disabled:t=false,isStreaming:r=false,children:o,className:s},n)=>{let a=b$1({onSubmit:e,disabled:t,isStreaming:r}),i=c=>{c.preventDefault(),a.handleSubmit();},p={...a,canSubmit:!!a.canSubmit};return jsx(x.Provider,{value:p,children:jsx("form",{ref:n,className:s,onSubmit:i,"data-submitting":a.isSubmitting,"data-can-submit":a.canSubmit,children:o})})});H.displayName="MessageComposer";var E=forwardRef((e,t)=>{let{content:r,handleContentChange:o,handleKeyDown:s,inputRef:n}=b();return jsx("textarea",{ref:i=>{n.current=i,typeof t=="function"?t(i):t&&(t.current=i);},value:r,onChange:i=>o(i.target.value),onKeyDown:s,...e})});E.displayName="ComposerInput";var N=forwardRef(({children:e,disabled:t,...r},o)=>{let{canSubmit:s,isSubmitting:n}=b();return jsx("button",{ref:o,type:"submit",disabled:t||!s,"data-submitting":n,...r,children:e})});N.displayName="ComposerSubmit";function G(e){let t=e.trim().split(/\s+/);return t.length===1?t[0].charAt(0).toUpperCase():(t[0].charAt(0)+t[t.length-1].charAt(0)).toUpperCase()}var w=forwardRef(({name:e,src:t,alt:r,role:o,fallback:s,children:n,...a},i)=>{let p=e?G(e):null;return jsx("div",{ref:i,"data-role":o,...a,children:n??jsx(Fragment,{children:t?jsx("img",{src:t,alt:r??e??o??"Avatar",style:{width:"100%",height:"100%"}}):s??p??o?.charAt(0).toUpperCase()})})});w.displayName="Avatar";var B=forwardRef(({content:e,isStreaming:t=false,showCursor:r=true,cursor:o,children:s,...n},a)=>jsxs("div",{ref:a,"data-streaming":t,...n,children:[s??e,t&&r&&(o??jsx("span",{"aria-hidden":"true",style:{display:"inline-block",width:"2px",height:"1em",backgroundColor:"currentColor",marginLeft:"2px",verticalAlign:"text-bottom",animation:"blink 1s step-end infinite"}}))]}));B.displayName="StreamingText";var A=createContext(null);function I(){let e=useContext(A);if(!e)throw new Error("useSuggestedActions must be used within a SuggestedActions component");return e}var k=forwardRef(({actions:e,onSelect:t,children:r,className:o},s)=>e.length===0?null:jsx(A.Provider,{value:{actions:e,onSelect:t},children:jsx("div",{ref:s,role:"group","aria-label":"Suggested actions",className:o,children:r})}));k.displayName="SuggestedActions";var V=forwardRef(({action:e,children:t,onClick:r,...o},s)=>{let{onSelect:n}=I();return jsx("button",{ref:s,type:"button",onClick:i=>{r?.(i),i.defaultPrevented||n(e);},...o,children:t??e.label})});V.displayName="ActionButton";var F=forwardRef(({children:e,dotCount:t=3,...r},o)=>jsx("div",{ref:o,role:"status","aria-label":"Assistant is typing",...r,children:e??jsx("span",{"aria-hidden":"true",children:Array.from({length:t}).map((s,n)=>jsx("span",{style:{display:"inline-block",width:"6px",height:"6px",borderRadius:"50%",backgroundColor:"currentColor",marginRight:n<t-1?"4px":0,animation:"typing 1.4s infinite ease-in-out",animationDelay:`${n*.2}s`}},n))})}));F.displayName="TypingIndicator";export{g as a,f as b,L as c,h as d,R as e,P as f,x as g,b as h,H as i,E as j,N as k,w as l,B as m,A as n,I as o,k as p,V as q,F as r};//# sourceMappingURL=chunk-7QNDCHY7.js.map
2
+ //# sourceMappingURL=chunk-7QNDCHY7.js.map