EDIT_ENGINE=grok по умолчанию, XAI_API_KEY/XAI_BASE_URL/XAI_IMAGE_MODEL/XAI_TIMEOUT_MS; Codex остаётся для подсказок и как fallback (EDIT_ENGINE=codex).
142 lines
11 KiB
Markdown
142 lines
11 KiB
Markdown
# Кадр — локальный AI-фоторедактор (v2)
|
||
|
||
Веб-приложение редактирует фотографии по текстовому промпту (через локальный
|
||
Codex CLI) и увеличивает разрешение через домашний сервер ComfyUI.
|
||
|
||
Поддерживаются фотографии JPEG, PNG, WebP и HEIC/HEIF. Файлы HEIC/HEIF
|
||
преобразуются на сервере в JPEG с помощью `heic-convert` до передачи в ComfyUI
|
||
или Codex.
|
||
|
||
---
|
||
|
||
## Архитектура v2
|
||
|
||
В версии v2 сервис переработан в **модульный монолит** (`src/`):
|
||
|
||
1. **Устойчивость к перезапуску**:
|
||
- Персистентный append-only JSONL-журнал заданий (`data/jobs.jsonl`). При рестарте сервера незавершённые задания Codex получают статус `interrupted` с возможностью повтора в один клик, а задания ComfyUI автоматически возвращаются в очередь.
|
||
- Персистентное хранилище сессий (`data/sessions.json`, хранятся sha256-хеши токенов). Владелец остаётся авторизован после обновления или перезапуска контейнера.
|
||
- Атомарная запись манифеста истории (`history.json.tmp` + `fsync` + `rename`) защищает от повреждения данных при внезапном выключении.
|
||
2. **Очередь заданий (FIFO) и группы конкурентности**:
|
||
- Вместо блокировки single-flight (429) запросы на редактирование и апскейл ставятся в очередь.
|
||
- Лимит параллельности: Codex (edit, suggest) = 1 поток; ComfyUI (upscale) = 2 потока.
|
||
- Лимит очереди: до 5 заданий на группу (защита от накликивания).
|
||
3. **Безопасность**:
|
||
- Пароль защищён scrypt-хешированием (`crypto.scrypt`) с постоянным временем сверки (`timingSafeEqual`).
|
||
- Ограничение попыток входа (Rate Limiting): 5 попыток за 15 минут на IP.
|
||
- Проверка Origin на state-changing запросах (CSRF).
|
||
- Проверка сигнатур файлов (magic bytes) для защиты от поддельных MIME-типов.
|
||
4. **Ускорение редактирования (Downscale)**:
|
||
- Автоматический даунскейл `sharp` до `EDIT_MAX_DIMENSION` (по умолчанию 1024 px) перед передачей в Codex с пропорциональным масштабированием области референса (`scaleRegion`). Сокращает время обработки больших фото в 1.5–2 раза.
|
||
5. **Мгновенные обновления статуса (SSE)**:
|
||
- Server-Sent Events (`GET /api/events`) передают смену статуса очереди и прогресс мгновенно.
|
||
- Автоматический фолбэк на опрос (`/api/jobs`) при сбоях сети или неподдерживаемых прокси.
|
||
6. **Клиентская часть на Vanilla ES-модулях**:
|
||
- Модули в `public/js/` загружаются напрямую браузером (`<script type="module">`) без этапа сборки.
|
||
|
||
---
|
||
|
||
## Как это работает
|
||
|
||
- **История версий**: каждая загрузка и результат (редактирование или апскейл) сохраняются в `data/history/` как отдельная версия. Доступно интерактивное дерево версий с ветвлением от исходника, каскадным удалением и свёрткой групп. Хранятся последние 50 версий (настраивается через `HISTORY_LIMIT`).
|
||
- **Редактирование**: сервер отправляет выбранную версию (и референс, если задан) с промптом в xAI Grok Imagine — `POST https://api.x.ai/v1/images/edits`, модель `grok-imagine-image-2.0`, изображения как base64 data URI, ответ `b64_json` (JPEG). Документация: https://docs.x.ai/docs/guides/image-generation. Прежний движок Codex CLI доступен через `EDIT_ENGINE=codex`; подсказки промптов по-прежнему генерирует Codex.
|
||
- **Увеличение разрешения**: 4× upscale через ComfyUI моделью `RealESRGAN_x4plus.safetensors` (или `4x_UltraSharp`).
|
||
- **Редактирование с референсом и областью**: можно задать референс — файл или версию из истории. На превью референса выделяется прямоугольник области; координаты передаются в промпт Codex.
|
||
- **Подсказки промптов**: мгновенные варианты из недавней истории и генерация новых вариантов через Codex.
|
||
|
||
---
|
||
|
||
## Переменные окружения
|
||
|
||
| Переменная | Тип | Дефолт | Описание |
|
||
|---|---|---|---|
|
||
| `PORT` | number | `3000` | Порт HTTP-сервера (кандидаты по умолчанию: 3000, 8080, 8090) |
|
||
| `COMFY_URL` | url | `http://192.168.31.240:8188` | Адрес домашнего сервера ComfyUI |
|
||
| `APP_USER` | string | `admin` | Логин владельца |
|
||
| `APP_PASSWORD` | string | *автогенерация* | Пароль владельца (если не задан — печатается в лог при старте) |
|
||
| `LOG_LEVEL` | string | `info` | Уровень логов (`debug`, `info`, `warn`, `error`) |
|
||
| `DATA_DIR` | path | `./data` | Каталог данных (сессии, журнал заданий, история) |
|
||
| `HISTORY_LIMIT` | number | `50` | Максимальное количество версий в истории |
|
||
| `MAX_UPLOAD_BYTES` | number | `26214400` (25 МБ) | Максимальный размер загружаемого файла |
|
||
| `EDIT_MAX_DIMENSION` | number | `1024` | Максимальный размер стороны при даунскейле перед Codex (0 — отключить) |
|
||
| `UPSCALE_MAX_DIMENSION`| number | `2048` | Предел входного разрешения перед 4× апскейлом |
|
||
| `CODEX_CLI_PATH` | string | `codex` / авто-поиск | Путь к бинарнику Codex CLI |
|
||
| `CODEX_EDIT_TIMEOUT_MS`| number | `420000` (7 мин) | Таймаут выполнения операции в Codex |
|
||
| `JOB_TIMEOUT_MS` | number | `300000` (5 мин) | Таймаут задачи в ComfyUI |
|
||
| `REQUEST_TIMEOUT_MS` | number | `15000` (15 с) | Таймаут сетевых HTTP-запросов |
|
||
| `SESSION_TTL_MS` | number | `604800000` (7 дней) | Срок жизни cookie-сессии (скользящий) |
|
||
| `SESSION_PERSIST` | bool | `true` | Сохранение сессий между перезапусками сервера |
|
||
| `CODEX_CONCURRENCY` | number | `1` | Число параллельных потоков Codex |
|
||
| `UPSCALE_CONCURRENCY` | number | `2` | Число параллельных потоков ComfyUI |
|
||
| `MAX_QUEUED_JOBS` | number | `5` | Максимальный размер очереди на группу |
|
||
| `SSE_ENABLED` | bool | `true` | Включение Server-Sent Events |
|
||
| `TRUST_PROXY` | bool | `false` | Доверие заголовкам прокси (`X-Forwarded-For` для rate-limiting) |
|
||
| `EDIT_ENGINE` | string | `grok` | Движок редактирования: `grok` (xAI Imagine API) или `codex` (Codex CLI) |
|
||
| `XAI_API_KEY` | string | — | API-ключ xAI (обязателен для `EDIT_ENGINE=grok`; в логи не выводится) |
|
||
| `XAI_BASE_URL` | string | `https://api.x.ai/v1` | Базовый URL xAI API |
|
||
| `XAI_IMAGE_MODEL` | string | `grok-imagine-image-2.0` | Модель редактирования изображений |
|
||
| `XAI_TIMEOUT_MS` | number | `180000` (3 мин) | Таймаут запроса к xAI |
|
||
|
||
---
|
||
|
||
## Запуск и разработка
|
||
|
||
### Локальный запуск
|
||
|
||
```powershell
|
||
# Установка зависимостей
|
||
npm install
|
||
|
||
# Запуск тестов
|
||
npm test
|
||
|
||
# Запуск приложения
|
||
npm start
|
||
|
||
# Запуск в режиме разработки с автоперезагрузкой
|
||
npm run start:dev
|
||
```
|
||
|
||
При первом запуске без `APP_PASSWORD` в консоль выводится баннер со случайным сгенерированным паролем.
|
||
|
||
---
|
||
|
||
## Авторизация и безопасность
|
||
|
||
Редактор защищён cookie-сессией `kadr_session` (`HttpOnly`, `SameSite=Lax`):
|
||
- Публичные маршруты: `GET /api/health`, `POST /api/login`, `GET /api/me`, `POST /api/logout`.
|
||
- Все остальные маршруты `/api/*` требуют авторизации (`401 Unauthorized`).
|
||
- Защита от перебора: после 5 неудачных попыток входа IP блокируется на 15 минут.
|
||
- Пароль проверяется функцией scrypt (`crypto.scrypt`) с защитой от атак по времени (`timingSafeEqual`).
|
||
|
||
---
|
||
|
||
## Логирование и диагностика
|
||
|
||
Все логи формируются в структурированном формате JSON-lines с обязательным полем `ts`, `level`, `msg` и `requestId`:
|
||
|
||
```json
|
||
{"ts":"2026-08-27T12:00:00.000Z","level":"info","msg":"HTTP запрос завершён","requestId":"req_a1b2c3d4","method":"GET","url":"/api/history","status":200,"durationMs":3}
|
||
```
|
||
|
||
Пароли, сессионные токены и персональные секреты автоматически маскируются (`[REDACTED]`).
|
||
|
||
---
|
||
|
||
## Docker и развёртывание
|
||
|
||
### Локальный Docker
|
||
|
||
```powershell
|
||
docker compose up -d --build
|
||
```
|
||
|
||
### Развёртывание в Portainer через Gitea Actions
|
||
|
||
1. Workflow `.gitea/workflows/test.yml` автоматически запускает автотесты при push в `main`.
|
||
2. Workflow `.gitea/workflows/build.yml` собирает образ на базе `node:22-bookworm-slim` и пушит в Gitea Registry.
|
||
3. Workflow `.gitea/workflows/deploy.yml` обновляет стек в Portainer с томами:
|
||
- `kadr-data:/app/data` — данные v2 (сессии, журнал, история);
|
||
- `kadr-history:/app/history` — старый том истории (автоматически мигрируется при первом старте v2);
|
||
- `/root/.codex:/root/.codex` — конфигурация и авторизация Codex.
|