Closes #578. Rewrites the user file library save-mode description in the English database.md and architecture.md guides (independent-new by default, parent-linked on overwrite) and updates all 20 translated copies of each, with i18n_source_hash re-stamped so the parity gate stays green.
15 KiB
description, i18n_output_hash, i18n_source_hash, i18n_provenance
| description | i18n_output_hash | i18n_source_hash | i18n_provenance |
|---|---|---|---|
| Структура монорепозитория, архитектура приложений и пакетов, жизненный цикл запроса и потребление ресурсов SnapOtter. | c47f9f3041c4 | a53946e760b0 | human |
Архитектура
SnapOtter представляет собой монорепозиторий, управляемый с помощью рабочих пространств pnpm и Turborepo. Он развёртывается как стек из 3 контейнеров Docker Compose: образ приложения SnapOtter, PostgreSQL 17 и Redis 8.
Структура проекта
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
Пакеты
@snapotter/image-engine
Основная библиотека обработки изображений, построенная на Sharp. Она выполняет все операции без 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 истощается до того, как его генерация будет утилизирована.
Модели не загружаются заранее. Каждый скрипт инструмента загружает веса своей модели с диска в момент запроса и освобождает их после завершения запроса. Полный профиль памяти см. в разделе Потребление ресурсов.
Поддерживаемые операции: удаление фона (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
Общие типы TypeScript, константы (такие как APP_VERSION и определения инструментов) и строки перевода i18n, используемые как фронтендом, так и бэкендом.
Приложения
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)
Одностраничное приложение на React 19, собранное с помощью Vite. Использует Zustand для управления состоянием, Tailwind CSS v4 для стилизации и Lucide для значков. Обменивается данными с API по REST и SSE (для отслеживания прогресса).
В число страниц входят: рабочее пространство инструментов, страница Files для управления постоянными загрузками и результатами, конструктор автоматизации/конвейеров и панель настроек администратора.
Собранный фронтенд отдаётся бэкендом Fastify в продакшене, поэтому в контейнере Docker нет отдельного веб-сервера.
Docs (apps/docs)
Этот сайт на VitePress. Развёртывается на Cloudflare Pages автоматически при пуше в main.
Как проходит запрос
- Пользователь выбирает инструмент в веб-интерфейсе и загружает файл.
- Фронтенд отправляет multipart-POST на
/api/v1/tools/:section/:toolIdс файлом и настройками. - Маршрут API проверяет входные данные с помощью Zod, затем запускает обработку.
- Для стандартных инструментов задача ставится в соответствующий пул BullMQ (image, media или docs в зависимости от модальности). Внутрипроцессный воркер BullMQ автоматически ориентирует изображение на основе метаданных EXIF, выполняет функцию обработки инструмента и возвращает результат.
- Для большинства инструментов ИИ мост TypeScript отправляет запрос на постоянный Python dispatcher. Вместо этого быстрый OCR вызывает Tesseract, а точный OCR запускает закрепленный исполняемый файл из активного неизменяемого поколения OCR. Запрошенный уровень OCR фиксируется при входе и никогда не изменяется автоматически во время выполнения.
- Прогресс задачи сохраняется в таблицу
jobsв PostgreSQL, поэтому состояние переживает перезапуски контейнера. Обновления в реальном времени доставляются через SSE по адресу/api/v1/jobs/:jobId/progress. - API возвращает
jobIdиdownloadUrl. Пользователь скачивает обработанный файл из/api/v1/download/:jobId/:filename.
Для конвейеров API подаёт вывод каждого шага как входные данные для следующего, выполняя их последовательно.
Для пакетной обработки API использует потоки BullMQ с дочерними задачами на каждый шаг и возвращает ZIP-файл со всеми обработанными файлами.
Потребление ресурсов
SnapOtter спроектирован для низкого потребления памяти в простое. Ничего не загружается заранее и не держится «прогретым» при запуске.
В простое
Процесс Node.js/Fastify, PostgreSQL и Redis запущены. Типичное потребление ОЗУ в простое составляет ~200-300 МБ по всем трём контейнерам (процесс Node.js, Postgres и Redis). Нет процесса Python, нет весов моделей в памяти.
Что запускается и когда
| Компонент | Запускается когда | Память во время активности |
|---|---|---|
| Сервер Fastify + Postgres + Redis | Запуск контейнера | ~200-300 МБ суммарно |
| Воркеры BullMQ | Запуск контейнера (внутрипроцессно) | Один воркер на пул (image, media, ai, docs, system) |
| Диспетчер Python | Первый запрос AI-инструмента | Интерпретатор Python + предварительно импортированные библиотеки (PIL, NumPy, MediaPipe, rembg), без весов моделей |
| Веса AI-моделей | Во время запроса конкретного инструмента | Загружаются с диска, освобождаются по завершении запроса |
Загрузка моделей
Все файлы весов моделей (суммарно несколько ГБ) постоянно находятся на диске в /opt/models/. Каждый скрипт AI-инструмента загружает в память только свою(-и) модель(-и) на время запроса, затем освобождает их. Некоторые скрипты явно вызывают del model и torch.cuda.empty_cache() после инференса, чтобы память возвращалась немедленно.
Между запросами кэша моделей нет. Запуск одного и того же AI-инструмента подряд перезагружает модель каждый раз. Это удерживает память в простое около нуля ценой задержки на загрузку модели при каждом AI-запросе.
Холодный старт первого AI-запроса
Диспетчер Python не запущен при старте контейнера. Первый AI-запрос запускает две вещи параллельно: диспетчер начинает прогреваться в фоне, а сам запрос откатывается к разовому порождению подпроцесса Python. Как только диспетчер сигнализирует о готовности, все последующие AI-запросы используют его напрямую и обходят затраты на порождение подпроцесса.