npm package: @primoia/vocall-vue
Description: Vocall Vue 3 SDK - WebSocket UI automation platform
Author: Primoia
Latest version: 1.0.0

# Vocall SDK for Vue

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.

---

## Features

- Reactive composables built on Vue 3 Composition API (`ref`, `inject`, `watch`)
- Plugin-based setup with a shared `VocallClient` instance across your application
- Two-way field binding: register any `<input>`, `<select>`, or `<textarea>` so the engine can fill, clear, focus, and highlight it
- Action registration: expose buttons and callbacks the engine can trigger remotely
- Pre-built UI components (`VocallChat`, `VocallFab`, `VocallStatus`) with scoped styles
- Streaming chat tokens with automatic message assembly
- Automatic WebSocket reconnection with exponential back-off (up to 10 attempts)
- Full TypeScript support with exported types for every protocol message

---

## Installation

```bash
npm install @vocall/vue-sdk
```

Peer dependency: **Vue >= 3.4.0**

---

## Quick Start

### 1. Register the plugin

```typescript
// main.ts
import { createApp } from 'vue';
import { VocallPlugin } from '@vocall/vue-sdk';
import App from './App.vue';

const app = createApp(App);

app.use(VocallPlugin, {
  url: 'ws://localhost:12900/connect',
  token: 'my-api-token',        // optional
  visitorId: 'visitor-abc-123',  // optional, auto-generated if omitted
});

app.mount('#app');
```

### 2. Connect and send messages

```vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { useVocall, useVocallField, type ManifestMessage } from '@vocall/vue-sdk';

const {
  status, messages, connected,
  connect, disconnect, sendText, client,
} = useVocall();

// Register a field so the engine can fill it
const { fieldRef, value: nameValue } = useVocallField({
  fieldId: 'customer_name',
  client,
});

// Build the manifest and connect
onMounted(() => {
  const manifest: ManifestMessage = {
    type: 'manifest',
    app: 'my-app',
    screens: {
      home: {
        id: 'home',
        label: 'Home',
        fields: [
          { id: 'customer_name', type: 'text', label: 'Customer Name', required: true },
        ],
      },
    },
    currentScreen: 'home',
  };
  connect(manifest);
});
</script>

<template>
  <p>Status: {{ status }}</p>
  <input ref="fieldRef" v-model="nameValue" placeholder="Customer Name" />
  <button @click="sendText('Fill the customer name with John Doe')">
    Ask Vocall
  </button>
</template>
```

---

## Composables API

### `useVocall(options?)`

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.

**Options** (`UseVocallOptions`):

| Property | Type | Default | Description |
|---|---|---|---|
| `serverUrl` | `string` | `ws://localhost:12900/connect` | WebSocket server URL |
| `token` | `string` | -- | Optional auth token |
| `visitorId` | `string` | auto-generated | Persistent visitor identifier |
| `autoDispose` | `boolean` | `true` | Dispose the client when the component unmounts |

**Returns** (`UseVocallReturn`):

| Property | Type | Description |
|---|---|---|
| `status` | `Ref<VocallStatus>` | Current connection/processing status (readonly) |
| `messages` | `Ref<ChatMessage[]>` | Reactive chat message list |
| `connected` | `Ref<boolean>` | Whether the WebSocket is connected (readonly) |
| `sessionId` | `Ref<string \| null>` | Session ID assigned by the server (readonly) |
| `pendingConfirmSeq` | `Ref<number>` | Sequence number of a pending confirmation (-1 if none) |
| `pendingConfirmMessage` | `Ref<string \| null>` | Text of a pending confirmation dialog |
| `sendText(text)` | `(text: string) => void` | Send a chat message to the engine |
| `sendConfirm(seq, confirmed)` | `(seq: number, confirmed: boolean) => void` | Respond to a confirmation request |
| `connect(manifest)` | `(manifest: ManifestMessage) => void` | Open the WebSocket and send the manifest |
| `disconnect()` | `() => void` | Close the connection |
| `sendState(screen)` | `(screen: string) => void` | Send current field state to the server |
| `clearMessages()` | `() => void` | Clear the local chat history |
| `client` | `VocallClient` | Underlying client instance for advanced use |

