mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
124 lines
14 KiB
Markdown
124 lines
14 KiB
Markdown
---
|
||||
|
|
description: "Структура монорепозиторію, архітектура застосунків і пакетів, життєвий цикл запиту та споживання ресурсів SnapOtter."
|
|||
|
|
i18n_source_hash: 9e8f80499a37
|
|||
|
|
i18n_provenance: human
|
|||
|
|
i18n_output_hash: 6a1de194bb8b
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Архітектура {#architecture}
|
|||
|
|
|
|||
|
|
SnapOtter є монорепозиторієм, що керується робочими просторами pnpm та Turborepo. Він розгортається як стек Docker Compose з 3 контейнерів: образ застосунку SnapOtter, PostgreSQL 17 і Redis 8.
|
|||
|
|
|
|||
|
|
## Структура проєкту {#project-structure}
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
snapotter/
|
|||
|
|
├── apps/
|
|||
|
|
│ ├── api/ # Fastify backend
|
|||
|
|
│ ├── web/ # React + Vite frontend
|
|||
|
|
│ └── docs/ # This VitePress site
|
|||
|
|
├── packages/
|
|||
|
|
│ ├── image-engine/ # Sharp-based image operations
|
|||
|
|
│ ├── media-engine/ # FFmpeg spawn + progress parsing
|
|||
|
|
│ ├── doc-engine/ # qpdf, LibreOffice, ghostscript wrappers
|
|||
|
|
│ ├── ai/ # Python AI model bridge
|
|||
|
|
│ └── shared/ # Types, constants, i18n
|
|||
|
|
└── docker/ # Dockerfile and Compose config
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Пакети {#packages}
|
|||
|
|
|
|||
|
|
### `@snapotter/image-engine` {#snapotter-image-engine}
|
|||
|
|
|
|||
|
|
Основна бібліотека обробки зображень, побудована на [Sharp](https://sharp.pixelplumbing.com/). Вона обробляє всі не-AI операції: зміну розміру, обрізку, обертання, віддзеркалення, конвертацію, стиснення, видалення метаданих та коригування кольорів (яскравість, контраст, насиченість, відтінки сірого, сепія, інверсія, канали кольору).
|
|||
|
|
|
|||
|
|
Цей пакет не має мережевих залежностей і працює повністю в процесі.
|
|||
|
|
|
|||
|
|
### `@snapotter/ai` {#snapotter-ai}
|
|||
|
|
|
|||
|
|
Проміжний шар, який викликає скрипти Python для ML-операцій. Під час першого використання цей шар запускає стійкий процес-диспетчер Python, який заздалегідь імпортує важкі бібліотеки (PIL, NumPy, MediaPipe, rembg), тож наступні AI-виклики пропускають накладні витрати на імпорт. Якщо диспетчер ще не готовий, шар повертається до породження свіжого підпроцесу Python на кожен запит.
|
|||
|
|
|
|||
|
|
**Моделі не завантажуються заздалегідь.** Скрипт кожного інструмента завантажує ваги своєї моделі з диска під час запиту і відкидає їх після завершення запиту. Дивіться [Споживання ресурсів](#resource-footprint) для повного профілю пам'яті.
|
|||
|
|
|
|||
|
|
Підтримувані операції: видалення фону (rembg/BiRefNet), збільшення роздільності (RealESRGAN), розмиття облич (MediaPipe), покращення облич (GFPGAN/CodeFormer), видалення об'єктів (LaMa ONNX), OCR (PaddleOCR/Tesseract), розфарбовування (DDColor), видалення шуму, видалення ефекту червоних очей, відновлення фото, генерація фото на паспорт, виправлення прозорості (BiRefNet HR-matting) та зміна розміру з урахуванням вмісту (двійковий файл Go caire).
|
|||
|
|
|
|||
|
|
Скрипти Python розташовані в `packages/ai/python/`. Образ Docker заздалегідь завантажує всі ваги моделей під час збірки, тож контейнер працює повністю офлайн.
|
|||
|
|
|
|||
|
|
### `@snapotter/shared` {#snapotter-shared}
|
|||
|
|
|
|||
|
|
Спільні типи TypeScript, константи (як-от `APP_VERSION` та визначення інструментів) і рядки перекладів i18n, які використовуються і фронтендом, і бекендом.
|
|||
|
|
|
|||
|
|
## Застосунки {#applications}
|
|||
|
|
|
|||
|
|
### API (`apps/api`) {#api-apps-api}
|
|||
|
|
|
|||
|
|
Сервер Fastify v5, що надає 241 маршрут інструментів у п'яти модальностях (image, video, audio, PDF, file) і обробляє:
|
|||
|
|
- Завантаження файлів, керування тимчасовим робочим простором та постійне зберігання файлів
|
|||
|
|
- Бібліотеку файлів користувача з ланцюжками версій (таблиця `user_files`) - кожен оброблений результат посилається назад на свій вихідний файл і фіксує, який інструмент було застосовано, з автоматично згенерованими мініатюрами для сторінки Files
|
|||
|
|
- Виконання інструментів (маршрутизує кожен запит інструмента до рушія зображень або мосту AI)
|
|||
|
|
- Оркестрацію конвеєрів (послідовне поєднання кількох інструментів)
|
|||
|
|
- Пакетну обробку з контролем паралельності через черги завдань BullMQ (пули: image, media, ai, docs, system)
|
|||
|
|
- Автентифікацію користувачів, RBAC (ролі admin/user з повним набором дозволів), керування ключами API та обмеження частоти запитів
|
|||
|
|
- Керування командами - CRUD лише для адміністраторів; користувачі призначаються до команди через поле `team` у своєму профілі
|
|||
|
|
- Налаштування середовища виконання - сховище «ключ-значення» в таблиці `settings`, яке керує `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit` та іншими операційними важелями без повторного розгортання
|
|||
|
|
- Власний брендинг та налаштування середовища виконання через параметри, що зберігаються в базі даних
|
|||
|
|
- Документацію Scalar/OpenAPI за адресою `/api/docs`
|
|||
|
|
- Обслуговування зібраного фронтенду як SPA у продакшні
|
|||
|
|
|
|||
|
|
Ключові залежності: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod для валідації.
|
|||
|
|
|
|||
|
|
Сервер обробляє коректне завершення роботи за SIGTERM/SIGINT: він осушує HTTP-з'єднання, зупиняє воркери BullMQ, вимикає диспетчер Python і закриває з'єднання з базою даних.
|
|||
|
|
|
|||
|
|
### Web (`apps/web`) {#web-apps-web}
|
|||
|
|
|
|||
|
|
Односторінковий застосунок на React 19, зібраний за допомогою Vite. Використовує Zustand для керування станом, Tailwind CSS v4 для стилізації та Lucide для іконок. Взаємодіє з API через REST та SSE (для відстеження прогресу).
|
|||
|
|
|
|||
|
|
Сторінки включають робочий простір інструментів, сторінку Files для керування постійними завантаженнями та результатами, конструктор автоматизації/конвеєрів та панель адміністративних налаштувань.
|
|||
|
|
|
|||
|
|
Зібраний фронтенд обслуговується бекендом Fastify у продакшні, тож окремого вебсервера в контейнері Docker немає.
|
|||
|
|
|
|||
|
|
### Docs (`apps/docs`) {#docs-apps-docs}
|
|||
|
|
|
|||
|
|
Цей сайт VitePress. Розгортається на Cloudflare Pages автоматично під час відправлення в `main`.
|
|||
|
|
|
|||
|
|
## Як проходить запит {#how-a-request-flows}
|
|||
|
|
|
|||
|
|
1. Користувач обирає інструмент у вебінтерфейсі та завантажує файл.
|
|||
|
|
2. Фронтенд надсилає multipart POST на `/api/v1/tools/:section/:toolId` з файлом і налаштуваннями.
|
|||
|
|
3. Маршрут API валідує вхідні дані за допомогою Zod, а потім розподіляє обробку.
|
|||
|
|
4. Для стандартних інструментів завдання ставиться в чергу до відповідного пулу BullMQ (image, media або docs залежно від модальності). Воркер BullMQ у процесі автоматично орієнтує зображення на основі метаданих EXIF, виконує функцію обробки інструмента й повертає результат.
|
|||
|
|
5. Для AI-інструментів міст TypeScript надсилає запит до стійкого диспетчера Python (або породжує свіжий підпроцес як запасний варіант), чекає його завершення та зчитує вихідний файл.
|
|||
|
|
6. Прогрес завдання зберігається в таблиці `jobs` у PostgreSQL, тож стан переживає перезапуски контейнера. Оновлення в реальному часі доставляються через SSE за адресою `/api/v1/jobs/:jobId/progress`.
|
|||
|
|
7. API повертає `jobId` та `downloadUrl`. Користувач завантажує оброблений файл з `/api/v1/download/:jobId/:filename`.
|
|||
|
|
|
|||
|
|
Для конвеєрів API подає вихід кожного кроку як вхід для наступного, виконуючи їх послідовно.
|
|||
|
|
|
|||
|
|
Для пакетної обробки API використовує потоки BullMQ з дочірніми завданнями для кожного кроку й повертає ZIP-файл з усіма обробленими файлами.
|
|||
|
|
|
|||
|
|
## Споживання ресурсів {#resource-footprint}
|
|||
|
|
|
|||
|
|
SnapOtter розроблений для низького споживання пам'яті в стані спокою. Нічого не завантажується заздалегідь і не тримається «розігрітим» під час запуску.
|
|||
|
|
|
|||
|
|
### У стані спокою {#at-idle}
|
|||
|
|
|
|||
|
|
Процес Node.js/Fastify, PostgreSQL і Redis працюють. Типове споживання RAM у стані спокою становить **~200-300 МБ** для всіх трьох контейнерів (процес Node.js, Postgres і Redis). Жодного процесу Python, жодних ваг моделей у пам'яті.
|
|||
|
|
|
|||
|
|
### Що запускається і коли {#what-starts-and-when}
|
|||
|
|
|
|||
|
|
| Компонент | Запускається коли | Пам'ять під час роботи |
|
|||
|
|
|-----------|-------------|---------------------|
|
|||
|
|
| Сервер Fastify + Postgres + Redis | Запуск контейнера | ~200-300 МБ загалом |
|
|||
|
|
| Воркери BullMQ | Запуск контейнера (у процесі) | Один воркер на пул (image, media, ai, docs, system) |
|
|||
|
|
| Диспетчер Python | Перший запит AI-інструмента | Інтерпретатор Python + заздалегідь імпортовані бібліотеки (PIL, NumPy, MediaPipe, rembg) - без ваг моделей |
|
|||
|
|
| Ваги моделей AI | Під час запиту конкретного інструмента | Завантажуються з диска, звільняються після завершення запиту |
|
|||
|
|
|
|||
|
|
### Завантаження моделей {#model-loading}
|
|||
|
|
|
|||
|
|
Усі файли ваг моделей (загалом кілька ГБ) весь час лежать на диску в `/opt/models/`. Скрипт кожного AI-інструмента завантажує в пам'ять лише свою модель (моделі) на час запиту, а потім звільняє їх. Деякі скрипти явно викликають `del model` і `torch.cuda.empty_cache()` після виведення, щоб пам'ять повернулася негайно.
|
|||
|
|
|
|||
|
|
Кешу моделей між запитами немає. Виконання того самого AI-інструмента підряд щоразу перезавантажує модель. Це утримує споживання пам'яті в стані спокою близьким до нуля ціною затримки на завантаження моделі під час кожного AI-запиту.
|
|||
|
|
|
|||
|
|
### Холодний старт першого AI-запиту {#first-ai-request-cold-start}
|
|||
|
|
|
|||
|
|
Диспетчер Python не працює під час запуску контейнера. Перший AI-запит запускає паралельно дві речі: диспетчер починає розігріватися у фоні, а сам запит повертається до одноразового породження підпроцесу Python. Щойно диспетчер сигналізує про готовність, усі наступні AI-запити використовують його безпосередньо й пропускають витрати на породження підпроцесу.
|