What’s Included in This Demo
The repository contains four demo applications, all powered by the Perso Interactive Web SDK:
All demos connect to the same Perso Interactive API and follow the same session lifecycle.
Prerequisites
Get your API Key
Login to your Perso Platform account and create an API Key.
- Node.js v20 or higher
- pnpm package manager (npm / yarn also work when installing the SDK as a dependency)
Browser requirements for WebRTC The avatar session relies onRTCPeerConnectionandMediaStreamAPIs. These only run in a secure context, meaning the page hosting the SDK must be served over:When
http://localhost/http://127.0.0.1(development), orhttps://…(production).enableVoiceChat/startProcessSTTis used, the browser prompts for microphone permission. Camera permission is not required because the avatar video is streamed from the server — the<video>element only receives the remote track. For headless testing (e.g. Playwright), launch Chromium with--use-fake-ui-for-media-stream --use-fake-device-for-media-streamand grant["microphone", "camera"]permissions so the session can negotiate without user interaction.
Installing the SDK
Install the SDK into your own project using your preferred package manager:ChatTool is used with Tool Calling. ChatState values are documented in ChatState values.
Server-side (Node.js/SvelteKit/Next.js):
TypeScript: The SDK ships with full type definitions viaexports.typesin itspackage.json. No manual setup is needed in a modern TypeScript project (moduleResolution: "bundler"or"node16") — just import from the subpath and types resolve automatically.
Running the demo repo (optional)
If you cloned the SDK repository itself to explore the demos, install its dependencies and build the SDK from the repo root:Minimum runnable example (from scratch)
If you want to integrate the SDK into a new project without using any of the framework demos, the shortest working setup is Vite + a small Node server. Vite serves the browser bundle; Node handles session issuance so the API key stays off the client.1
Create the project
2
package.json
3
server.mjs — issues a new session ID on each GET /api/session
4
index.html (served by Vite)
5
main.js — fetches a sessionId, creates the session, wires up UI
6
Run
http://localhost:5173) and you have a working avatar with text + voice chat. Replace pieces (e.g. swap Node for Next.js API routes, swap Vite for Webpack) as your stack requires — the public contract is just GET /api/session returning { apiServerUrl, sessionId }.\SvelteKit Demo
apps/svelte (@perso-interactive-sdk-web/app-svelte)
The SvelteKit demo demonstrates server-side session creation and is recommended if you need:
- Secure API key handling
- Session configuration
- SSR-compatible architecture
Configuration
Before running the demo, configure your API key. Createapps/svelte/.env and set environment variable:
- Fetches available models and settings
- Creates a Perso session ID
- Returns the session ID to the client
/session route and the avatar connects over WebRTC.\
Next.JS Demo
apps/nextjs (@perso-interactive-sdk-web/app-nextjs)
The Next.js demo is a React-based example with server-side session creation via the App Router.
Use this demo if you want:
- A React integration with Next.js App Router
- Server-side API key protection
- A production-like reference for SDK usage
Configuration
Create a.env.local file in apps/nextjs/:
/api/session route and the avatar connects over WebRTC.
Vanilla JavaScript Demo
apps/vanilla (@perso-interactive-sdk-web/app-vanilla)
The Vanilla demo is a minimal HTML + JavaScript example powered by Vite.
Use this demo if you want:
- The simplest possible integration
- No framework dependencies
- A quick reference for SDK usage
If port 5173 is already in use (for example, if you are running the SvelteKit or TypeScript demo concurrently), Vite will automatically pick the next available port — check the terminal output for the actual URL.
TypeScript Demo
apps/typescript (@perso-interactive-sdk-web/app-typescript)
The TypeScript demo is identical to the Vanilla demo in behavior and UI, but adds:
- Full SDK typings
- Compile-time safety
- Better IDE support
If port 5173 is already in use (for example, if you are running another demo concurrently), Vite will automatically pick the next available port — check the terminal output for the actual URL.
SDK Reference
Available SDK Utilities
Client vs Server module The SDK exposes different helpers depending on which subpath you import from.
perso-interactive-sdk-web/client— use in the browser. Exports top-level helpers likegetAllSettings,getLLMs,getTTSs,getSTTs,getModelStyles,getPrompts,getDocuments,getBackgroundImages,getMcpServers,getTextNormalizations,getSessionInfo, pluscreateSession,createSessionId,ChatTool,ChatState, etc.perso-interactive-sdk-web/server— use in Node/server environments. ExportscreateSessionId,getIntroMessage,getSessionTemplates,getSessionTemplate,ApiError, and thePersoUtilServerclass (static methods for fetching options). There is nogetAllSettingson the server subpath — usePersoUtilServerstatic methods instead.
Fetching configuration (browser)
You can retrieve configuration options in two ways fromperso-interactive-sdk-web/client.
1. All-in-one (getAllSettings)
Note:2. Individual getters Call only the helpers you need:getAllSettingsreturnsttsTypes/sttTypes(notttss/stts). The key names differ from the individual getter function names.
getLLMsgetModelStylesgetBackgroundImagesgetTTSsgetSTTsgetPromptsgetDocumentsgetMcpServersgetTextNormalizationsgetSessionInfo
Fetching configuration (server)
On the server, use thePersoUtilServer class — its methods are static and mirror the client-side helpers:
Error Handling
The SDK provides anApiError class for HTTP failures.
It includes:
status
code
detail
attr
Use this to map API errors to user-facing messages or retry logic.
Session Flow Overview
1
Collect the Perso Interactive API server URL and API key
2
Fetch configuration options for the UI.
Option A: Get All Settings
Option A: Get All Settings
getAllSettings(apiServerUrl, apiKey) imported from perso-interactive-sdk-web/client — returns every config (LLM, TTS, STT, model style, prompt, document, background, MCP servers, text normalizations) in one call. Ideal for initial UI load. On the server, use PersoUtilServer.getLLMs() etc. individually.Option B: Get Individual Settings
Option B: Get Individual Settings
Call only the individual getters you need —
getLLMs(), getTTSs(), getSTTs(), getModelStyles(), getPrompts(), getDocuments(), getBackgroundImages(), getMcpServers(), getTextNormalizations(). Use this when you want to configure only a subset, or refresh specific options selectively.3
Create Session
When the user clicks START, invoke
createSessionId on the server to obtain a fresh sessionId, deliver it to the browser (for example, via a GET /api/session endpoint your app exposes), then call createSession in the browser to obtain a Session object, and finally session.setSrc(videoElement) to bind the WebRTC media stream to a <video> element.4
Drive the conversation and UI via Session methods:
session.processChat(message)— send a user message; the SDK runs the LLM, TTS, and avatar animation.session.processTTSTF(message)— make the avatar speak an exact string, bypassing the LLM.session.subscribeChatLog((log) => ...)— render the chat transcript (newest entry at index[0]).session.subscribeChatStates((states) => ...)— react to state changes. An emptySetmeans the pipeline is fully idle; otherwise use the specific members (Speaking,Recording,Analyzing, etc.) to show busy indicators or disable UI.session.startProcessSTT()/session.stopProcessSTT()— enable microphone-driven voice input.- Provide
ChatToolinstances (see Tool Calling) for app-specific actions, and handle SDK errors via the provided callbacks.
Quick Look
Server Side
1. Create Session ID
Required fields whenusing_stf_webrtc: trueWhen creating a session withusing_stf_webrtc: true, the server requiresllm_type,tts_type,stt_type, and aprompt(prompt ID). Omitting any of them returns a400response such asPrompt or Agent is required for Capability LLM. Use the values returned fromPersoUtilServer(server) orgetAllSettings(client) to populate these fields — don’t hardcode them, as the available set varies per account.
Field value shapes (server-side) The option getters return objects — the session params expect specific fields from those objects:
model_style— the model style’sname(e.g."indian_m_6_rajesh-front-ivory_shirt-earnest"). For WebRTC sessions, filter to styles whereplatform_type === "webrtc".prompt— the prompt’sprompt_id(e.g."plp-ce0cd928..."). Not the prompt’snameorid.llm_type,tts_type,stt_type— the corresponding object’snamefield.
sessionId is single-use Each sessionId returned bycreateSessionIdis consumed by the first successfulcreateSessioncall that uses it. Re-using a sessionId — for example, after a page reload, a failed negotiation, or an app that re-connects — returns400: ICE server data is only available in created status. Generate a new sessionId on the server every time the browser starts a freshcreateSession. TheMinimum runnable exampleabove does this by re-fetchingGET /api/sessionon each page load.
2. Create Session WebRTC(Browser)
TheintroMessagereturned bygetIntroMessage()is not passed tocreateSession. Use it in your own UI (for example, render it as the avatar’s first message in the transcript).
Mounting the avatar to a video element
createSession does not attach to the DOM on its own. It returns a session object; you then bind that session to a <video> element by calling session.setSrc(videoElement).
chatbotWidthandchatbotHeightare in pixels.- Audio is delivered on the video element’s audio track (WebRTC
MediaStream). No separate<audio>element is required for voice chat. - For complete integration patterns (error handling, chat-state subscription, UI components), see the SvelteKit, Next.js, Vanilla, or TypeScript demos in the SDK repository.\
Sending messages and receiving replies
Once the avatar video is mounted, drive the conversation through methods on theSession object returned by createSession. The SDK handles the LLM call, TTS synthesis, and avatar animation — your app only needs to send text and render the updated chat log.
For a complete, runnable wiring of these APIs (HTML + event handlers + state UI), see the
main.js in Minimum runnable example above.\
Voice chat (optional)
To let the user talk to the avatar via microphone instead of typing, use the SDK’s built-in STT pipeline. Transcribed speech is automatically routed throughprocessChat, so the reply flows through the same subscribeChatLog callback as text input.
Use
ChatState.RECORDING in subscribeChatStates to render a live “listening” indicator. A mic-toggle button wired to these two calls is shown in main.js of Minimum runnable example.
ChatState values
ChatState is an enum exported from perso-interactive-sdk-web/client. subscribeChatStates hands you a Set<ChatState> (multiple states can be active at once) so your UI can reflect exactly what the pipeline is doing:
Important: always checkUse these to enable/disable the Send button, show a spinner, or gate further user input.\states.size === 0before the individual flags. Some stages (e.g.ANALYZING) can remain in the set briefly before the pipeline fully drains, so matching specific members first will leave your UI stuck on “Thinking…” after a reply has already finished.

