AI Студия — Фронтенд-спеки
Standalone React-SPA для работы с AI-генерацией фото блюд. Не часть Админки Франшизы — отдельный сабдомен (photo.nirbi.ru), к которому ERP редиректит с ?erp_token=<JWT>.
Связи с ERP
- Бизнес-спека: AI Студия — Бизнес-обзор
- Технический контракт: Photo Studio Service API
- Permissions: Permissions AI Студии
- Решение по auth: ADR-021
- Решение по Polza: ADR-022
Стек
React 18 + TypeScript strict + Vite + react-router-dom. Стилизация — inline-style токены из src/lib/tokens.ts (без Tailwind, без CSS Modules).
Авторизация и контекст пользователя
Токен поступает одним из двух путей:
- Bootstrap из URL: ERP-админка открывает
https://photo.nirbi.ru/?erp_token=<JWT>.AuthProvider(см.src/contexts/AuthContext.tsx) синхронно достаётerp_tokenиз query, кладёт вsessionStorage['gensvc_token'], чистит query. - Re-open: при возврате на сайт без query-параметра — читается из
sessionStorage.
sessionStorage, не localStorage
Токен живёт в
sessionStorage— закрытие вкладки логаут. Если у пользователя несколько вкладок ERP, каждая получит свой?erp_token=.
JWT декодируется на фронте (без проверки подписи — это делает бэк), permissions кладутся в AuthContext и доступны через useAuth(). Гейтинг компонентов:
const { permissions } = useAuth();
const canAdmin = permissions.includes("gensvc.preset.admin");Навигация и видимость разделов
| Раздел | Роут | Видимость |
|---|---|---|
| Студия (генерация) | /photo (default) | gensvc.access |
| История | /history | gensvc.access |
| Админка пресетов | /admin/presets | gensvc.preset.admin |
| Админка постеров | /admin/poster-templates | gensvc.preset.admin (используется тот же ключ) |
| Админка композиции | /admin/composition | gensvc.preset.admin |
| Нет доступа | /no-access | если нет ни одного gensvc.* permission |
Pull-down навигация в шапке — пункт «Админ» отображается только при gensvc.preset.admin.
Глобальные паттерны
Polling / live updates
Studio после POST /v1/jobs/... не блокирует форму — переход в фоновый режим (см. Студия § «Submit & banner»). История по /history поллит GET /v1/jobs каждые 5 секунд, пока есть pending|running jobs (см. История § «Active section»).
Notification API
При первом заходе на /history — запрашивается разрешение на browser notifications. При появлении нового succeeded job — new Notification("✅ Фото готово", { body: ... }) срабатывает даже из background tab.
Download через blob
Cross-origin <a download> на presigned MinIO URL не работает (браузер игнорирует download для другого origin). Поэтому скачивание идёт через fetch /v1/assets/{id} (auth-headers через apiFetch) → Blob → URL.createObjectURL → click.
Error states
API-ошибки — баннер сверху страницы (красный, dismissable). 401 → AuthContext.logout() → редирект на /no-access. 403 → inline-сообщение «нет прав на это действие» рядом с кнопкой.
Список экранов
- Студия — экран генерации (4 режима)
- История — список генераций с live updates
- Админка пресетов — CRUD системных стилей