--- description: "Структура монорепозитория, архитектура приложений и пакетов, жизненный цикл запроса и потребление ресурсов SnapOtter." i18n_output_hash: c47f9f3041c4 i18n_source_hash: a53946e760b0 i18n_provenance: human --- # Архитектура {#architecture} SnapOtter представляет собой монорепозиторий, управляемый с помощью рабочих пространств pnpm и Turborepo. Он развёртывается как стек из 3 контейнеров Docker Compose: образ приложения 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 используют постоянный dispatcher, который предварительно импортирует тяжелые библиотеки (PIL, NumPy, MediaPipe, rembg), поэтому последующие вызовы пропускают накладные расходы на импорт. OCR изолирован от этой изменяемой общей среды: `fast` вызывает собственный Tesseract, а `balanced` и `best` используют выделенный постоянный JSONL dispatcher, прикрепленный к активному неизменяемому поколению RapidOCR/ONNX. Каждый запрос содержит generation lease. Активация сначала запускает smoke test на кандидате, затем атомарно переключается на его dispatcher. Предыдущий dispatcher истощается до того, как его генерация будет утилизирована. **Модели не загружаются заранее.** Каждый скрипт инструмента загружает веса своей модели с диска в момент запроса и освобождает их после завершения запроса. Полный профиль памяти см. в разделе [Потребление ресурсов](#resource-footprint). Поддерживаемые операции: удаление фона (rembg/BiRefNet), масштабирование (RealESRGAN), размытие лица (MediaPipe), улучшение лица (GFPGAN/CodeFormer), стирание объекта (LaMa ONNX), OCR (Tesseract и RapidOCR с моделями PP-OCR ONNX), раскрашивание (DDColor), удаление шума, удаление эффекта красных глаз, восстановление фотографий, создание фотографий на паспорт, исправление прозрачности (BiRefNet HR-matting) и изменение размера с учетом содержимого (двоичный файл Go caire). Скрипты Python находятся в `packages/ai/python/`. Большие дополнительные пакеты моделей устанавливаются по требованию в постоянный том `/data/ai`. В Accurate OCR используются подписанные артефакты, специфичные для платформы; встроенный уровень Tesseract не требует загрузки пакета моделей. ### `@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`): по умолчанию сохранённое изменение хранится как независимый новый файл или как связанная с родителем версия, когда вы перезаписываете оригинал. Она записывает, какие инструменты были применены (`toolChain`), и получает автоматически генерируемую миниатюру для страницы 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. Для большинства инструментов ИИ мост TypeScript отправляет запрос на постоянный Python dispatcher. Вместо этого быстрый OCR вызывает Tesseract, а точный OCR запускает закрепленный исполняемый файл из активного неизменяемого поколения OCR. Запрошенный уровень OCR фиксируется при входе и никогда не изменяется автоматически во время выполнения. 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 запущены. Типичное потребление ОЗУ в простое составляет **~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-запросы используют его напрямую и обходят затраты на порождение подпроцесса.