This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

Architektura

SnapOtter jest monorepozytorium zarządzanym za pomocą przestrzeni roboczych pnpm i Turborepo. Wdraża się jako 3-kontenerowy stos Docker Compose: obraz aplikacji SnapOtter, PostgreSQL 17 i Redis 8.

Struktura projektu

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

Pakiety

@snapotter/image-engine

Podstawowa biblioteka przetwarzania obrazów zbudowana na Sharp. Obsługuje wszystkie operacje niezwiązane z AI: zmianę rozmiaru, kadrowanie, obrót, odbicie, konwersję, kompresję, usuwanie metadanych oraz korekty kolorów (jasność, kontrast, nasycenie, skala szarości, sepia, inwersja, kanały kolorów).

Ten pakiet nie ma zależności sieciowych i działa w całości w procesie.

@snapotter/ai

Warstwa pomostowa, która wywołuje skrypty Pythona dla operacji ML. Przy pierwszym użyciu most uruchamia trwały proces dyspozytora Pythona, który wstępnie importuje ciężkie biblioteki (PIL, NumPy, MediaPipe, rembg), dzięki czemu kolejne wywołania AI pomijają narzut związany z importem. Jeśli dyspozytor nie jest jeszcze gotowy, most awaryjnie uruchamia świeży podproces Pythona dla każdego żądania.

Modele nie są wstępnie ładowane. Każdy skrypt narzędzia ładuje swoje wagi modelu z dysku w momencie żądania i odrzuca je po zakończeniu żądania. Zobacz Zapotrzebowanie na zasoby, aby poznać pełny profil pamięci.

Obsługiwane operacje: usuwanie tła (rembg/BiRefNet), skalowanie w górę (RealESRGAN), rozmywanie twarzy (MediaPipe), poprawianie twarzy (GFPGAN/CodeFormer), wymazywanie obiektów (LaMa ONNX), OCR (PaddleOCR/Tesseract), koloryzacja (DDColor), usuwanie szumów, usuwanie efektu czerwonych oczu, renowacja zdjęć, generowanie zdjęć paszportowych, naprawa przezroczystości (matowanie BiRefNet HR) oraz zmiana rozmiaru z uwzględnieniem treści (binarka Go caire).

Skrypty Pythona znajdują się w packages/ai/python/. Obraz Docker wstępnie pobiera wszystkie wagi modeli podczas budowania, dzięki czemu kontener działa w pełni offline.

@snapotter/shared

Współdzielone typy TypeScript, stałe (takie jak APP_VERSION i definicje narzędzi) oraz ciągi tłumaczeń i18n używane zarówno przez frontend, jak i backend.

Aplikacje

API (apps/api)

Serwer Fastify v5 udostępniający 241 tras narzędzi w pięciu modalnościach (image, video, audio, PDF, file), który obsługuje:

  • Przesyłanie plików, zarządzanie tymczasową przestrzenią roboczą oraz trwałe przechowywanie plików
  • Bibliotekę plików użytkownika z łańcuchami wersji (tabela user_files) - każdy przetworzony wynik odsyła z powrotem do swojego pliku źródłowego i zapisuje, które narzędzie zostało zastosowane, z automatycznie generowanymi miniaturami dla strony Files
  • Wykonywanie narzędzi (kieruje każde żądanie narzędzia do silnika obrazów lub mostu AI)
  • Orkiestrację potoków (sekwencyjne łączenie wielu narzędzi w łańcuch)
  • Przetwarzanie wsadowe z kontrolą współbieżności za pomocą kolejek zadań BullMQ (pule: image, media, ai, docs, system)
  • Uwierzytelnianie użytkowników, RBAC (role admin/user z pełnym zestawem uprawnień), zarządzanie kluczami API oraz ograniczanie liczby żądań
  • Zarządzanie zespołami - CRUD tylko dla administratorów; użytkownicy są przypisywani do zespołu za pomocą pola team w swoim profilu
  • Ustawienia w czasie działania - magazyn klucz-wartość w tabeli settings, który steruje disabledTools, enableExperimentalTools, loginAttemptLimit i innymi operacyjnymi pokrętłami bez ponownego wdrażania
  • Niestandardowy branding i preferencje w czasie działania poprzez ustawienia oparte na bazie danych
  • Dokumentację Scalar/OpenAPI pod adresem /api/docs
  • Serwowanie zbudowanego frontendu jako SPA w środowisku produkcyjnym

Kluczowe zależności: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod do walidacji.

Serwer obsługuje płynne zamykanie na sygnał SIGTERM/SIGINT: opróżnia połączenia HTTP, zatrzymuje procesy robocze BullMQ, wyłącza dyspozytora Pythona i zamyka połączenie z bazą danych.

Web (apps/web)

