@miiflow/assistant-ui 0.1.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +563 -0
  3. package/dist/WelcomeScreen-CsFaFNcu.d.mts +246 -0
  4. package/dist/WelcomeScreen-TrcbOYob.d.ts +246 -0
  5. package/dist/avatar-D5eHcfjf.d.mts +109 -0
  6. package/dist/avatar-DftdWqSs.d.ts +109 -0
  7. package/dist/branding-SzYU4ncD.d.mts +18 -0
  8. package/dist/branding-SzYU4ncD.d.ts +18 -0
  9. package/dist/chunk-3E2HG62U.mjs +2 -0
  10. package/dist/chunk-3E2HG62U.mjs.map +1 -0
  11. package/dist/chunk-3ERHTQXR.js +2 -0
  12. package/dist/chunk-3ERHTQXR.js.map +1 -0
  13. package/dist/chunk-3GQNGDXX.mjs +22 -0
  14. package/dist/chunk-3GQNGDXX.mjs.map +1 -0
  15. package/dist/chunk-3KB4JYSQ.js +2 -0
  16. package/dist/chunk-3KB4JYSQ.js.map +1 -0
  17. package/dist/chunk-BA3VCHRC.js +22 -0
  18. package/dist/chunk-BA3VCHRC.js.map +1 -0
  19. package/dist/chunk-CRNBTU42.mjs +2 -0
  20. package/dist/chunk-CRNBTU42.mjs.map +1 -0
  21. package/dist/chunk-KPGHBLGY.mjs +2 -0
  22. package/dist/chunk-KPGHBLGY.mjs.map +1 -0
  23. package/dist/chunk-LJQHWCUK.js +2 -0
  24. package/dist/chunk-LJQHWCUK.js.map +1 -0
  25. package/dist/chunk-MFCWFFJV.mjs +2 -0
  26. package/dist/chunk-MFCWFFJV.mjs.map +1 -0
  27. package/dist/chunk-NSTK5EUQ.js +2 -0
  28. package/dist/chunk-NSTK5EUQ.js.map +1 -0
  29. package/dist/chunk-OCKHJ4WO.js +2 -0
  30. package/dist/chunk-OCKHJ4WO.js.map +1 -0
  31. package/dist/chunk-RTT6LULU.mjs +2 -0
  32. package/dist/chunk-RTT6LULU.mjs.map +1 -0
  33. package/dist/client/index.d.mts +249 -0
  34. package/dist/client/index.d.ts +249 -0
  35. package/dist/client/index.js +9 -0
  36. package/dist/client/index.js.map +1 -0
  37. package/dist/client/index.mjs +9 -0
  38. package/dist/client/index.mjs.map +1 -0
  39. package/dist/context/index.d.mts +43 -0
  40. package/dist/context/index.d.ts +43 -0
  41. package/dist/context/index.js +2 -0
  42. package/dist/context/index.js.map +1 -0
  43. package/dist/context/index.mjs +2 -0
  44. package/dist/context/index.mjs.map +1 -0
  45. package/dist/hooks/index.d.mts +109 -0
  46. package/dist/hooks/index.d.ts +109 -0
  47. package/dist/hooks/index.js +2 -0
  48. package/dist/hooks/index.js.map +1 -0
  49. package/dist/hooks/index.mjs +2 -0
  50. package/dist/hooks/index.mjs.map +1 -0
  51. package/dist/index.d.mts +157 -0
  52. package/dist/index.d.ts +157 -0
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/index.mjs +2 -0
  56. package/dist/index.mjs.map +1 -0
  57. package/dist/message-B21_kqE2.d.ts +78 -0
  58. package/dist/message-ufYsvKXP.d.mts +78 -0
  59. package/dist/primitives/index.d.mts +86 -0
  60. package/dist/primitives/index.d.ts +86 -0
  61. package/dist/primitives/index.js +2 -0
  62. package/dist/primitives/index.js.map +1 -0
  63. package/dist/primitives/index.mjs +2 -0
  64. package/dist/primitives/index.mjs.map +1 -0
  65. package/dist/streaming-CF63E6iS.d.mts +426 -0
  66. package/dist/streaming-CF63E6iS.d.ts +426 -0
  67. package/dist/styled/index.d.mts +477 -0
  68. package/dist/styled/index.d.ts +477 -0
  69. package/dist/styled/index.js +2 -0
  70. package/dist/styled/index.js.map +1 -0
  71. package/dist/styled/index.mjs +2 -0
  72. package/dist/styled/index.mjs.map +1 -0
  73. package/dist/styles-no-preflight.css +1 -0
  74. package/dist/styles.css +1 -0
  75. package/package.json +100 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Miiflow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,563 @@
