@primoia/vocall-vue 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,569 @@
1
+ # Vocall SDK for Vue
2
+
3
+ Vue 3 SDK for the Vocall platform -- connect your Vue application to a Vocall engine over WebSocket and let the AI assistant read, fill, and navigate your UI in real time.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Reactive composables built on Vue 3 Composition API (`ref`, `inject`, `watch`)
10
+ - Plugin-based setup with a shared `VocallClient` instance across your application
11
+ - Two-way field binding: register any `<input>`, `<select>`, or `<textarea>` so the engine can fill, clear, focus, and highlight it
12
+ - Action registration: expose buttons and callbacks the engine can trigger remotely
13
+ - Pre-built UI components (`VocallChat`, `VocallFab`, `VocallStatus`) with scoped styles
14
+ - Streaming chat tokens with automatic message assembly
15
+ - Automatic WebSocket reconnection with exponential back-off (up to 10 attempts)
16
+ - Full TypeScript support with exported types for every protocol message
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @vocall/vue-sdk
24
+ ```
25
+
26
+ Peer dependency: **Vue >= 3.4.0**
27
+
28
+ ---
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Register the plugin
33
+
34
+ ```typescript
35
+ // main.ts
36
+ import { createApp } from 'vue';
37
+ import { VocallPlugin } from '@vocall/vue-sdk';
38
+ import App from './App.vue';
39
+
40
+ const app = createApp(App);
41
+
42
+ app.use(VocallPlugin, {
43
+ url: 'ws://localhost:12900/connect',
44
+ token: 'my-api-token', // optional
45
+ visitorId: 'visitor-abc-123', // optional, auto-generated if omitted
46
+ });
47
+
48
+ app.mount('#app');
49
+ ```
50
+
51
+ ### 2. Connect and send messages
52
+
53
+ ```vue
54
+ <script setup lang="ts">
55
+ import { onMounted } from 'vue';
56
+ import { useVocall, useVocallField, type ManifestMessage } from '@vocall/vue-sdk';
57
+
58
+ const {
59
+ status, messages, connected,
60
+ connect, disconnect, sendText, client,
61
+ } = useVocall();
62
+
63
+ // Register a field so the engine can fill it
64
+ const { fieldRef, value: nameValue } = useVocallField({
65
+ fieldId: 'customer_name',
66
+ client,
67
+ });
68
+
69
+ // Build the manifest and connect
70
+ onMounted(() => {
71
+ const manifest: ManifestMessage = {
72
+ type: 'manifest',
73
+ app: 'my-app',
74
+ screens: {
75
+ home: {
76
+ id: 'home',
77
+ label: 'Home',
78
+ fields: [
79
+ { id: 'customer_name', type: 'text', label: 'Customer Name', required: true },
80
+ ],
81
+ },
82
+ },
83
+ currentScreen: 'home',
84
+ };
85
+ connect(manifest);
86
+ });
87
+ </script>
88
+
89
+ <template>
90
+ <p>Status: {{ status }}</p>
91
+ <input ref="fieldRef" v-model="nameValue" placeholder="Customer Name" />
92
+ <button @click="sendText('Fill the customer name with John Doe')">
93
+ Ask Vocall
94
+ </button>
95
+ </template>
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Composables API
101
+
102
+ ### `useVocall(options?)`
103
+
104
+ Main composable for connecting to the Vocall server. If the `VocallPlugin` was installed, the shared client is injected automatically; otherwise a new client is created from the provided options.
105
+
106
+ **Options** (`UseVocallOptions`):
107
+
108
+ | Property | Type | Default | Description |
109
+ |---|---|---|---|
110
+ | `serverUrl` | `string` | `ws://localhost:12900/connect` | WebSocket server URL |
111
+ | `token` | `string` | -- | Optional auth token |
112
+ | `visitorId` | `string` | auto-generated | Persistent visitor identifier |
113
+ | `autoDispose` | `boolean` | `true` | Dispose the client when the component unmounts |
114
+
115
+ **Returns** (`UseVocallReturn`):
116
+
117
+ | Property | Type | Description |
118
+ |---|---|---|
119
+ | `status` | `Ref<VocallStatus>` | Current connection/processing status (readonly) |
120
+ | `messages` | `Ref<ChatMessage[]>` | Reactive chat message list |
121
+ | `connected` | `Ref<boolean>` | Whether the WebSocket is connected (readonly) |
122
+ | `sessionId` | `Ref<string \| null>` | Session ID assigned by the server (readonly) |
123
+ | `pendingConfirmSeq` | `Ref<number>` | Sequence number of a pending confirmation (-1 if none) |
124
+ | `pendingConfirmMessage` | `Ref<string \| null>` | Text of a pending confirmation dialog |
125
+ | `sendText(text)` | `(text: string) => void` | Send a chat message to the engine |
126
+ | `sendConfirm(seq, confirmed)` | `(seq: number, confirmed: boolean) => void` | Respond to a confirmation request |
127
+ | `connect(manifest)` | `(manifest: ManifestMessage) => void` | Open the WebSocket and send the manifest |
128
+ | `disconnect()` | `() => void` | Close the connection |
129
+ | `sendState(screen)` | `(screen: string) => void` | Send current field state to the server |
130
+ | `clearMessages()` | `() => void` | Clear the local chat history |
131
+ | `client` | `VocallClient` | Underlying client instance for advanced use |
132
+
133
+ ---
134
+
135
+ ### `useVocallField(options)`
136
+
137
+ Registers a form field with the Vocall client so the engine can fill, clear, focus, and highlight it.
138
+
139
+ **Options** (`UseVocallFieldOptions`):
140
+
141
+ | Property | Type | Description |
142
+ |---|---|---|
143
+ | `fieldId` | `string` | Unique identifier matching a field in the manifest |
144
+ | `client` | `VocallClient` | Client instance from `useVocall().client` |
145
+ | `initialValue` | `string` | Optional initial value |
146
+
147
+ **Returns** (`UseVocallFieldReturn`):
148
+
149
+ | Property | Type | Description |
150
+ |---|---|---|
151
+ | `fieldRef` | `Ref<HTMLInputElement \| HTMLSelectElement \| HTMLTextAreaElement \| null>` | Template ref to bind to the element |
152
+ | `value` | `Ref<string>` | Reactive model value (use with `v-model`) |
153
+
154
+ **Example:**
155
+
156
+ ```vue
157
+ <script setup lang="ts">
158
+ import { useVocall, useVocallField } from '@vocall/vue-sdk';
159
+
160
+ const { client } = useVocall();
161
+
162
+ const { fieldRef: emailRef, value: emailValue } = useVocallField({
163
+ fieldId: 'email',
164
+ client,
165
+ });
166
+ </script>
167
+
168
+ <template>
169
+ <input ref="emailRef" v-model="emailValue" type="email" />
170
+ </template>
171
+ ```
172
+
173
+ ---
174
+
175
+ ### `useVocallAction(options)`
176
+
177
+ Registers an action handler with the Vocall client so the engine can trigger it via the `click` command.
178
+
179
+ **Options** (`UseVocallActionOptions`):
180
+
181
+ | Property | Type | Description |
182
+ |---|---|---|
183
+ | `actionId` | `string` | Unique identifier matching an action in the manifest |
184
+ | `client` | `VocallClient` | Client instance from `useVocall().client` |
185
+ | `handler` | `() => void \| Promise<void>` | Callback invoked when the engine triggers this action |
186
+
187
+ **Example:**
188
+
189
+ ```vue
190
+ <script setup lang="ts">
191
+ import { useVocall, useVocallAction } from '@vocall/vue-sdk';
192
+
193
+ const { client } = useVocall();
194
+
195
+ useVocallAction({
196
+ actionId: 'submit_form',
197
+ client,
198
+ handler: async () => {
199
+ await saveData();
200
+ console.log('Form submitted by Vocall');
201
+ },
202
+ });
203
+ </script>
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Plugin
209
+
210
+ ### `VocallPlugin`
211
+
212
+ Install Vocall as a Vue plugin to create a single shared `VocallClient` instance that is provided to all components via `inject`.
213
+
214
+ ```typescript
215
+ import { createApp } from 'vue';
216
+ import { VocallPlugin } from '@vocall/vue-sdk';
217
+
218
+ const app = createApp(App);
219
+
220
+ app.use(VocallPlugin, {
221
+ url: 'ws://localhost:12900/connect',
222
+ token: 'my-api-token',
223
+ visitorId: 'visitor-abc-123',
224
+ });
225
+
226
+ app.mount('#app');
227
+ ```
228
+
229
+ **Options** (`VocallPluginOptions`):
230
+
231
+ | Property | Type | Required | Description |
232
+ |---|---|---|---|
233
+ | `url` | `string` | No | WebSocket server URL. Defaults to `ws://localhost:12900/connect` |
234
+ | `token` | `string` | No | Optional authentication token |
235
+ | `visitorId` | `string` | No | Visitor identifier. Auto-generated and persisted in `localStorage` if omitted |
236
+
237
+ When the plugin is installed, the client is also available on `this.$vocall` for Options API components.
238
+
239
+ ---
240
+
241
+ ## Components
242
+
243
+ ### `VocallChat`
244
+
245
+ A full chat panel with message list, text input, and header showing connection status.
246
+
247
+ **Props:**
248
+
249
+ | Prop | Type | Description |
250
+ |---|---|---|
251
+ | `messages` | `ChatMessage[]` | Array of chat messages to display |
252
+ | `status` | `VocallStatus` | Current engine status |
253
+ | `connected` | `boolean` | Whether the client is connected |
254
+ | `sessionId` | `string \| null` | Optional session identifier |
255
+
256
+ **Events:**
257
+
258
+ | Event | Payload | Description |
259
+ |---|---|---|
260
+ | `send` | `text: string` | Emitted when the user submits a message |
261
+ | `clear` | -- | Emitted when the user clicks the clear button |
262
+ | `close` | -- | Emitted when the user clicks the close button |
263
+
264
+ ```vue
265
+ <template>
266
+ <VocallChat
267
+ :messages="messages"
268
+ :status="status"
269
+ :connected="connected"
270
+ @send="sendText"
271
+ @clear="clearMessages"
272
+ @close="chatOpen = false"
273
+ />
274
+ </template>
275
+ ```
276
+
277
+ ### `VocallFab`
278
+
279
+ A floating action button (fixed bottom-right) that changes color based on the engine status and shows a spinner during `thinking` or `executing` states.
280
+
281
+ **Props:**
282
+
283
+ | Prop | Type | Description |
284
+ |---|---|---|
285
+ | `status` | `VocallStatus` | Current engine status |
286
+ | `connected` | `boolean` | Whether the client is connected |
287
+
288
+ **Events:**
289
+
290
+ | Event | Description |
291
+ |---|---|
292
+ | `click` | Emitted when the FAB is clicked |
293
+
294
+ ```vue
295
+ <template>
296
+ <VocallFab :status="status" :connected="connected" @click="toggleChat" />
297
+ </template>
298
+ ```
299
+
300
+ ### `VocallStatus`
301
+
302
+ A small status pill that becomes visible when the engine is actively processing (thinking, executing, speaking, listening, or recording). Hidden during idle and disconnected states.
303
+
304
+ **Props:**
305
+
306
+ | Prop | Type | Description |
307
+ |---|---|---|
308
+ | `status` | `VocallStatus` | Current engine status |
309
+ | `connected` | `boolean` | Whether the client is connected |
310
+
311
+ ```vue
312
+ <template>
313
+ <VocallStatus :status="status" :connected="connected" />
314
+ </template>
315
+ ```
316
+
317
+ ---
318
+
319
+ ## VocallClient API
320
+
321
+ The `VocallClient` class manages the WebSocket connection, field/action registry, and command execution. It is typically accessed via `useVocall().client`.
322
+
323
+ ### Properties
324
+
325
+ | Property | Type | Description |
326
+ |---|---|---|
327
+ | `status` | `VocallStatus` | Current status |
328
+ | `messages` | `ChatMessage[]` | Chat message history |
329
+ | `sessionId` | `string \| null` | Server-assigned session ID |
330
+ | `connected` | `boolean` | Connection state |
331
+ | `registry` | `VocallFieldRegistry` | Field and action registry |
332
+ | `events` | `EventEmitter` | Internal event bus |
333
+ | `serverUrl` | `string` | WebSocket server URL (read/write) |
334
+ | `token` | `string \| undefined` | Auth token (read/write) |
335
+ | `visitorId` | `string` | Visitor identifier (read-only) |
336
+
337
+ ### Methods
338
+
339
+ | Method | Description |
340
+ |---|---|
341
+ | `connect(manifest)` | Open the WebSocket and send the manifest |
342
+ | `disconnect()` | Close the connection intentionally |
343
+ | `sendText(text)` | Send a chat message |
344
+ | `sendConfirm(seq, confirmed)` | Respond to a confirmation dialog |
345
+ | `sendResult(seq, results, currentScreen?)` | Send command execution results |
346
+ | `sendState(screen)` | Send current field state snapshot |
347
+ | `dispose()` | Disconnect, remove listeners, and clear registries |
348
+
349
+ ### Events
350
+
351
+ Subscribe to events via `client.events.on(event, handler)`:
352
+
353
+ | Event | Arguments | Description |
354
+ |---|---|---|
355
+ | `change` | -- | Fired whenever any reactive state changes |
356
+ | `confirm` | `seq: number, message: string` | A confirmation dialog was requested |
357
+ | `toast` | `message: string, level: string, duration: number` | A toast notification was requested |
358
+
359
+ ---
360
+
361
+ ## Manifest Structure
362
+
363
+ The manifest describes your application's screens, fields, and actions. It is sent to the engine on connection.
364
+
365
+ ```typescript
366
+ import type { ManifestMessage } from '@vocall/vue-sdk';
367
+
368
+ const manifest: ManifestMessage = {
369
+ type: 'manifest',
370
+ app: 'invoice-app',
371
+ version: '1.0.0',
372
+ currentScreen: 'dashboard',
373
+ user: {
374
+ name: 'Jane Doe',
375
+ email: 'jane@example.com',
376
+ org: 'Acme Corp',
377
+ role: 'admin',
378
+ },
379
+ persona: {
380
+ name: 'Emma',
381
+ role: 'Invoice Assistant',
382
+ instructions: 'Help the user issue invoices and manage taxpayers.',
383
+ },
384
+ context: {
385
+ locale: 'en-US',
386
+ timezone: 'America/New_York',
387
+ },
388
+ screens: {
389
+ dashboard: {
390
+ id: 'dashboard',
391
+ label: 'Dashboard',
392
+ route: '/dashboard',
393
+ fields: [
394
+ { id: 'search', type: 'text', label: 'Search', placeholder: 'Search...' },
395
+ ],
396
+ actions: [
397
+ { id: 'new_invoice', label: 'New Invoice' },
398
+ ],
399
+ },
400
+ invoice_form: {
401
+ id: 'invoice_form',
402
+ label: 'Issue Invoice',
403
+ route: '/invoices/new',
404
+ fields: [
405
+ { id: 'customer_name', type: 'text', label: 'Customer', required: true },
406
+ { id: 'amount', type: 'currency', label: 'Amount', required: true },
407
+ { id: 'due_date', type: 'date', label: 'Due Date' },
408
+ { id: 'notes', type: 'textarea', label: 'Notes', maxLength: 500 },
409
+ {
410
+ id: 'status',
411
+ type: 'select',
412
+ label: 'Status',
413
+ options: [
414
+ { value: 'draft', label: 'Draft' },
415
+ { value: 'sent', label: 'Sent' },
416
+ ],
417
+ },
418
+ ],
419
+ actions: [
420
+ { id: 'save_invoice', label: 'Save', requiresConfirmation: true },
421
+ { id: 'cancel', label: 'Cancel', destructive: true },
422
+ ],
423
+ modals: [
424
+ { id: 'customer_search', label: 'Search Customer', searchable: true },
425
+ ],
426
+ },
427
+ },
428
+ };
429
+ ```
430
+
431
+ ---
432
+
433
+ ## Field Types
434
+
435
+ The `FieldType` enum defines all supported field types:
436
+
437
+ | Type | Value | Description |
438
+ |---|---|---|
439
+ | `Text` | `text` | Plain text input |
440
+ | `Number` | `number` | Numeric input |
441
+ | `Currency` | `currency` | Monetary value input |
442
+ | `Date` | `date` | Date picker |
443
+ | `Datetime` | `datetime` | Date and time picker |
444
+ | `Email` | `email` | Email address input |
445
+ | `Phone` | `phone` | Phone number input |
446
+ | `Masked` | `masked` | Input with a custom mask pattern |
447
+ | `Select` | `select` | Dropdown select |
448
+ | `Autocomplete` | `autocomplete` | Autocomplete / typeahead |
449
+ | `Checkbox` | `checkbox` | Boolean checkbox |
450
+ | `Radio` | `radio` | Radio button group |
451
+ | `Textarea` | `textarea` | Multi-line text area |
452
+ | `File` | `file` | File upload |
453
+ | `Hidden` | `hidden` | Hidden field (not visible to the user) |
454
+
455
+ ---
456
+
457
+ ## UI Actions
458
+
459
+ The engine can send commands containing one or more of the following 14 UI actions:
460
+
461
+ | Action | Target | Description |
462
+ |---|---|---|
463
+ | `navigate` | `screen` | Navigate to a different screen |
464
+ | `fill` | `field` | Fill a field with a value (supports typewriter animation) |
465
+ | `clear` | `field` | Clear a field's value |
466
+ | `select` | `field` | Set the value of a select/dropdown field |
467
+ | `click` | `action` | Trigger a registered action handler |
468
+ | `highlight` | `field` | Temporarily highlight a field element |
469
+ | `focus` | `field` | Focus a field element |
470
+ | `scroll_to` | `field` | Scroll the viewport to a field |
471
+ | `show_toast` | -- | Display a toast notification |
472
+ | `ask_confirm` | -- | Show a confirmation dialog and wait for user response |
473
+ | `open_modal` | `modal` | Open a modal by ID with an optional search query |
474
+ | `close_modal` | -- | Close the currently open modal |
475
+ | `enable` | `field` | Enable a disabled field |
476
+ | `disable` | `field` | Disable a field |
477
+
478
+ Sequential actions (`navigate`, `click`, `open_modal`, `close_modal`, `ask_confirm`, `show_toast`) are executed one at a time. All other actions run in parallel for optimal performance. Failed actions are retried up to 3 times with a 300 ms delay.
479
+
480
+ ---
481
+
482
+ ## Demo
483
+
484
+ A demo application is available in the `demo/` directory. It demonstrates plugin setup, field registration, action handling, and the chat overlay using all three provided components.
485
+
486
+ ---
487
+
488
+ ## Testing
489
+
490
+ The SDK includes a comprehensive test suite with ~184 tests covering protocol types, WebSocket client, Vue composables, plugin integration, and voice utilities.
491
+
492
+ ### Running Tests
493
+
494
+ ```bash
495
+ npm test # Run all tests
496
+ npm test -- --coverage # Run with coverage report
497
+ ```
498
+
499
+ ### Test Structure
500
+
501
+ | File | Tests | What it covers |
502
+ |---|---|---|
503
+ | `src/protocol/__tests__/types.test.ts` | 24 | Protocol type enums, message interfaces, field types |
504
+ | `src/client/__tests__/vocall-client.test.ts` | 94 | WebSocket connection, reconnection, command execution, streaming |
505
+ | `src/composables/__tests__/use-vocall.test.ts` | 25 | useVocall composable, reactive state |
506
+ | `src/composables/__tests__/use-vocall-field.test.ts` | 13 | useVocallField composable, template ref binding |
507
+ | `src/composables/__tests__/use-vocall-action.test.ts` | 7 | useVocallAction composable, callback registration |
508
+ | `src/plugin/__tests__/vocall-plugin.test.ts` | 8 | VocallPlugin installation, provide/inject |
509
+ | `src/voice/__tests__/frame-splitter.test.ts` | 13 | Audio frame splitting for voice pipeline |
510
+
511
+ ### Test Configuration
512
+
513
+ - **Framework:** Vitest with Vue Test Utils
514
+ - **Environment:** jsdom
515
+ - **Config:** [vitest.config.ts](vitest.config.ts) | [src/testing/setup.ts](src/testing/setup.ts)
516
+
517
+ ---
518
+
519
+ ## Development
520
+
521
+ ### Building the SDK
522
+
523
+ ```bash
524
+ npm install
525
+ npm run build
526
+ ```
527
+
528
+ ### Running the Demo
529
+
530
+ The demo application is in the `demo/` directory:
531
+
532
+ ```bash
533
+ cd demo
534
+ npm install
535
+ npm run dev # http://localhost:5173
536
+ ```
537
+
538
+ 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.
539
+
540
+ ### Docker
541
+
542
+ When running from the workspace, the demo is available as a Docker container:
543
+
544
+ ```bash
545
+ # From the workspace root
546
+ make serve-all # Starts engine + all demos
547
+ # Vue demo at http://localhost:21004
548
+ ```
549
+
550
+ ---
551
+
552
+ ## Related
553
+
554
+ This SDK is part of the [Primoia Vocall Workspace](https://github.com/primoia/primoia-vocall-workspace).
555
+
556
+ | Resource | Link |
557
+ |---|---|
558
+ | Workspace (setup, Docker) | [primoia-vocall-workspace](https://github.com/primoia/primoia-vocall-workspace#readme) |
559
+ | Angular SDK | [vocall-sdk-angular](https://github.com/primoia/vocall-sdk-angular) |
560
+ | Next.js SDK | [vocall-sdk-nextjs](https://github.com/primoia/vocall-sdk-nextjs) |
561
+ | Kotlin SDK | [vocall-sdk-kotlin](https://github.com/primoia/vocall-sdk-kotlin) |
562
+ | Swift SDK | [vocall-sdk-swift](https://github.com/primoia/vocall-sdk-swift) |
563
+ | Flutter SDK | [jarvis-sdk-flutter](https://github.com/primoia/jarvis-sdk-flutter) |
564
+
565
+ ---
566
+
567
+ ## License
568
+
569
+ Proprietary. All rights reserved.