@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,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/)