Jednostronicowa aplikacja React 19 zbudowana za pomocą Vite. Używa Zustand do zarządzania stanem, Tailwind CSS v4 do stylizacji oraz Lucide do ikon. Komunikuje się z API przez REST i SSE (do śledzenia postępu).

Strony obejmują przestrzeń roboczą narzędzia, stronę Files do zarządzania trwałymi przesłaniami i wynikami, kreator automatyzacji/potoków oraz panel ustawień administratora.

Zbudowany frontend jest serwowany przez backend Fastify w środowisku produkcyjnym, więc w kontenerze Docker nie ma osobnego serwera WWW.

Docs (apps/docs)

Ta witryna VitePress. Wdrażana automatycznie do Cloudflare Pages przy wypchnięciu do main.

Jak przebiega żądanie

  1. Użytkownik wybiera narzędzie w interfejsie WWW i przesyła plik.
  2. Frontend wysyła wieloczęściowe żądanie POST do /api/v1/tools/:section/:toolId z plikiem i ustawieniami.
  3. Trasa API waliduje dane wejściowe za pomocą Zod, a następnie rozdziela przetwarzanie.
  4. W przypadku standardowych narzędzi zadanie jest kolejkowane do odpowiedniej puli BullMQ (image, media lub docs w zależności od modalności). Proces roboczy BullMQ działający w procesie automatycznie orientuje obraz na podstawie metadanych EXIF, uruchamia funkcję przetwarzającą narzędzia i zwraca wynik.
  5. W przypadku narzędzi AI most TypeScript wysyła żądanie do trwałego dyspozytora Pythona (lub awaryjnie uruchamia świeży podproces), czeka na jego zakończenie i odczytuje plik wyjściowy.
  6. Postęp zadania jest utrwalany w tabeli jobs w PostgreSQL, więc stan przetrwa ponowne uruchomienia kontenera. Aktualizacje w czasie rzeczywistym są dostarczane przez SSE pod adresem /api/v1/jobs/:jobId/progress.
  7. API zwraca jobId i downloadUrl. Użytkownik pobiera przetworzony plik z /api/v1/download/:jobId/:filename.

W przypadku potoków API podaje wynik każdego kroku jako dane wejściowe do następnego, uruchamiając je sekwencyjnie.

W przypadku przetwarzania wsadowego API używa przepływów BullMQ z zadaniami podrzędnymi dla poszczególnych kroków i zwraca plik ZIP ze wszystkimi przetworzonymi plikami.

Zapotrzebowanie na zasoby

SnapOtter został zaprojektowany z myślą o niskim zużyciu pamięci w stanie spoczynku. Nic nie jest wstępnie ładowane ani utrzymywane w gotowości przy starcie.

W stanie spoczynku

Proces Node.js/Fastify, PostgreSQL i Redis są uruchomione. Typowa pamięć RAM w stanie spoczynku wynosi ~200-300 MB we wszystkich trzech kontenerach (proces Node.js, Postgres i Redis). Brak procesu Pythona, brak wag modeli w pamięci.

Co się uruchamia i kiedy

KomponentUruchamia się, gdyPamięć w trakcie działania
Serwer Fastify + Postgres + RedisUruchomienie kontenera~200-300 MB łącznie
Procesy robocze BullMQUruchomienie kontenera (w procesie)Jeden proces roboczy na pulę (image, media, ai, docs, system)
Dyspozytor PythonaPierwsze żądanie narzędzia AIInterpreter Pythona + wstępnie zaimportowane biblioteki (PIL, NumPy, MediaPipe, rembg) - bez wag modeli
Wagi modeli AIW trakcie żądania konkretnego narzędziaŁadowane z dysku, zwalniane po zakończeniu żądania

Ładowanie modeli

Wszystkie pliki wag modeli (łącznie kilka GB) znajdują się na dysku w /opt/models/ przez cały czas. Każdy skrypt narzędzia AI ładuje do pamięci tylko własne modele na czas trwania żądania, po czym je zwalnia. Niektóre skrypty jawnie wywołują del model i torch.cuda.empty_cache() po inferencji, aby zapewnić natychmiastowy zwrot pamięci.

Między żądaniami nie ma pamięci podręcznej modeli. Uruchamianie tego samego narzędzia AI jedno po drugim ładuje model za każdym razem. Utrzymuje to pamięć w stanie spoczynku bliską zeru kosztem opóźnienia związanego z ładowaniem modelu przy każdym żądaniu AI.

Zimny start pierwszego żądania AI

Dyspozytor Pythona nie jest uruchomiony, gdy kontener startuje. Pierwsze żądanie AI wyzwala równolegle dwie rzeczy: dyspozytor zaczyna się rozgrzewać w tle, a samo żądanie awaryjnie korzysta z jednorazowego uruchomienia podprocesu Pythona. Gdy dyspozytor zasygnalizuje gotowość, wszystkie kolejne żądania AI używają go bezpośrednio i pomijają koszt uruchamiania podprocesu.