npm package: @primoia/vocall-react
Description: React/Next.js SDK for Vocall AAP protocol
Author: Primoia
Latest version: 1.0.0

# Vocall SDK for Next.js / React

Integrate AI-powered voice and chat assistants into React and Next.js applications using the Vocall AAP (Agent-Application Protocol).

---

## Features

- **React-native hooks** -- `useVocall`, `useVocallField`, and `useVocallAction` provide reactive state with zero boilerplate.
- **Plug-and-play components** -- `VocallChat`, `VocallFab`, and `VocallStatusPill` give you a full chat overlay out of the box.
- **Declarative manifest** -- Describe your screens, fields, and actions once; the engine handles the rest.
- **Automatic command execution** -- The SDK receives server commands (fill, navigate, click, etc.) and applies them to registered DOM elements.
- **Typewriter fill animation** -- Field values are filled character-by-character for a natural feel.
- **Auto-reconnect** -- Exponential back-off reconnection with up to 10 retry attempts.
- **Streaming tokens** -- Chat responses stream token-by-token via `useSyncExternalStore` for tear-free rendering.
- **Next.js App Router ready** -- All hooks and components are marked `'use client'`.

---

## Installation

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

**Peer dependencies:** `react >= 18.0.0` and `react-dom >= 18.0.0`.

---

## Quick Start

### 1. Wrap your app with `VocallProvider`

```tsx
// app/layout.tsx  (Next.js App Router example)
import { VocallProvider } from '@vocall/react-sdk';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <VocallProvider serverUrl="wss://your-vocall-server/connect">
          {children}
        </VocallProvider>
      </body>
    </html>
  );
}
```

### 2. Connect and send messages

```tsx
'use client';

import { useVocall, useVocallField, ManifestMessage, FieldType } from '@vocall/react-sdk';
import { useEffect, useState } from 'react';

const manifest: ManifestMessage = {
  type: 'manifest',
  app: 'my-app',
  screens: {
    home: {
      id: 'home',
      label: 'Home',
      fields: [
        { id: 'name', type: FieldType.Text, label: 'Name' },
        { id: 'email', type: FieldType.Email, label: 'Email' },
      ],
      actions: [
        { id: 'submit', label: 'Submit' },
      ],
    },
  },
};

export default function HomePage() {
  const { connect, connected, status, messages, sendText } = useVocall();
  const nameField = useVocallField('home', 'name');
  const emailField = useVocallField('home', 'email');

  useEffect(() => {
    connect(manifest);
  }, [connect]);

  return (
    <div>
      <p>Status: {status} | Connected: {String(connected)}</p>

      <input ref={nameField.ref} placeholder="Name" />
      <input ref={emailField.ref} placeholder="Email" />

      <ul>
        {messages.map((msg, i) => (
          <li key={i}><strong>{msg.role}:</strong> {msg.text}</li>
        ))}
      </ul>

      <input
        onKeyDown={(e) => {
          if (e.key === 'Enter') sendText(e.currentTarget.value);
        }}
        placeholder="Ask the assistant..."
      />
    </div>
  );
}
```

---

## Hooks API

### `useVocall()`

Main hook for interacting with the Vocall client. Must be used inside a `<VocallProvider>`.

```typescript
const {
  client,        // VocallClient    -- the underlying client instance
  status,        // VocallStatus    -- current status enum value
  messages,      // ChatMessage[]   -- conversation history (read-only)
  connected,     // boolean         -- true when the WebSocket is open
  sessionId,     // string | null   -- server-assigned session ID
  sendText,      // (text: string) => void
  connect,       // (manifest: ManifestMessage) => void
  disconnect,    // () => void
  clearMessages, // () => void
} = useVocall();
```

All reactive values (`status`, `messages`, `connected`, `sessionId`) are backed by `useSyncExternalStore` for concurrent-safe reads.

---

### `useVocallField(screenId, fieldId, options?)`

Registers a DOM element as a controllable field. The engine can then fill, clear, focus, highlight, scroll to, enable, or disable it.

```typescript
const { ref, registered } = useVocallField('invoice', 'recipient_name');
```

| Return    | Type                              | Description                                   |
|-----------|-----------------------------------|-----------------------------------------------|
| `ref`     | `(el: HTMLElement \| null) => void` | Callback ref -- attach to your input element. |
| `registered` | `boolean`                       | Whether the field is currently registered.    |

**Usage:**

```tsx
<input ref={ref} placeholder="Recipient" />
```

**Custom value accessors** (for controlled inputs):

```tsx
const [value, setValue] = useState('');
const { ref } = useVocallField('invoice', 'amount', {
  setValue: (v) => setValue(v),
  getValue: () => value,
});

<input ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
```

---

### `useVocallAction(screenId, actionId, callback)`

Registers a callback that the engine invokes when the server sends a `click` command for the given action.

```typescript
useVocallAction('invoice', 'submit', async () => {
  await submitForm();
});
```

