feat(dictation): gate streaming dictation behind a workspace toggle

Streaming (silence-cut) dictation was hardcoded on. Put it behind a per-workspace
flag settings.ai.dictationStreaming, default off, with batch dictation as the
default and fallback. Mirrors the existing settings.ai.dictation flag end to end:

- server: aiDictationStreaming on UpdateWorkspaceDto + workspace.service writes
  settings.ai.dictationStreaming via updateAiSettings (jsonb merge keeps siblings)
- client: IWorkspaceAiSettings.dictationStreaming, an optimistic "Streaming
  dictation" sub-toggle under "Voice dictation" (disabled when dictation is off)
- gate the MicButton streaming prop in the editor toolbar and chat composer on
  the flag instead of a literal true

When the flag is absent/false both call sites pass streaming=false, so the VAD
model/wasm are never fetched and behavior is unchanged. Reuses the existing STT
model and /ai-chat/transcribe — no new provider/model/endpoint fields.

Removes the backlog entry now that it is implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-22 23:59:35 +03:00
parent ee25d52965
commit 44a1b5b003
8 changed files with 96 additions and 108 deletions

View File

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