--- 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-запити використовують його безпосередньо й пропускають витрати на породження підпроцесу.