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
This commit is contained in:
2026-08-19 22:36:41 +03:00
parent 59726d394c
commit 99855dc2f6
3 changed files with 197 additions and 0 deletions
+13
View File
@@ -179,6 +179,10 @@ busy Time Arc += события personal с `mirror_to_default`; ghost/demand/ph
`write|admin`; Following не смешивать с share. Confirmed foreign bookings overlay на `write|admin`; Following не смешивать с share. Confirmed foreign bookings overlay на
default personal — только `role=owner` ids в ownSet. default personal — только `role=owner` ids в ownSet.
Канон ИИ клиента (подсказки / голос, Future — [PRODUCT-AI.md](PRODUCT-AI.md), см. §7.3):
умность — в ячейках; опциональные hint-ghosts и голос-кнопка mark → ActionPlan →
подтверждение. Не чат; AiRouter вне продукта.
## 4. Маршруты ## 4. Маршруты
Публичные: Публичные:
@@ -425,6 +429,15 @@ cold = файлы. UI истории — React, не iframe. Код: Back#76, Fr
Пикер адреса/пина; карточка с мини-картой и outbound Google/Яндекс/OSM; Пикер адреса/пина; карточка с мини-картой и outbound Google/Яндекс/OSM;
Discover ближайшие с опциональным радиусом. Код: Back#77, Front#74, DevOps#16. Discover ближайшие с опциональным радиусом. Код: Back#77, Front#74, DevOps#16.
## 7.3. Фаза 3 — ИИ клиента (Spec#19 / Spec#22)
Канон: [`PRODUCT-AI.md`](PRODUCT-AI.md). Позиция «ИИ молчит и делает»: L0 умность в
ячейках Time Arc (shipped, без LLM); L1 опциональные подсказки — ghosts `kind: 'hint'`
с причиной (Front#75); L2 голос-кнопка Time Arc mark, press-to-talk → ActionPlan-чипы
с подтверждением пишущих действий, палитра команд как fallback (Front#76). STT/intent
через Back-прокси `/v1/ai/*`, как GEO/OpenCage. Anti-scope: чат, Composer, AI-home,
авто-booking без подтверждения, подводка AiRouter к Front. Код — после гейтов канона.
## 8. Источники истины ## 8. Источники истины
- Маршруты и поведение: `EventHubBack/src/eventhub_app.erl` + handlers - Маршруты и поведение: `EventHubBack/src/eventhub_app.erl` + handlers
+183
View File
@@ -0,0 +1,183 @@
# ИИ клиента 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.
+1
View File
@@ -8,6 +8,7 @@
## 7. [Карта архитектуры для Zed/агентов](ZED-ARCHITECTURE.md) ## 7. [Карта архитектуры для Zed/агентов](ZED-ARCHITECTURE.md)
## 8. [Архив календаря (Фаза 3)](ARCHIVE.md) ## 8. [Архив календаря (Фаза 3)](ARCHIVE.md)
## 9. [Гео и карты (Фаза 3)](GEO.md) ## 9. [Гео и карты (Фаза 3)](GEO.md)
## 10. [ИИ клиента (Фаза 3)](PRODUCT-AI.md)
# **Репозитории разработки EventHub** # **Репозитории разработки EventHub**
## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack) ## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack)