Mutty — open-source платформа для голосового общения в реальном времени, построенная на базе LiveKit. Включает веб и десктоп-клиенты с коллаборативными модулями и AI-ботом.
Проект оригинально начат как Voice App, затем ребрендирован в Mutty.
- Голосовая связь в реальном времени (WebRTC, низкая задержка)
- Discord-подобные голосовые каналы
- Шаринг экрана со звуковыми сигналами (start/stop chime)
- Плавающие окна — коллаборативный whiteboard (tldraw) и shared notepad
- Бот-агент со встроенным AI через OpenRouter — стримит YouTube/Twitch/SoundCloud и отвечает в чате
- Чат с вложениями изображений, drag-and-drop, превью твитов
- Кроссплатформенность — веб (React), десктоп (Tauri + Rust) и Android
- Self-hosted — полностью под твоим контролем
Whiteboard использует tldraw — коммерческую библиотеку с платной лицензией.
- Для локальной разработки — ключ не нужен, оценочный режим работает полностью.
- Для production — если whiteboard используется активно, получите бесплатный Hobby license на tldraw.dev/r/pricing (для личных/некоммерческих проектов).
- Как указать ключ:
- Получите ключ на сайте tldraw
- Добавьте в
.env:TLDRAW_LICENSE_KEY=your_key_here
- Перезапустите frontend (
pnpm dev) — ключ встроится автоматически.
| Без ключа | С ключом |
|---|---|
| Оценочный режим | Полный функционал |
| Водянит "tldraw" на canvas (subtle watermark) | Без водянок |
┌─────────────────────┐ ┌──────────────────────────────┐
│ Web/Desktop │◄───────►│ Backend (Token Server) │
│ React + Vite │ HTTP │ Node.js / tsx │
│ :1420 / :1421 │ │ :4000 │
└──────────┬──────────┘ └──────────────┬───────────────┘
│ │
│ WebSocket / WebRTC │ HTTP (внутри Docker)
▼ ▼
┌───────────────────────────────────────────────────┐
│ LiveKit Server │
│ livekit-server:latest │
│ :7880 (WS) / :7881 (TCP) / :7882 (UDP) │
└──────────────────────┬────────────────────────────┘
│
WebSocket (внутри Docker)
▼
┌───────────────────────────────────────────────────┐
│ Agent Server (Python бот) │
│ livekit-agents + OpenRouter AI │
│ :8081 │
└───────────────────────────────────────────────────┘
Voice-app/
├── backend/ # Token Server (Node.js + tsx)
├── packages/
│ ├── web/ # Веб-клиент (React + Vite)
│ ├── desktop/ # Десктоп (Tauri v2 + Rust)
│ └── shared/ # Shared компоненты (React hooks, UI, collab)
├── agent-server/ # Python бот
└── livekit/ # Конфиги LiveKit
- Ядро WebRTC-инфраструктуры (Go), образ
livekit/livekit-server:latest - Медиа-маршрутизация, сигнализация, управление комнатами
- Порты:
7880— WebSocket (сигнализация)7881— TCP (fallback для WebRTC)7882— UDP (RTC, медиа)
- Конфиг:
livekit/livekit.prod.yaml(production) - Ключи: блок
keysв YAML должен совпадать сLIVEKIT_API_KEY/LIVEKIT_API_SECRETиз.env
- Стек: TypeScript + tsx +
livekit-server-sdkv2 + dotenv - За что отвечает: выдача JWT-токенов LiveKit для аутентификации в комнатах
- Порт:
4000 - Ключевые env:
LIVEKIT_WS_URL— URL, отдаваемый браузеру (localhostили твой домен)LIVEKIT_API_URL— авто-вычисляется из WS-URL (ws/→http/)TOKEN_CORS_ORIGINS— origins через запятую (пусто =[*])TOKEN_ROOM_MAX_LENGTH/TOKEN_IDENTITY_MAX_LENGTH/TOKEN_TTL
- Стек: React + TypeScript + Vite + Tailwind CSS + shadcn/ui + LiveKit SDK + React Router
- Порт:
1420(dev) - Фичи:
- Голос/видео, screen share
- Чат с вложениями изображений, drag-and-drop
- Коллаборативный whiteboard (tldraw + LiveKit data channels)
- Shared notepad (LiveKit data channels)
- Floating windows — drag, resize, minimize
- Стек: Tauri v2 (React frontend + Rust backend)
- Отсюда же собирается Android-приложение — см. раздел Android.
Десктопная точка входа в
src-tauri/src/main.rs, мобильная — вsrc/lib.rs - Порт:
1421(dev) - Фичи:
- Все фичи веб-клиента
- Глобальные горячие клавиши (настраиваемые через UI, работают даже когда окно не в фокусе)
- Трей (сворачивание в системный луч)
- Звуки шаринга экрана (start/stop chime)
- Agent controls — меню управления ботом (режимы, качество, очередь, громкость)
- Сплэш-скрин при запуске
- Стек: React + TypeScript
- Что внутри:
floating/— система плавающих окон (drag, resize, z-index, minimize)collab/— синхронизация через LiveKit data channels (broadcast диффов для whiteboard, JSON-reducer для notes; CRDT не используется, разрешение конфликтов — last-writer-wins)components/livekit/— UI компоненты комнаты (control bar, settings, chat)hooks/— React hooks (audio settings, camera, screen share, hotkeys)modules/tldraw/— tldraw-интеграция с коллаборациейmodules/notes/— shared notepad с экспортом в.txt
- Стек: Python 3.11 +
livekit-agents+yt-dlp+ffmpeg - Порт:
8081 - Модульная архитектура:
src/core/agent.py— основной оркестраторsrc/chat/— чат-шлюз и парсер командsrc/media/— стриминг, очередь воспроизведения, резолвер URLsrc/llm/— OpenRouter провайдер + промпты (AI-ассистент, поиск, плейлисты)src/memory/— in-memory store истории разговоров (per room, живёт только пока бот в комнате)
- Команды в чате (без "!"):
<url>— воспроизвести ссылку (YouTube / Twitch / SoundCloud / Telegram)add <url>/queue <url>— добавить в очередьskip/next— пропустить трекqueue/list— показать очередьclear— очистить очередьстоп/stop— остановитьpause/resume— пауза / продолжитьrepeat— повтор текущего трекааудио <url>— только звуквидео <url>— видео + звукyoutube <запрос>/soundcloud <запрос>— поискai <вопрос>/jarvis <вопрос>/бот <вопрос>— AI-ассистентplaylist <описание>/плейлист <описание>— AI генерирует плейлист
- AI через OpenRouter:
- Установи
OPENROUTER_API_KEYв env - Бот отвечает на вопросы, может искать и играть музыку по запросу
- Генерирует плейлисты по описанию (например, "lofi для программирования")
- Помнит контекст разговора в пределах сессии (in-memory per room, стирается когда бот покидает комнату)
- Установи
git clone https://github.com/Gaigen/Voice-app.git
cd Voice-apppnpm installcp .env.example .env# LiveKit ключи (в проде — ОБЯЗАТЕЛЬНО свои!)
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=secret
# URL для браузера (локально = localhost)
VITE_LIVEKIT_URL=ws://localhost:7880
VITE_TOKEN_ENDPOINT=http://localhost:4000/api/token
# Token Server
LIVEKIT_WS_URL=ws://livekit:7880
LIVEKIT_PUBLIC_WS_URL=ws://localhost:7880
# Опционально
# OPENROUTER_API_KEY=sk-or-v1-...
# AGENT_MEDIA_PROXY=http://proxy:8080
# AGENT_VIDEO_FPS=30
# AGENT_VIDEO_QUALITY=720p# Собрать и запустить все сервисы
docker-compose up -d --build# 1. Инфраструктура в Docker
docker-compose -f docker-compose.dev.yml up -d
# 2. Веб-клиент локально
cd packages/web
pnpm dev # http://localhost:1420
# 3. Десктоп локально
cd packages/desktop
pnpm tauri dev # http://localhost:1421 + Tauri window| Сервис | URL | Описание |
|---|---|---|
| Web Client | http://localhost:1420 |
Веб-интерфейс |
| Desktop Client | pnpm tauri dev |
Нативное окно |
| Token Server | http://localhost:4000 |
API выдачи токенов (/health) |
| LiveKit WS | ws://localhost:7880 |
WebSocket (сигнализация) |
| LiveKit Admin | http://localhost:7880 |
HTTP API LiveKit |
| Agent Server | http://localhost:8081 |
YouTube/стриминг бот |
Захардкожены в packages/shared/src/hooks/useModuleToggle.ts, через UI не настраиваются.
| Комбо | Действие |
|---|---|
Ctrl + B |
Открыть/закрыть Whiteboard |
Ctrl + M |
Открыть/закрыть Notes |
| Комбо (дефолт) | Действие | Настраиваемый |
|---|---|---|
Ctrl + M |
Mute/unmute mic | ✅ |
Ctrl + F |
Full mute toggle | ✅ |
Ctrl + B |
Whiteboard toggle | ✅ |
Ctrl + N |
Notes toggle | ✅ |
Настройка — в Settings → Hotkeys. Работает глобально, даже когда окно свёрнуто.
⚠️ Глобальные хоткеи работают только на Windows. Они реализованы опросомGetAsyncKeyStateвsrc-tauri/src/main.rsпод#[cfg(target_os = "windows")]. На macOS и Linux они не срабатывают, причём браузерный фолбэк в десктоп-сборке тоже отключён — то есть хоткеи там не работают вообще.
ℹ️ Обратите внимание: Notes на вебе висит на
Ctrl + M, а на десктопе по умолчанию наCtrl + N. На десктопе комбинацию можно переназначить, на вебе — нет.
Android-приложение собирается из того же пакета packages/desktop — Tauri v2 умеет
собирать мобильную цель из общего кода. Отдельного пакета нет.
Собирается в CI. Тег v* запускает workflow .github/workflows/release.yml, который
кладёт APK и десктопные сборки в один черновик релиза.
- ABI: только
arm64-v8a. На 32-битных устройствах не установится minSdk 24(Android 7+), проверялось на Android 9 и выше- Сборка debug, самоподписанная — ставится сайдлоадом
Нужны: Rust с таргетом aarch64-linux-android, JDK 17, Android SDK (platform 34,
build-tools 34) и NDK r27c.
export ANDROID_HOME=~/android-sdk NDK_HOME=~/android-ndk-r27c JAVA_HOME=~/jdk17
cd packages/desktop
CARGO_PROFILE_DEV_STRIP=symbols pnpm exec tauri android build --debug --target aarch64 --apkТри грабли, каждая стоит отдельного круга отладки:
- Tauri CLI ищет
$ANDROID_HOME/cmdline-tools/bin/sdkmanager, а стандартная установка кладёт его вcmdline-tools/latest/bin. Без симлинкаtauri android initпадает с невнятным «Skipping Android Studio command line tools installation». - NDK распаковывать только
unzip. Распаковщик без поддержки симлинков (напримерpython -m zipfile) превращаетclangв текстовый файл со строкойclang-18, и это всплывает лишь на линковке, после успешной компиляции всех объектных файлов. CARGO_PROFILE_DEV_STRIP=symbolsобязателен — иначе APK раздувается до ~170 МБ вместо ~27 МБ. УдалениеjniLibs.keepDebugSymbolsне помогает: AGP пропускает стрип, когда NDK не подключён к Gradle-проекту. После смены размера библиотеки удаляйтеgen/android/app/build, иначе Gradle оставит внутри APK дыру от прежней версии.
На первом экране, кроме адреса и ника, есть переключатель Secure / Insecure. Он
задаёт схему сразу для token-эндпоинта и для WebSocket. Это важно, потому что адрес
WebSocket приходит с сервера (LIVEKIT_PUBLIC_WS_URL), и без переключателя выбрать
ws:// вместо wss:// было бы нельзя. Если подключение не проходит — смените положение.
Debug-сборка разрешает нешифрованный трафик (usesCleartextTraffic), поэтому ws://
и http:// работают. В release-сборке это придётся включать отдельно.
| Функция | Причина |
|---|---|
| Шаринг экрана | в Android WebView нет getDisplayMedia |
| Глобальные хоткеи | реализованы только под Windows |
| Трей, autostart | десктопные понятия |
| Перетаскивание плавающих окон | whiteboard и Notes открываются, но UI рассчитан на мышь |
Голос, видео, чат и вложения работают.
В .env и livekit/livekit.prod.yaml — задай реальные ключи.
# С HTTPS
VITE_LIVEKIT_URL=wss://example.com/livekit
VITE_TOKEN_ENDPOINT=https://example.com/api/token
LIVEKIT_WS_URL=wss://example.com/livekit
# Без HTTPS (по IP)
VITE_LIVEKIT_URL=ws://YOUR_SERVER_IP:7880
VITE_TOKEN_ENDPOINT=http://YOUR_SERVER_IP:4000/api/tokendocker-compose build --no-cache frontend
docker-compose up -d| Маршрут | Бэкенд |
|---|---|
/ |
web :80 |
/api/token |
backend :4000 |
/livekit |
livekit :7880 |
| Порт | Назначение |
|---|---|
80 / 443 |
HTTP/HTTPS через reverse proxy |
7880 |
WebSocket (LiveKit) |
7881 |
TCP fallback (WebRTC) |
7882/UDP |
Медиа (WebRTC) |
Для пользователей за симметричным NAT — настрой TURN в livekit.prod.yaml.
Voice-app/
├── docker-compose.yml # Production
├── docker-compose.dev.yml # Development (LiveKit + Backend)
├── .env.example # Шаблон переменных
├── pnpm-workspace.yaml # pnpm monorepo
│
├── backend/ # Token Server (Node.js + tsx)
│ ├── Dockerfile
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
│
├── packages/
│ ├── web/ # Веб-клиент (React + Vite)
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ └── ...
│ │ ├── package.json
│ │ └── vite.config.ts
│ │
│ ├── desktop/ # Десктоп (Tauri v2)
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── components/
│ │ │ └── hooks/
│ │ ├── src-tauri/ # Rust код
│ │ │ ├── src/main.rs # Глобальные хоткеи, трей
│ │ │ └── tauri.conf.json
│ │ └── package.json
│ │
│ └── shared/ # Shared компоненты
│ ├── src/
│ │ ├── floating/ # Плавающие окна (drag, resize)
│ │ ├── collab/ # Коллаб-синхронизация (LiveKit data channels)
│ │ ├── components/livekit/ # UI комнаты
│ │ ├── modules/
│ │ │ ├── tldraw/ # tldraw + LiveKit data channels
│ │ │ └── notes/ # Shared notepad + export .txt
│ │ ├── hooks/ # React hooks
│ │ └── config.ts # Shared константы
│ └── package.json
│
├── agent-server/ # Python бот
│ ├── Dockerfile
│ ├── main.py # Entrypoint
│ ├── requirements.txt
│ └── src/
│ ├── core/agent.py # Оркестратор
│ ├── chat/ # Чат-шлюз, парсер команд
│ ├── media/ # Стриминг, очередь, резолвер
│ ├── llm/ # OpenRouter AI
│ └── memory/ # In-memory store истории
│
└── livekit/ # Конфиги LiveKit
└── livekit.prod.yaml
docker-compose logs -f # смотри логи
docker-compose ps # статус- Проверь
VITE_LIVEKIT_URLв.env(должен =ws://localhost:7880) - Пересобрей:
docker-compose build --no-cache frontend && docker-compose up -d
- UDP порт
7882должен быть открыт - Для внешних подключений — настрой TURN
- Без HTTPS WebRTC работает только на localhost
LIVEKIT_API_KEY/LIVEKIT_API_SECRETв.envиlivekit/livekit.prod.yamlдолжны совпадать- Healthcheck бэкенда:
http://localhost:4000/health
- Проверь
OPENROUTER_API_KEY(опционально, без него AI-фичи не работают, но стриминг да) - Проверь
AGENT_MEDIA_PROXYесли yt-dlp не достаёт YouTube - Логи:
docker-compose logs -f agent-server
MIT