---

### `useVocallField(options)`

Registers a form field with the Vocall client so the engine can fill, clear, focus, and highlight it.

**Options** (`UseVocallFieldOptions`):

| Property | Type | Description |
|---|---|---|
| `fieldId` | `string` | Unique identifier matching a field in the manifest |
| `client` | `VocallClient` | Client instance from `useVocall().client` |
| `initialValue` | `string` | Optional initial value |

**Returns** (`UseVocallFieldReturn`):

| Property | Type | Description |
|---|---|---|
| `fieldRef` | `Ref<HTMLInputElement \| HTMLSelectElement \| HTMLTextAreaElement \| null>` | Template ref to bind to the element |
| `value` | `Ref<string>` | Reactive model value (use with `v-model`) |

**Example:**

```vue
<script setup lang="ts">
import { useVocall, useVocallField } from '@vocall/vue-sdk';

const { client } = useVocall();

const { fieldRef: emailRef, value: emailValue } = useVocallField({
  fieldId: 'email',
  client,
});
</script>

<template>
  <input ref="emailRef" v-model="emailValue" type="email" />
</template>
```

---

### `useVocallAction(options)`

Registers an action handler with the Vocall client so the engine can trigger it via the `click` command.

**Options** (`UseVocallActionOptions`):

| Property | Type | Description |
|---|---|---|
| `actionId` | `string` | Unique identifier matching an action in the manifest |
| `client` | `VocallClient` | Client instance from `useVocall().client` |
| `handler` | `() => void \| Promise<void>` | Callback invoked when the engine triggers this action |

**Example:**

```vue
<script setup lang="ts">
import { useVocall, useVocallAction } from '@vocall/vue-sdk';

const { client } = useVocall();

useVocallAction({
  actionId: 'submit_form',
  client,
  handler: async () => {
    await saveData();
    console.log('Form submitted by Vocall');
  },
});
</script>
```

---

## Plugin

### `VocallPlugin`

Install Vocall as a Vue plugin to create a single shared `VocallClient` instance that is provided to all components via `inject`.

```typescript
import { createApp } from 'vue';
import { VocallPlugin } from '@vocall/vue-sdk';

const app = createApp(App);

app.use(VocallPlugin, {
  url: 'ws://localhost:12900/connect',
  token: 'my-api-token',
  visitorId: 'visitor-abc-123',
});

app.mount('#app');
```

**Options** (`VocallPluginOptions`):

| Property | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | No | WebSocket server URL. Defaults to `ws://localhost:12900/connect` |
| `token` | `string` | No | Optional authentication token |
| `visitorId` | `string` | No | Visitor identifier. Auto-generated and persisted in `localStorage` if omitted |

When the plugin is installed, the client is also available on `this.$vocall` for Options API components.

---

## Components

### `VocallChat`

A full chat panel with message list, text input, and header showing connection status.

**Props:**

| Prop | Type | Description |
|---|---|---|
| `messages` | `ChatMessage[]` | Array of chat messages to display |
| `status` | `VocallStatus` | Current engine status |
| `connected` | `boolean` | Whether the client is connected |
| `sessionId` | `string \| null` | Optional session identifier |

**Events:**

| Event | Payload | Description |
|---|---|---|
| `send` | `text: string` | Emitted when the user submits a message |
| `clear` | -- | Emitted when the user clicks the clear button |
| `close` | -- | Emitted when the user clicks the close button |

```vue
<template>
  <VocallChat
    :messages="messages"
    :status="status"
    :connected="connected"
    @send="sendText"
    @clear="clearMessages"
    @close="chatOpen = false"
  />
</template>
```

### `VocallFab`

A floating action button (fixed bottom-right) that changes color based on the engine status and shows a spinner during `thinking` or `executing` states.

**Props:**

| Prop | Type | Description |
|---|---|---|
| `status` | `VocallStatus` | Current engine status |
| `connected` | `boolean` | Whether the client is connected |

**Events:**

| Event | Description |
|---|---|
| `click` | Emitted when the FAB is clicked |

```vue
<template>
  <VocallFab :status="status" :connected="connected" @click="toggleChat" />
</template>
```

### `VocallStatus`

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.

**Props:**

