Files
aleksey 99855dc2f6 docs(ai): канон PRODUCT-AI (Фаза 3, волна 3) — ИИ молчит и делает
PRODUCT-AI.md: L0 умность в ячейках (Time Arc v1-v4 shipped); L1 подсказки
ghosts kind=hint (Front#75); L2 голос-кнопка mark press-to-talk -> ActionPlan
с подтверждением пишущих действий + палитра fallback (Front#76). STT/intent
через Back-прокси /v1/ai/*; anti-scope: чат, Composer, AI-home, авто-booking,
AiRouter в Front. Ссылки: FrontSpec §3.3 + новый §7.3, README.

Refs EventHub/EventHubSpec#22
2026-08-19 22:36:41 +03:00

184 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ИИ клиента CalenTIQ (Фаза 3, волна 3)
Канон продуктового ИИ: умная сетка + опциональные предиктивные подсказки + голосовой центр выполнения **без чата**. Трекер: [Spec#22](https://git.sabilin.com/EventHub/EventHubSpec/issues/22), эпик [Spec#19](https://git.sabilin.com/EventHub/EventHubSpec/issues/19).
Реализация (Future): [Front#75](https://git.sabilin.com/EventHub/EventHubFront/issues/75) подсказки, [Front#76](https://git.sabilin.com/EventHub/EventHubFront/issues/76) голосовой центр. Этот документ — контракт; код подтягивается после него.
## Позиция: «ИИ молчит и делает»
ИИ CalenTIQ — не собеседник и не интерфейс. Это слой, который **предлагает действия**, а человек их подтверждает и исполняет. Никакого свободного текста от ИИ, никакого диалога, никаких уточняющих вопросов, никакой «личности ассистента».
Три следствия:
1. Результат любой ИИ-операции — **план действий в UI** (чипы, ghosts, навигация), а не текст.
2. Неопределённость разрешается **выбором из кандидатов**, не разговором.
3. Запись в API — только после явного подтверждения человека.
## Уровни ИИ
| Уровень | Что | Статус | Где живёт |
|---------|-----|--------|-----------|
| **L0 — умность в ячейках** | детерминированный скоринг Time Arc (без LLM, без Back) | shipped (v1v4) | ячейки сетки |
| **L1 — предиктивные подсказки** | те же ghosts, `kind: 'hint'` + причина «почему» | Future (Front#75), opt-in | ячейки сетки |
| **L2 — голосовой центр** | голос → ActionPlan → подтверждение | Future (Front#76), opt-in | кнопка Time Arc mark |
Единый визуальный язык: подсказка — **не** тост и **не** баннер, а ghost прямо в сетке. Интеллект всегда в ячейках — подсказки тоже в ячейках. Новый UI-слой не изобретается: расширяется уже shipped-механика ghosts (`copy` / `demand` / `phrase` / `repeat``+hint`).
## Статус Time Arc v1v4 (L0, shipped)
См. FrontSpec §3.3. Кратко:
- **v1 (Front#50):** клиентский скоринг лучшего часа: свободные слоты студии ∩ занятость пользователя × mood. Черновики владельца из прошлой недели. Без LLM и без Back.
- **v2 (Front#51):** память отказов `eh.timeArc.skips` (штраф часу/мастеру, дуга переезжает); буст мастера с ≥2 confirmed; тепло месяца.
- **v3:** дуга между календарями — `/following` и `/search` баннеры ближайшего часа.
- **v4 (Front#53):** demand-черновики по pending occupancy; локальный разбор фразы → phrase-черновик у владельца (long-press). Без LLM / AiRouter.
Всё L0 — локальные вычисления Front. Этот канон L0 **не меняет**.
## Голос-кнопка (L2, контракт Front#76)
### Кнопка
- Кнопка = **Time Arc mark** рядом с BrandWordmark в AppShell (см. FrontSpec §3.2) — всегда видна, отдельного слота не занимает.
- **Press-to-talk**: нажал и держишь → говоришь → отпустил → разбор. Отпустил без звука — ничего не происходит. Никакого «зависшего» прослушивания, таймеров VAD и модалок.
- Состояния (4): `idle` / `listening` / `processing` / `done` — анимируются **самим mark** (дуга заполняется, пока держишь). Бренд сам себе индикатор.
### Палитра быстрых команд (обязательный fallback)
Голос и тап — **два канала одного движка действий**. Кнопка при недоступности голоса (offline, mic denied, STT упал) открывает палитру тапабельных типовых действий из того же enum интентов. UX голоса не умирает ни в одном degrade-сценарии.
### Результат — план действий
После разбора показывается стек action-чипов (план), а не текст. Пишущие действия — с одной кнопкой «Выполнить»; обратимые исполняются сразу (см. классы ниже).
## ActionPlan (контракт later)
STT + intent-разбор выдают структурированный план, не текст. Намётка схемы:
```json
{
"version": 1,
"locale": "ru",
"confidence": 0.87,
"actions": [
{ "id": "a1", "intent": "open_calendar", "target": { "calendar_hint": "аурора" }, "resolved": null },
{ "id": "a2", "intent": "select_hour", "time": { "hour": 19, "day": "next", "dow": 2 } },
{ "id": "a3", "intent": "book", "confirm": "required" }
],
"candidates": []
}
```
### Интенты — конечный enum
Каждый интент исполняется через **уже существующий** UI-флоу. Новых write-эндпоинтов в Back нет.
| Intent | Глаголы (ru / en) | Исполняется через |
|--------|-------------------|-------------------|
| `navigate` | перейти, открой, покажи / go, open | роуты FrontSpec §4 |
| `open_calendar` | календарь + название | `/c/:id`; fuzzy-match по `GET /v1/calendars` + following |
| `select_hour` | «в 19», «завтра в 11» | курсор `calendarContextStore` + подсветка ячейки |
| `book` | запиши, записаться / book | существующий флоу Book (mandatory confirm + owner pending) |
| `create_slot_draft` | создай слот, поставь окно | механика phrase-черновиков v4: голос — ещё один источник фразы |
| `confirm_booking` / `decline_booking` | подтверди, отклони заявку | `PUT /v1/bookings/:id` через `/bookings` |
| `follow` / `unfollow` | подпишись, отслеживай | `POST/DELETE /v1/calendars/:id/follow` |
| `search` | найди, ищи | `/search` с заполненными фильтрами |
Всё вне enum — отбрасывается одним тостом, без retry-цикла.
### Разбор: rule-first
Двухслойный: словарь глаголов + regex покрывает типовые фразы («запиши к Ане завтра в 19» — глагол + имя + время, детерминированно). LLM-провайдер вызывается **только** при miss и обязан вернуть тот же ActionPlan-JSON — никогда свободный текст.
`create_slot_draft` у владельца использует локальный разбор фразы из v4 → **полностью офлайн** rule-path.
### Resolution и кандидаты (вместо вопросов)
Каждый `target.hint` резолвится **на клиенте** по локальным данным (календари, following, bookings, roster). Не резолвится → пишется в `candidates` и показывается чипами на выбор. Уточняющих вопросов нет по контракту.
### Классы действий
| Класс | Примеры | Подтверждение |
|-------|---------|----------------|
| **Обратимые** | `navigate`, `open_calendar`, `select_hour`, `search` | исполняются сразу — навигацию всегда можно отменить «назад» |
| **Пишущие** | `book`, `create_slot_draft`, `confirm/decline_booking`, `follow` | обязательный preview + явный тап «Выполнить» |
Обязательное подтверждение перед записью в API соблюдается буквально: запись в API — это только пишущие действия.
### Dry-run preview
Пишущие действия рендерятся в сетку **до** «Выполнить»: `create_slot_draft` — ghost-ячейкой, `book` — подсветкой целевого часа. Превью плана — тем же визуальным языком, что результат: пользователь видит ровно то, что получит.
### Исполнение
Последовательное, стоп на первой ошибке, без авто-продолжения. `book` всегда последний в плане и всегда с confirm; сверху — существующий owner-confirm pending (двойная защита записи).
## Предиктивные подсказки (L1, контракт Front#75)
- Подсказка = ghost `kind: 'hint'` в сетке + причина «почему».
- **Причины — шаблоны i18n, не LLM**: «вы обычно по вторникам в 19:00». Объяснимость без провайдера.
- Сигналы — **local-first**: паттерны bookings, `eh.timeArc.skips`, mood; ничего не уходит провайдерам.
- Частотный cap: не более N hint в неделю на экран (N уточняет Front#75).
- Dismiss пишет штраф в `timeArcMemoryStore` — тот же механизм skips; подсказка обучается отказам локально.
## Opt-in, privacy, degrade
### Opt-in
- L1 и L2 — **default off**. Тумблеры в `preferences` (`PATCH /v1/user/me`), как mood; до логина — выключены безусловно.
- Выключенный L1: ghosts v1–v4 работают как сейчас, `hint` не рисуется. Выключенный L2: mark не реагирует на hold, палитра скрыта.
### Приват-инварианты
- Транскрипт живёт **только в памяти** на время показа плана; не пишется ни в localStorage, ни в Back.
- Контекст календаря (события, bookings, предпочтения) **никогда** не уходит провайдерам — только аудио/фраза запроса.
- Аудио не сохраняется: стриминг в STT, discard после транскрипта.
### Degrade-матрица
| Сценарий | L1 подсказки | L2 голос |
|----------|--------------|----------|
| Offline | работают (local-first) | inert-state + тултип; у owner доступен rule-path `create_slot_draft` |
| Mic denied / нет микрофона | n/a | открывается палитра быстрых команд |
| STT недоступен / 5xx / таймаут | n/a | один тост; палитра как fallback |
| Низкий confidence | n/a | один тост «не расслышал», без retry-цикла |
| Средний confidence | n/a | чипы-кандидаты вместо прямого исполнения |
| Target не найден | подсказка не показывается | кандидаты; пустой список → тост |
| Write-ошибка при исполнении (409/403/402) | n/a | план остановлен; стандартная ошибка флоу (402 → `/subscription`) |
| Opt-in off | ghosts v1v4 без изменений | mark без реакции |
**Инвариант:** ни одно состояние ИИ-слоя не блокирует первичные флоу (сетка, запись, навигация живут независимо).
## Провайдер и Back-прокси (контракт later)
- STT и intent-разбор — **продуктовый провайдер** (вендор STT + мелкий LLM для intent-JSON при miss rule-слоя).
- Front ходит только в прокси Back (`/v1/ai/*`): ключи провайдера не покидают бэкенд, единая точка rate-limit, аудита и смены провайдера. Паттерн идентичен GEO/OpenCage (см. [GEO.md](GEO.md)).
- Back не персистит аудио и транскрипты.
- **EventHubAiRouter — не продуктовый путь**: это dev-tooling для coding-агентов (Zed). Продуктовый ИИ-клиент о нём не знает; зависимостей Front → AiRouter нет и не будет.
## Anti-scope
Вне продукта (зафиксировано):
- чат с ИИ в любой форме; Composer; отдельная AI-вкладка / AI-home;
- авто-booking и любые записи в API без подтверждения человека;
- свободные текстовые ответы ИИ и уточняющие вопросы;
- подводка EventHubAiRouter / Zed к клиентскому приложению;
- персист аудио/транскриптов, отправка контекста календаря провайдерам.
## Критерии старта Future-реализаций
Метрики для решения (собираются в ходе L0/Front#75):
- hint CTR; plan-confirm-rate; plan-abort-rate; доля rule-path vs provider-path.
Гейты:
- **Подсказки (Front#75):** ≥20% активных пользователей с ≥2 confirmed bookings через ghosts за месяц + явное продуктовое решение.
- **Голос (Front#76):** после пилота подсказок; STT-провайдер с RU и приемлемым ToS; пилот на IFT; минимум один сценарий с конверсией лучше ручного пути.
## Future Stories
- [Front#75](https://git.sabilin.com/EventHub/EventHubFront/issues/75) — предиктивные подсказки (ghosts `kind: 'hint'`).
- [Front#76](https://git.sabilin.com/EventHub/EventHubFront/issues/76) — голосовой центр: mark press-to-talk + палитра + ActionPlan.