AI Студия — Фронтенд-спеки

Standalone React-SPA для работы с AI-генерацией фото блюд. Не часть Админки Франшизы — отдельный сабдомен (photo.nirbi.ru), к которому ERP редиректит с ?erp_token=<JWT>.

Связи с ERP

Стек

React 18 + TypeScript strict + Vite + react-router-dom. Стилизация — inline-style токены из src/lib/tokens.ts (без Tailwind, без CSS Modules).

Авторизация и контекст пользователя

Токен поступает одним из двух путей:

  1. Bootstrap из URL: ERP-админка открывает https://photo.nirbi.ru/?erp_token=<JWT>. AuthProvider (см. src/contexts/AuthContext.tsx) синхронно достаёт erp_token из query, кладёт в sessionStorage['gensvc_token'], чистит query.
  2. 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
История/historygensvc.access
Админка пресетов/admin/presetsgensvc.preset.admin
Админка постеров/admin/poster-templatesgensvc.preset.admin (используется тот же ключ)
Админка композиции/admin/compositiongensvc.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) → BlobURL.createObjectURL → click.

Error states

API-ошибки — баннер сверху страницы (красный, dismissable). 401 → AuthContext.logout() → редирект на /no-access. 403 → inline-сообщение «нет прав на это действие» рядом с кнопкой.

Список экранов

Ссылки