@hsafa/ui-sdk 0.3.3 → 0.4.1

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.
@@ -0,0 +1,408 @@
1
+ # Migrating from HsafaChat to Headless Hooks
2
+
3
+ This guide helps you migrate from using the pre-built `HsafaChat` component to the headless hooks for full UI customization.
4
+
5
+ ## Why Migrate?
6
+
7
+ **Benefits of Headless Hooks:**
8
+ - 🎨 Complete control over UI/UX
9
+ - 🔧 Custom styling with your design system
10
+ - ⚡ Optimize for your specific use case
11
+ - 📦 Use only what you need (smaller bundle)
12
+ - 🧩 Integrate seamlessly with existing components
13
+
14
+ ## Before: Using HsafaChat
15
+
16
+ ```tsx
17
+ import { HsafaProvider, HsafaChat } from '@hsafa/ui-sdk';
18
+
19
+ function App() {
20
+ return (
21
+ <HsafaProvider baseUrl="http://localhost:3000">
22
+ <HsafaChat
23
+ agentId="my-agent"
24
+ theme="dark"
25
+ primaryColor="#0ea5e9"
26
+ HsafaTools={{
27
+ customTool: async (input) => {
28
+ return { result: 'Done!' };
29
+ }
30
+ }}
31
+ HsafaUI={{
32
+ CustomCard: ({ data }) => <div>{data.text}</div>
33
+ }}
34
+ />
35
+ </HsafaProvider>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ## After: Using Headless Hooks
41
+
42
+ ```tsx
43
+ import { useHsafaAgent, useAutoScroll } from '@hsafa/ui-sdk';
44
+
45
+ function App() {
46
+ const agent = useHsafaAgent({
47
+ agentId: 'my-agent',
48
+ baseUrl: 'http://localhost:3000',
49
+
50
+ // Same tools, just passed differently
51
+ tools: {
52
+ customTool: async (input) => {
53
+ return { result: 'Done!' };
54
+ }
55
+ },
56
+
57
+ // Same UI components
58
+ uiComponents: {
59
+ CustomCard: ({ data }) => <div>{data.text}</div>
60
+ },
61
+
62
+ // Theme colors for built-in forms
63
+ colors: {
64
+ primaryColor: '#0ea5e9',
65
+ backgroundColor: '#0B0B0F',
66
+ textColor: '#EDEEF0',
67
+ }
68
+ });
69
+
70
+ const scrollRef = useAutoScroll<HTMLDivElement>(agent.isLoading);
71
+
72
+ return (
73
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
74
+ {/* Messages */}
75
+ <div ref={scrollRef} style={{ flex: 1, overflow: 'auto', padding: '20px' }}>
76
+ {agent.messages.map(msg => (
77
+ <div key={msg.id} style={{ marginBottom: '15px' }}>
78
+ <strong>{msg.role}:</strong> {msg.content}
79
+ </div>
80
+ ))}
81
+ {agent.isLoading && <div>Loading...</div>}
82
+ </div>
83
+
84
+ {/* Input */}
85
+ <div style={{ padding: '20px' }}>
86
+ <input
87
+ value={agent.input}
88
+ onChange={(e) => agent.setInput(e.target.value)}
89
+ onKeyPress={(e) => e.key === 'Enter' && agent.sendMessage()}
90
+ disabled={agent.isLoading}
91
+ />
92
+ <button onClick={() => agent.sendMessage()} disabled={agent.isLoading}>
93
+ Send
94
+ </button>
95
+ </div>
96
+ </div>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ## Feature-by-Feature Migration
102
+
103
+ ### 1. Basic Chat
104
+
105
+ **Before:**
106
+ ```tsx
107
+ <HsafaChat agentId="my-agent" />
108
+ ```
109
+
110
+ **After:**
111
+ ```tsx
112
+ const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '...' });
113
+ // Then build your UI using agent.messages, agent.input, etc.
114
+ ```
115
+
116
+ ### 2. File Uploads
117
+
118
+ **Before:**
119
+ Built-in file upload in HsafaChat
120
+
121
+ **After:**
122
+ ```tsx
123
+ import { useFileUpload } from '@hsafa/ui-sdk';
124
+
125
+ const fileUpload = useFileUpload('http://localhost:3000');
126
+
127
+ // In your component:
128
+ <input
129
+ type="file"
130
+ ref={fileUpload.fileInputRef}
131
+ onChange={(e) => fileUpload.handleFileSelection(e.target.files, setError)}
132
+ />
133
+
134
+ // When sending:
135
+ agent.sendMessage({
136
+ text: agent.input,
137
+ files: fileUpload.attachments.map(att => ({
138
+ type: 'file',
139
+ url: att.url,
140
+ mediaType: att.mimeType,
141
+ }))
142
+ });
143
+ ```
144
+
145
+ ### 3. Chat History
146
+
147
+ **Before:**
148
+ Built-in history modal in HsafaChat
149
+
150
+ **After:**
151
+ ```tsx
152
+ import { useChatStorage } from '@hsafa/ui-sdk';
153
+
154
+ const storage = useChatStorage({
155
+ agentId: 'my-agent',
156
+ chatId: agent.chatId,
157
+ messages: agent.messages,
158
+ isLoading: agent.isLoading,
159
+ autoSave: true,
160
+ autoRestore: true,
161
+ });
162
+
163
+ // Build your own history UI:
164
+ <div>
165
+ {storage.chatList.map(chat => (
166
+ <div key={chat.id} onClick={() => storage.switchToChat(chat.id, agent.setMessages)}>
167
+ {chat.title}
168
+ </div>
169
+ ))}
170
+ </div>
171
+ ```
172
+
173
+ ### 4. Message Editing
174
+
175
+ **Before:**
176
+ Built-in edit functionality in HsafaChat
177
+
178
+ **After:**
179
+ ```tsx
180
+ import { useMessageEditor } from '@hsafa/ui-sdk';
181
+
182
+ const editor = useMessageEditor({
183
+ messages: agent.messages,
184
+ isLoading: agent.isLoading,
185
+ sendMessage: agent.sendMessage,
186
+ setMessages: agent.setMessages,
187
+ });
188
+
189
+ // In your message rendering:
190
+ {editor.isEditing(msg.id) ? (
191
+ <div>
192
+ <textarea
193
+ value={editor.editingText}
194
+ onChange={(e) => editor.setEditingText(e.target.value)}
195
+ />
196
+ <button onClick={() => editor.saveEdit(msg.id)}>Save</button>
197
+ <button onClick={editor.cancelEdit}>Cancel</button>
198
+ </div>
199
+ ) : (
200
+ <div>
201
+ {msg.content}
202
+ <button onClick={() => editor.startEdit(msg.id, msg.content)}>Edit</button>
203
+ </div>
204
+ )}
205
+ ```
206
+
207
+ ### 5. Custom Tools
208
+
209
+ **Before:**
210
+ ```tsx
211
+ <HsafaChat
212
+ HsafaTools={{
213
+ myTool: async (input) => { /* ... */ }
214
+ }}
215
+ />
216
+ ```
217
+
218
+ **After:**
219
+ ```tsx
220
+ const agent = useHsafaAgent({
221
+ tools: {
222
+ myTool: async (input) => { /* ... */ }
223
+ }
224
+ });
225
+ ```
226
+
227
+ ### 6. Custom UI Components
228
+
229
+ **Before:**
230
+ ```tsx
231
+ <HsafaChat
232
+ HsafaUI={{
233
+ MyComponent: ({ data }) => <div>{data.text}</div>
234
+ }}
235
+ />
236
+ ```
237
+
238
+ **After:**
239
+ ```tsx
240
+ const agent = useHsafaAgent({
241
+ uiComponents: {
242
+ MyComponent: ({ data }) => <div>{data.text}</div>
243
+ }
244
+ });
245
+ ```
246
+
247
+ ### 7. Theme/Colors
248
+
249
+ **Before:**
250
+ ```tsx
251
+ <HsafaChat
252
+ theme="dark"
253
+ primaryColor="#0ea5e9"
254
+ backgroundColor="#0B0B0F"
255
+ textColor="#EDEEF0"
256
+ />
257
+ ```
258
+
259
+ **After:**
260
+ ```tsx
261
+ const agent = useHsafaAgent({
262
+ colors: {
263
+ primaryColor: '#0ea5e9',
264
+ backgroundColor: '#0B0B0F',
265
+ textColor: '#EDEEF0',
266
+ // ... other colors
267
+ }
268
+ });
269
+
270
+ // Then apply these colors in your own styling
271
+ ```
272
+
273
+ ### 8. Callbacks
274
+
275
+ **Before:**
276
+ ```tsx
277
+ <HsafaChat
278
+ onMessagesChange={(messages) => console.log(messages)}
279
+ />
280
+ ```
281
+
282
+ **After:**
283
+ ```tsx
284
+ const agent = useHsafaAgent({
285
+ onMessagesChange: (messages) => console.log(messages),
286
+ onFinish: (message) => console.log('Message done:', message),
287
+ onError: (error) => console.error('Error:', error),
288
+ });
289
+ ```
290
+
291
+ ### 9. Dynamic Pages
292
+
293
+ **Before:**
294
+ ```tsx
295
+ <HsafaChat
296
+ dynamicPageTypes={[
297
+ { type: 'product-catalog', schema: { /* ... */ } }
298
+ ]}
299
+ />
300
+ ```
301
+
302
+ **After:**
303
+ ```tsx
304
+ const agent = useHsafaAgent({
305
+ dynamicPageTypes: [
306
+ { type: 'product-catalog', schema: { /* ... */ } }
307
+ ]
308
+ });
309
+
310
+ // Access dynamic page operations:
311
+ agent.dynamicPage?.getOperations()
312
+ ```
313
+
314
+ ## Common Patterns
315
+
316
+ ### Pattern 1: Minimal Chat
317
+
318
+ ```tsx
319
+ function MinimalChat() {
320
+ const agent = useHsafaAgent({
321
+ agentId: 'my-agent',
322
+ baseUrl: 'http://localhost:3000',
323
+ });
324
+
325
+ return (
326
+ <div className="chat-container">
327
+ <div className="messages">
328
+ {agent.messages.map(msg => (
329
+ <div key={msg.id} className={msg.role}>
330
+ {msg.content}
331
+ </div>
332
+ ))}
333
+ </div>
334
+ <div className="input-area">
335
+ <input
336
+ value={agent.input}
337
+ onChange={(e) => agent.setInput(e.target.value)}
338
+ onKeyPress={(e) => e.key === 'Enter' && agent.sendMessage()}
339
+ />
340
+ <button onClick={() => agent.sendMessage()}>Send</button>
341
+ </div>
342
+ </div>
343
+ );
344
+ }
345
+ ```
346
+
347
+ ### Pattern 2: Full-Featured Chat
348
+
349
+ ```tsx
350
+ function FullChat() {
351
+ const agent = useHsafaAgent({ /* config */ });
352
+ const fileUpload = useFileUpload('http://localhost:3000');
353
+ const storage = useChatStorage({ /* config */ });
354
+ const editor = useMessageEditor({ /* config */ });
355
+ const scrollRef = useAutoScroll(agent.isLoading);
356
+
357
+ return (
358
+ <div className="chat-layout">
359
+ {/* Sidebar with history */}
360
+ <aside>
361
+ {storage.chatList.map(chat => (
362
+ <ChatItem key={chat.id} {...chat} />
363
+ ))}
364
+ </aside>
365
+
366
+ {/* Main chat */}
367
+ <main>
368
+ <div ref={scrollRef} className="messages">
369
+ {agent.messages.map(msg => (
370
+ <Message key={msg.id} {...msg} editor={editor} />
371
+ ))}
372
+ </div>
373
+ <ChatInput
374
+ agent={agent}
375
+ fileUpload={fileUpload}
376
+ />
377
+ </main>
378
+ </div>
379
+ );
380
+ }
381
+ ```
382
+
383
+ ## Migration Checklist
384
+
385
+ - [ ] Identify all HsafaChat props you're using
386
+ - [ ] Map each prop to the corresponding hook configuration
387
+ - [ ] Replace `<HsafaChat />` with your custom UI
388
+ - [ ] Use `useHsafaAgent` for core chat functionality
389
+ - [ ] Add `useFileUpload` if you need file attachments
390
+ - [ ] Add `useChatStorage` if you need chat history
391
+ - [ ] Add `useMessageEditor` if you need message editing
392
+ - [ ] Add `useAutoScroll` for auto-scrolling behavior
393
+ - [ ] Test all functionality thoroughly
394
+ - [ ] Update your styling to match your design system
395
+
396
+ ## Benefits After Migration
397
+
398
+ 1. **Full Control**: Style everything exactly as you want
399
+ 2. **Better Integration**: Seamlessly match your existing design system
400
+ 3. **Optimized Bundle**: Only include the hooks you need
401
+ 4. **Enhanced UX**: Build flows specific to your use case
402
+ 5. **Easier Testing**: Test UI and logic independently
403
+
404
+ ## Need Help?
405
+
406
+ - 📚 [Headless Usage Guide](./HEADLESS_USAGE.md)
407
+ - 📁 [Example Implementations](../examples/)
408
+ - 🐛 [Report Issues](https://github.com/husamabusafa/hsafa/issues)
package/docs/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # HSAFA UI SDK — Advanced Guide
2
2
 
3
- Modern React SDK for integrating AI agents built with HSAFA AI Agent Studio into any web app. This guide covers architecture, setup, customization, streaming, actions, UI component injection, theming, i18n/RTL, persistence, and best practices.
3
+ Modern React SDK for integrating AI agents built with HSAFA AI Agent Studio into any web app. This guide covers architecture, setup, customization, streaming, UI component injection, theming, RTL, persistence, and best practices.
4
4
 
5
5
  - **Package**: `@hsafa/ui-sdk`
6
6
  - **Entry point**: `sdk/src/index.ts`
7
- - **Core building blocks**: `HsafaProvider`, `HsafaChat`, `useHsafaAction`, `useHsafaComponent`, `useHsafa`
7
+ - **Core building blocks**: `HsafaProvider`, `HsafaChat`, `useHsafaAgent`
8
8
  - **Generated API reference**: `sdk/docs/api/` (TypeDoc)
9
+ - **Handbook (recommended)**: `sdk/docs/handbook/`
9
10
 
10
11
  ---
11
12
 
@@ -52,14 +53,10 @@ What this does under the hood:
52
53
 
53
54
  ## 3) Architecture overview
54
55
 
55
- - **Provider**: `HsafaProvider` stores SDK config and dynamic registries:
56
- - `actions: Map<string, HsafaActionHandler>`
57
- - `components: Map<string, React.ComponentType>`
58
- - **Chat UI**: `HsafaChat` handles input, history, streaming, rendering of assistant responses, and attachments.
59
- - **Streaming**: `useAgentStreaming()` parses NDJSON event stream into a structured timeline:
60
- - `first-agent-*`, `main-agent-*` events, tool calls/results, reasoning, and final response items.
61
- - **Action execution**: agent’s response items can include `type: 'action'`. Use `useHsafaAction()` to register handlers.
62
- - **UI injection**: agent’s response items can include `type: 'ui'` to render custom components registered via `useHsafaComponent()`.
56
+ - **Provider**: `HsafaProvider` stores SDK config and global UI preferences; tracks per-chat streaming/open state and dynamic page types.
57
+ - **Chat UI**: `HsafaChat` handles input, history, streaming, and rendering of tool-driven UI (`HsafaUI`) and inline forms.
58
+ - **Streaming**: Vercel AI SDK v5 `useChat()` is used under the hood (or via the headless `useHsafaAgent()`).
59
+ - **Tools & UI**: Provide tools via `HsafaTools` (or `tools` in headless) and UI components via `HsafaUI` (or `uiComponents` in headless).
63
60
 
64
61
  Server contract (by default used by `HsafaChat`):
65
62
  - POST `{baseUrl}/api/run/:agentId` — streaming NDJSON
@@ -72,22 +69,19 @@ Server contract (by default used by `HsafaChat`):
72
69
  `HsafaProvider` (see `sdk/src/providers/HsafaProvider.tsx`):
73
70
  - Props:
74
71
  - `baseUrl?: string` — base URL for API calls (e.g. `""` for same-origin or `"https://api.example.com"`).
72
+ - `dir?: 'ltr' | 'rtl'`, `theme?: 'dark' | 'light'`
75
73
  - Context (`useHsafa()`):
76
74
  - `baseUrl?: string`
77
- - `actions: Map<string, HsafaActionHandler>`
78
- - `components: Map<string, React.ComponentType<any>>`
79
- - `registerAction(name, handler) => unregister()`
80
- - `unregisterAction(name, handler?)`
81
- - `registerComponent(name, component) => unregister()`
82
- - `unregisterComponent(name, component?)`
75
+ - Per-chat state: `setStreamingState(chatId, isStreaming)`, `setChatOpenState(chatId, isOpen)`, `currentChatId`, `setCurrentChatId`
76
+ - Dynamic pages: `dynamicPageTypes`, `registerDynamicPageTypes(chatId, types)`, `unregisterDynamicPageTypes(chatId)`
83
77
 
84
78
  Example using `useHsafa()` directly:
85
79
  ```tsx
86
80
  import { useHsafa } from '@hsafa/ui-sdk';
87
81
 
88
82
  function Debug() {
89
- const { actions, components, baseUrl } = useHsafa();
90
- // inspect registries or baseUrl
83
+ const { baseUrl } = useHsafa();
84
+ // inspect config if needed
91
85
  return null;
92
86
  }
93
87
  ```
@@ -120,55 +114,42 @@ Behavior highlights:
120
114
  - Persists chat sessions under `localStorage` prefix `hsafaChat_${agentId}`.
121
115
  - Supports file/image attachments with size checks and server upload.
122
116
  - Streams partial updates; auto-scrolls intelligently; preserves scroll when toggling reasoning.
123
- - Renders assistant "items" including markdown, mermaid diagrams, actions, and custom UI components.
117
+ - Renders assistant "items" including markdown, mermaid diagrams, and custom UI components.
124
118
 
125
119
  ---
126
120
 
127
- ## 6) Registering actions (agent -> app)
121
+ ## 6) Providing tools (agent -> UI)
128
122
 
129
- Use `useHsafaAction(name, handler)` to make functions callable by the agent. The handler receives `(params, meta)` where `meta` includes the `trigger` type:
130
- - `'partial'` — called during streaming (when explicitly enabled by the agent/SDK logic)
131
- - `'params_complete'` — parameters stabilized mid-stream (debounced and deduped)
132
- - `'final'` — after finalization
123
+ Expose frontend-executed tools to the agent by passing them as `HsafaTools` (or `tools` in headless). Tools can be standard functions or streaming-friendly objects with `executeEachToken` (executed progressively during streaming).
133
124
 
134
125
  ```tsx
135
- import { useHsafaAction } from '@hsafa/ui-sdk';
136
-
137
- export function CartActions() {
138
- useHsafaAction('addToCart', async (params, meta) => {
139
- // params: arbitrary JSON inferred by the agent
140
- // meta: { name, trigger, index, assistantMessageId?, chatId? }
141
- await fetch('/api/cart', { method: 'POST', body: JSON.stringify(params) });
142
- return { success: true };
143
- });
144
- return null;
145
- }
126
+ const tools = {
127
+ add: async ({ a, b }: any) => ({ sum: a + b }),
128
+ editObject: { executeEachToken: true, tool: async (input: any) => {/* ... */} }
129
+ };
130
+
131
+ <HsafaChat agentId="my-agent" HsafaTools={tools} />
146
132
  ```
147
133
 
148
- Advanced execution behavior is implemented in `useActions()` / `useStreaming()` (parameter stabilization, debouncing, final guarantees). For most apps, just register via `useHsafaAction()`.
134
+ Execution behavior (parameter stabilization, debouncing, final guarantees) is handled internally by the SDK.
149
135
 
150
136
  ---
151
137
 
152
- ## 7) Registering UI components (agent-rendered UI)
138
+ ## 7) Providing UI components (agent-rendered)
153
139
 
154
- Use `useHsafaComponent(name, Component)` to expose UI that the agent can render with an item of shape `{ type: 'ui', component: name, props: {...} }`.
140
+ Expose renderable components to the agent via `HsafaUI` (or `uiComponents` in headless). The agent can render them by name using the UI tool.
155
141
 
156
142
  ```tsx
157
- import { useHsafaComponent } from '@hsafa/ui-sdk';
158
-
159
143
  function ProductCard({ name, price }: { name: string; price: number }) {
160
144
  return <div>{name}: ${price}</div>;
161
145
  }
162
146
 
163
- export function UIRegistry() {
164
- useHsafaComponent('ProductCard', ProductCard);
165
- return null;
166
- }
147
+ <HsafaChat agentId="my-agent" HsafaUI={{ ProductCard }} />
167
148
  ```
168
149
 
169
150
  If an agent returns an unregistered component name, `HsafaChat` will render a helpful placeholder with the props payload so you can register it.
170
151
 
171
- Relevant renderer: `sdk/src/components/AssistantMessageItems.tsx`.
152
+ Relevant renderer: `sdk/src/components/hsafa-chat/AssistantMassage.tsx`.
172
153
 
173
154
  ---
174
155
 
@@ -176,8 +157,7 @@ Relevant renderer: `sdk/src/components/AssistantMessageItems.tsx`.
176
157
 
177
158
  `HsafaChat` expects a streaming sequence from the server that ultimately yields a `response` with `items`:
178
159
  - Strings are rendered as Markdown (`MarkdownRendererWithMermaid`).
179
- - Objects with `type: 'ui'` render registered components via the provider registry.
180
- - Objects with `type: 'action'` show execution status and trigger corresponding action handlers.
160
+ - Objects with `type: 'ui'` render components provided via `HsafaUI`/`uiComponents`.
181
161
  - Tool calls/results and reasoning are also visualized.
182
162
 
183
163
  Example of a single final item payload (simplified):
@@ -214,16 +194,10 @@ Markdown with Mermaid is supported via `MarkdownRenderer` / `MarkdownRendererWit
214
194
 
215
195
  ---
216
196
 
217
- ## 10) i18n and RTL
218
-
219
- - Supported languages: `'en' | 'ar'` (see `sdk/src/i18n/translations.ts`).
220
- - You can pass `language` and/or `dir` to `HsafaChat`:
197
+ ## 10) RTL and copy overrides
221
198
 
222
- ```tsx
223
- <HsafaChat agentId="my-agent" language="ar" dir="rtl" />
224
- ```
225
-
226
- Common UI strings for input/header/history are localized. You can still override `placeholder` and `title` directly.
199
+ - You can pass `dir` to `HsafaChat` (or set globally via `HsafaProvider`).
200
+ - Override UI copy via props (e.g., `placeholder`, `title`) or render custom components above the input.
227
201
 
228
202
  ---
229
203
 
@@ -255,7 +229,7 @@ Server is expected to return:
255
229
 
256
230
  ## 13) Streaming contract (server)
257
231
 
258
- The server should stream NDJSON lines with event `type` keys consumed by `useAgentStreaming()` (see `sdk/src/hooks/useAgentStreaming.ts`). Common events include:
232
+ The server should stream NDJSON lines with event `type` keys consumed by Vercel AI SDK v5 `useChat()` (converted via `toUIMessageStream`). Common events include:
259
233
  - `first-agent-start|partial|end`
260
234
  - `main-agent-start|skipped|reasoning-start|reasoning-delta|reasoning-end`
261
235
  - `main-agent-tool-call-start|tool-call|tool-result|tool-error`
@@ -264,7 +238,7 @@ The server should stream NDJSON lines with event `type` keys consumed by `useAge
264
238
  - `final` (with `value.items`)
265
239
  - `error`
266
240
 
267
- You can adopt any LLM/tooling backend as long as you conform to the above event stream and endpoints.
241
+ You can adopt any LLM/tooling backend as long as you conform to the above event stream and endpoints. See also the handbook: `sdk/docs/handbook/04-Streaming-and-Transport.md` and `08-Server-Integration.md`.
268
242
 
269
243
  ---
270
244
 
@@ -275,23 +249,22 @@ See ready-to-run examples in `sdk/examples/`:
275
249
  - `ecommerce-agent.tsx`
276
250
  - `nested-chat-example.tsx`
277
251
 
278
- A minimal page:
252
+ A minimal page with custom UI and tools:
279
253
  ```tsx
280
- import { HsafaProvider, HsafaChat, useHsafaAction, useHsafaComponent } from '@hsafa/ui-sdk';
254
+ import { HsafaProvider, HsafaChat } from '@hsafa/ui-sdk';
281
255
 
282
- function Registry() {
283
- useHsafaAction('notify', async (params) => {
284
- console.log('Notify', params);
285
- });
286
- useHsafaComponent('ProductCard', (p: any) => <div>{p.name}: ${p.price}</div>);
287
- return null;
288
- }
256
+ const tools = {
257
+ add: async ({ a, b }: any) => ({ sum: a + b })
258
+ };
259
+
260
+ const UI = {
261
+ ProductCard: (p: any) => <div>{p.name}: ${p.price}</div>
262
+ };
289
263
 
290
264
  export default function Page() {
291
265
  return (
292
266
  <HsafaProvider baseUrl="">
293
- <Registry />
294
- <HsafaChat agentId="my-agent" theme="dark" />
267
+ <HsafaChat agentId="my-agent" theme="dark" HsafaTools={tools} HsafaUI={UI} />
295
268
  </HsafaProvider>
296
269
  );
297
270
  }
@@ -303,8 +276,7 @@ export default function Page() {
303
276
 
304
277
  - Ensure your server responds to `POST /api/run/:agentId` with `Content-Type: application/x-ndjson` and streams events, not a single JSON.
305
278
  - If attachments fail, verify `POST /api/uploads` exists and returns the required JSON fields.
306
- - Unregistered UI component? Check the `component` name in items and make sure you called `useHsafaComponent(name, Comp)` under the same `HsafaProvider`.
307
- - Actions not firing? Confirm the action name matches and that the agent actually emits `type: 'action'` items. Check browser console warnings from `useActions()`/`useStreaming()`.
279
+ - Unregistered UI component? Check the `component` name in items and make sure it exists in `HsafaUI` (or `uiComponents` for headless usage).
308
280
 
309
281
  ---
310
282