The callback ref is kept stable internally, so the latest closure is always called without re-registering on every render.

---

## Components

### `VocallChat`

A ready-made chat panel with message bubbles, status indicators, and a text input.

```tsx
import { VocallChat } from '@vocall/react-sdk';

<VocallChat
  open={chatOpen}
  onClose={() => setChatOpen(false)}
  assistantName="Emma"
  className="my-chat"
  style={{ bottom: 80 }}
/>
```

| Prop            | Type                  | Default   | Description                         |
|-----------------|-----------------------|-----------|-------------------------------------|
| `open`          | `boolean`             | --        | Controls visibility.                |
| `onClose`       | `() => void`          | --        | Called when the user clicks close.  |
| `assistantName` | `string`              | `"Emma"`  | Name shown in the header.           |
| `className`     | `string`              | --        | CSS class for the container.        |
| `style`         | `React.CSSProperties` | --        | Inline styles for the container.    |

---

### `VocallFab`

A floating action button that reflects the current connection status through color changes and displays a spinner during `thinking` and `executing` states.

```tsx
import { VocallFab } from '@vocall/react-sdk';

<VocallFab onClick={() => setChatOpen((o) => !o)} />
```

| Prop        | Type                  | Description                      |
|-------------|-----------------------|----------------------------------|
| `onClick`   | `() => void`          | Called when the FAB is clicked.  |
| `className` | `string`              | CSS class for the button.        |
| `style`     | `React.CSSProperties` | Inline styles for the button.    |

---

### `VocallStatusPill`

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.

```tsx
import { VocallStatusPill } from '@vocall/react-sdk';

<VocallStatusPill className="my-status" />
```

| Prop        | Type                  | Description                    |
|-------------|-----------------------|--------------------------------|
| `className` | `string`              | CSS class for the pill.        |
| `style`     | `React.CSSProperties` | Inline styles for the pill.    |

---

## VocallClient API

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()`.

### Constructor

```typescript
const client = new VocallClient(serverUrl, { token?, visitorId? });
```

| Parameter   | Type     | Description                                                |
|-------------|----------|------------------------------------------------------------|
| `serverUrl` | `string` | WebSocket URL of the Vocall engine (e.g. `wss://host/connect`). |
| `token`     | `string` | Optional authentication token.                             |
| `visitorId` | `string` | Optional visitor ID. Auto-generated and persisted in `localStorage` if omitted. |

### Core Methods

| Method                                               | Description                                          |
|------------------------------------------------------|------------------------------------------------------|
| `connect(manifest: ManifestMessage)`                 | Opens the WebSocket and sends the manifest.          |
| `disconnect()`                                       | Closes the connection intentionally (no reconnect).  |
| `sendText(text: string)`                             | Sends a user chat message to the engine.             |
| `sendConfirm(seq: number, confirmed: boolean)`       | Responds to an `ask_confirm` action.                 |
| `sendResult(seq: number, results, state?)`           | Sends command execution results back to the engine.  |
| `sendState(state: StateMessage)`                     | Sends current UI state to the engine.                |
| `clearMessages()`                                    | Clears the local chat history.                       |
| `destroy()`                                          | Disconnects and removes all listeners.               |

### State Getters

| Property    | Type                      | Description                                  |
|-------------|---------------------------|----------------------------------------------|
| `status`    | `VocallStatus`            | Current engine status.                       |
| `connected` | `boolean`                 | Whether the WebSocket is open.               |
| `messages`  | `readonly ChatMessage[]`  | Conversation history.                        |
| `sessionId` | `string \| null`          | Server-assigned session ID.                  |
| `visitorId` | `string`                  | Persistent visitor identifier.               |

### Callbacks

Set these on the client instance to handle navigation, modals, toasts, and confirmation dialogs in your application:

```typescript
client.onNavigate  = (screenId) => router.push(`/${screenId}`);
client.onToast     = (message, level, duration) => showToast(message, level);
client.onConfirm   = (seq, message) => showConfirmDialog(seq, message);
client.onOpenModal = (modalId, query) => openModal(modalId);
client.onCloseModal = () => closeModal();
```

### Field and Action Registration

These methods are called automatically by `useVocallField` and `useVocallAction`, but are available for manual use:

| Method                                                 | Description                                |
|--------------------------------------------------------|--------------------------------------------|
| `registerField(screenId, fieldId, entry)`              | Registers a field element.                 |
| `unregisterField(screenId, fieldId)`                   | Removes a field registration.              |
| `registerAction(screenId, actionId, callback)`         | Registers an action callback.              |
| `unregisterAction(screenId, actionId)`                 | Removes an action registration.            |
| `unregisterScreen(screenId)`                           | Removes all fields and actions for a screen. |

---

## Manifest Structure

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

