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

15 KiB
Raw Permalink Blame History

ИИ клиента CalenTIQ (Фаза 3, волна 3)

Канон продуктового ИИ: умная сетка + опциональные предиктивные подсказки + голосовой центр выполнения без чата. Трекер: Spec#22, эпик Spec#19.

Реализация (Future): Front#75 подсказки, Front#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-разбор выдают структурированный план, не текст. Намётка схемы:

{
  "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 v1–v4 без изменений mark без реакции

Инвариант: ни одно состояние ИИ-слоя не блокирует первичные флоу (сетка, запись, навигация живут независимо).

Провайдер и Back-прокси (контракт later)

  • STT и intent-разбор — продуктовый провайдер (вендор STT + мелкий LLM для intent-JSON при miss rule-слоя).
  • Front ходит только в прокси Back (/v1/ai/*): ключи провайдера не покидают бэкенд, единая точка rate-limit, аудита и смены провайдера. Паттерн идентичен GEO/OpenCage (см. 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 — предиктивные подсказки (ghosts kind: 'hint').
  • Front#76 — голосовой центр: mark press-to-talk + палитра + ActionPlan.