@primoia/vocall-react 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Primoia
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,519 @@
1
+ # Vocall SDK for Next.js / React
2
+
3
+ Integrate AI-powered voice and chat assistants into React and Next.js applications using the Vocall AAP (Agent-Application Protocol).
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **React-native hooks** -- `useVocall`, `useVocallField`, and `useVocallAction` provide reactive state with zero boilerplate.
10
+ - **Plug-and-play components** -- `VocallChat`, `VocallFab`, and `VocallStatusPill` give you a full chat overlay out of the box.
11
+ - **Declarative manifest** -- Describe your screens, fields, and actions once; the engine handles the rest.
12
+ - **Automatic command execution** -- The SDK receives server commands (fill, navigate, click, etc.) and applies them to registered DOM elements.
13
+ - **Typewriter fill animation** -- Field values are filled character-by-character for a natural feel.
14
+ - **Auto-reconnect** -- Exponential back-off reconnection with up to 10 retry attempts.
15
+ - **Streaming tokens** -- Chat responses stream token-by-token via `useSyncExternalStore` for tear-free rendering.
16
+ - **Next.js App Router ready** -- All hooks and components are marked `'use client'`.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @vocall/react-sdk
24
+ ```
25
+
26
+ **Peer dependencies:** `react >= 18.0.0` and `react-dom >= 18.0.0`.
27
+
28
+ ---
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Wrap your app with `VocallProvider`
33
+
34
+ ```tsx
35
+ // app/layout.tsx (Next.js App Router example)
36
+ import { VocallProvider } from '@vocall/react-sdk';
37
+
38
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
39
+ return (
40
+ <html lang="en">
41
+ <body>
42
+ <VocallProvider serverUrl="wss://your-vocall-server/connect">
43
+ {children}
44
+ </VocallProvider>
45
+ </body>
46
+ </html>
47
+ );
48
+ }
49
+ ```
50
+
51
+ ### 2. Connect and send messages
52
+
53
+ ```tsx
54
+ 'use client';
55
+
56
+ import { useVocall, useVocallField, ManifestMessage, FieldType } from '@vocall/react-sdk';
57
+ import { useEffect, useState } from 'react';
58
+
59
+ const manifest: ManifestMessage = {
60
+ type: 'manifest',
61
+ app: 'my-app',
62
+ screens: {
63
+ home: {
64
+ id: 'home',
65
+ label: 'Home',
66
+ fields: [
67
+ { id: 'name', type: FieldType.Text, label: 'Name' },
68
+ { id: 'email', type: FieldType.Email, label: 'Email' },
69
+ ],
70
+ actions: [
71
+ { id: 'submit', label: 'Submit' },
72
+ ],
73
+ },
74
+ },
75
+ };
76
+
77
+ export default function HomePage() {
78
+ const { connect, connected, status, messages, sendText } = useVocall();
79
+ const nameField = useVocallField('home', 'name');
80
+ const emailField = useVocallField('home', 'email');
81
+
82
+ useEffect(() => {
83
+ connect(manifest);
84
+ }, [connect]);
85
+
86
+ return (
87
+ <div>
88
+ <p>Status: {status} | Connected: {String(connected)}</p>
89
+
90
+ <input ref={nameField.ref} placeholder="Name" />
91
+ <input ref={emailField.ref} placeholder="Email" />
92
+
93
+ <ul>
94
+ {messages.map((msg, i) => (
95
+ <li key={i}><strong>{msg.role}:</strong> {msg.text}</li>
96
+ ))}
97
+ </ul>
98
+
99
+ <input
100
+ onKeyDown={(e) => {
101
+ if (e.key === 'Enter') sendText(e.currentTarget.value);
102
+ }}
103
+ placeholder="Ask the assistant..."
104
+ />
105
+ </div>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Hooks API
113
+
114
+ ### `useVocall()`
115
+
116
+ Main hook for interacting with the Vocall client. Must be used inside a `<VocallProvider>`.
117
+
118
+ ```typescript
119
+ const {
120
+ client, // VocallClient -- the underlying client instance
121
+ status, // VocallStatus -- current status enum value
122
+ messages, // ChatMessage[] -- conversation history (read-only)
123
+ connected, // boolean -- true when the WebSocket is open
124
+ sessionId, // string | null -- server-assigned session ID
125
+ sendText, // (text: string) => void
126
+ connect, // (manifest: ManifestMessage) => void
127
+ disconnect, // () => void
128
+ clearMessages, // () => void
129
+ } = useVocall();
130
+ ```
131
+
132
+ All reactive values (`status`, `messages`, `connected`, `sessionId`) are backed by `useSyncExternalStore` for concurrent-safe reads.
133
+
134
+ ---
135
+
136
+ ### `useVocallField(screenId, fieldId, options?)`
137
+
138
+ Registers a DOM element as a controllable field. The engine can then fill, clear, focus, highlight, scroll to, enable, or disable it.
139
+
140
+ ```typescript
141
+ const { ref, registered } = useVocallField('invoice', 'recipient_name');
142
+ ```
143
+
144
+ | Return | Type | Description |
145
+ |-----------|-----------------------------------|-----------------------------------------------|
146
+ | `ref` | `(el: HTMLElement \| null) => void` | Callback ref -- attach to your input element. |
147
+ | `registered` | `boolean` | Whether the field is currently registered. |
148
+
149
+ **Usage:**
150
+
151
+ ```tsx
152
+ <input ref={ref} placeholder="Recipient" />
153
+ ```
154
+
155
+ **Custom value accessors** (for controlled inputs):
156
+
157
+ ```tsx
158
+ const [value, setValue] = useState('');
159
+ const { ref } = useVocallField('invoice', 'amount', {
160
+ setValue: (v) => setValue(v),
161
+ getValue: () => value,
162
+ });
163
+
164
+ <input ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
165
+ ```
166
+
167
+ ---
168
+
169
+ ### `useVocallAction(screenId, actionId, callback)`
170
+
171
+ Registers a callback that the engine invokes when the server sends a `click` command for the given action.
172
+
173
+ ```typescript
174
+ useVocallAction('invoice', 'submit', async () => {
175
+ await submitForm();
176
+ });
177
+ ```
178
+
179
+ The callback ref is kept stable internally, so the latest closure is always called without re-registering on every render.
180
+
181
+ ---
182
+
183
+ ## Components
184
+
185
+ ### `VocallChat`
186
+
187
+ A ready-made chat panel with message bubbles, status indicators, and a text input.
188
+
189
+ ```tsx
190
+ import { VocallChat } from '@vocall/react-sdk';
191
+
192
+ <VocallChat
193
+ open={chatOpen}
194
+ onClose={() => setChatOpen(false)}
195
+ assistantName="Emma"
196
+ className="my-chat"
197
+ style={{ bottom: 80 }}
198
+ />
199
+ ```
200
+
201
+ | Prop | Type | Default | Description |
202
+ |-----------------|-----------------------|-----------|-------------------------------------|
203
+ | `open` | `boolean` | -- | Controls visibility. |
204
+ | `onClose` | `() => void` | -- | Called when the user clicks close. |
205
+ | `assistantName` | `string` | `"Emma"` | Name shown in the header. |
206
+ | `className` | `string` | -- | CSS class for the container. |
207
+ | `style` | `React.CSSProperties` | -- | Inline styles for the container. |
208
+
209
+ ---
210
+
211
+ ### `VocallFab`
212
+
213
+ A floating action button that reflects the current connection status through color changes and displays a spinner during `thinking` and `executing` states.
214
+
215
+ ```tsx
216
+ import { VocallFab } from '@vocall/react-sdk';
217
+
218
+ <VocallFab onClick={() => setChatOpen((o) => !o)} />
219
+ ```
220
+
221
+ | Prop | Type | Description |
222
+ |-------------|-----------------------|----------------------------------|
223
+ | `onClick` | `() => void` | Called when the FAB is clicked. |
224
+ | `className` | `string` | CSS class for the button. |
225
+ | `style` | `React.CSSProperties` | Inline styles for the button. |
226
+
227
+ ---
228
+
229
+ ### `VocallStatusPill`
230
+
231
+ A small inline pill that appears only when the assistant is actively working (thinking, executing, speaking, listening, or recording). Hides automatically when idle or disconnected.
232
+
233
+ ```tsx
234
+ import { VocallStatusPill } from '@vocall/react-sdk';
235
+
236
+ <VocallStatusPill className="my-status" />
237
+ ```
238
+
239
+ | Prop | Type | Description |
240
+ |-------------|-----------------------|--------------------------------|
241
+ | `className` | `string` | CSS class for the pill. |
242
+ | `style` | `React.CSSProperties` | Inline styles for the pill. |
243
+
244
+ ---
245
+
246
+ ## VocallClient API
247
+
248
+ The `VocallClient` class manages the WebSocket connection and the field/action registries. You can access it directly via `useVocallClient()` or through the `client` property returned by `useVocall()`.
249
+
250
+ ### Constructor
251
+
252
+ ```typescript
253
+ const client = new VocallClient(serverUrl, { token?, visitorId? });
254
+ ```
255
+
256
+ | Parameter | Type | Description |
257
+ |-------------|----------|------------------------------------------------------------|
258
+ | `serverUrl` | `string` | WebSocket URL of the Vocall engine (e.g. `wss://host/connect`). |
259
+ | `token` | `string` | Optional authentication token. |
260
+ | `visitorId` | `string` | Optional visitor ID. Auto-generated and persisted in `localStorage` if omitted. |
261
+
262
+ ### Core Methods
263
+
264
+ | Method | Description |
265
+ |------------------------------------------------------|------------------------------------------------------|
266
+ | `connect(manifest: ManifestMessage)` | Opens the WebSocket and sends the manifest. |
267
+ | `disconnect()` | Closes the connection intentionally (no reconnect). |
268
+ | `sendText(text: string)` | Sends a user chat message to the engine. |
269
+ | `sendConfirm(seq: number, confirmed: boolean)` | Responds to an `ask_confirm` action. |
270
+ | `sendResult(seq: number, results, state?)` | Sends command execution results back to the engine. |
271
+ | `sendState(state: StateMessage)` | Sends current UI state to the engine. |
272
+ | `clearMessages()` | Clears the local chat history. |
273
+ | `destroy()` | Disconnects and removes all listeners. |
274
+
275
+ ### State Getters
276
+
277
+ | Property | Type | Description |
278
+ |-------------|---------------------------|----------------------------------------------|
279
+ | `status` | `VocallStatus` | Current engine status. |
280
+ | `connected` | `boolean` | Whether the WebSocket is open. |
281
+ | `messages` | `readonly ChatMessage[]` | Conversation history. |
282
+ | `sessionId` | `string \| null` | Server-assigned session ID. |
283
+ | `visitorId` | `string` | Persistent visitor identifier. |
284
+
285
+ ### Callbacks
286
+
287
+ Set these on the client instance to handle navigation, modals, toasts, and confirmation dialogs in your application:
288
+
289
+ ```typescript
290
+ client.onNavigate = (screenId) => router.push(`/${screenId}`);
291
+ client.onToast = (message, level, duration) => showToast(message, level);
292
+ client.onConfirm = (seq, message) => showConfirmDialog(seq, message);
293
+ client.onOpenModal = (modalId, query) => openModal(modalId);
294
+ client.onCloseModal = () => closeModal();
295
+ ```
296
+
297
+ ### Field and Action Registration
298
+
299
+ These methods are called automatically by `useVocallField` and `useVocallAction`, but are available for manual use:
300
+
301
+ | Method | Description |
302
+ |--------------------------------------------------------|--------------------------------------------|
303
+ | `registerField(screenId, fieldId, entry)` | Registers a field element. |
304
+ | `unregisterField(screenId, fieldId)` | Removes a field registration. |
305
+ | `registerAction(screenId, actionId, callback)` | Registers an action callback. |
306
+ | `unregisterAction(screenId, actionId)` | Removes an action registration. |
307
+ | `unregisterScreen(screenId)` | Removes all fields and actions for a screen. |
308
+
309
+ ---
310
+
311
+ ## Manifest Structure
312
+
313
+ The manifest describes your application's screens, fields, and actions to the Vocall engine. It is sent automatically upon connection.
314
+
315
+ ```typescript
316
+ import { ManifestMessage, FieldType } from '@vocall/react-sdk';
317
+
318
+ const manifest: ManifestMessage = {
319
+ type: 'manifest',
320
+ app: 'my-erp',
321
+ version: '1.0.0',
322
+ currentScreen: 'dashboard',
323
+ user: {
324
+ name: 'Jane Doe',
325
+ email: 'jane@example.com',
326
+ org: 'Acme Corp',
327
+ role: 'admin',
328
+ },
329
+ persona: {
330
+ name: 'Emma',
331
+ role: 'ERP Assistant',
332
+ instructions: 'Help the user manage invoices and clients.',
333
+ },
334
+ context: {
335
+ locale: 'en-US',
336
+ tenant: 'acme',
337
+ },
338
+ screens: {
339
+ dashboard: {
340
+ id: 'dashboard',
341
+ label: 'Dashboard',
342
+ route: '/dashboard',
343
+ fields: [
344
+ { id: 'search', type: FieldType.Text, label: 'Search' },
345
+ ],
346
+ actions: [
347
+ { id: 'refresh', label: 'Refresh Data' },
348
+ { id: 'export', label: 'Export CSV', requiresConfirmation: true },
349
+ ],
350
+ modals: [
351
+ { id: 'client_picker', label: 'Select Client', searchable: true },
352
+ ],
353
+ },
354
+ invoice: {
355
+ id: 'invoice',
356
+ label: 'New Invoice',
357
+ route: '/invoices/new',
358
+ fields: [
359
+ { id: 'recipient', type: FieldType.Autocomplete, label: 'Recipient', required: true },
360
+ { id: 'amount', type: FieldType.Currency, label: 'Amount', min: 0 },
361
+ { id: 'due_date', type: FieldType.Date, label: 'Due Date', required: true },
362
+ { id: 'notes', type: FieldType.Textarea, label: 'Notes', maxLength: 500 },
363
+ {
364
+ id: 'status',
365
+ type: FieldType.Select,
366
+ label: 'Status',
367
+ options: [
368
+ { value: 'draft', label: 'Draft' },
369
+ { value: 'sent', label: 'Sent' },
370
+ ],
371
+ },
372
+ ],
373
+ actions: [
374
+ { id: 'save_draft', label: 'Save Draft' },
375
+ { id: 'send', label: 'Send Invoice', destructive: true, requiresConfirmation: true },
376
+ ],
377
+ },
378
+ },
379
+ };
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Field Types
385
+
386
+ The `FieldType` enum defines the supported field types in the manifest:
387
+
388
+ | Value | Description |
389
+ |----------------|-------------------------------------------------|
390
+ | `text` | Plain text input. |
391
+ | `number` | Numeric input. |
392
+ | `currency` | Currency/monetary value input. |
393
+ | `date` | Date picker. |
394
+ | `datetime` | Date and time picker. |
395
+ | `email` | Email address input. |
396
+ | `phone` | Phone number input. |
397
+ | `masked` | Input with a custom mask pattern. |
398
+ | `select` | Dropdown select (provide `options`). |
399
+ | `autocomplete` | Searchable select / autocomplete. |
400
+ | `checkbox` | Boolean checkbox. |
401
+ | `radio` | Radio button group. |
402
+ | `textarea` | Multi-line text area. |
403
+ | `file` | File upload. |
404
+ | `hidden` | Hidden field (not visible to the user). |
405
+
406
+ ---
407
+
408
+ ## UI Actions
409
+
410
+ The Vocall engine can send 14 UI actions to the client. These are executed automatically by the SDK when commands arrive over the WebSocket.
411
+
412
+ | Action | Target | Description |
413
+ |----------------|-----------|------------------------------------------------------|
414
+ | `navigate` | screen | Navigate to a different screen. |
415
+ | `fill` | field | Set a field's value (with optional typewriter animation). |
416
+ | `clear` | field | Clear a field's value. |
417
+ | `select` | field | Set the selected value of a select/radio field. |
418
+ | `click` | action | Invoke a registered action callback. |
419
+ | `highlight` | field | Scroll to and visually highlight a field. |
420
+ | `focus` | field | Move focus to a field. |
421
+ | `scroll_to` | field | Scroll a field into view. |
422
+ | `show_toast` | -- | Display a toast notification. |
423
+ | `ask_confirm` | -- | Prompt the user for confirmation. |
424
+ | `open_modal` | modal | Open a modal dialog. |
425
+ | `close_modal` | -- | Close the currently open modal. |
426
+ | `enable` | field | Enable a disabled input. |
427
+ | `disable` | field | Disable an input. |
428
+
429
+ Actions are classified as **sequential** (`navigate`, `click`, `open_modal`, `close_modal`, `ask_confirm`, `show_toast`) or **parallel** (all others). Sequential actions execute one at a time in order; parallel actions execute concurrently for performance. All actions include automatic retry logic with up to 3 attempts.
430
+
431
+ ---
432
+
433
+ ## Demo
434
+
435
+ A working demo application is available in the `demo/` directory. It demonstrates the full integration including provider setup, field binding, action registration, and the chat overlay components.
436
+
437
+ ---
438
+
439
+ ## Testing
440
+
441
+ The SDK includes a comprehensive test suite with ~156 tests covering protocol types, WebSocket client, React hooks, context provider, and components.
442
+
443
+ ### Running Tests
444
+
445
+ ```bash
446
+ npm test # Run all tests
447
+ npm test -- --coverage # Run with coverage report
448
+ ```
449
+
450
+ ### Test Structure
451
+
452
+ | File | Tests | What it covers |
453
+ |---|---|---|
454
+ | `src/protocol/__tests__/types.test.ts` | 30 | Protocol type enums, message interfaces, field types |
455
+ | `src/client/__tests__/vocall-client.test.ts` | 94 | WebSocket connection, reconnection, command execution, streaming |
456
+ | `src/hooks/__tests__/use-vocall.test.tsx` | 10 | useVocall hook, state reactivity |
457
+ | `src/hooks/__tests__/use-vocall-field.test.tsx` | 7 | useVocallField hook, DOM element binding |
458
+ | `src/hooks/__tests__/use-vocall-action.test.tsx` | 4 | useVocallAction hook, callback registration |
459
+ | `src/context/__tests__/vocall-provider.test.tsx` | 11 | VocallProvider context, client lifecycle |
460
+
461
+ ### Test Configuration
462
+
463
+ - **Framework:** Vitest with React Testing Library
464
+ - **Environment:** jsdom
465
+ - **Config:** [vitest.config.ts](vitest.config.ts) | [vitest.setup.ts](vitest.setup.ts)
466
+
467
+ ---
468
+
469
+ ## Development
470
+
471
+ ### Building the SDK
472
+
473
+ ```bash
474
+ npm install
475
+ npm run build
476
+ ```
477
+
478
+ ### Running the Demo
479
+
480
+ The demo application is in the `demo/` directory:
481
+
482
+ ```bash
483
+ cd demo
484
+ npm install
485
+ npm run dev # http://localhost:3000
486
+ ```
487
+
488
+ The demo connects to a Vocall server at `ws://localhost:12900/connect`. See the [workspace README](https://github.com/primoia/primoia-vocall-workspace#quick-start) for local server setup.
489
+
490
+ ### Docker
491
+
492
+ When running from the workspace, the demo is available as a Docker container:
493
+
494
+ ```bash
495
+ # From the workspace root
496
+ make serve-all # Starts engine + all demos
497
+ # Next.js demo at http://localhost:21003
498
+ ```
499
+
500
+ ---
501
+
502
+ ## Related
503
+
504
+ This SDK is part of the [Primoia Vocall Workspace](https://github.com/primoia/primoia-vocall-workspace).
505
+
506
+ | Resource | Link |
507
+ |---|---|
508
+ | Workspace (setup, Docker) | [primoia-vocall-workspace](https://github.com/primoia/primoia-vocall-workspace#readme) |
509
+ | Angular SDK | [vocall-sdk-angular](https://github.com/primoia/vocall-sdk-angular) |
510
+ | Vue SDK | [vocall-sdk-vue](https://github.com/primoia/vocall-sdk-vue) |
511
+ | Kotlin SDK | [vocall-sdk-kotlin](https://github.com/primoia/vocall-sdk-kotlin) |
512
+ | Swift SDK | [vocall-sdk-swift](https://github.com/primoia/vocall-sdk-swift) |
513
+ | Flutter SDK | [jarvis-sdk-flutter](https://github.com/primoia/jarvis-sdk-flutter) |
514
+
515
+ ---
516
+
517
+ ## License
518
+
519
+ Proprietary. All rights reserved.