1
+ # @miiflow/assistant-ui
2
+
3
+ React components and hooks for building custom Miiflow chat interfaces. Install as an npm package for full control over layout, styling, and behavior.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @miiflow/assistant-ui
9
+ ```
10
+
11
+ **Peer dependencies:** `react >= 18`, `react-dom >= 18`
12
+
13
+ The styled components use [TailwindCSS](https://tailwindcss.com/). If your project doesn't use Tailwind, import the pre-built CSS instead:
14
+
15
+ ```ts
16
+ import "@miiflow/assistant-ui/styles.css";
17
+ ```
18
+
19
+ If you're embedding inside an existing page and want to avoid Tailwind's preflight (CSS reset) affecting the host page:
20
+
21
+ ```ts
22
+ import "@miiflow/assistant-ui/styles-no-preflight.css";
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```tsx
28
+ import { useMiiflowChat } from "@miiflow/assistant-ui/client";
29
+ import {
30
+ ChatProvider,
31
+ ChatLayout,
32
+ ChatHeader,
33
+ MessageList,
34
+ Message,
35
+ MessageComposer,
36
+ WelcomeScreen,
37
+ } from "@miiflow/assistant-ui/styled";
38
+ import "@miiflow/assistant-ui/styles.css";
39
+
40
+ function Chat() {
41
+ const {
42
+ messages,
43
+ isStreaming,
44
+ streamingMessageId,
45
+ sendMessage,
46
+ uploadFile,
47
+ startNewThread,
48
+ branding,
49
+ brandingCSSVars,
50
+ loading,
51
+ } = useMiiflowChat({
52
+ publicKey: "pk_live_...",
53
+ assistantId: "ast_...",
54
+ });
55
+
56
+ if (loading) return <div>Loading...</div>;
57
+
58
+ const isEmpty = messages.length === 0;
59
+
60
+ return (
61
+ <ChatProvider
62
+ messages={messages}
63
+ isStreaming={isStreaming}
64
+ streamingMessageId={streamingMessageId}
65
+ onSendMessage={sendMessage}
66
+ >
67
+ <div style={{ height: "100vh", ...brandingCSSVars }}>
68
+ <ChatLayout
69
+ isEmpty={isEmpty}
70
+ header={
71
+ <ChatHeader
72
+ title={branding?.customName ?? "Assistant"}
73
+ logo={branding?.chatbotLogo}
74
+ actions={[
75
+ { id: "new", label: "New chat", onClick: startNewThread },
76
+ ]}
77
+ />
78
+ }
79
+ welcomeScreen={
80
+ <WelcomeScreen
81
+ welcomeText={branding?.welcomeMessage}
82
+ placeholders={branding?.rotatingPlaceholders}
83
+ suggestions={branding?.presetQuestions}
84
+ onSubmit={sendMessage}
85
+ onSuggestionClick={sendMessage}
86
+ />
87
+ }
88
+ messageList={
89
+ <MessageList>
90
+ {messages.map((msg) => (
91
+ <Message
92
+ key={msg.id}
93
+ message={msg}
94
+ reasoning={msg.reasoning}
95
+ suggestedActions={msg.suggestedActions}
96
+ onSuggestedAction={(a) => sendMessage(a.value)}
97
+ />
98
+ ))}
99
+ </MessageList>
100
+ }
101
+ composer={
102
+ <MessageComposer
103
+ onSubmit={sendMessage}
104
+ onUploadFile={uploadFile}
105
+ disabled={isStreaming}
106
+ placeholder={branding?.chatboxPlaceholder}
107
+ />
108
+ }
109
+ />
110
+ </div>
111
+ </ChatProvider>
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## Configuration Reference
117
+
118
+ Pass a `MiiflowChatConfig` object to `useMiiflowChat`:
119
+
120
+ | Field | Type | Required | Description |
121
+ |-------|------|----------|-------------|
122
+ | `publicKey` | `string` | Yes | Public API key from the Miiflow dashboard |
123
+ | `assistantId` | `string` | Yes | Assistant ID from the Miiflow dashboard |
124
+ | `userId` | `string` | No | User ID for identity tracking |
125
+ | `userName` | `string` | No | User display name |
126
+ | `userEmail` | `string` | No | User email |
127
+ | `userMetadata` | `string` | No | JSON string of custom user metadata |
128
+ | `hmac` | `string` | No | HMAC for identity verification |
129
+ | `timestamp` | `string` | No | Timestamp for HMAC verification |
130
+ | `baseUrl` | `string` | No | Override API endpoint (default: `https://api.miiflow.ai/api`) |
131
+ | `responseTimeout` | `number` | No | SSE stream timeout in ms (default: `60000`) |
132
+
133
+ ## Hook API — `useMiiflowChat`
134
+
135
+ ```ts
136
+ import { useMiiflowChat } from "@miiflow/assistant-ui/client";
137
+
138
+ const result = useMiiflowChat(config);
139
+ ```
140
+
141
+ ### State
142
+
143
+ | Property | Type | Description |
144
+ |----------|------|-------------|
145
+ | `messages` | `ChatMessage[]` | Messages in the conversation |
146
+ | `isStreaming` | `boolean` | Whether a response is currently streaming |
147
+ | `streamingMessageId` | `string \| null` | ID of the message being streamed |
148
+ | `loading` | `boolean` | Whether the session is still initializing |
149
+ | `error` | `string \| null` | Error message if initialization or sending failed |
150
+ | `session` | `EmbedSession \| null` | Current session data |
151
+ | `branding` | `BrandingData \| null` | Branding configuration from the dashboard |
152
+ | `brandingCSSVars` | `CSSProperties` | CSS custom properties derived from branding |
153
+
154
+ ### Actions
155
+
156
+ | Method | Signature | Description |
157
+ |--------|-----------|-------------|
158
+ | `sendMessage` | `(content: string, attachmentIds?: string[]) => Promise<void>` | Send a message to the assistant |
159
+ | `uploadFile` | `(file: File) => Promise<string>` | Upload a file and get an attachment ID |
160
+ | `startNewThread` | `() => Promise<string>` | Start a new conversation thread |
161
+ | `registerTool` | `(tool: ClientToolDefinition) => Promise<void>` | Register a client-side tool |
162
+ | `registerTools` | `(tools: ClientToolDefinition[]) => Promise<void>` | Register multiple tools |
163
+ | `sendSystemEvent` | `(event: SystemEvent) => Promise<void>` | Send an invisible system event |
164
+
165
+ ## Components Reference
166
+
167
+ ### `ChatProvider`
168
+
169
+ Wraps children and provides chat context via React context.
170
+
171
+ | Prop | Type | Default | Description |
172
+ |------|------|---------|-------------|
173
+ | `messages` | `ChatMessage[]` | — | Messages to display |
174
+ | `isStreaming` | `boolean` | `false` | Whether a response is streaming |
175
+ | `streamingMessageId` | `string \| null` | `null` | ID of the streaming message |
176
+ | `viewerRole` | `ParticipantRole` | `"user"` | Viewer's role (determines message alignment) |
177
+ | `onSendMessage` | `(content: string, attachments?: File[]) => Promise<void>` | — | Message send handler |
178
+ | `onStopStreaming` | `() => void` | — | Stop streaming handler |
179
+ | `onRetryLastMessage` | `() => Promise<void>` | — | Retry last message handler |
180
+ | `onVisualizationAction` | `(event: VisualizationActionEvent) => void` | — | Callback for form/card interactions |
181
+
182
+ ### `ChatLayout`
183
+
184
+ Handles the empty-to-active state transition with crossfade animation. Accepts render slots for each section.
185
+
186
+ | Prop | Type | Default | Description |
187
+ |------|------|---------|-------------|
188
+ | `isEmpty` | `boolean` | — | Whether the chat has no messages |
189
+ | `header` | `ReactNode` | — | Header slot (rendered in both states) |
190
+ | `welcomeScreen` | `ReactNode` | — | Content for empty state |
191
+ | `messageList` | `ReactNode` | — | Message list for active state |
192
+ | `composer` | `ReactNode` | — | Composer for active state |
193
+ | `footer` | `ReactNode` | — | Extra content between list and composer |
194
+ | `variant` | `"standalone" \| "embedded" \| "widget"` | `"standalone"` | Layout variant |
195
+ | `className` | `string` | — | Additional CSS classes |
196
+
197
+ ### `WelcomeScreen`
198
+
199
+ Empty state with rotating placeholder text and suggestion cards.
200
+
201
+ | Prop | Type | Default | Description |
202
+ |------|------|---------|-------------|
203
+ | `placeholders` | `string[]` | `[]` | Rotating placeholder strings |
204
+ | `suggestions` | `string[]` | `[]` | Preset suggestion cards |
205
+ | `onSubmit` | `(message: string) => void` | — | Submit handler for built-in input |
206
+ | `onSuggestionClick` | `(suggestion: string) => void` | — | Suggestion card click handler |
207
+ | `welcomeText` | `string` | `"How can I help you today?"` | Heading text |
208
+ | `composerSlot` | `ReactNode` | — | Override default input with custom composer |
209
+ | `className` | `string` | — | Additional CSS classes |
210
+
211
+ ### `MessageList`
212
+
213
+ Scrollable message container with auto-scroll.
214
+
215
+ | Prop | Type | Default | Description |
216
+ |------|------|---------|-------------|
217
+ | `children` | `ReactNode` | — | Message elements |
218
+ | `autoScroll` | `boolean` | `true` | Auto-scroll to bottom on new messages |
219
+ | `className` | `string` | — | Additional CSS classes |
220
+
221
+ ### `Message`
222
+
223
+ Individual message with markdown rendering, reasoning panel, citations, and visualizations.
224
+
225
+ | Prop | Type | Default | Description |
226
+ |------|------|---------|-------------|
227
+ | `message` | `MessageData` | — | Message data object |
228
+ | `viewerRole` | `ParticipantRole` | `"user"` | Viewer's role (determines alignment) |
229
+ | `showAvatar` | `boolean` | `true` | Show participant avatar |
230
+ | `showTimestamp` | `boolean` | `true` | Show message timestamp |
231
+ | `renderMarkdown` | `boolean` | `true` | Render content as markdown |
232
+ | `reasoning` | `StreamingChunk[]` | — | Reasoning/thinking chunks for collapsible panel |
233
+ | `suggestedActions` | `SuggestedAction[]` | — | Suggested follow-up actions |
234
+ | `onSuggestedAction` | `(action: SuggestedAction) => void` | — | Suggested action click handler |
235
+ | `citations` | `SourceReference[]` | — | Citation sources to display |
236
+ | `visualizations` | `VisualizationChunkData[]` | — | Inline visualizations |
237
+ | `className` | `string` | — | Additional CSS classes |
238
+
239
+ ### `MessageComposer`
240
+
241
+ Rich text editor (Lexical) with file upload, drag-and-drop, and Enter-to-send.
242
+
243
+ | Prop | Type | Default | Description |
244
+ |------|------|---------|-------------|
245
+ | `onSubmit` | `(content: string, attachments?: File[]) => Promise<void>` | — | Submit handler |
246
+ | `onUploadFile` | `(file: File) => Promise<string>` | — | File upload handler (returns attachment ID) |
247
+ | `onAttach` | `(files: File[]) => void` | — | Called when files are attached |
248
+ | `disabled` | `boolean` | `false` | Disable the composer |
249
+ | `supportsAttachments` | `boolean` | `true` | Enable file attachments |
250
+ | `allowedFileTypes` | `string[]` | images, docs, videos | Allowed MIME types |
251
+ | `maxFileSize` | `number` | `104857600` (100MB) | Max file size in bytes |
252
+ | `placeholder` | `string` | `"Type a message..."` | Placeholder text |
253
+ | `isSubmitting` | `boolean` | `false` | Show loading state on send button |
254
+ | `className` | `string` | — | Additional CSS classes |
255
+
256
+ ### `ChatHeader`
257
+
258
+ Title bar with logo, subtitle, action menu, and close button.
259
+
260
+ | Prop | Type | Default | Description |
261
+ |------|------|---------|-------------|
262
+ | `title` | `string` | — | Assistant name |
263
+ | `subtitle` | `string` | — | Description or status text |
264
+ | `logo` | `string \| ReactNode` | — | Logo URL or custom element |
265
+ | `actions` | `ChatHeaderAction[]` | — | Menu items (`{ id, label, icon?, onClick, disabled? }`) |
266
+ | `showClose` | `boolean` | — | Show close button |
267
+ | `onClose` | `() => void` | — | Close button handler |
268
+ | `loading` | `boolean` | — | Show loading skeleton |
269
+ | `className` | `string` | — | Additional CSS classes |
270
+ | `style` | `CSSProperties` | — | Inline styles |
271
+
272
+ ## Styling & Theming
273
+
274
+ ### CSS Import
275
+
276
+ Import the stylesheet to get default styles for all components:
277
+
278
+ ```ts
279
+ import "@miiflow/assistant-ui/styles.css";
280
+ ```
281
+
282
+ ### Branding via CSS Variables
283
+
284
+ The `brandingCSSVars` object from `useMiiflowChat` contains CSS custom properties derived from your dashboard branding settings. Spread it onto the container element:
285
+
286
+ ```tsx
287
+ <div style={brandingCSSVars}>
288
+ <ChatLayout ... />
289
+ </div>
290
+ ```
291
+
292
+ Available CSS variables:
293
+
294
+ | Variable | Source | Description |
295
+ |----------|--------|-------------|
296
+ | `--chat-primary` | `backgroundBubbleColor` | Primary accent color |
297
+ | `--chat-user-message-bg` | `backgroundBubbleColor` | User message bubble background |
298
+ | `--chat-header-bg` | `headerBackgroundColor` | Header background color |
299
+ | `--chat-message-font-size` | `messageFontSize` | Base message font size |
300
+
301
+ ### TailwindCSS Customization
302
+
303
+ All components accept a `className` prop for Tailwind utility overrides:
304
+
305
+ ```tsx
306
+ <MessageComposer className="rounded-none border-0" />
307
+ ```
308
+
309
+ ## File Uploads
310
+
311
+ Pass `onUploadFile={uploadFile}` to `MessageComposer` to enable server-side file uploads:
312
+
313
+ ```tsx
314
+ const { sendMessage, uploadFile } = useMiiflowChat(config);
315
+
316
+ <MessageComposer
317
+ onSubmit={sendMessage}
318
+ onUploadFile={uploadFile}
319
+ supportsAttachments={true}
320
+ />
321
+ ```
322
+
323
+ 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.
324
+
325
+ ## Client-Side Tools
326
+
327
+ Register tools that the assistant can invoke on the client:
328
+
329
+ ```ts
330
+ const { registerTool } = useMiiflowChat(config);
331
+
332
+ await registerTool({
333
+ name: "get_weather",
334
+ description: "Get current weather for a city",
335
+ parameters: {
336
+ type: "object",
337
+ properties: {
338
+ city: { type: "string", description: "City name" },
339
+ },
340
+ required: ["city"],
341
+ },
342
+ handler: async (params) => {
343
+ const response = await fetch(`/api/weather?city=${params.city}`);
344
+ return response.json();
345
+ },
346
+ });
347
+ ```
348
+
349
+ Tools are automatically re-registered when starting a new thread via `startNewThread()`.
350
+
351
+ 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.
352
+
353
+ ## System Events
354
+
355
+ Send invisible context events that the assistant can use to inform its responses:
356
+
357
+ ```ts
358
+ const { sendSystemEvent } = useMiiflowChat(config);
359
+
360
+ await sendSystemEvent({
361
+ action: "page_navigation",
362
+ description: "User navigated to /pricing",
363
+ followUpInstruction: "If relevant, mention our pricing plans",
364
+ });
365
+ ```
366
+
367
+ | Field | Type | Required | Description |
368
+ |-------|------|----------|-------------|
369
+ | `action` | `string` | Yes | Event identifier |
370
+ | `description` | `string` | Yes | Human-readable description of what happened |
371
+ | `followUpInstruction` | `string` | Yes | Instruction for the assistant |
372
+ | `metadata` | `Record<string, unknown>` | No | Additional structured data |
373
+
374
+ ## Identity Verification (HMAC)
375
+
376
+ For secure identity verification, compute an HMAC on your server and pass it to the config:
377
+
378
+ ```tsx
379
+ useMiiflowChat({
380
+ publicKey: "pk_live_...",
381
+ assistantId: "ast_...",
382
+ userId: "user_123",
383
+ userName: "Jane Doe",
384
+ userEmail: "jane@example.com",
385
+ hmac: serverComputedHmac,
386
+ timestamp: serverTimestamp,
387
+ });
388
+ ```
389
+
390
+ The `hmac` and `timestamp` should be generated server-side using your secret key. See the Miiflow dashboard for your HMAC secret.
391
+
392
+ ## Visualizations
393
+
394
+ Assistant messages can contain rich visualizations (charts, tables, forms, etc.) rendered inline via `[VIZ:id]` markers. The `Message` component handles this automatically when you pass the `visualizations` prop.
395
+
396
+ ### Built-in Types
397
+
398
+ | Type | Component | Description |
399
+ |------|-----------|-------------|
400
+ | `chart` | `ChartVisualization` | Line, bar, pie, area, scatter charts (Recharts) |
401
+ | `table` | `TableVisualization` | Sortable, paginated data tables |
402
+ | `card` | `CardVisualization` | Structured cards with sections, actions, images |
403
+ | `kpi` | `KpiVisualization` | Key performance indicator metrics with trends |
404
+ | `code_preview` | `CodePreviewVisualization` | Syntax-highlighted code blocks |
405
+ | `form` | `FormVisualization` | Interactive forms with validation |
406
+
407
+ ### Visualization Registry
408
+
409
+ Instead of a hardcoded switch, visualizations are resolved through a registry. You can register custom visualization types that the `VisualizationRenderer` will render automatically:
410
+
411
+ ```ts
412
+ import {
413
+ registerVisualization,
414
+ getVisualization,
415
+ getRegisteredTypes,
416
+ } from "@miiflow/assistant-ui/styled";
417
+
418
+ // Register a custom visualization type
419
+ registerVisualization("my_widget", {
420
+ component: MyWidgetComponent,
421
+ schema: myWidgetZodSchema, // optional — enables data validation
422
+ });
423
+
424
+ // Check what's registered
425
+ console.log(getRegisteredTypes());
426
+ // ["chart", "table", "card", "kpi", "code_preview", "form", "my_widget"]
427
+ ```
428
+
429
+ Your component receives these props:
430
+
431
+ ```ts
432
+ interface VisualizationComponentProps {
433
+ data: any;
434
+ config?: VisualizationConfig;
435
+ isStreaming?: boolean;
436
+ onAction?: (event: VisualizationActionEvent) => void;
437
+ }
438
+ ```
439
+
440
+ **Overriding built-ins:** Call `registerVisualization("chart", { component: MyChart })` to replace a built-in type with your own implementation. The last registration wins.
441
+
442
+ ### Schema Validation
443
+
444
+ Each built-in type has a [Zod](https://zod.dev) schema registered alongside its component. When a schema is present, `VisualizationRenderer` validates the data before rendering. Invalid data shows a descriptive error fallback instead of crashing.
445
+
446
+ You can import the schemas directly for use in your own code:
447
+
448
+ ```ts
449
+ import {
450
+ chartVisualizationSchema,
451
+ tableVisualizationSchema,
452
+ cardVisualizationSchema,
453
+ kpiVisualizationSchema,
454
+ codePreviewVisualizationSchema,
455
+ formVisualizationSchema,
456
+ } from "@miiflow/assistant-ui/styled";
457
+
458
+ const result = chartVisualizationSchema.safeParse(data);
459
+ if (!result.success) {
460
+ console.error("Invalid chart data:", result.error.issues);
461
+ }
462
+ ```
463
+
464
+ To add validation to a custom type, pass a `schema` when registering:
465
+
466
+ ```ts
467
+ import { z } from "zod";
468
+
469
+ const mySchema = z.object({
470
+ message: z.string(),
471
+ count: z.number().min(0),
472
+ });
473
+
474
+ registerVisualization("my_widget", {
475
+ component: MyWidget,
476
+ schema: mySchema,
477
+ });
478
+ ```
479
+
480
+ **Note:** `zod` is a peer dependency (`>= 3.0.0`). Install it in your project if you haven't already.
481
+
482
+ ### Interaction Callbacks
483
+
484
+ Forms and cards can trigger user interactions (submit, cancel, button click). Instead of listening for global `CustomEvent`s, pass a callback through `ChatProvider`:
485
+
486
+ ```tsx
487
+ function handleVisualizationAction(event: VisualizationActionEvent) {
488
+ switch (event.type) {
489
+ case "form_submit":
490
+ console.log("Form submitted:", event.action, event.data);
491
+ // Send the form data back to the assistant, save to DB, etc.
492
+ break;
493
+ case "form_cancel":
494
+ console.log("Form cancelled:", event.action);
495
+ break;
496
+ case "card_action":
497
+ console.log("Card action clicked:", event.action);
498
+ break;
499
+ }
500
+ }
501
+
502
+ <ChatProvider
503
+ messages={messages}
504
+ onSendMessage={sendMessage}
505
+ onVisualizationAction={handleVisualizationAction}
506
+ >
507
+ ...
508
+ </ChatProvider>
509
+ ```
510
+
511
+ The `VisualizationActionEvent` type is a discriminated union:
512
+
513
+ ```ts
514
+ type VisualizationActionEvent =
515
+ | { type: "form_submit"; action: string; data: Record<string, unknown> }
516
+ | { type: "form_cancel"; action: string }
517
+ | { type: "card_action"; action: string };
518
+ ```
519
+
520
+ **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`).
521
+
522
+ ### Using `VisualizationRenderer` Standalone
523
+
524
+ You can render visualizations outside of `Message` by using `VisualizationRenderer` directly:
525
+
526
+ ```tsx
527
+ import { VisualizationRenderer } from "@miiflow/assistant-ui/styled";
528
+
529
+ <VisualizationRenderer
530
+ data={{
531
+ id: "viz-1",
532
+ type: "chart",
533
+ title: "Monthly Revenue",
534
+ data: {
535
+ chartType: "bar",
536
+ series: [{ name: "Revenue", data: [{ x: "Jan", y: 100 }, { x: "Feb", y: 150 }] }],
537
+ },
538
+ }}
539
+ onAction={(event) => console.log(event)}
540
+ />
541
+ ```
542
+
543
+ ## Package Exports
544
+
545
+ | Import | Description |
546
+ |--------|-------------|
547
+ | `@miiflow/assistant-ui` | Core types, context, hooks, primitives |
548
+ | `@miiflow/assistant-ui/styled` | TailwindCSS-styled components, visualization registry, schemas |
549
+ | `@miiflow/assistant-ui/client` | `useMiiflowChat` hook, session utilities, types |
550
+ | `@miiflow/assistant-ui/primitives` | Headless unstyled component primitives |
551
+ | `@miiflow/assistant-ui/styles.css` | Full CSS (includes Tailwind preflight) |
552
+ | `@miiflow/assistant-ui/styles-no-preflight.css` | CSS without preflight (for embedding in existing pages) |
553
+
554
+ ### Key Exports from `@miiflow/assistant-ui/styled`
555
+
556
+ **Visualization Registry:**
557
+ `registerVisualization`, `getVisualization`, `getRegisteredTypes`, `VisualizationEntry`
558
+
559
+ **Visualization Schemas:**
560
+ `chartVisualizationSchema`, `tableVisualizationSchema`, `cardVisualizationSchema`, `kpiVisualizationSchema`, `codePreviewVisualizationSchema`, `formVisualizationSchema`
561
+
562
+ **Types:**
563
+ `VisualizationActionEvent`, `VisualizationChunkData`, `VisualizationConfig`, `VisualizationType`