| Prop | Type | Description |
|---|---|---|
| `status` | `VocallStatus` | Current engine status |
| `connected` | `boolean` | Whether the client is connected |

```vue
<template>
  <VocallStatus :status="status" :connected="connected" />
</template>
```

---

## VocallClient API

The `VocallClient` class manages the WebSocket connection, field/action registry, and command execution. It is typically accessed via `useVocall().client`.

### Properties

| Property | Type | Description |
|---|---|---|
| `status` | `VocallStatus` | Current status |
| `messages` | `ChatMessage[]` | Chat message history |
| `sessionId` | `string \| null` | Server-assigned session ID |
| `connected` | `boolean` | Connection state |
| `registry` | `VocallFieldRegistry` | Field and action registry |
| `events` | `EventEmitter` | Internal event bus |
| `serverUrl` | `string` | WebSocket server URL (read/write) |
| `token` | `string \| undefined` | Auth token (read/write) |
| `visitorId` | `string` | Visitor identifier (read-only) |

### Methods

| Method | Description |
|---|---|
| `connect(manifest)` | Open the WebSocket and send the manifest |
| `disconnect()` | Close the connection intentionally |
| `sendText(text)` | Send a chat message |
| `sendConfirm(seq, confirmed)` | Respond to a confirmation dialog |
| `sendResult(seq, results, currentScreen?)` | Send command execution results |
| `sendState(screen)` | Send current field state snapshot |
| `dispose()` | Disconnect, remove listeners, and clear registries |

### Events

Subscribe to events via `client.events.on(event, handler)`:

| Event | Arguments | Description |
|---|---|---|
| `change` | -- | Fired whenever any reactive state changes |
| `confirm` | `seq: number, message: string` | A confirmation dialog was requested |
| `toast` | `message: string, level: string, duration: number` | A toast notification was requested |

---

## Manifest Structure

The manifest describes your application's screens, fields, and actions. It is sent to the engine on connection.

```typescript
import type { ManifestMessage } from '@vocall/vue-sdk';

const manifest: ManifestMessage = {
  type: 'manifest',
  app: 'invoice-app',
  version: '1.0.0',
  currentScreen: 'dashboard',
  user: {
    name: 'Jane Doe',
    email: 'jane@example.com',
    org: 'Acme Corp',
    role: 'admin',
  },
  persona: {
    name: 'Emma',
    role: 'Invoice Assistant',
    instructions: 'Help the user issue invoices and manage taxpayers.',
  },
  context: {
    locale: 'en-US',
    timezone: 'America/New_York',
  },
  screens: {
    dashboard: {
      id: 'dashboard',
      label: 'Dashboard',
      route: '/dashboard',
      fields: [
        { id: 'search', type: 'text', label: 'Search', placeholder: 'Search...' },
      ],
      actions: [
        { id: 'new_invoice', label: 'New Invoice' },
      ],
    },
    invoice_form: {
      id: 'invoice_form',
      label: 'Issue Invoice',
      route: '/invoices/new',
      fields: [
        { id: 'customer_name', type: 'text', label: 'Customer', required: true },
        { id: 'amount', type: 'currency', label: 'Amount', required: true },
        { id: 'due_date', type: 'date', label: 'Due Date' },
        { id: 'notes', type: 'textarea', label: 'Notes', maxLength: 500 },
        {
          id: 'status',
          type: 'select',
          label: 'Status',
          options: [
            { value: 'draft', label: 'Draft' },
            { value: 'sent', label: 'Sent' },
          ],
        },
      ],
      actions: [
        { id: 'save_invoice', label: 'Save', requiresConfirmation: true },
        { id: 'cancel', label: 'Cancel', destructive: true },
      ],
      modals: [
        { id: 'customer_search', label: 'Search Customer', searchable: true },
      ],
    },
  },
};
```

---

## Field Types

The `FieldType` enum defines all supported field types:

