Files
photo-editor/README.md
Coder bea2c61216 feat: редактирование фото через xAI Grok Imagine API (/v1/images/edits) вместо Codex CLI
EDIT_ENGINE=grok по умолчанию, XAI_API_KEY/XAI_BASE_URL/XAI_IMAGE_MODEL/XAI_TIMEOUT_MS;
Codex остаётся для подсказок и как fallback (EDIT_ENGINE=codex).
2026-11-04 19:01:50 +07:00

142 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Кадр — локальный 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.