@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.
- package/README.md +133 -2
- package/dist/index.cjs +23 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +572 -20
- package/dist/index.d.ts +572 -20
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/docs/CUSTOM_UI_EXAMPLES.md +309 -0
- package/docs/DYNAMIC_PAGE_SCHEMAS.md +261 -0
- package/docs/HEADLESS_QUICK_REFERENCE.md +426 -0
- package/docs/HEADLESS_USAGE.md +682 -0
- package/docs/MIGRATION_TO_HEADLESS.md +408 -0
- package/docs/README.md +43 -71
- package/docs/handbook/00-Overview.md +69 -0
- package/docs/handbook/01-Quickstart.md +133 -0
- package/docs/handbook/02-Architecture.md +75 -0
- package/docs/handbook/03-Components-and-Hooks.md +81 -0
- package/docs/handbook/04-Streaming-and-Transport.md +73 -0
- package/docs/handbook/05-Tools-and-UI.md +73 -0
- package/docs/handbook/06-Storage-and-History.md +63 -0
- package/docs/handbook/07-Dynamic-Pages.md +49 -0
- package/docs/handbook/08-Server-Integration.md +84 -0
- package/docs/handbook/09-Agent-Studio-Client.md +40 -0
- package/docs/handbook/10-Examples-and-Recipes.md +154 -0
- package/docs/handbook/11-API-Reference-Map.md +48 -0
- package/docs/handbook/README.md +24 -0
- package/examples/custom-tools-example.tsx +401 -0
- package/examples/custom-ui-customizations-example.tsx +543 -0
- package/examples/dynamic-page-example.tsx +380 -0
- package/examples/headless-chat-example.tsx +537 -0
- package/examples/minimal-headless-example.tsx +142 -0
- package/package.json +3 -2
|
@@ -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,
|
|
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`, `
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- **
|
|
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
|
-
-
|
|
78
|
-
-
|
|
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 {
|
|
90
|
-
// inspect
|
|
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,
|
|
117
|
+
- Renders assistant "items" including markdown, mermaid diagrams, and custom UI components.
|
|
124
118
|
|
|
125
119
|
---
|
|
126
120
|
|
|
127
|
-
## 6)
|
|
121
|
+
## 6) Providing tools (agent -> UI)
|
|
128
122
|
|
|
129
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
134
|
+
Execution behavior (parameter stabilization, debouncing, final guarantees) is handled internally by the SDK.
|
|
149
135
|
|
|
150
136
|
---
|
|
151
137
|
|
|
152
|
-
## 7)
|
|
138
|
+
## 7) Providing UI components (agent-rendered)
|
|
153
139
|
|
|
154
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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)
|
|
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
|
-
|
|
223
|
-
|
|
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 `
|
|
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
|
|
254
|
+
import { HsafaProvider, HsafaChat } from '@hsafa/ui-sdk';
|
|
281
255
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
<
|
|
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
|
|
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
|
|