### Start Node.js Application Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/README.md Starts a Node.js application. This command is used to run the main script of the project after all dependencies have been installed and configurations are set. ```bash npm start ``` -------------------------------- ### Install Node.js Dependencies Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/README.md Installs project dependencies using npm. Ensure you have Node.js and npm installed. This command is typically run after cloning a project and setting up environment variables. ```bash npm install ``` -------------------------------- ### Python FastAPI App Setup Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/README.md This snippet shows the basic setup of a FastAPI application, including essential imports and the main app instance. It also defines the root endpoint '/' which serves the main HTML template. ```python import uvicorn from fastapi import FastAPI, Request, Form, UploadFile, File from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from pathlib import Path from simple_webchat.config import settings from simple_webchat.openai_client import OpenAIClient from simple_webchat.avatartalk_client import AvatarTalkClient app = FastAPI() templates = Jinja2Templates(directory="templates") # Initialize clients openai_client = OpenAIClient() avatartalk_client = AvatarTalkClient() @app.get("/", response_class=HTMLResponse) async def get(request: Request): return templates.TemplateResponse("index.html", {"request": request}) # Add other routes here... if __name__ == "__main__": uvicorn.run(app, host=settings.APP_HOST, port=settings.APP_PORT) ``` -------------------------------- ### Bash Uv Server Start Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/README.md This bash command starts the FastAPI application using the `uv` ASGI server in app mode. It's a simpler way to run the application compared to uvicorn. ```bash uv run simple-webchat ``` -------------------------------- ### Running the LiveKit WebChat App with UV Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-webchat/README.md This command shows how to run the FastAPI LiveKit WebChat application using the `uv` command-line tool. `uv` is a fast Python package installer and application runner. This is a streamlined way to start the development server. Ensure you have `uv` installed and the `.env` file configured. ```bash uv run livekit-webchat ``` -------------------------------- ### Bash Uvicorn Server Start Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/README.md This bash command starts the FastAPI application using uvicorn with hot-reloading enabled. It specifies the module, app instance, and port for the server. ```bash uvicorn simple_webchat.app:app --reload --port 8000 ``` -------------------------------- ### Clone LiveKit Agents Repository and Navigate to Example Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-agents/README.md These commands demonstrate how to clone the LiveKit Agents repository from GitHub and navigate into the specific directory for the AvatarTalk examples. This is a prerequisite for setting up and running the integration. ```bash git clone https://github.com/livekit/agents.git cd agents cd examples/avatar_agents/avatartalk/ ``` -------------------------------- ### Environment Variable Setup (.env example) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/simple-webchat/README.md This snippet shows the structure of the .env file, which is used to configure API keys and other settings for the application. It highlights required keys like OPENAI_API_KEY and AVATARTALK_API_KEY, along with optional parameters for customization. ```dotenv OPENAI_API_KEY=sk-... OPENAI_MODEL=gpt-4o-mini OPENAI_STT_MODEL=whisper-1 AVATARTALK_API_KEY=at_... AVATARTALK_API_BASE=https://api.avatartalk.ai AVATARTALK_AVATAR=european_woman AVATARTALK_EMOTION=neutral AVATARTALK_LANGUAGE=en AVATARTALK_DELAYED=false APP_HOST=127.0.0.1 APP_PORT=8000 APP_DEBUG=true ``` -------------------------------- ### Install Dependencies and Run AvatarTalk Agent Worker Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-agents/README.md This snippet shows the final steps to run the AvatarTalk integration with LiveKit Agents. It involves installing the necessary Python dependencies using pip and then executing the agent worker script with the 'dev' argument. ```bash pip install -r requirements.txt python agent_worker.py dev ``` -------------------------------- ### Create LiveKit Session Example Request Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/API.md An example cURL command to demonstrate how to send a POST request to create a LiveKit session with the specified parameters. ```bash curl -X POST https://api.avatartalk.ai/livekit/create-session \ -H "Authorization: Bearer your_api_key" \ -H "Content-Type: application/json" \ -d '{ "room_name": "demo-room", "room_token": "eyJhbGc...", "listener_token": "eyJhbGc...", "livekit_url": "wss://my-livekit.com", "avatar": "japanese_woman", "emotion": "happy" }' ``` -------------------------------- ### Node.js Server Setup for LiveKit WebChat Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/livekit-webchat/README.md Sets up an Express server for the LiveKit WebChat application. It handles environment variable loading, defines routes for the UI, session management, chat, voice input, and transcription. Dependencies include Express, Nunjucks, OpenAI SDK, LiveKit Server SDK, and WebSocket client. ```javascript const express = require('express'); const nunjucks = require('nunjucks'); const dotenv = require('dotenv'); const { getChatCompletion, getAudioTranscription, } = require('./openai_client'); const { mintRoom, mintUserToken } = require('./livekit_client'); const { infer } = require('./avatartalk_ws'); dotenv.config(); const app = express(); const port = process.env.APP_PORT || 8000; // Configure Nunjucks nunjucks.configure('templates', { autoescape: true, express: app, }); app.use(express.json()); // for parsing application/json app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded app.use(express.static('public')); // Routes app.get('/', async (req, res) => { // Render the main chat UI res.render('index.html'); }); app.post('/session', async (req, res) => { // Mint a new LiveKit room and a user token for the frontend try { const roomName = `webchat-${Date.now()}`; const { token: roomToken } = await mintRoom(roomName); const { token: userToken } = await mintUserToken(roomName); res.json({ roomName, roomToken, userToken, }); } catch (error) { console.error('Error minting session:', error); res.status(500).json({ error: 'Failed to mint session' }); } }); app.post('/chat', async (req, res) => { // Handle text chat input const { roomName, message } = req.body; try { const completion = await getChatCompletion(message); await infer(roomName, completion); res.json({ reply: completion }); } catch (error) { console.error('Error processing chat:', error); res.status(500).json({ error: 'Failed to process chat' }); } }); app.post('/voice', async (req, res) => { // Handle voice input (audio data) // This endpoint is a placeholder and would typically involve // receiving audio data, transcribing it, and then sending to infer. // For simplicity, we'll simulate a response. const { roomName, audioDataBase64 } = req.body; try { // In a real scenario, you'd decode audioDataBase64, transcribe it, // and then call infer. const transcription = 'Simulated transcription from voice input'; const completion = await getChatCompletion(transcription); await infer(roomName, completion); res.json({ reply: completion, transcribed: transcription }); } catch (error) { console.error('Error processing voice:', error); res.status(500).json({ error: 'Failed to process voice' }); } }); app.post('/transcribe', async (req, res) => { // Handle audio transcription request const { audioDataBase64 } = req.body; try { const transcription = await getAudioTranscription(Buffer.from(audioDataBase64, 'base64')); res.json({ transcription }); } catch (error) { console.error('Error transcribing audio:', error); res.status(500).json({ error: 'Failed to transcribe audio' }); } }); app.listen(port, () => { console.log(`Server running at http://${process.env.APP_HOST || '127.0.0.1'}:${port}`); }); ``` -------------------------------- ### Request Video from Text - Example Request Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/API.md An example cURL command to demonstrate creating a video request from text and receiving a Lightning invoice. ```bash curl -X POST https://api.avatartalk.ai/lightning/request-video/text \ -H "Content-Type: application/json" \ -d '{ "text": "Welcome to AvatarTalk! This is a demonstration of our text-to-speech technology." }' ``` -------------------------------- ### Lightning Payment Workflow Example (cURL and jq) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/API.md This bash script demonstrates a complete Lightning payment workflow, starting with requesting a video from text, extracting the invoice and amount using jq, and preparing for payment. It uses cURL for API requests. ```bash # Step 1: Request a video from text RESPONSE=$(curl -X POST https://api.avatartalk.ai/lightning/request-video/text \ -H "Content-Type: application/json" \ -d '{"text": "Hello from AvatarTalk!"}') # Extract invoice and amount INVOICE=$(echo $RESPONSE | jq -r '.bolt11_invoice') AMOUNT=$(echo $RESPONSE | jq -r '.amount') echo "Invoice: $INVOICE" echo "Amount: $AMOUNT sats" ``` -------------------------------- ### Start Audio Recording (JavaScript) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/templates/index.html This function initiates audio recording using the MediaRecorder API. It checks for existing media recorders and streams, stopping them if necessary. It then sets up a new MediaRecorder with the audio stream and defines an 'ondataavailable' handler to process recorded chunks. Event listeners are attached to a push-to-talk button ($ptt) to call this function on 'mousedown' and 'touchstart'. Limitations: requires user permission for microphone access. ```javascript function startRecording() { try { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); } navigator.mediaDevices.getUserMedia({ audio: true }).then(s => { stream = s; mediaRecorder = new MediaRecorder(stream); mediaRecorder.addEventListener('dataavailable', async (e) => { const blob = new Blob([e.data], { type: 'audio/webm' }); const formData = new FormData(); formData.append('file', blob, 'audio.webm'); formData.append('avatar', $selAvatar.value); formData.append('emotion', $selEmotion.value); formData.append('language', $selLanguage.value); try { const resp = await fetch('/upload', { method: 'POST', body: formData }); const data = await resp.json(); if (!resp.ok) throw new Error(data.error || 'Upload failed'); const assistant = data.assistant_text || ''; addMsg('assistant', assistant); history.push({ role: 'assistant', content: assistant }); const inf = data.inference || {}; if (inf.mp4_url) { $video.src = inf.mp4_url; $videoWrap.classList.remove('hidden'); try { await $video.play(); } catch { } } const links = []; if (inf.mp4_url) links.push(`MP4`); if (inf.html_url) links.push(`Viewer`); $videoLinks.innerHTML = links.length ? `Links: ${links.join(' ยท ')}` : ''; } catch (err) { addMsg('assistant', `Voice error: ${err.message || err}`); } finally { $ptt.disabled = false; } }); mediaRecorder.start(); $ptt.textContent = 'โ— Recording...'; $ptt.style.background = '#8a1b1b'; }).catch(err => { addMsg('assistant', `Mic error: ${err.message || err}`); }); } catch (err) { addMsg('assistant', `Mic error: ${err.message || err}`); } } ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/youtube-rtmp-streamer/README.md Installs the necessary project dependencies using uv, a fast Python package installer. It synchronizes packages and installs pre-commit hooks for code quality. ```shell uv sync pre-commit install ``` -------------------------------- ### Copy Environment File Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/README.md Copies an example environment file to a new file for configuration. This is a common practice in Node.js projects to set up API keys and other sensitive information before running the application. ```bash cp .env.example .env ``` -------------------------------- ### LiveKit Web Chat Integration (Node.js) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Node.js LiveKit webchat example where the assistant speaks into a LiveKit room via the /ws/infer endpoint. Requires Node.js 18+ and specific LiveKit and API key configurations in the environment. ```bash cp .env.example .env # Set OPENAI_API_KEY, AVATARTALK_API_KEY, LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET in .env npm install npm start # Access at http://127.0.0.1:8000 ``` -------------------------------- ### Running the LiveKit WebChat App with Uvicorn Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-webchat/README.md This command demonstrates how to run the FastAPI LiveKit WebChat application using uvicorn, a Python ASGI server. It's useful for development and deployment, allowing for hot-reloading when code changes. Ensure you have `uv` installed and the `.env` file configured. ```bash uv run uvicorn livekit_webchat.app:app --reload --port 8000 ``` -------------------------------- ### LiveKit Web Chat Integration (Python) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Python LiveKit webchat example where the assistant speaks into a LiveKit room via the /ws/infer endpoint. Requires Python 3.10+, uv, and specific LiveKit and API key configurations in the environment. ```bash Create .env with OPENAI_API_KEY, AVATARTALK_API_KEY, LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET uv run livekit-webchat # Access at http://127.0.0.1:8000 ``` -------------------------------- ### API Inference (Streaming Output) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Example of how to call the AvatarTalk.ai inference endpoint with the `stream=true` parameter to receive MP4 video data streamed in real time. This is useful for live applications. Requires an API key for authentication. ```bash curl -X POST https://api.avatartalk.ai/inference?stream=true \ -H "Authorization: Bearer {YOUR_API_KEY}" \ -d '{"text": "Streaming test.", "avatar": "african_woman", "emotion": "happy", "language": "en"}' > output.mp4 ``` -------------------------------- ### Node.js environment configuration example Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/youtube-rtmp-streamer/README.md Example of environment variable configuration for the Node.js YouTube RTMP streamer. This involves copying a template file and filling in required API keys and stream details. ```env # Copy .env.example to .env and fill in values OPENAI_API_KEY=your_openai_api_key AVATARTALK_API_KEY=your_avatartalk_api_key YOUTUBE_API_KEY=your_youtube_api_key YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2 YOUTUBE_STREAM_KEY=your_youtube_stream_key # Optional keys below # YOUTUBE_LIVE_ID=your_youtube_live_id # AVATARTALK_AVATAR=default_avatar # AVATARTALK_LANGUAGE=en # AVATARTALK_TOPICS_FILE=topics.txt # AVATARTALK_MODEL=gpt-4o-mini ``` -------------------------------- ### API Inference (JSON Output) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Example of how to call the AvatarTalk.ai inference endpoint to generate a talking avatar video. This endpoint returns a JSON object containing URLs for the MP4 video and an HTML player. Requires an API key for authentication. ```bash curl -X POST https://api.avatartalk.ai/inference \ -H "Authorization: Bearer {YOUR_API_KEY}" \ -d '{"text": "Hello, this is a test.", "avatar": "african_man", "emotion": "neutral", "language": "en"}' ``` -------------------------------- ### Handle Voice Input and WebSocket for Audio Streaming (JavaScript) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-webchat/templates/index.html This snippet manages microphone input using the MediaRecorder API and streams audio data over a WebSocket connection. It handles starting and stopping recordings, configuring WebSocket parameters, and sending audio chunks. Dependencies include the MediaRecorder API and WebSocket API. ```javascript async function startRecording() { try { const sessionId = "session-id-placeholder"; // Replace with actual session ID if (document.getElementById('stream-audio').checked) { // Open WS relay and stream chunks if (!sessionId) throw new Error('No session'); const params = new URLSearchParams({ session_id: sessionId, avatar: $selAvatar.value, emotion: $selEmotion.value, language: $selLanguage.value, increase_resolution: String($increase.checked), }); const url = `${wsScheme()}//${location.host}/ws/audio?${params.toString()}`; clearLivekitContainer(); audioWS = new WebSocket(url); audioWS.binaryType = 'arraybuffer'; audioWS.onopen = () => { mediaRecorder.start(250); }; audioWS.onerror = (e) => { addMsg('assistant', `Audio WS error`); }; audioWS.onclose = () => { // no-op }; mediaRecorder.ondataavailable = async (e) => { if (e.data && e.data.size && audioWS && audioWS.readyState === WebSocket.OPEN) { try { const buf = await e.data.arrayBuffer(); audioWS.send(buf); } catch { } } }; } else { mediaRecorder.start(); } $ptt.textContent = 'โ— Recording...'; $ptt.style.background = '#8a1b1b'; } } catch (err) { addMsg('assistant', `Mic error: ${err.message || err}`); } } function stopRecording() { try { if (mediaRecorder && mediaRecorder.state !== 'inactive') { mediaRecorder.stop(); } if (audioWS && audioWS.readyState === WebSocket.OPEN) { audioWS.close(); } } finally { if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; } $ptt.textContent = '๐ŸŽ™๏ธ Hold to talk'; $ptt.style.background = ''; } } $ptt.addEventListener('mousedown', (e) => { e.preventDefault(); startRecording(); }); $ptt.addEventListener('touchstart', (e) => { e.preventDefault(); startRecording(); }); const stopEvents = ['mouseup', 'mouseleave', 'touchend', 'touchcancel']; stopEvents.forEach(ev => $ptt.addEventListener(ev, (e) => { e.preventDefault(); stopRecording(); })); // Initialize joinLiveKit().catch(err => addMsg('assistant', `Setup error: ${err.message || err}`)); ``` -------------------------------- ### POST /inference - Regular Video Generation Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Generates a talking avatar video from text and returns URLs for the MP4 and HTML. The URLs are trigger URLs that initiate processing on first access and serve cached content subsequently. ```APIDOC ## POST /inference ### Description Generates a talking avatar video from text and returns URLs for the MP4 and HTML. The URLs are trigger URLs that initiate processing on first access and serve cached content subsequently. ### Method POST ### Endpoint https://api.avatartalk.ai/inference ### Headers - **Authorization** (string) - Required - `Bearer {YOUR_API_KEY}` ### Parameters (body) - **text** (string) - Required - Text to be spoken by the avatar. - **avatar** (string) - Required - Identifier for the avatar (e.g., `african_man`). - **emotion** (string) - Required - Emotion of the avatar (e.g., `neutral`, `happy`). - **language** (string) - Required - Language of the text (e.g., `en`, `es`, `fr`). - **delayed** (boolean) - Optional - If true, returns trigger URLs without upfront processing. ### Request Example ```json { "text": "Hello, this is a test.", "avatar": "african_man", "emotion": "neutral", "language": "en", "delayed": false } ``` ### Response #### Success Response (200) - **mp4_url** (string) - URL to the generated MP4 video. - **html_url** (string) - URL to the HTML file. #### Response Example ```json { "mp4_url": "https://example.com/path/to/video.mp4", "html_url": "https://example.com/path/to/index.html" } ``` ``` -------------------------------- ### Python Environment Configuration Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/README.md This Python module handles loading environment variables using `python-dotenv` and defines a settings class using Pydantic for validation. It specifies default values for various configuration parameters. ```python from pydantic_settings import BaseSettings from pathlib import Path class Settings(BaseSettings): # OpenAI settings OPENAI_API_KEY: str OPENAI_MODEL: str = "gpt-4o-mini" OPENAI_STT_MODEL: str = "whisper-1" # AvatarTalk settings AVATARTALK_API_KEY: str AVATARTALK_API_BASE: str = "https://api.avatartalk.ai" AVATARTALK_AVATAR: str = "european_woman" AVATARTALK_EMOTION: str = "neutral" AVATARTALK_LANGUAGE: str = "en" AVATARTALK_DELAYED: bool = False # App settings APP_HOST: str = "127.0.0.1" APP_PORT: int = 8000 APP_DEBUG: bool = True class Config: env_file = Path(__file__).parent.parent / ".env" env_file_encoding = 'utf-8' settings = Settings() ``` -------------------------------- ### LiveKit Client Connection and Event Handling (JavaScript) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/livekit-webchat/templates/index.html Establishes a connection to a LiveKit room, subscribes to audio and video tracks, and handles track subscription/unsubscription events. It appends attached media elements to a designated container and cleans them up when unsubscribed. Dependencies include the LivekitClient library. ```javascript let sessionId = null; let lkRoom = null; function removeKind(container, tagNames) { const tags = Array.isArray(tagNames) ? tagNames : [tagNames]; Array.from(container.children).forEach((el) => { const t = el.tagName.toLowerCase(); if (tags.includes(t)) el.remove(); }); } function clearLivekitContainer() { const c = document.getElementById('livekitContainer'); if (c) c.replaceChildren(); } async function joinLiveKit() { const resp = await fetch('/session', { method: 'POST' }); const data = await resp.json(); if (!resp.ok) throw new Error(data.error || 'Failed to create session'); sessionId = data.session_id; const token = data.token; const url = data.livekit_url; lkRoom = new LivekitClient.Room(); await lkRoom.connect(url, token); lkRoom.on(LivekitClient.RoomEvent.TrackSubscribed, (track, publication, participant) => { const c = document.getElementById('livekitContainer'); if (!c) return; // Replace previous of the same kind to avoid stacking if (track.kind === 'video') removeKind(c, 'video'); if (track.kind === 'audio') removeKind(c, 'audio'); const el = track.attach(); el.setAttribute('playsinline', 'true'); el.style.width = '100%'; el.style.height = '100%'; el.style.objectFit = 'cover'; c.appendChild(el); }); lkRoom.on(LivekitClient.RoomEvent.TrackUnsubscribed, (track) => { track.detach().forEach(el => el.remove()); }); } ``` -------------------------------- ### Python OpenAI Chat Client Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/simple-webchat/README.md This Python class encapsulates interactions with the OpenAI API for chat completions and audio transcription. It uses environment variables for API keys and model configurations. ```python import os import openai from pathlib import Path from pydantic import BaseModel from dotenv import load_dotenv from simple_webchat.config import settings class OpenAIChatResponse(BaseModel): role: str content: str class OpenAIClient: def __init__(self): load_dotenv() openai.api_key = settings.OPENAI_API_KEY def chat(self, messages: list[dict]) -> str: try: response = openai.chat.completions.create( model=settings.OPENAI_MODEL, messages=messages, ) return response.choices[0].message.content except Exception as e: print(f"Error calling OpenAI chat: {e}") return "Sorry, I encountered an error." def transcribe_audio(self, audio_file: Path) -> str: try: with open(audio_file, "rb") as f: transcript = openai.audio.transcriptions.create( model=settings.OPENAI_STT_MODEL, file=f, ) return transcript.text except Exception as e: print(f"Error transcribing audio: {e}") return "" ``` -------------------------------- ### Web Chat with Text and Streaming (Python) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Python (FastAPI) implementation of a text-first web chat. It supports optional voice input and streaming playback via a server proxy. Requires Python 3.10+ and uv for package management. ```bash Create .env with OPENAI_API_KEY and AVATARTALK_API_KEY uv run simple-webchat # Access at http://127.0.0.1:8000 ``` -------------------------------- ### Express Server Setup and Routes (app.js) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/simple-webchat/README.md This Node.js code defines the Express application, sets up middleware, and registers various API endpoints for handling chat, voice input, transcription, and streaming. It demonstrates how to manage requests and responses for different functionalities. ```javascript import express from "express"; import path from "path"; import multer from "multer"; import OpenAI from "openai"; import dotenv from "dotenv"; import { v4 as uuidv4 } from "uuid"; import fs from "fs"; dotenv.config(); const app = express(); const port = process.env.APP_PORT || 8000; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // --- Middleware --- app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static("public")); app.set("view engine", "njk"); // --- In-memory storage for streams --- const streams = {}; // --- Routes --- app.get("/", (req, res) => { res.render("index"); }); app.get("/healthz", (req, res) => { res.status(200).send("OK"); }); // POST /chat - handles text-based chat app.post("/chat", async (req, res) => { try { const userInput = req.body.message; const assistantReply = await getOpenAIChatCompletion(userInput); const avatartalkResponse = await callAvatarTalkAPI(assistantReply); res.json(avatartalkResponse); } catch (error) { console.error("Error in /chat:", error); res.status(500).json({ error: "Failed to process chat request" }); } }); // POST /voice - handles voice input (upload audio file) const upload = multer({ dest: "uploads/" }); app.post("/voice", upload.single("audio"), async (req, res) => { if (!req.file) { return res.status(400).send("No audio file uploaded."); } try { const transcription = await transcribeAudio(req.file.path); const assistantReply = await getOpenAIChatCompletion(transcription); const avatartalkResponse = await callAvatarTalkAPI(assistantReply, true); // Assume voice input might need streaming res.json(avatartalkResponse); } catch (error) { console.error("Error in /voice:", error); res.status(500).json({ error: "Failed to process voice request" }); } finally { // Clean up uploaded file fs.unlink(req.file.path, (err) => { if (err) console.error("Error deleting temp audio file:", err); }); } }); // POST /transcribe - direct audio transcription app.post("/transcribe", upload.single("audio"), async (req, res) => { if (!req.file) { return res.status(400).send("No audio file uploaded."); } try { const transcription = await transcribeAudio(req.file.path); res.json({ transcription }); } catch (error) { console.error("Error in /transcribe:", error); res.status(500).json({ error: "Failed to transcribe audio" }); } finally { fs.unlink(req.file.path, (err) => { if (err) console.error("Error deleting temp audio file:", err); }); } }); // POST /chat_stream - handles streaming chat response app.post("/chat_stream", async (req, res) => { try { const userInput = req.body.message; const assistantReply = await getOpenAIChatCompletion(userInput); const streamId = uuidv4(); streams[streamId] = { status: "pending", response: null, chunks: [], createdAt: Date.now() }; callAvatarTalkStreamAPI(assistantReply, streamId, res); } catch (error) { console.error("Error in /chat_stream:", error); res.status(500).json({ error: "Failed to initiate streaming chat" }); } }); // GET /stream/{id}.mp4 - serves the streamed MP4 video app.get("/stream/:id.mp4", async (req, res) => { const streamId = req.params.id; const streamData = streams[streamId]; if (!streamData) { return res.status(404).send("Stream not found or expired."); } if (streamData.status === "error") { return res.status(500).send("Stream generation failed."); } // Set appropriate headers for video streaming res.setHeader("Content-Type", "video/mp4"); res.setHeader("Transfer-Encoding", "chunked"); res.setHeader("Connection", "keep-alive"); res.setHeader("Cache-Control", "no-cache"); // Stream the chunks streamData.chunks.forEach(chunk => res.write(chunk)); // Handle stream completion or errors const interval = setInterval(() => { if (streamData.status === "completed") { clearInterval(interval); // Optionally clean up stream data after a short delay setTimeout(() => delete streams[streamId], 60000); // Clean up after 1 minute } else if (streamData.status === "error") { clearInterval(interval); res.end(); // End the response on error } }, 100); // Check every 100ms // Handle client disconnection req.on("close", () => { clearInterval(interval); delete streams[streamId]; }); }); // --- Helper Functions --- async function getOpenAIChatCompletion(prompt) { try { const completion = await openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], model: process.env.OPENAI_MODEL || "gpt-4o-mini", }); return completion.choices[0]?.message?.content; } catch (error) { console.error("Error calling OpenAI:", error); throw error; } } async function transcribeAudio(filePath) { try { const file = fs.createReadStream(filePath); const transcription = await openai.audio.transcriptions.create({ file: file, model: process.env.OPENAI_STT_MODEL || "whisper-1", }); return transcription.text; } catch (error) { console.error("Error transcribing audio:", error); throw error; } } async function callAvatarTalkAPI(text, isVoiceInput = false) { const apiUrl = process.env.AVATARTALK_API_BASE || "https://api.avatartalk.ai"; const apiKey = process.env.AVATARTALK_API_KEY; if (!apiKey) { throw new Error("AVATARTALK_API_KEY is not set."); } const response = await fetch(`${apiUrl}/inference`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify({ text: text, avatar: process.env.AVATARTALK_AVATAR || "european_woman", emotion: process.env.AVATARTALK_EMOTION || "neutral", language: process.env.AVATARTALK_LANGUAGE || "en", delayed: process.env.AVATARTALK_DELAYED === 'true' || false, stream: isVoiceInput ? true : false, // Example: force stream for voice input }), }); if (!response.ok) { const errorBody = await response.text(); console.error(`AvatarTalk API Error: ${response.status} - ${errorBody}`); throw new Error(`AvatarTalk API request failed: ${response.status}`); } return response.json(); } async function callAvatarTalkStreamAPI(text, streamId, res) { const apiUrl = process.env.AVATARTALK_API_BASE || "https://api.avatartalk.ai"; const apiKey = process.env.AVATARTALK_API_KEY; if (!apiKey) { streams[streamId].status = "error"; console.error("AVATARTALK_API_KEY is not set."); return res.status(500).send("Server configuration error."); } try { const streamResponse = await fetch(`${apiUrl}/inference?stream=true`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify({ text: text, avatar: process.env.AVATARTALK_AVATAR || "european_woman", emotion: process.env.AVATARTALK_EMOTION || "neutral", language: process.env.AVATARTALK_LANGUAGE || "en", delayed: false, // Streaming implies not delayed }), }); if (!streamResponse.ok) { const errorBody = await streamResponse.text(); console.error(`AvatarTalk Stream API Error: ${streamResponse.status} - ${errorBody}`); streams[streamId].status = "error"; return res.status(500).send("Failed to start video stream."); } const reader = streamResponse.body.getReader(); let videoChunks = []; let isFirstChunk = true; while (true) { const { done, value } = await reader.read(); if (done) { streams[streamId].status = "completed"; // The response is already sent to the client via res.write, no need to send final JSON here break; } // Assuming the response is chunked MP4 data const chunk = Buffer.from(value); streams[streamId].chunks.push(chunk); // Send the first chunk with appropriate headers if not already sent if (isFirstChunk) { res.status(200); // Headers are set in the GET /stream/:id.mp4 route handler isFirstChunk = false; } res.write(chunk); } } catch (error) { console.error("Error during AvatarTalk streaming:", error); streams[streamId].status = "error"; if (!res.writableEnded) { res.status(500).send("Error during video stream generation."); } } } // --- Server Start --- app.listen(port, () => { console.log(`Server running at http://${process.env.APP_HOST || '127.0.0.1'}:${port}`); }); ``` -------------------------------- ### Web Chat with Text and Streaming (Node.js) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Node.js (Express) implementation of a text-first web chat. It supports optional voice input and streaming playback via a server proxy. Requires Node.js 18+ and environment variables for API keys. ```bash cp .env.example .env # Set OPENAI_API_KEY and AVATARTALK_API_KEY in .env npm install npm start # Access at http://127.0.0.1:8000 ``` -------------------------------- ### OpenAI Chat and Speech-to-Text Client (Node.js) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/livekit-webchat/README.md Provides utility functions to interact with the OpenAI API for chat completions and audio transcription. It uses the 'openai' npm package and requires the OPENAI_API_KEY environment variable. The functions abstract the API calls for easier integration into the web chat application. ```javascript const OpenAI = require('openai'); const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); async function getChatCompletion(prompt) { // Generates a chat completion response from OpenAI. // Uses OPENAI_MODEL environment variable, defaults to 'gpt-4o-mini'. // Input: string (user prompt) // Output: string (AI's response) try { const completion = await openai.chat.completions.create({ messages: [{ role: 'user', content: prompt }], model: process.env.OPENAI_MODEL || 'gpt-4o-mini', }); return completion.choices[0].message.content; } catch (error) { console.error('Error getting chat completion:', error); throw error; } } async function getAudioTranscription(audioBuffer) { // Transcribes audio data to text using OpenAI's Whisper model. // Uses OPENAI_STT_MODEL environment variable, defaults to 'whisper-1'. // Input: Buffer (audio data) // Output: string (transcribed text) try { const transcription = await openai.audio.transcriptions.create({ file: { // The file needs to be readable by the OpenAI SDK. // For simplicity, we assume audioBuffer is directly usable or converted. // In a real application, you might need to save to a temporary file or stream. name: 'audio.webm', // Or appropriate filename based on MIME type blob: audioBuffer, // Directly passing buffer if SDK supports, otherwise needs adaptation }, model: process.env.OPENAI_STT_MODEL || 'whisper-1', }); return transcription.text; } catch (error) { console.error('Error getting audio transcription:', error); throw error; } } module.exports = { getChatCompletion, getAudioTranscription, }; ``` -------------------------------- ### LiveKit Token Minting Client (Node.js) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/nodejs/livekit-webchat/README.md Manages the creation of JWT tokens for LiveKit. It includes functions to mint a token for creating a room and another for joining a room as a user. This is crucial for authenticating clients with the LiveKit server. Requires LiveKit URL, API Key, and API Secret from environment variables. ```javascript const { AccessToken } = require('livekit-server-sdk'); const LIVEKIT_URL = process.env.LIVEKIT_URL; const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY; const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET; const LIVEKIT_TOKEN_TTL = parseInt(process.env.LIVEKIT_TOKEN_TTL || '3600', 10); function mintRoom(roomName) { // Mints a token that allows creating a LiveKit room. // This token is typically used by the server to manage rooms. // Input: string (roomName) // Output: Promise resolving to { token: string } const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, { identity: 'server-admin', ttl: LIVEKIT_TOKEN_TTL, }); at.addGrant({ roomCreate: true }); // Grant permission to create rooms const token = at.toJwt(); return Promise.resolve({ token }); } function mintUserToken(roomName) { // Mints a token for a user to join a specific LiveKit room. // The frontend JavaScript will use this token. // Input: string (roomName) // Output: Promise resolving to { token: string } const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, { identity: `user-${Date.now()}`, ttl: LIVEKIT_TOKEN_TTL, }); // Grant permissions for joining and subscribing to the room at.addGrant({ roomJoin: true, room: roomName }); const token = at.toJwt(); return Promise.resolve({ token }); } module.exports = { mintRoom, mintUserToken, }; ``` -------------------------------- ### Python Simple Web Chat with FastAPI Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/README.md A full-stack web chat application using FastAPI and Jinja2. It supports text chat, push-to-talk transcription, and optional streaming playback. Requires Python 3.10+ and environment variables for API keys. ```python # Example usage (conceptual, actual code in project) from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates app = FastAPI() templates = Jinja2Templates(directory="templates") @app.get("/", response_class=HTMLResponse) async def read_root(request: Request): return templates.TemplateResponse("index.html", {"request": request}) # Additional endpoints for chat, transcription, etc. would be here. # Requires .env with OPENAI_API_KEY and AVATARTALK_API_KEY # Run with: uv run simple-webchat ``` -------------------------------- ### Python LiveKit Webchat Integration Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/python/README.md Integrates AvatarTalk AI with LiveKit for real-time communication. The assistant speaks into a LiveKit room via a WebSocket endpoint. Requires Python 3.10+ and LiveKit/OpenAI API credentials in environment variables. ```python # Example usage (conceptual, actual code in project) import asyncio from fastapi import FastAPI, WebSocket app = FastAPI() @app.websocket("/ws/infer") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() while True: data = await websocket.receive_text() # Process data, interact with AvatarTalk AI and LiveKit await websocket.send_text(f"Message text was: {data}") # Requires .env with OPENAI_API_KEY, AVATARTALK_API_KEY, LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET # Run with: uv run livekit-webchat ``` -------------------------------- ### Regular Inference Request Example (JSON Response) Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/API.md Shows the structure of a successful JSON response from a regular POST /inference request. It includes details like video URLs, status, and consumption metrics. ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "status": "success", "stream": false, "text": "Hello, this is a test message", "created_at": "2025-09-29T11:50:26.890669Z", "language": "en", "credits_consumed": 5, "avatar": "african_man", "emotion": "neutral", "file_size_bytes": 2048576, "inference_duration_ms": 3500, "video_duration_seconds": 4.2, "html_url": "https://api.avatartalk.ai/inference/123e4567-e89b-12d3-a456-426614174000/video.html", "mp4_url": "https://api.avatartalk.ai/inference/123e4567-e89b-12d3-a456-426614174000/video.mp4" } ``` -------------------------------- ### POST /inference?stream=true - Real-time Streaming Video Source: https://github.com/avatartalk-ai/avatartalk-examples/blob/main/README.md Generates a talking avatar video from text and streams the MP4 video data in real time. This is useful for live applications where immediate video output is required. ```APIDOC ## POST /inference?stream=true ### Description Generates a talking avatar video from text and streams the MP4 video data in real time. This is useful for live applications where immediate video output is required. ### Method POST ### Endpoint https://api.avatartalk.ai/inference?stream=true ### Headers - **Authorization** (string) - Required - `Bearer {YOUR_API_KEY}` ### Parameters (body) - **text** (string) - Required - Text to be spoken by the avatar. - **avatar** (string) - Required - Identifier for the avatar (e.g., `african_man`). - **emotion** (string) - Required - Emotion of the avatar (e.g., `neutral`, `happy`). - **language** (string) - Required - Language of the text (e.g., `en`, `es`, `fr`). - **delayed** (boolean) - Optional - If true, returns trigger URLs without upfront processing. ### Request Example ```json { "text": "Streaming video example.", "avatar": "asian_woman", "emotion": "happy", "language": "en", "delayed": false } ``` ### Response #### Success Response (200) - **(binary)** - MP4 video data streamed in real time. #### Response Example *(Binary stream of MP4 data)* ```