mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
* fix: make OCR portable and reliable * fix: harden OCR installation portability * fix: pin OCR partials across downloads * fix: make OCR execution reliably asynchronous * fix: harden OCR portability and docs routes * fix: preserve decoder and docs safeguards
124 lines
10 KiB
Markdown
124 lines
10 KiB
Markdown
---
|
|
description: "Struktura monorepozytorium, architektura aplikacji i pakietów, cykl życia żądania oraz zapotrzebowanie na zasoby w SnapOtter."
|
|
i18n_output_hash: 571d8852f6c2
|
|
i18n_source_hash: 733cb3c10884
|
|
i18n_provenance: human
|
|
---
|
|
|
|
# Architektura {#architecture}
|
|
|
|
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 {#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
|
|
```
|
|
|
|
## Pakiety {#packages}
|
|
|
|
### `@snapotter/image-engine` {#snapotter-image-engine}
|
|
|
|
Podstawowa biblioteka przetwarzania obrazów zbudowana na [Sharp](https://sharp.pixelplumbing.com/). 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` {#snapotter-ai}
|
|
|
|
Warstwa pomostowa wywołująca środowiska wykonawcze natywne i Python ML. Większość narzędzi Python używa trwałego dispatcher, który wstępnie importuje duże biblioteki (PIL, NumPy, MediaPipe, rembg), więc kolejne wywołania pomijają obciążenie związane z importem. OCR jest izolowany od tego zmiennego środowiska współdzielonego: `fast` wywołuje natywny Tesseract, podczas gdy `balanced` i `best` używają dedykowanego, trwałego JSONL dispatcher przypiętego do aktywnej, niezmiennej generacji RapidOCR/ONNX. Każde żądanie zawiera generation lease. Aktywacja najpierw uruchamia smoke test na kandydacie, a następnie atomowo przełącza się na jego dispatcher. Poprzednie dispatcher drenuje, zanim zostanie wygenerowane, i zostanie usunięte.
|
|
|
|
**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](#resource-footprint), aby poznać pełny profil pamięci.
|
|
|
|
Obsługiwane operacje: usuwanie tła (rembg/BiRefNet), skalowanie (RealESRGAN), rozmycie twarzy (MediaPipe), ulepszanie twarzy (GFPGAN/CodeFormer), usuwanie obiektów (LaMa ONNX), OCR (Tesseract i RapidOCR z modelami PP-OCR ONNX), kolorowanie (DDColor), usuwanie szumu, efekt czerwonych oczu usuwanie, przywracanie zdjęć, generowanie zdjęć paszportowych, utrwalanie przezroczystości (matowanie BiRefNet HR) i zmiana rozmiaru z uwzględnieniem zawartości (Go Caire binary).
|
|
|
|
Skrypty Python są dostępne w `packages/ai/python/`. Duże opcjonalne pakiety modeli są instalowane na żądanie w trwałym woluminie `/data/ai`. Dokładny OCR wykorzystuje podpisane artefakty specyficzne dla platformy; wbudowana warstwa Tesseract nie wymaga pobierania pakietu modeli.
|
|
|
|
### `@snapotter/shared` {#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 {#applications}
|
|
|
|
### API (`apps/api`) {#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`) {#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`) {#docs-apps-docs}
|
|
|
|
Ta witryna VitePress. Wdrażana automatycznie do Cloudflare Pages przy wypchnięciu do `main`.
|
|
|
|
## Jak przebiega żądanie {#how-a-request-flows}
|
|
|
|
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 większości narzędzi AI most TypeScript wysyła żądanie do trwałego Python dispatcher. Zamiast tego szybki OCR wywołuje Tesseract, a dokładny OCR uruchamia przypięty plik wykonywalny z aktywnej, niezmiennej generacji OCR. Żądany poziom OCR jest ustalany na wejściu i nigdy nie jest zmieniany w trybie cichym podczas wykonywania.
|
|
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 {#resource-footprint}
|
|
|
|
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 {#at-idle}
|
|
|
|
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 {#what-starts-and-when}
|
|
|
|
| Komponent | Uruchamia się, gdy | Pamięć w trakcie działania |
|
|
|-----------|-------------|---------------------|
|
|
| Serwer Fastify + Postgres + Redis | Uruchomienie kontenera | ~200-300 MB łącznie |
|
|
| Procesy robocze BullMQ | Uruchomienie kontenera (w procesie) | Jeden proces roboczy na pulę (image, media, ai, docs, system) |
|
|
| Dyspozytor Pythona | Pierwsze żądanie narzędzia AI | Interpreter Pythona + wstępnie zaimportowane biblioteki (PIL, NumPy, MediaPipe, rembg) - bez wag modeli |
|
|
| Wagi modeli AI | W trakcie żądania konkretnego narzędzia | Ładowane z dysku, zwalniane po zakończeniu żądania |
|
|
|
|
### Ładowanie modeli {#model-loading}
|
|
|
|
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 {#first-ai-request-cold-start}
|
|
|
|
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.
|