| Type | Value | Description |
|---|---|---|
| `Text` | `text` | Plain text input |
| `Number` | `number` | Numeric input |
| `Currency` | `currency` | Monetary value input |
| `Date` | `date` | Date picker |
| `Datetime` | `datetime` | Date and time picker |
| `Email` | `email` | Email address input |
| `Phone` | `phone` | Phone number input |
| `Masked` | `masked` | Input with a custom mask pattern |
| `Select` | `select` | Dropdown select |
| `Autocomplete` | `autocomplete` | Autocomplete / typeahead |
| `Checkbox` | `checkbox` | Boolean checkbox |
| `Radio` | `radio` | Radio button group |
| `Textarea` | `textarea` | Multi-line text area |
| `File` | `file` | File upload |
| `Hidden` | `hidden` | Hidden field (not visible to the user) |

---

## UI Actions

The engine can send commands containing one or more of the following 14 UI actions:

| Action | Target | Description |
|---|---|---|
| `navigate` | `screen` | Navigate to a different screen |
| `fill` | `field` | Fill a field with a value (supports typewriter animation) |
| `clear` | `field` | Clear a field's value |
| `select` | `field` | Set the value of a select/dropdown field |
| `click` | `action` | Trigger a registered action handler |
| `highlight` | `field` | Temporarily highlight a field element |
| `focus` | `field` | Focus a field element |
| `scroll_to` | `field` | Scroll the viewport to a field |
| `show_toast` | -- | Display a toast notification |
| `ask_confirm` | -- | Show a confirmation dialog and wait for user response |
| `open_modal` | `modal` | Open a modal by ID with an optional search query |
| `close_modal` | -- | Close the currently open modal |
| `enable` | `field` | Enable a disabled field |
| `disable` | `field` | Disable a field |

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.

---

## Demo

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.

---

## Testing

The SDK includes a comprehensive test suite with ~184 tests covering protocol types, WebSocket client, Vue composables, plugin integration, and voice utilities.

### Running Tests

```bash
npm test                # Run all tests
npm test -- --coverage  # Run with coverage report
```

### Test Structure

| File | Tests | What it covers |
|---|---|---|
| `src/protocol/__tests__/types.test.ts` | 24 | Protocol type enums, message interfaces, field types |
| `src/client/__tests__/vocall-client.test.ts` | 94 | WebSocket connection, reconnection, command execution, streaming |
| `src/composables/__tests__/use-vocall.test.ts` | 25 | useVocall composable, reactive state |
| `src/composables/__tests__/use-vocall-field.test.ts` | 13 | useVocallField composable, template ref binding |
| `src/composables/__tests__/use-vocall-action.test.ts` | 7 | useVocallAction composable, callback registration |
| `src/plugin/__tests__/vocall-plugin.test.ts` | 8 | VocallPlugin installation, provide/inject |
| `src/voice/__tests__/frame-splitter.test.ts` | 13 | Audio frame splitting for voice pipeline |

### Test Configuration

- **Framework:** Vitest with Vue Test Utils
- **Environment:** jsdom
- **Config:** [vitest.config.ts](vitest.config.ts) | [src/testing/setup.ts](src/testing/setup.ts)

---

## Development

### Building the SDK

```bash
npm install
npm run build
```

### Running the Demo

The demo application is in the `demo/` directory:

```bash
cd demo
npm install
npm run dev        # http://localhost:5173
```

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.

### Docker

When running from the workspace, the demo is available as a Docker container:

```bash
# From the workspace root
make serve-all     # Starts engine + all demos
# Vue demo at http://localhost:21004
```

---

## Related

This SDK is part of the [Primoia Vocall Workspace](https://github.com/primoia/primoia-vocall-workspace).

| Resource | Link |
|---|---|
| Workspace (setup, Docker) | [primoia-vocall-workspace](https://github.com/primoia/primoia-vocall-workspace#readme) |
| Angular SDK | [vocall-sdk-angular](https://github.com/primoia/vocall-sdk-angular) |
| Next.js SDK | [vocall-sdk-nextjs](https://github.com/primoia/vocall-sdk-nextjs) |
| Kotlin SDK | [vocall-sdk-kotlin](https://github.com/primoia/vocall-sdk-kotlin) |
| Swift SDK | [vocall-sdk-swift](https://github.com/primoia/vocall-sdk-swift) |
| Flutter SDK | [jarvis-sdk-flutter](https://github.com/primoia/jarvis-sdk-flutter) |

---

## License

Proprietary. All rights reserved.