Search K
Architectuur
SnapOtter is een monorepo beheerd met pnpm-workspaces en Turborepo. Het wordt uitgerold als een Docker Compose-stack met 3 containers: de SnapOtter-app-image, PostgreSQL 17 en Redis 8.
Projectstructuur
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 configPackages
@snapotter/image-engine
De kernbibliotheek voor beeldverwerking, gebouwd op Sharp. Deze handelt alle niet-AI-bewerkingen af: vergroten/verkleinen, bijsnijden, roteren, spiegelen, converteren, comprimeren, metadata verwijderen en kleuraanpassingen (helderheid, contrast, verzadiging, grijstinten, sepia, inverteren, kleurkanalen).
Dit package heeft geen netwerkafhankelijkheden en draait volledig in-process.
@snapotter/ai
Een brugjlaag die Python-scripts aanroept voor ML-bewerkingen. Bij het eerste gebruik start de brug een persistent Python-dispatcherproces dat zware bibliotheken (PIL, NumPy, MediaPipe, rembg) vooraf importeert, zodat latere AI-aanroepen de importoverhead overslaan. Als de dispatcher nog niet klaar is, valt de brug terug op het opstarten van een verse Python-subprocess per verzoek.
Modellen worden niet vooraf geladen. Elk toolscript laadt zijn modelgewichten bij het verzoek van schijf en verwijdert ze zodra het verzoek klaar is. Zie Resourcegebruik voor het volledige geheugenprofiel.
Ondersteunde bewerkingen: achtergrondverwijdering (rembg/BiRefNet), upscaling (RealESRGAN), gezichtsvervaging (MediaPipe), gezichtsverbetering (GFPGAN/CodeFormer), objecten wissen (LaMa ONNX), OCR (PaddleOCR/Tesseract), inkleuren (DDColor), ruisverwijdering, rode-ogenverwijdering, fotorestauratie, generatie van pasfoto's, transparantie herstellen (BiRefNet HR-matting) en contentbewust vergroten/verkleinen (Go caire-binary).
De Python-scripts staan in packages/ai/python/. De Docker-image downloadt alle modelgewichten vooraf tijdens de build, zodat de container volledig offline werkt.
@snapotter/shared
Gedeelde TypeScript-types, constanten (zoals APP_VERSION en tooldefinities) en i18n-vertaalstrings die door zowel de frontend als de backend worden gebruikt.
Applicaties
API (apps/api)
Een Fastify v5-server die 241 toolroutes over vijf modaliteiten (image, video, audio, PDF, file) blootstelt en het volgende afhandelt:
- Bestandsuploads, beheer van tijdelijke werkruimte en persistente bestandsopslag
- Gebruikersbibliotheek voor bestanden met versieketens (
user_files-tabel) - elk verwerkt resultaat verwijst terug naar het bronbestand en registreert welke tool is toegepast, met automatisch gegenereerde miniaturen voor de Files-pagina - Tooluitvoering (routeert elk toolverzoek naar de image-engine of AI-brug)
- Pijplijnorkestratie (meerdere tools sequentieel aan elkaar koppelen)
- Batchverwerking met concurrentiebeheer via BullMQ-taakwachtrijen (pools: image, media, ai, docs, system)
- Gebruikersauthenticatie, RBAC (admin/user-rollen met een volledige set permissies), beheer van API-sleutels en rate limiting
- Teambeheer - alleen voor admins, CRUD; gebruikers worden aan een team toegewezen via het
team-veld op hun profiel - Runtime-instellingen - een key-value store in de
settings-tabel diedisabledTools,enableExperimentalTools,loginAttemptLimiten andere operationele knoppen aanstuurt zonder opnieuw uit te rollen - Aangepaste branding en runtime-voorkeuren via database-ondersteunde instellingen
- Scalar/OpenAPI-documentatie op
/api/docs - De gebouwde frontend als SPA serveren in productie
Belangrijkste dependencies: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod voor validatie.
De server handelt een nette afsluiting af bij SIGTERM/SIGINT: hij drainert HTTP-verbindingen, stopt BullMQ-workers, sluit de Python-dispatcher af en sluit de databaseverbinding.
Web (apps/web)
Een single-page app in React 19, gebouwd met Vite. Gebruikt Zustand voor statebeheer, Tailwind CSS v4 voor styling en Lucide voor iconen. Communiceert met de API via REST en SSE (voor voortgangsregistratie).
Pagina's omvatten een toolwerkruimte, een Files-pagina voor het beheren van persistente uploads en resultaten, een automatiserings-/pijplijnbouwer en een admin-instellingenpaneel.
De gebouwde frontend wordt in productie geserveerd door de Fastify-backend, dus er is geen aparte webserver in de Docker-container.
Docs (apps/docs)
Deze VitePress-site. Wordt automatisch uitgerold naar Cloudflare Pages bij een push naar main.
Hoe een verzoek verloopt
- De gebruiker kiest een tool in de web-UI en uploadt een bestand.
- De frontend stuurt een multipart POST naar
/api/v1/tools/:section/:toolIdmet het bestand en de instellingen. - De API-route valideert de invoer met Zod en start vervolgens de verwerking.
- Voor standaardtools wordt de taak in de juiste BullMQ-pool geplaatst (image, media of docs op basis van modaliteit). De in-process BullMQ-worker oriënteert de afbeelding automatisch op basis van EXIF-metadata, voert de procesfunctie van de tool uit en geeft het resultaat terug.
- Voor AI-tools stuurt de TypeScript-brug een verzoek naar de persistent Python-dispatcher (of start een verse subprocess als fallback), wacht tot deze klaar is en leest het uitvoerbestand.
- Taakvoortgang wordt vastgelegd in de
jobs-tabel in PostgreSQL, zodat de state herstarts van de container overleeft. Realtime-updates worden geleverd via SSE op/api/v1/jobs/:jobId/progress. - De API retourneert een
jobIdendownloadUrl. De gebruiker downloadt het verwerkte bestand vanaf/api/v1/download/:jobId/:filename.
Voor pijplijnen voert de API de uitvoer van elke stap als invoer aan de volgende, en draait ze sequentieel.
Voor batchverwerking gebruikt de API BullMQ-flows met per-stap onderliggende taken en retourneert een ZIP-bestand met alle verwerkte bestanden.
Resourcegebruik
SnapOtter is ontworpen voor laag geheugengebruik bij inactiviteit. Er wordt bij het opstarten niets vooraf geladen of warm gehouden.
Bij inactiviteit
Het Node.js/Fastify-proces, PostgreSQL en Redis draaien. Typisch RAM-gebruik bij inactiviteit is ~200-300 MB verdeeld over alle drie de containers (Node.js-proces, Postgres en Redis). Geen Python-proces, geen modelgewichten in het geheugen.
Wat er start, en wanneer
| Component | Start wanneer | Geheugen tijdens actief zijn |
|---|---|---|
| Fastify-server + Postgres + Redis | Bij het starten van de container | ~200-300 MB totaal |
| BullMQ-workers | Bij het starten van de container (in-process) | Eén worker per pool (image, media, ai, docs, system) |
| Python-dispatcher | Bij het eerste AI-toolverzoek | Python-interpreter + vooraf geïmporteerde bibliotheken (PIL, NumPy, MediaPipe, rembg) - geen modelgewichten |
| AI-modelgewichten | Tijdens het verzoek van de specifieke tool | Van schijf geladen, vrijgegeven wanneer het verzoek klaar is |
Modellen laden
Alle modelgewichtbestanden (samen enkele GB) staan te allen tijde op schijf in /opt/models/. Elk AI-toolscript laadt alleen zijn eigen model(len) in het geheugen voor de duur van een verzoek en geeft ze daarna vrij. Sommige scripts roepen expliciet del model en torch.cuda.empty_cache() aan na de inferentie om ervoor te zorgen dat het geheugen onmiddellijk wordt teruggegeven.
Er is geen modelcache tussen verzoeken. Dezelfde AI-tool achter elkaar draaien laadt het model telkens opnieuw. Dit houdt het geheugengebruik bij inactiviteit vrijwel op nul, ten koste van een laadvertraging voor het model bij elk AI-verzoek.
Cold start bij het eerste AI-verzoek
De Python-dispatcher draait niet wanneer de container start. Het eerste AI-verzoek zet twee dingen parallel in gang: de dispatcher begint op de achtergrond op te warmen, en het verzoek zelf valt terug op het opstarten van een eenmalige Python-subprocess. Zodra de dispatcher aangeeft klaar te zijn, gebruiken alle volgende AI-verzoeken deze rechtstreeks en slaan ze de kosten van het opstarten van een subprocess over.
