Files
gitmost/docs/backlog/streaming-dictation-feature-toggle.md
claude_code ee25d52965 docs(backlog): gate streaming dictation behind a feature toggle (default off)
Design entry: hide the silence-cut streaming dictation path behind a per-
workspace settings.ai.dictationStreaming flag, default false, with batch
dictation as the default and fallback. Reuses the existing STT model and
/ai-chat/transcribe — no new provider/model/endpoint fields. Lists the server
+ client touch points, acceptance criteria, and edge cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:40:53 +03:00

107 lines
6.9 KiB
Markdown

# Стриминговая (по тишине) диктовка под фиче-тогглом, по умолчанию ВЫКЛ
Статус: **открыто.**
## Контекст
Стриминговая диктовка (нарезка по тишине через 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))
— не мержится.