@liyalabs/liya-3d-avatar-widget-vanilla-js
v0.1.1
Published
3D Talking Avatar Widget - AI Assistant with real-time lip-sync (Vanilla JS / PHP / any non-framework environment)
Maintainers
Readme
@liyalabs/liya-3d-avatar-widget-vanilla-js
3D Talking Avatar Widget — AI Assistant with real-time lip-sync animation for Vanilla JS, PHP and any non-framework environment.
Origin
This package is a framework-agnostic port of @liyalabs/liya-3d-avatar-widget-vuejs.
| | Vue JS (upstream) | Vanilla JS (this package) |
|---|---|---|
| Runtime | Vue 3 + Vite | Plain JS / UMD / ESM |
| Usage | <LiyaAvatarWidget /> component | new LiyaAvatarWidget(el, opts) |
| PHP | ❌ | ✅ |
| CDN ready | ❌ | ✅ |
| No build step | ❌ | ✅ |
| Three.js | bundled | bundled |
Screenshots
Widget & Avatar Modal
| Widget (Chat Panel) | Avatar Modal (Lip-sync) |
|---------------------|------------------------|
|
|
|
Mobile
| Mobile Widget | Mobile Avatar |
|---------------|---------------|
|
|
|
Avatar Lip-sync in Action
| Idle | Speaking |
|------|----------|
|
|
|
Features
- 🎭 3D Avatar — Three.js powered 3D avatar with customizable models (GLB/GLTF)
- 👄 Lip-Sync — Real-time lip synchronization using viseme data (AudioContext clock)
- 🎤 Voice Input — Web Speech API push-to-talk & continuous mode
- 🔊 Voice Output — Text-to-speech with avatar animation
- 💬 Full Chat — Message history, suggestion chips, typing indicator
- 📎 File Upload — Attach files to conversations
- 🖼️ Media Display — Inline image and video rendering in chat messages
- 🖥️ Kiosk / Modal-Kiosk — Full-screen presentation layouts
- 📱 iOS safe area —
env(safe-area-inset-*)support across all breakpoints - 🌗 Dark / Light themes — CSS variable driven
- 📐 Responsive — Mobile → 4K/UHD breakpoints
- 🌐 i18n — Turkish, English and Chinese support
Installation
npm install @liyalabs/liya-3d-avatar-widget-vanilla-js
# or
yarn add @liyalabs/liya-3d-avatar-widget-vanilla-js
# or
pnpm add @liyalabs/liya-3d-avatar-widget-vanilla-jsOr via CDN (no build step):
<link rel="stylesheet" href="https://unpkg.com/@liyalabs/liya-3d-avatar-widget-vanilla-js/dist/liya-3d-avatar-widget-vanilla-js.css" />
<script src="https://unpkg.com/@liyalabs/liya-3d-avatar-widget-vanilla-js/dist/liya-3d-avatar-widget-vanilla-js.iife.js"></script>Quick Start
1. HTML + CDN (simplest)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet"
href="https://unpkg.com/@liyalabs/liya-3d-avatar-widget-vanilla-js/dist/liya-3d-avatar-widget-vanilla-js.css" />
</head>
<body>
<div id="liya-widget"></div>
<script src="https://unpkg.com/@liyalabs/liya-3d-avatar-widget-vanilla-js/dist/liya-3d-avatar-widget-vanilla-js.iife.js"></script>
<script>
const widget = new LiyaAvatarWidget(document.getElementById('liya-widget'), {
apiKey: 'your-api-key',
baseUrl: 'https://app-1-ai.liyalabs.com', // Your assigned backend URL (see GAR section)
assistantId: 'your-assistant-id',
assistantName: 'AI Assistant',
mode: 'widget', // 'widget' | 'kiosk' | 'modal_kiosk'
locale: 'tr', // 'tr' | 'en' | 'zh'
});
</script>
</body>
</html>2. ES Module (bundler / Vite / Webpack)
import LiyaAvatarWidget from '@liyalabs/liya-3d-avatar-widget-vanilla-js'
import '@liyalabs/liya-3d-avatar-widget-vanilla-js/style.css'
const widget = new LiyaAvatarWidget(document.getElementById('liya-widget'), {
apiKey: 'your-api-key',
baseUrl: 'https://app-1-ai.liyalabs.com',
assistantId: 'your-assistant-id',
assistantName: 'AI Assistant',
mode: 'widget',
locale: 'tr',
})3. PHP / Laravel Blade
<!-- resources/views/layouts/app.blade.php -->
<link rel="stylesheet"
href="{{ asset('vendor/liya/liya-3d-avatar-widget-vanilla-js.css') }}">
<div id="liya-widget"></div>
<script src="{{ asset('vendor/liya/liya-3d-avatar-widget-vanilla-js.iife.js') }}"></script>
<script>
const widget = new LiyaAvatarWidget(document.getElementById('liya-widget'), {
apiKey: '{{ config("liya.api_key") }}',
baseUrl: '{{ config("liya.base_url") }}',
assistantId: '{{ config("liya.assistant_id") }}',
mode: 'widget',
locale: 'tr',
});
</script>Options
All options are passed as the second argument to the constructor.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | LiyaLabs API key |
| baseUrl | string | required | Your backend URL (see GAR section) |
| assistantId | string | '' | Target assistant ID |
| assistantName | string | '' | Display name for the AI assistant |
| mode | string | 'widget' | 'widget' | 'kiosk' | 'modal_kiosk' |
| locale | string | browser default | 'tr' | 'en' | 'zh' |
| theme | string | 'dark' | 'dark' | 'light' |
| position | string | 'bottom-right' | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' |
| avatarModelUrl | string | '' | URL to GLB/GLTF avatar model (auto-fetched from backend if omitted) |
| welcomeMessage | string | '' | Welcome message shown on load |
| welcomeSuggestions | string[] | [] | Quick reply suggestion chips |
| placeholder | string | '' | Chat input placeholder text |
| showBranding | boolean | true | Show Liya branding footer |
| showVoice | boolean | true | Show voice input button |
| voiceEnabled | boolean | true | Enable voice (false = disabled mic for STANDARD accounts) |
| showFileUpload | boolean | true | Show file upload button |
| showAvatarButton | boolean | true | Show "Talk with Avatar" button |
| autoSpeak | boolean | true | Auto-speak assistant responses |
| animateButton | boolean | true | Attention animation on the toggle button |
| viewOnPageStart | boolean | false | Auto-open widget on page load |
| closeButtonEnabled | boolean | true | Show close button |
| offsetX | number | 20 | Horizontal offset in pixels |
| offsetY | number | 20 | Vertical offset in pixels |
| lipSyncIntensity | number | 0.5 | Mouth opening intensity (0–1) |
Events
Listen to widget lifecycle events via the on method:
const widget = new LiyaAvatarWidget(el, opts)
widget.on('opened', () => console.log('Chat panel opened'))
widget.on('closed', () => console.log('Chat panel closed'))
widget.on('avatarOpened', () => console.log('Avatar modal opened'))
widget.on('avatarClosed', () => console.log('Avatar modal closed'))
widget.on('messageSent', (text) => console.log('User sent:', text))
widget.on('messageReceived', (text) => console.log('Assistant replied:', text))
widget.on('speaking-changed',(active) => console.log('Speaking:', active))| Event | Payload | Description |
|-------|---------|-------------|
| opened | — | Chat panel opened |
| closed | — | Chat panel closed |
| avatarOpened | — | Avatar modal opened |
| avatarClosed | — | Avatar modal closed |
| messageSent | string | User sent a message |
| messageReceived | string | Assistant responded |
| speaking-changed | boolean | Avatar speaking state changed |
API Methods
// Programmatically open / close
widget.open()
widget.close()
// Send a message from code
widget.sendMessage('Merhaba!')
// Destroy and clean up
widget.destroy()Avatar Models
The widget supports GLB/GLTF models with ARKit-compatible blend shapes for lip-sync:
- Ready Player Me avatars (recommended — full ARKit blend shape support)
- Custom models with viseme morph targets
Supported Viseme Morph Targets
viseme_PP, viseme_FF, viseme_TH, viseme_DD, viseme_kk,
viseme_CH, viseme_SS, viseme_nn, viseme_RR, viseme_aa,
viseme_E, viseme_I, viseme_O, viseme_UMedia Display
When the AI assistant returns images or videos (e.g. from image generation), the widget automatically renders them as inline thumbnails in the chat bubble.
Chat messages include a media field:
interface MessageMediaItem {
type: 'image' | 'video'
url: string
alt?: string
source?: string
}Backend Requirements
This widget requires the Liya AI backend. The complete OpenAPI specification (LiyaAi-Api-External-V0, v0.1.0) is available on the developer page:
Developer Docs & API Reference →
Download the spec directly (Endpoints tab → OpenAPI Specification):
| Language | View / Download | |----------|----------------| | 🇬🇧 English | LiyaAi-Api-External-V0-en.yaml | | 🇹🇷 Türkçe | LiyaAi-Api-External-V0-tr.yaml |
The spec follows Semantic Versioning. While on 0.x, breaking changes may occur without prior notice — subscribe to liyalabs.com/changelog.
Core Endpoints Used
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/v1/external/chat/ | Send message, get response |
| POST | /api/v1/external/chat/with-files/ | Send message with file attachments |
| GET | /api/v1/external/sessions/ | List sessions |
| POST | /api/v1/external/sessions/ | Create session |
| DELETE | /api/v1/external/sessions/{id}/ | Delete session |
| GET | /api/v1/external/sessions/{id}/history/ | Get chat history |
| POST | /api/v1/external/files/ | Upload file |
| GET | /api/v1/external/assistants/ | List assistants |
| POST | /api/v1/external/tts/ | Text-to-speech (audio blob) |
| POST | /api/v1/external/avatar/speech/ | TTS with viseme data (PREMIUM+) |
| GET | /api/v1/external/avatar/model/ | Get avatar model URL (PREMIUM+) |
| GET | /api/v1/external/user/access/ | Check feature access |
| GET | /api/v1/external/tasks/{id}/status/ | Async task status (image/video gen) |
Authentication
All requests require the X-API-Key header:
X-API-Key: your-api-keyGet your API key from your Liya AI Dashboard under Projects → API Keys.
GAR (Global Application Router)
Liya AI uses a distributed backend architecture. Each project is routed to a specific backend instance via GAR.
Your backend URL is shown in your dashboard under Settings → API Configuration:
https://app-{X}-ai.liyalabs.comWhere {X} is your assigned instance number.
| Instance | Backend URL |
|----------|-------------|
| 1 | https://app-1-ai.liyalabs.com |
| 2 | https://app-2-ai.liyalabs.com |
| 3 | https://app-3-ai.liyalabs.com |
Use this URL as the baseUrl in your widget options.
Theme Customization
Pass a theme object to customize colors:
const widget = new LiyaAvatarWidget(el, {
apiKey: 'your-api-key',
baseUrl: 'https://app-1-ai.liyalabs.com',
theme: {
primaryColor: '#6366f1',
backgroundColor: '#ffffff',
textColor: '#374151',
borderRadius: '16px',
fontFamily: 'Inter, sans-serif',
widgetSize: 'medium', // 'small' | 'medium' | 'large'
zIndex: 9999
}
})Kiosk Mode
Kiosk mode renders a full-screen avatar experience — ideal for interactive displays, reception kiosks and digital signage:
const widget = new LiyaAvatarWidget(el, {
apiKey: 'your-api-key',
baseUrl: 'https://app-1-ai.liyalabs.com',
assistantId: 'your-assistant-id',
mode: 'kiosk', // or 'modal_kiosk' for overlay mode
avatarModelUrl: 'https://your-cdn.com/avatar.glb',
locale: 'tr',
autoSpeak: true,
})Account Types & Feature Access
| Feature | STANDARD | PREMIUM | PREMIUM PLUS | |---------|----------|---------|--------------| | Chat | ✅ | ✅ | ✅ | | File Upload | ❌ | ✅ | ✅ | | Voice Input (STT) | ✅ | ✅ | ✅ | | Voice Output (TTS) | ❌ | ✅ | ✅ | | 3D Avatar | ❌ | ✅ | ✅ | | Custom Avatar Model | ❌ | ❌ | ✅ | | Kiosk Mode | ❌ | ❌ | ✅ |
Browser Support
| Browser | Chat | Voice Input | 3D Avatar | Audio | |---------|------|-------------|-----------|-------| | Chrome 90+ | ✅ | ✅ | ✅ | Opus | | Firefox 90+ | ✅ | ✅ | ✅ | Opus | | Safari 15+ | ✅ | ✅ (iPadOS 16+) | ✅ | MP3 | | Edge 90+ | ✅ | ✅ | ✅ | Opus | | iOS Safari | ✅ | ✅ (iOS 16+) | ✅ | MP3 |
Minimum requirements: ES6+, WebGL 1.0, AudioContext
Development
# Install dependencies
npm install
# Watch build (development)
npm run dev
# Production build
npm run buildChangelog
0.1.0 (2025-07-10)
Features
- feat: Lip-sync overhaul —
AudioContext.currentTimeclock instead ofperformance.now()for precise audio/viseme synchronisation (matches Vue upstream) - feat: Smooth exponential mouth morphing — exponential smoothing formula
min(speed × (0.5 + |diff| × 0.5), 0.08)eliminates jitter (matches Vue upstream) - feat:
startVisemeSequencenow fires beforeaudio.start()for zero-latency sync - feat: Kiosk topbar — Settings and Close buttons moved to top-right actions group
- feat: Chinese (ZH) language support added to i18n
- feat: Media display — inline image and video rendering in chat messages
- feat:
MessageMediaItemtype support (backendmedia[]array + markdown fallback) - fix: ARKit blendshape: inactive mouth morphs now smoothly lerp to 0 (no stuck shapes)
- fix: Removed random intensity (
0.6 + Math.random() × 0.4) — replaced with stablelipSyncIntensity - chore: All debug
console.log/console.warncalls removed from source;drop_consoleenforced at build level
0.0.2
- Kiosk / modal_kiosk layout fixes
- Message bubbles no longer clipped at the bottom (
flex-start+ auto-scroll) - Avatar canvas height calculated from actual rendered controls height
- Large-screen / 4K canvas overflow fixed
- New
@media (min-width: 1920px)and@media (min-width: 2560px)breakpoints
0.0.1
- Initial public release
- 3D avatar with real-time lip-sync
- Voice input / output
- File upload support
- Session history
- Multi-language support (TR / EN)
- Standard, modal_kiosk and kiosk modes
Live Demo
- Platform: ai.liyalabs.com — Create an assistant and interact with the 3D avatar
- Website: liyalabs.com
License
MIT © Liya Labs
Support
- 🌐 Website: liyalabs.com
- 📖 API Docs: ai.liyalabs.com/developer
- 🐛 Issues: GitHub Issues
- 📧 Email: [email protected]
💡 For Vue 3 projects, use the upstream package:
@liyalabs/liya-3d-avatar-widget-vuejs
