@primoia/vocall-react
v1.0.0
Published
React/Next.js SDK for Vocall AAP protocol
Maintainers
Readme
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, anduseVocallActionprovide reactive state with zero boilerplate. - Plug-and-play components --
VocallChat,VocallFab, andVocallStatusPillgive 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
useSyncExternalStorefor tear-free rendering. - Next.js App Router ready -- All hooks and components are marked
'use client'.
Installation
npm install @vocall/react-sdkPeer dependencies: react >= 18.0.0 and react-dom >= 18.0.0.
Quick Start
1. Wrap your app with VocallProvider
// 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
'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>.
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.
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:
<input ref={ref} placeholder="Recipient" />Custom value accessors (for controlled inputs):
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.
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.
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.
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.
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
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:
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.
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: '[email protected]',
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
npm test # Run all tests
npm test -- --coverage # Run with coverage reportTest 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.setup.ts
Development
Building the SDK
npm install
npm run buildRunning the Demo
The demo application is in the demo/ directory:
cd demo
npm install
npm run dev # http://localhost:3000The demo connects to a Vocall server at ws://localhost:12900/connect. See the workspace README for local server setup.
Docker
When running from the workspace, the demo is available as a Docker container:
# From the workspace root
make serve-all # Starts engine + all demos
# Next.js demo at http://localhost:21003Related
This SDK is part of the Primoia Vocall Workspace.
| Resource | Link | |---|---| | Workspace (setup, Docker) | primoia-vocall-workspace | | Angular SDK | vocall-sdk-angular | | Vue SDK | vocall-sdk-vue | | Kotlin SDK | vocall-sdk-kotlin | | Swift SDK | vocall-sdk-swift | | Flutter SDK | jarvis-sdk-flutter |
License
Proprietary. All rights reserved.
