Skip to content
Getting started/Frameworks & lifecycles

ALLPERSONAS / DEVELOPERS

Frameworks & lifecycles

One renderer for your browser framework.

Native API

Call createFace(host, options) after mounting, update the controller when your state changes, and call face.destroy() before removing the view.

assistant.ts
import { createFace } from '@allpersonas/core';

export function mountAssistant(host: HTMLElement) {
  host.style.width = '280px';
  host.style.height = '340px';
  const face = createFace(host, {
    avatar: 'ruby',
    expression: 'attentive',
  });

  face.update({ thinking: true });
  // When your agent responds:
  face.update({ thinking: false, expression: 'joyful' });

  // Call the returned function when your view unmounts.
  return () => face.destroy();
}

face.update(options) merges state. Set an optional value to undefined to restore its default. See the FaceOptions reference for every input.

Web Component

Browser entry
import { defineAvatarElement } from '@allpersonas/core';

defineAvatarElement();
HTML
<allpersonas-face
  avatar="ruby"
  expression="curious"
  style="width: 280px; height: 340px"
></allpersonas-face>

Register once in your browser entry point. Registration is idempotent and does nothing on the server. The element contains its styles in Shadow DOM, starts when connected, cleans up when removed, and supports reconnection.

Attributes include avatar, expression, thinking, speaking, animated, decorative, mouth-open, artwork-src, and label. Boolean attributes accept "true" and "false"; an empty attribute means true. Removing an attribute restores its default.

Configure callbacks and state
import type { AllPersonasFaceElement } from '@allpersonas/core';

export function connectAudioLevel(
  element: AllPersonasFaceElement,
  readRmsLevel: () => number | null,
) {
  element.configure({ speaking: true, getLevel: readRmsLevel });
  return () => element.configure({ speaking: false, getLevel: undefined });
}

Call this when audio actually starts and call its cleanup on pause, waiting, or completion. Use configure for function-valued options and word-boundary refs that cannot be expressed as attributes.

Framework lifecycles

FrameworkLifecycleIntegration
ReactuseEffect → returned cleanupCreate a face after mounting, update it in an effect, and destroy it in cleanup.
VueonMounted → onBeforeUnmountCreate a face when mounted, update from a watcher, and destroy before unmount.
SvelteonMount → returned cleanupCreate a face in onMount, update it as state changes, and return its cleanup.
AngularngAfterViewInit → ngOnDestroyCreate after the host view exists, update from state changes, and destroy on teardown.
Assistant.tsx — React using core
'use client';

import { useEffect, useRef } from 'react';
import {
  createFace,
  type Expression,
  type FaceController,
} from '@allpersonas/core';

export function Assistant({
  expression = 'attentive',
  thinking = false,
}: { expression?: Expression; thinking?: boolean }) {
  const host = useRef<HTMLDivElement>(null);
  const controller = useRef<FaceController | null>(null);

  useEffect(() => {
    if (!host.current) return;
    const face = createFace(host.current, { avatar: 'ruby' });
    controller.current = face;
    return () => {
      controller.current = null;
      face.destroy();
    };
  }, []);

  useEffect(() => {
    controller.current?.update({ expression, thinking });
  }, [expression, thinking]);

  return <div ref={host} style={{ width: 280, height: 340 }} />;
}

Keep the host empty in your framework’s template so the SDK can own its contents. React development mode may mount, clean up, and mount again; returning destroy handles that lifecycle.

When using the Web Component in templates, configure Vue’s compilerOptions.isCustomElement or Angular’s CUSTOM_ELEMENTS_SCHEMA. Calling createFace on a regular DOM host needs no custom-element template configuration. This SDK renders in browsers; native mobile UI needs a separate renderer.

Server rendering

Static HTML
import { renderFace } from '@allpersonas/core';

export const assistantHtml = renderFace(
  { avatar: 'ruby', expression: 'attentive', animated: false },
  'assistant-1',
);

Importing core and calling renderFace are safe on the server. It returns static HTML with escaped option values, styles, artwork, and accessible labeling. Use a unique instance ID for each rendered face. Mount createFace on the same SDK-owned container in the browser to replace the static face with an active one.

A Web Component renders after registration in the browser. Use renderFace when the initial server response needs a visible face. For several faces, a shared artworkSrc avoids repeating the bundled data URL in HTML.

Speech lifecycle

Mount this in a DOM container. Its buttons start and stop browser speech; call the returned cleanup function when the view unmounts.

speech.ts
import { AvatarSpeech, createFace } from '@allpersonas/core';

export function mountTalkingAssistant(container: HTMLElement) {
  const root = document.createElement('div');
  root.innerHTML = `
    <div data-face style="width:280px;height:340px"></div>
    <button data-speak type="button">Say hello</button>
    <button data-stop type="button">Stop</button>
    <p data-error role="alert"></p>
  `;
  container.append(root);
  const face = createFace(root.querySelector<HTMLElement>('[data-face]')!);
  const speakButton = root.querySelector<HTMLButtonElement>('[data-speak]')!;
  const stopButton = root.querySelector<HTMLButtonElement>('[data-stop]')!;
  const error = root.querySelector<HTMLElement>('[data-error]')!;
  const speech = new AvatarSpeech();
  let disposed = false;
  const unsubscribe = speech.subscribe(() => {
    const state = speech.getSnapshot();
    face.update({
      expression: state.expression,
      speaking: state.speaking,
      boundary: speech.boundary,
    });
    error.textContent = state.error ?? '';
  });

  const sayHello = () => {
    void speech.speak([
      { expression: 'joyful', text: 'It is good to meet you.' },
      { expression: 'curious', text: 'What is on your mind?' },
    ]).catch((cause: unknown) => {
      if (!disposed) error.textContent = cause instanceof Error
        ? cause.message : 'Speech could not start.';
    });
  };
  speakButton.addEventListener('click', sayHello);
  stopButton.addEventListener('click', speech.stop);

  return () => {
    disposed = true;
    speakButton.removeEventListener('click', sayHello);
    stopButton.removeEventListener('click', speech.stop);
    unsubscribe();
    speech.stop();
    face.destroy();
    root.remove();
  };
}