Голосовой ввод (storm-media)
Голосовой ввод в AI-чат
С версии 6.6.6668 в AI-чате можно диктовать сообщения голосом: кнопка микрофона в поле ввода (или горячая клавиша Ctrl+Space) записывает голосовую заметку до 60 секунд, распознаёт её и вставляет текст в поле ввода.
Распознавание речи выполняет отдельный сервис storm-media (модель Whisper). Основное приложение остаётся тонким прокси: браузер отправляет запись на бэкенд Storm, тот передаёт её в storm-media и возвращает распознанный текст. Эта статья описывает, как развернуть storm-media и включить голосовой ввод.
Как это работает
Браузер (запись микрофоном, ≤60 сек)
│ POST /api/v1/ai/chat/transcribe
▼
Бэкенд Storm ──── Authorization: Bearer <stormMediaToken> ────► storm-media (Whisper)
▲ │
└──────────────── распознанный текст ◄────────────────────────────┘
- Запись ведёт браузер (MediaRecorder), поэтому страница Storm должна открываться по HTTPS — иначе браузер не даст доступ к микрофону.
- Лимиты: до 60 секунд и до 4 МБ на заметку (минута голоса в webm/opus — около 1 МБ).
- Распознавание минутной заметки на GPU занимает единицы секунд.
Шаг 1. Требования
| Условие | Где | Комментарий |
|---|---|---|
MCP_ENABLED=true | ENV основного приложения | Голосовой ввод — часть AI-модуля, как и чат (см. Включение чата) |
| AI-модуль в лицензии | Лицензия (enableAiFeatures) | Без него API чата закрыто |
| HTTPS у Storm | Ваш прокси/балансировщик | Требование браузера для доступа к микрофону |
| Сервис storm-media | Отдельный хост/контейнер | См. Шаг 2. Для локального распознавания нужен GPU NVIDIA |
Нужен ли GPU?
storm-media поддерживает два режима распознавания (ASR_BACKEND):
local(по умолчанию) — модель faster-whisperlarge-v3работает внутри контейнера. Данные не покидают ваш контур, но нужен GPU NVIDIA (~5 ГБ видеопамяти). Возможен запуск на CPU (ASR_DEVICE=cpu), но распознавание будет заметно медленнее.remote— storm-media проксирует аудио на внешний OpenAI-совместимый эндпоинт/v1/audio/transcriptions(свой Whisper-сервер или облачный сервис). GPU на хосте storm-media не нужен.
Шаг 2. Развернуть storm-media
Образ собирается в том же реестре, что и enterprise-приложение:
docker pull cr.selcloud.ru/stormbpmn-enterprise/storm-media:latest
Пример docker-compose.yml для локального распознавания на GPU:
services:
storm-media:
image: cr.selcloud.ru/stormbpmn-enterprise/storm-media:latest
restart: unless-stopped
environment:
INTERNAL_TOKEN: "<секрет — придумайте длинную случайную строку>"
ASR_BACKEND: "local"
ports:
- "8085:8080" # порт наружу — на ваше усмотрение
volumes:
- whisper_models:/models/whisper # кэш весов модели (~3 ГБ), переживает обновления
- media_tmp:/tmp/storm-media
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 5
volumes:
whisper_models:
media_tmp:
Для режима remote уберите блок deploy: (GPU не нужен) и добавьте переменные:
environment:
INTERNAL_TOKEN: "<секрет>"
ASR_BACKEND: "remote"
ASR_BASE_URL: "http://whisper-host:8000" # OpenAI-совместимый эндпоинт, БЕЗ /v1
ASR_API_KEY: "<ключ, если требуется>"
Ключевые переменные окружения storm-media:
| Переменная | Описание | По умолчанию |
|---|---|---|
| INTERNAL_TOKEN | Shared-secret: запросы без заголовка X-Internal-Token отклоняются. Пусто = проверка отключена (только для доверенной сети) | пусто |
| ASR_BACKEND | local (Whisper внутри) или remote (внешний эндпоинт) | local |
| ASR_MODEL | Модель faster-whisper | large-v3 |
| ASR_DEVICE | cuda / cpu / auto | cuda |
| ASR_CACHE_DIR | Каталог кэша весов (монтируйте volume) | /models/whisper |
| ASR_BASE_URL | Для remote: адрес эндпоинта без /v1 | пусто |
| ASR_API_KEY | Для remote: ключ внешнего сервиса | пусто |
Проверка работоспособности: GET /health (живость), GET /ready (модель загружена и готова), GET /metrics (Prometheus).
Первый запуск в режиме local
Веса Whisper (~3 ГБ) не вшиты в образ — при первом старте сервис скачивает их с Hugging Face в volume whisper_models. Хосту нужен разовый доступ в интернет. Для полностью изолированного контура заполните volume заранее (перенесите каталог кэша с машины с интернетом) — дальше сервис работает офлайн. Пока модель грузится, GET /ready отвечает ошибкой — это нормально.
Шаг 3. Настроить доступ бэкенда к storm-media
Бэкенд Storm ходит в storm-media с заголовком Authorization: Bearer <stormMediaToken>, а сам storm-media проверяет заголовок X-Internal-Token. Есть два способа связать их:
Вариант А — напрямую (доверенная приватная сеть). Запустите storm-media с пустым INTERNAL_TOKEN (проверка отключена) и укажите в настройках Storm адрес сервиса напрямую. Убедитесь, что порт storm-media недоступен снаружи периметра.
Вариант Б — через reverse-proxy (рекомендуется). Поставьте перед storm-media nginx, который проверяет Bearer-токен и подставляет X-Internal-Token:
location /media/ {
if ($http_authorization != "Bearer <ваш-внешний-токен>") { return 401; }
proxy_set_header X-Internal-Token "<значение INTERNAL_TOKEN storm-media>";
proxy_pass http://127.0.0.1:8085/; # порт storm-media
client_max_body_size 10m;
}
Шаг 4. Включить голосовой ввод в админ-панели
Все настройки — в Административной панели, раздел «🤖 AI-ассистент», подгруппа «Голосовой ввод». Применяются на лету, без перезапуска.
| Настройка (ключ) | Тип | Описание |
|---|---|---|
Голосовой ввод в чате (voiceInputEnabled) | тумблер | Показывает кнопку микрофона в чате. По умолчанию выключен |
storm-media: Base URL (stormMediaBaseUrl) | строка | Адрес storm-media, без /v1 — путь дописывается автоматически. Для варианта А: http://media-host:8085; для варианта Б: https://host/media |
storm-media: токен (Bearer) (stormMediaToken) | строка | Токен, который бэкенд отправляет в Authorization: Bearer. Для варианта А укажите любую непустую строку (сервис её не проверяет), для варианта Б — внешний токен из конфига nginx. Поле обязательно: пусто = «не настроено» |
Язык распознавания (голос) (voiceInputLanguage) | выбор | auto / ru / en / de. Пусто = auto (на коротких клипах авто-определение изредка ошибается — при однородной аудитории лучше зафиксировать язык) |
Итоговый чек-лист:
- storm-media запущен,
GET /readyотвечает успешно; MCP_ENABLED=true, лицензия с AI-модулем;- В админ-панели заполнены
stormMediaBaseUrlиstormMediaToken, включён тумблер «Голосовой ввод в чате»; - Storm открыт по HTTPS — в поле ввода чата появилась кнопка микрофона.
Устранение неполадок
| Симптом | Причина | Решение |
|---|---|---|
| Кнопки микрофона нет | Тумблер voiceInputEnabled выключен, чат недоступен (MCP_ENABLED/лицензия) | Проверьте условия из чек-листа; сам чат должен работать |
| Браузер не даёт доступ к микрофону | Storm открыт по HTTP | Настройте HTTPS (требование браузера, а не Storm) |
| «Голосовой ввод не настроен» | Пустой stormMediaBaseUrl или stormMediaToken | Заполните обе настройки в админ-панели |
| «Голосовой ввод отключён» | Тумблер voiceInputEnabled выключен | Включите тумблер |
| «Не удалось распознать запись» | storm-media недоступен, неверный токен/URL, ошибка распознавания | Проверьте GET /health и GET /ready storm-media, логи бэкенда (строки storm-media) и логи контейнера storm-media |
| «Аудиозапись длиннее 60 секунд» / «слишком большая» | Превышены лимиты заметки (60 сек / 4 МБ) | Диктуйте короче — лимит защищает GPU от длинных файлов |
| Первые запросы падают после рестарта storm-media | Модель ещё загружается | Дождитесь успешного GET /ready |