@primoia/vocall-angular
v1.0.1
Published
Angular SDK for Vocall - WebSocket UI automation platform
Maintainers
Readme
Vocall SDK for Angular
Integrate voice and chat-driven UI automation into your Angular applications using the Vocall AAP protocol.
Features
- Bidirectional WebSocket communication with the Vocall engine
- Reactive state management via RxJS
BehaviorSubjectobservables - Automatic NgZone integration for seamless change detection
- Screen-scoped field and action registration
- 14 built-in UI actions (fill, navigate, click, highlight, and more)
- Typewriter-style animated field filling
- Automatic reconnection with exponential backoff
- Persistent visitor identification via
localStorage - Streaming chat token support for real-time responses
- Compatible with Angular 18 and 19 (standalone components)
Installation
npm install @vocall/angular-sdkPeer Dependencies
The SDK requires the following peer dependencies:
{
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"rxjs": "^7.8.0"
}Quick Start
Both VocallService and FieldRegistryService are provided in root, so no additional module configuration is needed.
1. Connect to the Vocall server
import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
import {
VocallService,
ManifestMessage,
FieldType,
VocallStatus,
} from '@vocall/angular-sdk';
@Component({
selector: 'app-my-form',
standalone: true,
template: `
<form>
<label for="name">Name</label>
<input #nameInput id="name" type="text" />
<label for="email">Email</label>
<input #emailInput id="email" type="email" />
<button type="button" (click)="onSubmit()">Submit</button>
</form>
<p>Status: {{ vocall.status$ | async }}</p>
`,
})
export class MyFormComponent implements OnInit, OnDestroy {
@ViewChild('nameInput', { static: true }) nameInput!: ElementRef<HTMLInputElement>;
@ViewChild('emailInput', { static: true }) emailInput!: ElementRef<HTMLInputElement>;
constructor(public vocall: VocallService) {}
ngOnInit(): void {
const manifest: ManifestMessage = {
type: 'manifest',
app: 'my-app',
screens: {
form: {
id: 'form',
label: 'Contact Form',
fields: [
{ id: 'name', type: FieldType.Text, label: 'Name', required: true },
{ id: 'email', type: FieldType.Email, label: 'Email', required: true },
],
actions: [
{ id: 'submit', label: 'Submit' },
],
},
},
};
// Register fields
this.vocall.registerField('form', 'name', {
element: this.nameInput.nativeElement,
setValue: (v) => (this.nameInput.nativeElement.value = v),
getValue: () => this.nameInput.nativeElement.value,
});
this.vocall.registerField('form', 'email', {
element: this.emailInput.nativeElement,
setValue: (v) => (this.emailInput.nativeElement.value = v),
getValue: () => this.emailInput.nativeElement.value,
});
// Register actions
this.vocall.registerAction('form', 'submit', () => this.onSubmit());
// Set callbacks
this.vocall.onNavigate = (screenId) => console.log('Navigate to:', screenId);
this.vocall.onToast = (message, level) => console.log(`[${level}] ${message}`);
// Connect
this.vocall.connect('ws://localhost:12900/connect', manifest);
}
onSubmit(): void {
console.log('Form submitted');
}
ngOnDestroy(): void {
this.vocall.unregisterScreen('form');
this.vocall.disconnect();
}
}API Reference
VocallService
The primary injectable service. Provided in root -- no module imports required.
Observables
| Observable | Type | Description |
|---|---|---|
| status$ | Observable<VocallStatus> | Current engine status (disconnected, idle, thinking, executing, etc.) |
| connected$ | Observable<boolean> | Whether the WebSocket connection is active |
| messages$ | Observable<ChatMessage[]> | Full chat message history (user, agent, system) |
| sessionId$ | Observable<string \| null> | Current session ID assigned by the server |
| rawMessage$ | Observable<Record<string, unknown>> | Every raw incoming server message |
Synchronous Getters
| Getter | Type | Description |
|---|---|---|
| status | VocallStatus | Current status value |
| connected | boolean | Current connection state |
| messages | ChatMessage[] | Current message array |
| sessionId | string \| null | Current session ID |
| client | VocallClient \| null | Underlying client instance |
Methods
// Connection
connect(serverUrl: string, manifest: ManifestMessage, options?: { token?: string; visitorId?: string }): void
disconnect(): void
// Messaging
sendText(text: string): void
sendConfirm(seq: number, confirmed: boolean): void
sendResult(seq: number, results: ActionResult[], state?: StateMessage): void
sendState(state: StateMessage): void
clearMessages(): void
// Field & action registration
registerField(screenId: string, fieldId: string, entry: FieldEntry): void
unregisterField(screenId: string, fieldId: string): void
unregisterScreen(screenId: string): void
registerAction(screenId: string, actionId: string, callback: ActionCallback): void
unregisterAction(screenId: string, actionId: string): voidCallbacks
Set these on the service to handle server-initiated UI actions:
// Navigation between screens
vocall.onNavigate = (screenId: string) => { ... };
// Toast/snackbar notifications
vocall.onToast = (message: string, level: string, duration?: number) => { ... };
// Confirmation dialogs
vocall.onConfirm = (seq: number, message: string) => {
const confirmed = window.confirm(message);
vocall.sendConfirm(seq, confirmed);
};
// Modal open/close
vocall.onOpenModal = (modalId: string, query?: string) => { ... };
vocall.onCloseModal = () => { ... };FieldRegistryService
Manages field and action registrations across Angular components. Provided in root.
// Field registration
registerField(screenId: string, fieldId: string, entry: FieldEntry): void
unregisterField(screenId: string, fieldId: string): void
unregisterScreen(screenId: string): void
// Action registration
registerAction(screenId: string, actionId: string, callback: ActionCallback): void
unregisterAction(screenId: string, actionId: string): void
// Lookups
getField(screenId: string, fieldId: string): FieldEntry | undefined
findField(fieldId: string): FieldEntry | undefined
getAction(screenId: string, actionId: string): ActionCallback | undefined
findAction(actionId: string): ActionCallback | undefinedFieldEntry
Each registered field must provide:
interface FieldEntry {
element: HTMLElement; // The DOM element
setValue: (value: string) => void; // Programmatic value setter
getValue: () => string; // Current value getter
}VocallStatus
enum VocallStatus {
Disconnected = 'disconnected',
Idle = 'idle',
Listening = 'listening',
Recording = 'recording',
Thinking = 'thinking',
Speaking = 'speaking',
Executing = 'executing',
}Manifest Structure
The manifest describes your application's screens, fields, and actions. It is sent to the server immediately after connection.
const manifest: ManifestMessage = {
type: 'manifest',
app: 'my-application',
version: '1.0.0',
currentScreen: 'dashboard',
user: {
name: 'John Doe',
email: '[email protected]',
org: 'Acme Corp',
role: 'admin',
},
persona: {
name: 'Assistant',
role: 'helper',
instructions: 'Help the user fill out forms.',
},
screens: {
dashboard: {
id: 'dashboard',
label: 'Dashboard',
route: '/dashboard',
fields: [
{
id: 'search',
type: FieldType.Text,
label: 'Search',
placeholder: 'Search...',
},
],
actions: [
{ id: 'refresh', label: 'Refresh' },
{ id: 'export', label: 'Export', requiresConfirmation: true },
],
modals: [
{ id: 'details', label: 'Details', searchable: true },
],
},
form: {
id: 'form',
label: 'Registration Form',
route: '/register',
fields: [
{ id: 'full_name', type: FieldType.Text, label: 'Full Name', required: true },
{ id: 'birth_date', type: FieldType.Date, label: 'Birth Date' },
{ id: 'country', type: FieldType.Select, label: 'Country', options: [
{ value: 'us', label: 'United States' },
{ value: 'br', label: 'Brazil' },
]},
{ id: 'notes', type: FieldType.Textarea, label: 'Notes', maxLength: 500 },
{ id: 'agree', type: FieldType.Checkbox, label: 'I agree to terms' },
],
actions: [
{ id: 'submit', label: 'Submit' },
{ id: 'cancel', label: 'Cancel', destructive: true },
],
},
},
};FieldDescriptor Properties
| Property | Type | Description |
|---|---|---|
| id | string | Unique field identifier within the screen |
| type | FieldType | Field type (see below) |
| label | string | Human-readable label |
| required | boolean | Whether the field is required |
| mask | string | Input mask pattern (for masked fields) |
| placeholder | string | Placeholder text |
| options | SelectOption[] | Options for select/autocomplete fields |
| source | string | Data source identifier |
| min | number | Minimum value |
| max | number | Maximum value |
| maxLength | number | Maximum character length |
| readOnly | boolean | Whether the field is read-only |
Field Types
The FieldType enum defines all supported field types:
| Type | Value | Description |
|---|---|---|
| Text | text | Standard text input |
| Number | number | Numeric input |
| Currency | currency | Currency/monetary 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 mask pattern |
| Select | select | Dropdown select |
| Autocomplete | autocomplete | Autocomplete/typeahead input |
| Checkbox | checkbox | Checkbox toggle |
| Radio | radio | Radio button group |
| Textarea | textarea | Multi-line text area |
| File | file | File upload input |
| Hidden | hidden | Hidden field |
UI Actions
The Vocall engine can send 14 UI actions to the client. These are executed automatically by the SDK when a command message is received.
| 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 a select/dropdown value |
| click | action | Trigger a registered action callback |
| highlight | field | Scroll to and visually highlight a field |
| focus | field | Focus 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 current modal |
| enable | field | Enable a disabled field |
| disable | field | Disable a field |
Actions are categorized internally as sequential (navigate, click, open_modal, close_modal, ask_confirm, show_toast) or parallel (all others). Sequential actions run in order; parallel actions execute concurrently for performance.
Demo
A demo application is available in the demo/ directory of this repository. It provides a working example of field registration, action handling, and WebSocket communication with the Vocall engine.
Testing
The SDK includes a comprehensive test suite with ~180 tests covering protocol types, field registry, WebSocket client, and the Angular service layer.
Running Tests
npm test # Run all tests
npm test -- --coverage # Run with coverage reportTest Structure
| File | Tests | What it covers |
|---|---|---|
| src/lib/protocol/types.spec.ts | 29 | Protocol type enums, message interfaces, field types |
| src/lib/services/field-registry.service.spec.ts | 29 | Field and action registration, lookups, screen management |
| src/lib/client/vocall-client.spec.ts | 79 | WebSocket connection, reconnection, command execution, streaming |
| src/lib/services/vocall.service.spec.ts | 43 | Angular service wrapper, RxJS observables, NgZone integration |
Test Configuration
- Framework: Jest with ts-jest
- Environment: jsdom
- Config: jest.config.ts | tsconfig.spec.json
Development
Building the SDK
npm install
npm run buildRunning the Demo
The demo application is in the demo/ directory:
cd demo
npm install
npm start # http://localhost:4200The 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
# Angular demo at http://localhost:21002Related
This SDK is part of the Primoia Vocall Workspace.
| Resource | Link | |---|---| | Workspace (setup, Docker) | primoia-vocall-workspace | | Next.js SDK | vocall-sdk-nextjs | | 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.
