Build a full-featured Anam application with advanced UI/UX and event handling
Build a production-ready web application that showcases the full capabilities of Anam personas. This comprehensive guide walks you through creating an interactive AI assistant with modern UI components, real-time event handling, chat history, talk commands, and production-grade security practices.
This comprehensive guide takes an additive approach, covering a wide range of Anam features and implementation patterns. Each section builds upon the previous ones, allowing you to create increasingly sophisticated functionality.
We intentionally provide detailed, complete code examples to make integration with AI coding assistants seamless. Use the buttons in the top-right corner of this page to open the content in your preferred LLM or copy the entire guide for easy reference.
You likely won’t need every component shown in this guide. After completing the initial setup (Steps 1-4), use the right-hand navigation to jump directly to the specific features you want to implement:
Start with the basic setup, then selectively add the advanced features that match your use case.
Let’s start by building a simple working version, then enhance it with advanced features. This foundational setup creates a minimal web application with three main files:
Copy
Ask AI
anam-production-app/├── server.js # Express server for secure API key handling├── package.json # Node.js dependencies├── public/ # Static files served to the browser│ ├── index.html # Main HTML page with video element│ └── script.js # Client-side JavaScript for persona control└── .env # Environment variables
1
Create project directory
Copy
Ask AI
mkdir anam-production-appcd anam-production-app
2
Initialize Node.js project
Copy
Ask AI
npm init -y
This creates a package.json file for managing dependencies.
3
Create public directory
Copy
Ask AI
mkdir public
The public folder will contain your HTML and JavaScript files that are served to the browser.
4
Install dependencies
Copy
Ask AI
npm install express dotenv
We’re installing Express for the server and dotenv for environment variable management. The Anam SDK will be loaded directly from a CDN in the browser.
5
Configure environment variables
Create a .env file in your project root to store your API key securely:
.env
Copy
Ask AI
ANAM_API_KEY=your-api-key-here
Replace your-api-key-here with your actual Anam API key. Never commit this file to version control.
Anam personas communicate through an event system that allows your application to respond to connection changes, conversation updates, and user interactions. Let’s enhance our basic app with event handling to create a more responsive experience.
The Anam SDK uses an event-driven architecture where you can listen for specific events and react accordingly. Here’s the process for listening to events:
1
Import the event types
Import the specific event types you want to listen to:
Copy
Ask AI
import { AnamEvent } from "https://esm.sh/@anam-ai/js-sdk@latest/dist/module/types";
2
Define your event listener function
Create a function to handle the event:
Copy
Ask AI
function onSessionReady() { console.log('Session Ready!'); // Do something when the session is ready}
A common use case for event listeners is to update the UI based on the transcription of the conversation. Let’s look at how we can implement this using Anam events.
Now let’s add the event listener to handle complete conversation updates:
public/script.js
Copy
Ask AI
import { createClient } from "https://esm.sh/@anam-ai/js-sdk@latest";import { AnamEvent } from "https://esm.sh/@anam-ai/js-sdk@latest/dist/module/types";let anamClient = null;// Get DOM elementsconst startButton = document.getElementById("start-button");const stopButton = document.getElementById("stop-button");const videoElement = document.getElementById("persona-video");const loadingMessage = document.getElementById("loading-message");const chatHistory = document.getElementById("chat-history");function showLoadingState() { if (loadingMessage) { loadingMessage.style.display = 'block'; }}function hideLoadingState() { if (loadingMessage) { loadingMessage.style.display = 'none'; }}function updateChatHistory(messages) { if (!chatHistory) return; // Clear existing content chatHistory.innerHTML = ''; if (messages.length === 0) { chatHistory.innerHTML = '<p>Start a conversation to see your chat history</p>'; return; } // Add each message to the chat history messages.forEach(message => { const messageDiv = document.createElement('div'); messageDiv.style.marginBottom = '10px'; messageDiv.style.padding = '5px'; messageDiv.style.borderRadius = '5px'; if (message.role === 'user') { messageDiv.style.backgroundColor = '#e3f2fd'; messageDiv.innerHTML = `<strong>You:</strong> ${message.content}`; } else { messageDiv.style.backgroundColor = '#f1f8e9'; messageDiv.innerHTML = `<strong>Cara:</strong> ${message.content}`; } chatHistory.appendChild(messageDiv); }); // Scroll to bottom chatHistory.scrollTop = chatHistory.scrollHeight;}async function startChat() { try { startButton.disabled = true; showLoadingState(); // Get session token from your server const response = await fetch("/api/session-token", { method: "POST", }); const { sessionToken } = await response.json(); // Create the Anam client anamClient = createClient(sessionToken); // Listen for SESSION_READY event to hide loading state anamClient.addListener(AnamEvent.SESSION_READY, () => { console.log('Session is ready!'); hideLoadingState(); startButton.disabled = true; stopButton.disabled = false; }); // Listen for MESSAGE_HISTORY_UPDATED to update chat history anamClient.addListener(AnamEvent.MESSAGE_HISTORY_UPDATED, (messages) => { console.log('Conversation updated:', messages); updateChatHistory(messages); }); // Start streaming to the video element await anamClient.streamToVideoElement("persona-video"); console.log("Chat started successfully!"); } catch (error) { console.error("Failed to start chat:", error); startButton.disabled = false; hideLoadingState(); }}function stopChat() { if (anamClient) { // Disconnect the client anamClient.stopStreaming(); anamClient = null; // Clear video element and chat history videoElement.srcObject = null; updateChatHistory([]); // Update button states startButton.disabled = false; stopButton.disabled = true; console.log("Chat stopped."); }}// Add event listenersstartButton.addEventListener("click", startChat);stopButton.addEventListener("click", stopChat);
The MESSAGE_HISTORY_UPDATED event provides an array of message objects with role (“user” or “assistant”) and content properties. This gives you the complete conversation transcript each time someone finishes speaking.
Now let’s enhance the experience by showing live transcription as the persona speaks using the MESSAGE_STREAM_EVENT_RECEIVED event:
Copy
Ask AI
<!-- Add this to your HTML after the chat history --><div id="live-transcript" style="margin-top: 10px; padding: 10px; background-color: #e6e0ff; border-radius: 5px;"> <strong>Persona Live:</strong> <span id="transcript-text"></span></div>
And update your JavaScript to handle real-time events:
Copy
Ask AI
// Add this event listener inside your startChat function after the MESSAGE_HISTORY_UPDATED listener// Listen for real-time transcription eventsanamClient.addListener(AnamEvent.MESSAGE_STREAM_EVENT_RECEIVED, (event) => { const liveTranscript = document.getElementById("live-transcript"); const transcriptText = document.getElementById("transcript-text"); console.log("event", event); if (event.role === "persona") { // Show persona speaking in real-time if (liveTranscript && transcriptText) { transcriptText.textContent = transcriptText.textContent + event.content; } } else if (event.role === "user") { // clear the persona live transcript when the user speaks if (liveTranscript && transcriptText) { transcriptText.textContent = ""; } }});
The MESSAGE_STREAM_EVENT_RECEIVED event fires continuously as speech is being processed. Use event.type to distinguish between ‘persona’ (AI speaking) and ‘user’ (user speaking) events.
Your app now displays both complete conversation history and real-time transcription as the persona speaks!
Beyond voice interaction, you can programmatically send messages to your persona using the talk command. This is useful for creating interactive experiences, automated workflows, or custom UI controls.
The talk command allows you to send text messages that the persona will speak directly. This can be used to instruct the persona to say something in response to a user action other than voice input, such as a button click or when certain UI elements are shown on screen.
You can send a talk command by calling the talk method on the Anam client during a session.
Copy
Ask AI
anamClient.talk("Hello, how are you?");
Let’s use this to add a talk command UI to our app.
The streamToVideoElement() method accepts an optional audio input stream, allowing you to process or record user audio before sending it to the persona:
Copy
Ask AI
// Get and potentially process user audioconst userInputStream = await navigator.mediaDevices.getUserMedia({ audio: true });// Start recording user inputconst userRecorder = new MediaRecorder(userInputStream);userRecorder.start();// Use the same stream for persona inputawait anamClient.streamToVideoElement("persona-video", userInputStream);
For capturing and processing the persona’s output (video and audio), use the stream() method which returns the raw output streams:
Copy
Ask AI
const userInputStream = await navigator.mediaDevices.getUserMedia({ audio: true });const [videoStream] = await anamClient.stream(userInputStream);// Now you have full control over the output streamvideoElement.srcObject = videoStream;// Extract audio for separate processingconst audioTracks = videoStream.getAudioTracks();const personaAudioStream = new MediaStream(audioTracks);
Congratulations! You’ve built a comprehensive Anam application that demonstrates both basic and advanced stream management capabilities. Your application now includes the core patterns needed for building production persona experiences: secure authentication, event-driven updates, real-time communication, programmatic control, and advanced media stream handling.
Core Persona Functionality: A complete WebRTC-based streaming connection that enables natural voice conversations with an AI persona, including video rendering and bidirectional audio communication.
Event-Driven Architecture: Robust event handling that responds to session state changes, connection events, and conversation updates in real-time using the Anam SDK’s event system.
Live Conversation Tracking: Real-time transcription display using MESSAGE_STREAM_EVENT_RECEIVED events that shows what the persona is saying as they speak, plus complete conversation history through MESSAGE_HISTORY_UPDATED events.
Programmatic Control: Talk command functionality that allows you to send text messages directly to the persona, enabling you to react to user interactions and trigger automated responses.
Session Management: Secure server-side session token generation that properly handles API key protection and provides temporary access tokens for client-side connections.
Loading State Management: Responsive loading indicators that use the SESSION_READY event to provide proper user feedback during connection establishment and session initialization.
Advanced Stream Recording: Manual stream management with separate user and persona audio recording capabilities, enabling conversation logging, quality assurance, and custom audio processing workflows.
Integrate Your Own AI: Want to use your own language model instead of Anam’s default brain? Check out our Custom LLM Integration guide to learn how to connect your persona to custom AI models and APIs.