* 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
9.2 KiB
description, i18n_output_hash, i18n_source_hash, i18n_provenance
| description | i18n_output_hash | i18n_source_hash | i18n_provenance |
|---|---|---|---|
| Monorepo-struktur, app- och paketarkitektur, förfrågningslivscykel och resursavtryck för SnapOtter. | bafad20476af | 733cb3c10884 | human |
Arkitektur
SnapOtter är ett monorepo som hanteras med pnpm workspaces och Turborepo. Det distribueras som en Docker Compose-stack med 3 containrar: SnapOtter-appavbildningen, PostgreSQL 17 och Redis 8.
Projektstruktur
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
Paket
@snapotter/image-engine
Kärnbiblioteket för bildbehandling byggt på Sharp. Det hanterar alla icke-AI-operationer: storleksändring, beskärning, rotation, spegling, konvertering, komprimering, borttagning av metadata och färgjusteringar (ljusstyrka, kontrast, mättnad, gråskala, sepia, invertering, färgkanaler).
Detta paket har inga nätverksberoenden och körs helt in-process.
@snapotter/ai
Ett brygglager som anropar native och Python ML körtider. De flesta Python-verktyg använder en beständig dispatcher som förimporterar tunga bibliotek (PIL, NumPy, MediaPipe, rembg) så att efterföljande anrop hoppar över importkostnader. OCR är isolerad från den föränderliga delade miljön: fast anropar inbyggd Tesseract, medan balanced och best använder en dedikerad beständig JSONL dispatcher som är fäst vid den aktiva oföränderliga RapidOCR/ONNX-generationen. Varje begäran innehåller en generation lease. Aktivering kör först en smoke test på en kandidat och växlar sedan atomärt till dess dispatcher. Den tidigare dispatcher dräneras innan den genereras sopsamlas.
Modeller är inte förinlästa. Varje verktygsskript laddar sina modellvikter från disk vid förfrågningstillfället och kasserar dem när förfrågan är klar. Se Resursavtryck för den fullständiga minnesprofilen.
Operationer som stöds: bakgrundsborttagning (rembg/BiRefNet), uppskalning (RealESRGAN), ansiktsoskärpa (MediaPipe), ansiktsförbättring (GFPGAN/CodeFormer), objektradering (LaMa ONNX), OCR (Tesseract och RapidOCR med PP-OCR ONNX-modeller), färgläggning (DDColor), bullerborttagning, borttagning av röda ögon, foto restaurering, generering av passfoto, transparensfixering (BiRefNet HR-matta), och innehållsmedveten storleksändring (Go caire binär).
Python-skript live i packages/ai/python/. Stora valfria modellpaket installeras på begäran i den ihållande /data/ai-volymen. Exakt OCR använder signerade, plattformsspecifika artefakter; den inbyggda Tesseract-nivån kräver ingen nedladdning av modellpaket.
@snapotter/shared
Delade TypeScript-typer, konstanter (som APP_VERSION och verktygsdefinitioner) och i18n-översättningssträngar som används av både frontend och backend.
Applikationer
API (apps/api)
En Fastify v5-server som exponerar 241 verktygsrutter över fem modaliteter (image, video, audio, PDF, file) och som hanterar:
- Filuppladdningar, hantering av tillfällig arbetsyta och beständig fillagring
- Användarens filbibliotek med versionskedjor (
user_files-tabellen) - varje bearbetat resultat länkar tillbaka till sin källfil och registrerar vilket verktyg som applicerades, med autogenererade miniatyrbilder för Files-sidan - Verktygsexekvering (dirigerar varje verktygsförfrågan till bildmotorn eller AI-bryggan)
- Pipeline-orkestrering (kedjar samman flera verktyg sekventiellt)
- Batchbearbetning med samtidighetskontroll via BullMQ-jobbköer (pooler: image, media, ai, docs, system)
- Användarautentisering, RBAC (admin/user-roller med en fullständig behörighetsuppsättning), hantering av API-nycklar och hastighetsbegränsning
- Teamhantering - endast admin-CRUD; användare tilldelas ett team via
team-fältet på sin profil - Körtidsinställningar - ett nyckel-värde-lager i
settings-tabellen som styrdisabledTools,enableExperimentalTools,loginAttemptLimitoch andra driftsrattar utan att distribuera om - Anpassad varumärkesprofilering och körtidsinställningar via databasbaserade inställningar
- Scalar/OpenAPI-dokumentation på
/api/docs - Serverar den byggda frontenden som en SPA i produktion
Viktiga beroenden: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod för validering.
Servern hanterar smidig avstängning vid SIGTERM/SIGINT: den dränerar HTTP-anslutningar, stoppar BullMQ-workers, stänger av Python-dispatchern och stänger databasanslutningen.
Web (apps/web)
En React 19 single-page-app byggd med Vite. Använder Zustand för tillståndshantering, Tailwind CSS v4 för styling och Lucide för ikoner. Kommunicerar med API:et över REST och SSE (för förloppsspårning).
Sidorna inkluderar en verktygsarbetsyta, en Files-sida för hantering av beständiga uppladdningar och resultat, en automatisering/pipelinebyggare och en admininställningspanel.
Den byggda frontenden serveras av Fastify-backenden i produktion, så det finns ingen separat webbserver i Docker-containern.
Docs (apps/docs)
Denna VitePress-webbplats. Distribueras automatiskt till Cloudflare Pages vid push till main.
Hur en förfrågan flödar
- Användaren väljer ett verktyg i webbgränssnittet och laddar upp en fil.
- Frontenden skickar en multipart-POST till
/api/v1/tools/:section/:toolIdmed filen och inställningarna. - API-rutten validerar indata med Zod och dirigerar sedan bearbetningen.
- För standardverktyg köas jobbet till lämplig BullMQ-pool (image, media eller docs baserat på modalitet). Den in-process-körda BullMQ-workern orienterar bilden automatiskt baserat på EXIF-metadata, kör verktygets bearbetningsfunktion och returnerar resultatet.
- För de flesta AI-verktyg skickar TypeScript-bryggan en begäran till den beständiga Python dispatcher. Snabb OCR anropar istället Tesseract, och exakt OCR startar den fästa körbara filen från den aktiva oföränderliga OCR-generationen. Den begärda OCR-nivån är fixerad vid inträde och ändras aldrig tyst under exekvering.
- Jobbförlopp bevaras i
jobs-tabellen i PostgreSQL så att tillståndet överlever containeromstarter. Realtidsuppdateringar levereras via SSE på/api/v1/jobs/:jobId/progress. - API:et returnerar en
jobIdochdownloadUrl. Användaren laddar ner den bearbetade filen från/api/v1/download/:jobId/:filename.
För pipelines matar API:et utdata från varje steg som indata till nästa och kör dem sekventiellt.
För batchbearbetning använder API:et BullMQ-flöden med underjobb per steg och returnerar en ZIP-fil med alla bearbetade filer.
Resursavtryck
SnapOtter är utformat för låg minnesanvändning i viloläge. Ingenting förinläses eller hålls varmt vid start.
I viloläge
Node.js/Fastify-processen, PostgreSQL och Redis körs. Typiskt vilo-RAM är ~200-300 MB över alla tre containrar (Node.js-processen, Postgres och Redis). Ingen Python-process, inga modellvikter i minnet.
Vad som startar, och när
| Komponent | Startar när | Minne medan aktiv |
|---|---|---|
| Fastify-server + Postgres + Redis | Containerstart | ~200-300 MB totalt |
| BullMQ-workers | Containerstart (in-process) | En worker per pool (image, media, ai, docs, system) |
| Python-dispatcher | Första AI-verktygsförfrågan | Python-tolk + förimporterade bibliotek (PIL, NumPy, MediaPipe, rembg) - inga modellvikter |
| AI-modellvikter | Under det specifika verktygets förfrågan | Laddade från disk, frigjorda när förfrågan är klar |
Modellinläsning
Alla modellviktsfiler (totalt flera GB) ligger på disk i /opt/models/ hela tiden. Varje AI-verktygsskript laddar endast sin egen modell(er) i minnet under en förfrågans varaktighet och frigör dem sedan. Vissa skript anropar uttryckligen del model och torch.cuda.empty_cache() efter inferens för att säkerställa att minne returneras omedelbart.
Det finns ingen modellcache mellan förfrågningar. Att köra samma AI-verktyg direkt efter varandra laddar om modellen varje gång. Detta håller vilominnet nära noll till priset av en modellinläsningsfördröjning vid varje AI-förfrågan.
Kallstart vid första AI-förfrågan
Python-dispatchern körs inte när containern startar. Den första AI-förfrågan utlöser två saker parallellt: dispatchern börjar värmas upp i bakgrunden, och själva förfrågan faller tillbaka på en engångsskapad Python-subprocess. När dispatchern signalerar redo använder alla efterföljande AI-förfrågningar den direkt och hoppar över kostnaden för subprocess-skapande.