From 411671bad238784c4fb8631b0d2c501579a59c6b Mon Sep 17 00:00:00 2001 From: vvzvlad Date: Thu, 18 Jun 2026 19:44:16 +0300 Subject: [PATCH] docs(backlog): STT extra providers + async transcription roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs/backlog/stt-providers-and-async.md: how to add new synchronous STT request formats (Deepgram, native Gemini, ElevenLabs) via the explicit sttApiStyle axis, which providers are inherently async and don't fit the current sync model, and a target job-based async architecture (BullMQ job table, sync+async unification, polling -> push -> live streaming) with the migration path and security/cleanup considerations. Add docs/streaming-dictation-plan.md — a design document for true "text appears as you speak" dictation via the OpenAI Realtime API. - Maps the current batch dictation flow (client MediaRecorder -> single blob -> POST /ai-chat/transcribe) and why streaming is impossible there. - Documents the Realtime API contract (transcription session, ephemeral token, pcm16 audio, input_audio_buffer.append, input_audio_transcription delta/completed events, server_vad). - Recommends a server-side WS proxy transport (key stays server-side, SSRF-guarded, provider-agnostic via sttBaseUrl) over direct browser WebRTC, and a ProseMirror decoration for interim text with final-only commit to avoid polluting Yjs collab/history. - Covers config additions, AudioWorklet PCM16 capture, security per repo conventions, edge cases, phased rollout, risks, and impacted files. --- docs/backlog/stt-providers-and-async.md | 192 +++++++++++ docs/streaming-dictation-plan.md | 421 ++++++++++++++++++++++++ 2 files changed, 613 insertions(+) create mode 100644 docs/backlog/stt-providers-and-async.md create mode 100644 docs/streaming-dictation-plan.md diff --git a/docs/backlog/stt-providers-and-async.md b/docs/backlog/stt-providers-and-async.md new file mode 100644 index 00000000..58da2879 --- /dev/null +++ b/docs/backlog/stt-providers-and-async.md @@ -0,0 +1,192 @@ +# STT: дополнительные провайдеры и переход на асинхронную схему + +> Статус: беклог / план развития. Контекст — фича «голосовая диктовка» (STT, +> speech-to-text): кнопка-микрофон в чате агента и в редакторе, аудио +> распознаётся на сервере через AI-провайдер воркспейса. Документ фиксирует +> (1) какие ещё форматы STT-API имеет смысл поддержать и как, и (2) как в +> будущем перейти с текущей синхронной схемы (push-to-talk) на асинхронную. + +## 1. Где мы сейчас + +Распознавание построено как **синхронный запрос-ответ**: + +- Клиент пишет звук (`MediaRecorder`), POST-ит blob → сервер распознаёт → + возвращает `{ text }`, который вставляется в ввод. Никакого состояния задачи нет. +- Клиентская часть: `apps/client/src/features/dictation/` (`hooks/use-dictation.ts`, + `components/mic-button.tsx`, `services/dictation-service.ts`). +- Эндпоинт: `POST /ai-chat/transcribe` + (`apps/server/src/core/ai-chat/ai-chat.controller.ts`) — фича-гейт + `settings.ai.dictation`, throttle, лимит 25 МБ, whitelist mime, вывод реальной + ошибки провайдера (`describeProviderError`), формат контейнера выводится из mime. +- Тонкая обёртка: `apps/server/src/core/ai-chat/ai-transcription.service.ts` → + делегирует в `AiService.transcribe(workspaceId, audio, format)`. +- Выбор кодировки запроса — **явное** поле `sttApiStyle` + (`apps/server/src/integrations/ai/ai.types.ts`, `SttApiStyle`, + `STT_API_STYLES`): + - `multipart` — OpenAI-совместимый `POST /v1/audio/transcriptions` (form-data) + через AI SDK (`createOpenAI(...).transcription()` + `experimental_transcribe`); + - `json` — OpenRouter-стиль: `POST {baseURL}/audio/transcriptions`, + `Content-Type: application/json`, тело `{ model, input_audio: { data:, format } }`, + ответ `{ text }` (`AiService.transcribeJsonBase64`). +- Поле прокладывается как любой не-секрет: `resolve()` / `getMasked()` / + whitelist в `AiSettingsService.update` + (`apps/server/src/integrations/ai/ai-settings.service.ts`) **и** массив + `ALLOWED` в `WorkspaceRepo.updateAiProviderSettings` + (`apps/server/src/database/repos/workspace/workspace.repo.ts`). +- UI: селектор «Request format» на карточке Voice / STT + (`apps/client/.../settings/components/ai-provider-settings.tsx`) + + кнопка «Test endpoint» (бэкенд-проба — тихий WAV через тот же `transcribe`). + +**Важно:** `multipart` уже покрывает почти всю экосистему — её реализуют OpenAI, +Azure OpenAI (Whisper), Groq, Together, Fireworks, DeepInfra, vLLM, LM Studio, +whisper.cpp/llama.cpp server, `speaches`, `faster-whisper-server`, WhisperX. +Для них **новый формат не нужен**, достаточно base URL + модель + ключ. +`json` покрывает OpenRouter. Ось `sttApiStyle` — это абстракция над +*контрактом запроса/ответа*: каждый реально иной контракт = одно значение enum ++ одна ветка-энкодер. + +### Точки расширения для нового СИНХРОННОГО формата (чек-лист) + +1. `ai.types.ts` — добавить значение в `SttApiStyle` и `STT_API_STYLES`. +2. `dto/update-ai-settings.dto.ts` — `@IsIn(STT_API_STYLES)` подхватит автоматически. +3. `ai.service.ts` — ветка в `transcribe()` + приватный энкодер + (по образцу `transcribeJsonBase64`): сборка запроса, заголовок авторизации, + `!res.ok` → бросок со статусом+телом (без утечки ключа), парс ответа в `text`. +4. Клиент: `ai-settings-service.ts` (тип `SttApiStyle`), опция в `