```typescript
import { ManifestMessage, FieldType } from '@vocall/react-sdk';

const manifest: ManifestMessage = {
  type: 'manifest',
  app: 'my-erp',
  version: '1.0.0',
  currentScreen: 'dashboard',
  user: {
    name: 'Jane Doe',
    email: 'jane@example.com',
    org: 'Acme Corp',
    role: 'admin',
  },
  persona: {
    name: 'Emma',
    role: 'ERP Assistant',
    instructions: 'Help the user manage invoices and clients.',
  },
  context: {
    locale: 'en-US',
    tenant: 'acme',
  },
  screens: {
    dashboard: {
      id: 'dashboard',
      label: 'Dashboard',
      route: '/dashboard',
      fields: [
        { id: 'search', type: FieldType.Text, label: 'Search' },
      ],
      actions: [
        { id: 'refresh', label: 'Refresh Data' },
        { id: 'export', label: 'Export CSV', requiresConfirmation: true },
      ],
      modals: [
        { id: 'client_picker', label: 'Select Client', searchable: true },
      ],
    },
    invoice: {
      id: 'invoice',
      label: 'New Invoice',
      route: '/invoices/new',
      fields: [
        { id: 'recipient', type: FieldType.Autocomplete, label: 'Recipient', required: true },
        { id: 'amount', type: FieldType.Currency, label: 'Amount', min: 0 },
        { id: 'due_date', type: FieldType.Date, label: 'Due Date', required: true },
        { id: 'notes', type: FieldType.Textarea, label: 'Notes', maxLength: 500 },
        {
          id: 'status',
          type: FieldType.Select,
          label: 'Status',
          options: [
            { value: 'draft', label: 'Draft' },
            { value: 'sent', label: 'Sent' },
          ],
        },
      ],
      actions: [
        { id: 'save_draft', label: 'Save Draft' },
        { id: 'send', label: 'Send Invoice', destructive: true, requiresConfirmation: true },
      ],
    },
  },
};
```

---

## Field Types

The `FieldType` enum defines the supported field types in the manifest:

| Value          | Description                                     |
|----------------|-------------------------------------------------|
| `text`         | Plain text input.                               |
| `number`       | Numeric input.                                  |
| `currency`     | Currency/monetary value input.                  |
| `date`         | Date picker.                                    |
| `datetime`     | Date and time picker.                           |
| `email`        | Email address input.                            |
| `phone`        | Phone number input.                             |
| `masked`       | Input with a custom mask pattern.               |
| `select`       | Dropdown select (provide `options`).            |
| `autocomplete` | Searchable select / autocomplete.               |
| `checkbox`     | Boolean checkbox.                               |
| `radio`        | Radio button group.                             |
| `textarea`     | Multi-line text area.                           |
| `file`         | File upload.                                    |
| `hidden`       | Hidden field (not visible to the user).         |

---

## UI Actions

The Vocall engine can send 14 UI actions to the client. These are executed automatically by the SDK when commands arrive over the WebSocket.

| Action         | Target    | Description                                          |
|----------------|-----------|------------------------------------------------------|
| `navigate`     | screen    | Navigate to a different screen.                      |
| `fill`         | field     | Set a field's value (with optional typewriter animation). |
| `clear`        | field     | Clear a field's value.                               |
| `select`       | field     | Set the selected value of a select/radio field.      |
| `click`        | action    | Invoke a registered action callback.                 |
| `highlight`    | field     | Scroll to and visually highlight a field.            |
| `focus`        | field     | Move focus to a field.                               |
| `scroll_to`    | field     | Scroll a field into view.                            |
| `show_toast`   | --        | Display a toast notification.                        |
| `ask_confirm`  | --        | Prompt the user for confirmation.                    |
| `open_modal`   | modal     | Open a modal dialog.                                 |
| `close_modal`  | --        | Close the currently open modal.                      |
| `enable`       | field     | Enable a disabled input.                             |
| `disable`      | field     | Disable an input.                                    |

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.

---

## Demo

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.

---

## Testing

The SDK includes a comprehensive test suite with ~156 tests covering protocol types, WebSocket client, React hooks, context provider, and components.

### 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` | 30 | Protocol type enums, message interfaces, field types |
| `src/client/__tests__/vocall-client.test.ts` | 94 | WebSocket connection, reconnection, command execution, streaming |
| `src/hooks/__tests__/use-vocall.test.tsx` | 10 | useVocall hook, state reactivity |
| `src/hooks/__tests__/use-vocall-field.test.tsx` | 7 | useVocallField hook, DOM element binding |
| `src/hooks/__tests__/use-vocall-action.test.tsx` | 4 | useVocallAction hook, callback registration |
| `src/context/__tests__/vocall-provider.test.tsx` | 11 | VocallProvider context, client lifecycle |

### Test Configuration

- **Framework:** Vitest with React Testing Library
- **Environment:** jsdom
- **Config:** [vitest.config.ts](vitest.config.ts) | [vitest.setup.ts](vitest.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:3000
```

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
# Next.js demo at http://localhost:21003
```

---

## 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) |
| Vue SDK | [vocall-sdk-vue](https://github.com/primoia/vocall-sdk-vue) |
| 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.