@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,426 @@
|
|
|
1
|
+
# Headless Hooks - Quick Reference
|
|
2
|
+
|
|
3
|
+
A quick reference guide for all headless hooks in the Hsafa SDK.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hsafa/ui-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Import
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {
|
|
15
|
+
useHsafaAgent,
|
|
16
|
+
useChatStorage,
|
|
17
|
+
useMessageEditor,
|
|
18
|
+
useFileUpload,
|
|
19
|
+
useAutoScroll,
|
|
20
|
+
} from '@hsafa/ui-sdk';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## useHsafaAgent
|
|
26
|
+
|
|
27
|
+
**Purpose:** Main hook for agent chat functionality
|
|
28
|
+
|
|
29
|
+
### Configuration
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
const agent = useHsafaAgent({
|
|
33
|
+
// Required
|
|
34
|
+
agentId: string,
|
|
35
|
+
|
|
36
|
+
// Optional
|
|
37
|
+
baseUrl?: string,
|
|
38
|
+
tools?: Record<string, Function | { tool: Function, executeEachToken?: boolean }>,
|
|
39
|
+
uiComponents?: Record<string, React.ComponentType>,
|
|
40
|
+
dynamicPageTypes?: Array<DynamicPageTypeConfig>,
|
|
41
|
+
onFinish?: (message: any) => void,
|
|
42
|
+
onError?: (error: Error) => void,
|
|
43
|
+
onMessagesChange?: (messages: any[]) => void,
|
|
44
|
+
initialMessages?: any[],
|
|
45
|
+
colors?: {
|
|
46
|
+
primaryColor?: string,
|
|
47
|
+
backgroundColor?: string,
|
|
48
|
+
borderColor?: string,
|
|
49
|
+
textColor?: string,
|
|
50
|
+
accentColor?: string,
|
|
51
|
+
mutedTextColor?: string,
|
|
52
|
+
inputBackground?: string,
|
|
53
|
+
cardBackground?: string,
|
|
54
|
+
hoverBackground?: string,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### API
|
|
60
|
+
|
|
61
|
+
| Property | Type | Description |
|
|
62
|
+
|----------|------|-------------|
|
|
63
|
+
| `input` | `string` | Current input text |
|
|
64
|
+
| `setInput(value)` | `(value: string) => void` | Set input text |
|
|
65
|
+
| `messages` | `any[]` | All chat messages |
|
|
66
|
+
| `isLoading` | `boolean` | Whether agent is processing |
|
|
67
|
+
| `status` | `'idle' \| 'submitted' \| 'streaming'` | Current status |
|
|
68
|
+
| `error` | `Error \| undefined` | Any error that occurred |
|
|
69
|
+
| `sendMessage(options?)` | `(options?: { text?: string; files?: any[] }) => Promise<void>` | Send a message |
|
|
70
|
+
| `stop()` | `() => void` | Stop generation |
|
|
71
|
+
| `newChat()` | `() => void` | Start new chat |
|
|
72
|
+
| `setMessages(msgs)` | `(messages: any[]) => void` | Set messages directly |
|
|
73
|
+
| `chatApi` | `object` | Direct access to AI SDK chat API |
|
|
74
|
+
| `chatId` | `string` | Current chat ID |
|
|
75
|
+
| `tools` | `object` | All available tools |
|
|
76
|
+
| `uiComponents` | `object` | All UI components |
|
|
77
|
+
| `dynamicPage` | `object \| null` | Dynamic page operations |
|
|
78
|
+
| `formHostRef` | `React.MutableRefObject` | Form host elements ref |
|
|
79
|
+
| `formStateRef` | `React.MutableRefObject` | Form state ref |
|
|
80
|
+
| `cleanupForms()` | `() => void` | Cleanup all forms |
|
|
81
|
+
| `onUISuccess(id, name)` | `(toolCallId: string, toolName: string) => void` | UI success callback |
|
|
82
|
+
| `onUIError(id, name, err)` | `(toolCallId: string, toolName: string, error: Error) => void` | UI error callback |
|
|
83
|
+
|
|
84
|
+
### Example
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
const agent = useHsafaAgent({
|
|
88
|
+
agentId: 'my-agent',
|
|
89
|
+
baseUrl: 'http://localhost:3000',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
<input value={agent.input} onChange={(e) => agent.setInput(e.target.value)} />
|
|
93
|
+
<button onClick={() => agent.sendMessage()}>Send</button>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## useChatStorage
|
|
99
|
+
|
|
100
|
+
**Purpose:** Persist and manage chat history
|
|
101
|
+
|
|
102
|
+
### Configuration
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const storage = useChatStorage({
|
|
106
|
+
// Required
|
|
107
|
+
agentId: string,
|
|
108
|
+
chatId: string,
|
|
109
|
+
messages: any[],
|
|
110
|
+
|
|
111
|
+
// Optional
|
|
112
|
+
isLoading?: boolean,
|
|
113
|
+
autoSave?: boolean, // default: true
|
|
114
|
+
autoRestore?: boolean, // default: true
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### API
|
|
119
|
+
|
|
120
|
+
| Property | Type | Description |
|
|
121
|
+
|----------|------|-------------|
|
|
122
|
+
| `chatList` | `ChatMetadata[]` | All saved chats |
|
|
123
|
+
| `currentChatMeta` | `ChatMetadata \| null` | Current chat metadata |
|
|
124
|
+
| `refreshChatList()` | `() => void` | Refresh chat list |
|
|
125
|
+
| `loadChat(chatId)` | `(chatId: string) => SavedChat \| null` | Load specific chat |
|
|
126
|
+
| `saveCurrentChat()` | `() => void` | Save current chat manually |
|
|
127
|
+
| `deleteChat(chatId)` | `(chatId: string) => void` | Delete a chat |
|
|
128
|
+
| `switchToChat(chatId, setMessages)` | `(chatId: string, setMessages: Function) => void` | Switch to chat |
|
|
129
|
+
| `createNewChat(onNewChat)` | `(onNewChat: Function) => void` | Create new chat |
|
|
130
|
+
| `searchChats(query)` | `(query: string) => ChatMetadata[]` | Search chats |
|
|
131
|
+
| `storage` | `object` | Underlying storage utility |
|
|
132
|
+
|
|
133
|
+
### Types
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
interface ChatMetadata {
|
|
137
|
+
id: string;
|
|
138
|
+
title: string;
|
|
139
|
+
createdAt: number;
|
|
140
|
+
updatedAt: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface SavedChat {
|
|
144
|
+
id: string;
|
|
145
|
+
messages: any[];
|
|
146
|
+
agentId: string;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Example
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
const storage = useChatStorage({
|
|
154
|
+
agentId: 'my-agent',
|
|
155
|
+
chatId: agent.chatId,
|
|
156
|
+
messages: agent.messages,
|
|
157
|
+
isLoading: agent.isLoading,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
<button onClick={() => storage.createNewChat(agent.newChat)}>New Chat</button>
|
|
161
|
+
{storage.chatList.map(chat => (
|
|
162
|
+
<div onClick={() => storage.switchToChat(chat.id, agent.setMessages)}>
|
|
163
|
+
{chat.title}
|
|
164
|
+
</div>
|
|
165
|
+
))}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## useMessageEditor
|
|
171
|
+
|
|
172
|
+
**Purpose:** Edit messages and regenerate responses
|
|
173
|
+
|
|
174
|
+
### Configuration
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
const editor = useMessageEditor({
|
|
178
|
+
// Required
|
|
179
|
+
messages: any[],
|
|
180
|
+
isLoading: boolean,
|
|
181
|
+
sendMessage: (options: { text: string; files?: any[] }) => Promise<void>,
|
|
182
|
+
setMessages: (messages: any[]) => void,
|
|
183
|
+
|
|
184
|
+
// Optional
|
|
185
|
+
baseUrl?: string,
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### API
|
|
190
|
+
|
|
191
|
+
| Property | Type | Description |
|
|
192
|
+
|----------|------|-------------|
|
|
193
|
+
| `editingMessageId` | `string \| null` | ID of message being edited |
|
|
194
|
+
| `editingText` | `string` | Current edit text |
|
|
195
|
+
| `setEditingText(text)` | `(text: string) => void` | Set edit text |
|
|
196
|
+
| `editAttachments` | `any[]` | Attachments for edited message |
|
|
197
|
+
| `setEditAttachments(atts)` | `(attachments: any[]) => void` | Set edit attachments |
|
|
198
|
+
| `editUploading` | `boolean` | Whether uploading files |
|
|
199
|
+
| `startEdit(id, text, atts?)` | `(messageId: string, text: string, attachments?: any[]) => void` | Start editing |
|
|
200
|
+
| `cancelEdit()` | `() => void` | Cancel editing |
|
|
201
|
+
| `saveEdit(id)` | `(messageId: string) => Promise<void>` | Save and regenerate |
|
|
202
|
+
| `isEditing(id)` | `(messageId: string) => boolean` | Check if editing |
|
|
203
|
+
| `addEditAttachments(files)` | `(files: FileList) => Promise<void>` | Add attachments |
|
|
204
|
+
| `removeEditAttachment(id)` | `(id: string) => void` | Remove attachment |
|
|
205
|
+
|
|
206
|
+
### Example
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
const editor = useMessageEditor({
|
|
210
|
+
messages: agent.messages,
|
|
211
|
+
isLoading: agent.isLoading,
|
|
212
|
+
sendMessage: agent.sendMessage,
|
|
213
|
+
setMessages: agent.setMessages,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
{editor.isEditing(msg.id) ? (
|
|
217
|
+
<div>
|
|
218
|
+
<textarea
|
|
219
|
+
value={editor.editingText}
|
|
220
|
+
onChange={(e) => editor.setEditingText(e.target.value)}
|
|
221
|
+
/>
|
|
222
|
+
<button onClick={() => editor.saveEdit(msg.id)}>Save</button>
|
|
223
|
+
<button onClick={editor.cancelEdit}>Cancel</button>
|
|
224
|
+
</div>
|
|
225
|
+
) : (
|
|
226
|
+
<button onClick={() => editor.startEdit(msg.id, msg.content)}>Edit</button>
|
|
227
|
+
)}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## useFileUpload
|
|
233
|
+
|
|
234
|
+
**Purpose:** Handle file attachments
|
|
235
|
+
|
|
236
|
+
### Configuration
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
const fileUpload = useFileUpload(baseUrl: string);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### API
|
|
243
|
+
|
|
244
|
+
| Property | Type | Description |
|
|
245
|
+
|----------|------|-------------|
|
|
246
|
+
| `attachments` | `Attachment[]` | Current attachments |
|
|
247
|
+
| `uploading` | `boolean` | Whether uploading |
|
|
248
|
+
| `fileInputRef` | `React.RefObject<HTMLInputElement>` | File input ref |
|
|
249
|
+
| `formatBytes(bytes)` | `(bytes: number) => string` | Format file size |
|
|
250
|
+
| `handleRemoveAttachment(id)` | `(id: string) => void` | Remove attachment |
|
|
251
|
+
| `handleFileSelection(files, setError)` | `(files: FileList \| null, setError: Function) => Promise<void>` | Handle file selection |
|
|
252
|
+
| `clearAttachments()` | `() => void` | Clear all attachments |
|
|
253
|
+
| `MAX_UPLOAD_SIZE` | `number` | Max upload size (25MB) |
|
|
254
|
+
|
|
255
|
+
### Types
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
interface Attachment {
|
|
259
|
+
id: string;
|
|
260
|
+
name?: string;
|
|
261
|
+
url: string;
|
|
262
|
+
mimeType?: string;
|
|
263
|
+
size?: number;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Example
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const fileUpload = useFileUpload('http://localhost:3000');
|
|
271
|
+
|
|
272
|
+
<input
|
|
273
|
+
type="file"
|
|
274
|
+
ref={fileUpload.fileInputRef}
|
|
275
|
+
onChange={(e) => fileUpload.handleFileSelection(e.target.files, setError)}
|
|
276
|
+
multiple
|
|
277
|
+
/>
|
|
278
|
+
|
|
279
|
+
<button onClick={() => fileUpload.fileInputRef.current?.click()}>
|
|
280
|
+
Attach Files
|
|
281
|
+
</button>
|
|
282
|
+
|
|
283
|
+
{fileUpload.attachments.map(att => (
|
|
284
|
+
<div key={att.id}>
|
|
285
|
+
{att.name} ({fileUpload.formatBytes(att.size || 0)})
|
|
286
|
+
<button onClick={() => fileUpload.handleRemoveAttachment(att.id)}>×</button>
|
|
287
|
+
</div>
|
|
288
|
+
))}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## useAutoScroll
|
|
294
|
+
|
|
295
|
+
**Purpose:** Auto-scroll to bottom during streaming
|
|
296
|
+
|
|
297
|
+
### Configuration
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
const scrollRef = useAutoScroll<HTMLDivElement>(shouldScroll: boolean);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### API
|
|
304
|
+
|
|
305
|
+
Returns a ref to attach to your scrollable container.
|
|
306
|
+
|
|
307
|
+
### Example
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
const scrollRef = useAutoScroll<HTMLDivElement>(agent.isLoading);
|
|
311
|
+
|
|
312
|
+
<div ref={scrollRef} style={{ overflow: 'auto', height: '400px' }}>
|
|
313
|
+
{agent.messages.map(msg => (
|
|
314
|
+
<div key={msg.id}>{msg.content}</div>
|
|
315
|
+
))}
|
|
316
|
+
</div>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Common Patterns
|
|
322
|
+
|
|
323
|
+
### Basic Chat
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
function BasicChat() {
|
|
327
|
+
const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '...' });
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div>
|
|
331
|
+
<div>
|
|
332
|
+
{agent.messages.map(msg => <div key={msg.id}>{msg.content}</div>)}
|
|
333
|
+
</div>
|
|
334
|
+
<input value={agent.input} onChange={(e) => agent.setInput(e.target.value)} />
|
|
335
|
+
<button onClick={() => agent.sendMessage()}>Send</button>
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### With File Upload
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
function ChatWithFiles() {
|
|
345
|
+
const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '...' });
|
|
346
|
+
const fileUpload = useFileUpload('http://localhost:3000');
|
|
347
|
+
|
|
348
|
+
const handleSend = () => {
|
|
349
|
+
agent.sendMessage({
|
|
350
|
+
text: agent.input,
|
|
351
|
+
files: fileUpload.attachments.map(att => ({
|
|
352
|
+
type: 'file',
|
|
353
|
+
url: att.url,
|
|
354
|
+
mediaType: att.mimeType,
|
|
355
|
+
}))
|
|
356
|
+
});
|
|
357
|
+
fileUpload.clearAttachments();
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<div>
|
|
362
|
+
<input type="file" ref={fileUpload.fileInputRef} onChange={/*...*/} />
|
|
363
|
+
<button onClick={handleSend}>Send</button>
|
|
364
|
+
</div>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### With Chat History
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
function ChatWithHistory() {
|
|
373
|
+
const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '...' });
|
|
374
|
+
const storage = useChatStorage({
|
|
375
|
+
agentId: 'my-agent',
|
|
376
|
+
chatId: agent.chatId,
|
|
377
|
+
messages: agent.messages,
|
|
378
|
+
isLoading: agent.isLoading,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div>
|
|
383
|
+
<aside>
|
|
384
|
+
{storage.chatList.map(chat => (
|
|
385
|
+
<div onClick={() => storage.switchToChat(chat.id, agent.setMessages)}>
|
|
386
|
+
{chat.title}
|
|
387
|
+
</div>
|
|
388
|
+
))}
|
|
389
|
+
</aside>
|
|
390
|
+
<main>
|
|
391
|
+
{/* Chat UI */}
|
|
392
|
+
</main>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Full Featured
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
function FullChat() {
|
|
402
|
+
const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '...' });
|
|
403
|
+
const fileUpload = useFileUpload('http://localhost:3000');
|
|
404
|
+
const storage = useChatStorage({ /*...*/ });
|
|
405
|
+
const editor = useMessageEditor({ /*...*/ });
|
|
406
|
+
const scrollRef = useAutoScroll(agent.isLoading);
|
|
407
|
+
|
|
408
|
+
// Build your complete UI with all features
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Tips
|
|
415
|
+
|
|
416
|
+
1. **Always provide baseUrl** - Either via hook config or HsafaProvider
|
|
417
|
+
2. **Handle loading states** - Disable inputs when `agent.isLoading` is true
|
|
418
|
+
3. **Clear attachments after send** - Call `fileUpload.clearAttachments()`
|
|
419
|
+
4. **Use TypeScript** - All hooks are fully typed
|
|
420
|
+
5. **Check examples** - See `/examples` folder for complete implementations
|
|
421
|
+
|
|
422
|
+
## Links
|
|
423
|
+
|
|
424
|
+
- [Full Headless Guide](./HEADLESS_USAGE.md)
|
|
425
|
+
- [Migration Guide](./MIGRATION_TO_HEADLESS.md)
|
|
426
|
+
- [Examples](../examples/)
|