diff --git a/docs/backlog/streaming-dictation-feature-toggle.md b/docs/backlog/streaming-dictation-feature-toggle.md new file mode 100644 index 00000000..402c41f5 --- /dev/null +++ b/docs/backlog/streaming-dictation-feature-toggle.md @@ -0,0 +1,106 @@ +# Стриминговая (по тишине) диктовка под фиче-тогглом, по умолчанию ВЫКЛ + +Статус: **открыто.** + +## Контекст + +Стриминговая диктовка (нарезка по тишине через Silero VAD, +`@ricky0123/vad-web`) уже в `develop` и сейчас **жёстко включена**: `MicButton` +получает проп `streaming` литералом `true` в двух местах — редактор +([dictation-group.tsx](../../apps/client/src/features/editor/components/fixed-toolbar/groups/dictation-group.tsx)) +и чат +([chat-input.tsx](../../apps/client/src/features/ai-chat/components/chat-input.tsx)). +Фича экспериментальная: + +- тяжёлые ассеты (ONNX-модель + ORT-wasm, 13–26 МБ, грузятся в браузер при + первом использовании); +- задержка инициализации модели на первом клике (компиляция wasm + подъём + inference-сессии — повторяется на каждую загрузку страницы); +- много мелких запросов на `/ai-chat/transcribe` (по одному на сегмент речи) + вместо одного на запись. + +Её нужно сделать **opt-in на воркспейс, по умолчанию выключенной**, с обычной +батч-диктовкой как дефолтом и фолбэком. + +## Цель + +Спрятать стриминговый путь за булевым флагом воркспейса +`settings.ai.dictationStreaming` (default `false`). Выкл → текущая стабильная +батч-диктовка. Вкл → стриминговая. + +**Минимализм (явно):** один булев флаг, переиспользуем существующую STT-модель +и эндпоинт `/ai-chat/transcribe`, **без новых полей провайдера / модели / +эндпоинта / секретов** — осознанное требование после претензий к realtime-PR +(#118) за лишние поля настроек. + +## Дизайн + +### Сервер + +- В типе AI-настроек + ([integrations/ai/ai.types.ts](../../apps/server/src/integrations/ai/ai.types.ts)) + и в + [dto/update-ai-settings.dto.ts](../../apps/server/src/integrations/ai/dto/update-ai-settings.dto.ts) + добавить `dictationStreaming?: boolean` рядом с уже существующим флагом + `dictation`. Проверить, валидируется ли апдейт настроек по whitelist + (`ai-settings.service.ts`) — если да, внести ключ; иначе passthrough. +- Это **только клиентский поведенческий флаг**: эндпоинт транскрипции и + STT-модель не меняются (стриминг переиспользует `/ai-chat/transcribe`). + Флаг просто отдаётся в составе `settings.ai`, который клиент уже читает. + +### Клиент + +- Тип + [features/workspace/types/workspace.types.ts](../../apps/client/src/features/workspace/types/workspace.types.ts) + (`settings.ai`, рядом с `dictation?: boolean`): добавить + `dictationStreaming?: boolean`. +- UI + [ai-provider-settings.tsx](../../apps/client/src/features/workspace/components/settings/components/ai-provider-settings.tsx): + добавить Switch «Streaming dictation (cut on pauses)» **внутри/рядом** с + тумблером «Voice dictation» — активен только когда `dictation` включена (это + под-режим диктовки). Оптимистичный апдейт по образцу `dictation` + (см. `handleDictationToggle` и запись `ai: { ...ai, dictation: value }`), + пишет `settings.ai.dictationStreaming`. Default off. Новый i18n-ключ. +- Гейтинг: в `dictation-group.tsx` и `chat-input.tsx` заменить жёсткий + `streaming` (литерал `true`) на `streaming={settings.ai.dictationStreaming === true}`. + Проп `streaming` у `MicButton` уже выбирает хук (`useStreamingDictation` vs + `useDictation`) — там менять ничего не нужно. + +## Критерии приёмки + +- Свежий воркспейс (флага нет) → mic-кнопка использует **батч**-диктовку; + ассеты VAD (ONNX/wasm) **не грузятся** (ленивый `import()` в + `useStreamingDictation.start()` срабатывает только при `streaming` и клике, + которого при выкл не будет — оба хука инертны до `start()`). +- Тоггл вкл → стриминговая диктовка работает и в редакторе, и в чате. +- Тоггл выкл → возврат к батчу; стриминговые ассеты не подгружаются. +- Нет новых полей модели / эндпоинта / секрета — переиспользуется + диктовочная STT-модель и `/ai-chat/transcribe`. +- Флаг персистится на воркспейс и гейтится как прочие `settings.ai.*`. + +## Затрагиваемые файлы (указатели) + +- **Сервер:** `integrations/ai/ai.types.ts`, + `integrations/ai/dto/update-ai-settings.dto.ts`, + `integrations/ai/ai-settings.service.ts` (если есть нормализация/whitelist). +- **Клиент:** `features/workspace/types/workspace.types.ts`, + `features/workspace/components/settings/components/ai-provider-settings.tsx` + (Switch + i18n), `features/editor/components/fixed-toolbar/groups/dictation-group.tsx`, + `features/ai-chat/components/chat-input.tsx`. + +## Заметки / краевые случаи + +- Батч-диктовка остаётся дефолтом и фолбэком (в т.ч. если стриминговая + инициализация падает). +- Подтвердить, что выкл-состояние не тянет ни одного VAD-байта: `MicButton` + хоть и вызывает оба хука безусловно (правило хуков), оба инертны до + `start()`, поэтому при `streaming=false` модель/wasm не запрашиваются. +- **Не** добавлять отдельные модель/эндпоинт под стриминг — переиспользовать + диктовочные (явное требование после realtime-PR). + +## Вне scope + +- Preload / мгновенный старт и латентность инициализации модели — отдельный + follow-up. +- Realtime-websocket путь (PR #118, [streaming-dictation-plan.md](../streaming-dictation-plan.md)) + — не мержится.