Real-time speech translation with optional lip-synced avatar integration for Agora App Builder.
- Translate Audio - Real-time speech translation to 9+ languages
- Avatar Mode - Lip-synced avatar speaks the translated audio
- Persistent Avatar - Avatar can run independently, switching between original and translated audio
This repository contains customization files that overlay onto Agora App Builder:
palabra/
├── client/ # Frontend customization (overlay files)
│ └── customization/
│ ├── index.tsx # Entry point - wraps VideoCall with TranslationProvider
│ └── palabra/
│ ├── TranslationProvider.tsx # Core translation/avatar logic
│ └── TranslationMenuItem.tsx # Menu UI components
├── server/ # Go backend (runs in Docker)
├── docs/ # Documentation
└── app-builder/ # GITIGNORED - App Builder cloned here at build time
How customization works:
- Agora App Builder is a standalone video conferencing app
- App Builder supports a
customization/folder for extending functionality - This repo provides customization files that add Palabra translation features
- At build time, files from
client/customization/are copied into App Builder'stemplate/customization/ - The
index.tsxentry point wraps the VideoCall component withTranslationProvider
App Builder source: https://github.com/AgoraIO-Community/app-builder-core
- Ubuntu 20.04+ (x86-64 architecture required)
- Docker and Docker Compose
- Node.js 18+
- Nginx
- Domain name with SSL certificate
cd /home/ubuntu
git clone https://github.com/BenWeekes/palabra.git
cd palabracd server
cp .env.example .env
nano .envRequired .env settings:
# Agora Credentials
APP_ID=<your_agora_app_id>
APP_CERTIFICATE=<your_agora_certificate>
CUSTOMER_ID=<your_customer_id>
CUSTOMER_CERTIFICATE=<your_customer_certificate>
# Palabra Credentials
PALABRA_CLIENT_ID=<your_palabra_client_id>
PALABRA_CLIENT_SECRET=<your_palabra_client_secret>
# Database
POSTGRES_USER=appbuilder
POSTGRES_PASSWORD=<strong_password>
POSTGRES_DB=appbuilder
# Server Configuration
PORT=8080
SCHEME=https
ALLOWED_ORIGIN=https://yourdomain.com
# Avatar Mode (set to true to enable Anam avatar)
ENABLE_ANAM=true
ANAM_API_KEY=<your_anam_api_key>
ANAM_BASE_URL=https://api.anam.ai/v1
ANAM_AVATAR_ID=<your_anam_avatar_id>
ANAM_QUALITY=high
ANAM_VIDEO_ENCODING=H264
# Session Protection
PALABRA_SESSION_TIMEOUT_MINUTES=10
PALABRA_IDLE_TIMEOUT_SECONDS=60Start backend:
sudo docker compose up -d --build
# Verify it's running
curl http://localhost:7080/v1/palabra/tasks
# Should return: {"success":true,"tasks":[]}cd /home/ubuntu/palabra
# Clone Agora App Builder
git clone https://github.com/AgoraIO-Community/app-builder-core.git app-builder
cd app-builder
# Copy customization files
cp -r ../client/customization/palabra template/customization/
cp ../client/customization/index.tsx template/customization/
# Apply patch to filter translation UIDs from video tiles (REQUIRED)
cp ../client/app-builder-patches/VideoComponent.tsx template/src/pages/video-call/
# Copy config files
cp config.json template/config.json
cp theme.json template/theme.json
# Update config.json with your domain
nano template/config.jsonUpdate these values in template/config.json:
{
"APP_ID": "<your_agora_app_id>",
"FRONTEND_ENDPOINT": "https://yourdomain.com",
"BACKEND_ENDPOINT": "https://yourdomain.com",
"PALABRA_BACKEND_ENDPOINT": "https://yourdomain.com"
}Build:
# Install UI Kit
npm run uikit
# Install dependencies
cd template
npm install --legacy-peer-deps
cd ..
# Build for production
npm run web-buildsudo mkdir -p /var/www/palabra
sudo cp -r Builds/web/* /var/www/palabra/
sudo chown -R www-data:www-data /var/www/palabrasudo nano /etc/nginx/sites-available/palabraserver {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# API proxy to backend (Docker bound to localhost only)
location /v1/ {
proxy_pass http://localhost:7080/v1/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}
location /query {
proxy_pass http://localhost:7080/query;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Frontend static files
location / {
root /var/www/palabra;
index index.html;
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|wasm|mp4|ttf)$ {
root /var/www/palabra;
expires 1y;
add_header Cache-Control "public, immutable";
}
}Nginx uses URL path to route requests: /v1/*, /query, /oauth, /pstn are proxied to the backend Docker container on localhost:7080, while everything else serves the frontend static files from /var/www/palabra/.
Enable site:
# Remove default site if it occupies port 443
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/palabra /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxsudo certbot --nginx -d yourdomain.com# Test backend API
curl https://yourdomain.com/v1/palabra/tasks
# Test avatar endpoints
curl -X POST https://yourdomain.com/v1/avatar/start \
-H "Content-Type: application/json" \
-d '{"channel":"test","sourceUid":"123"}'- Join a video call with 2+ participants
- Click the 3-dot menu on a remote participant's video
- Select "Start Avatar" to show lip-synced avatar
- Select "Translate Audio" and choose target language
- Avatar will speak the translated audio
| Endpoint | Method | Description |
|---|---|---|
/v1/palabra/start |
POST | Start translation for a user |
/v1/palabra/stop |
POST | Stop translation |
/v1/palabra/tasks |
GET | List active translation tasks |
/v1/avatar/start |
POST | Start persistent avatar |
/v1/avatar/stop |
POST | Stop persistent avatar |
| Mode | Config | Description |
|---|---|---|
| Audio-Only | ENABLE_ANAM=false |
Translation audio only (UID 3000+) |
| Avatar | ENABLE_ANAM=true |
Lip-synced avatar video+audio (UID 4000+) |
| Mode | Flow | Description |
|---|---|---|
| Non-Persistent | Call /v1/palabra/start directly |
Each translation creates its own avatar. Bot subscribes to Palabra UID. |
| Persistent | Call /v1/avatar/start first, then /v1/palabra/start |
Avatar stays active, seamlessly switches between original and translated audio. |
Persistent Avatar Benefits:
- Avatar appears immediately (no wait for translation to start)
- Seamless audio: original audio plays until translation kicks in (no silence gap)
- Fast switching: stop translation returns to original audio instantly
| Range | Purpose |
|---|---|
| 1-2999 | Regular users |
| 3000-3999 | Palabra translation streams |
| 4000-4999 | Anam avatar streams |
| 5000+ | Backend bot workers |
The frontend VideoComponent.tsx patch filters UIDs 3000-4999 from video tiles so translation/avatar streams don't appear as separate participants.
cd /home/ubuntu/palabra
git pull
# Rebuild backend
cd server
sudo docker compose down
sudo docker compose up -d --build
# Rebuild frontend
cd ../app-builder
cp -r ../client/customization/palabra template/customization/
cp ../client/customization/index.tsx template/customization/
cp ../client/app-builder-patches/VideoComponent.tsx template/src/pages/video-call/
npm run web-build
sudo cp -r Builds/web/* /var/www/palabra/Backend not responding:
sudo docker logs server --tail 50Avatar 401 Unauthorized:
- Verify
ANAM_API_KEYin.envis correct - Recreate container after .env changes:
sudo docker compose down && sudo docker compose up -d
Frontend not loading customization:
- Ensure
template/customization/index.tsxexists - Rebuild:
npm run web-build
CORS errors:
- Verify
ALLOWED_ORIGINin.envmatches your domain - Use same-origin setup (frontend and API on same port via nginx)
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Browser │────▶│ Nginx :443 │────▶│ Backend :7080 │
│ (Frontend) │ │ (SSL + Proxy) │ │ (localhost only)│
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Palabra API │
│ Anam API │
│ Agora RTC │
└─────────────────┘
How nginx routes requests: Nginx listens on port 443 (standard HTTPS) and uses URL path to decide where to send each request. API paths (/v1/*, /query) are proxied to the Go backend on localhost:7080. All other paths serve the frontend static files from /var/www/palabra/. The Docker ports for the backend and database are bound to 127.0.0.1 only, so they are not accessible from the internet.
The following settings are applied for production:
| Setting | File | Value |
|---|---|---|
| CORS debug | server/cmd/video_conferencing/server.go |
Debug: false |
| Log level | server/config.json |
LOG_LEVEL: "WARN" |
| Docker ports | server/docker-compose.yml |
Bound to 127.0.0.1 only |
| Nginx port | /etc/nginx/sites-available/palabra |
443 (standard HTTPS) |
- palabra-integrate.md - Detailed integration guide
- anam-integrate.md - Anam avatar setup
- app-builder-dev.md - Development guide
Copyright © 2021 Agora Lab, Inc.