SnapOtter एक मोनोरेपो है जिसे pnpm workspaces और Turborepo के साथ प्रबंधित किया जाता है। यह एक 3-कंटेनर Docker Compose स्टैक के रूप में परिनियोजित होता है: SnapOtter ऐप इमेज, PostgreSQL 17, और Redis 8।
[Sharp](https://sharp.pixelplumbing.com/) पर बनी मुख्य इमेज प्रोसेसिंग लाइब्रेरी। यह सभी नॉन-AI ऑपरेशन संभालती है: resize, crop, rotate, flip, convert, compress, strip metadata, और रंग समायोजन (brightness, contrast, saturation, grayscale, sepia, invert, color channels)।
इस पैकेज की कोई नेटवर्क डिपेंडेंसी नहीं है और यह पूरी तरह इन-प्रोसेस चलता है।
एक ब्रिज परत जो मूल और Python ML रनटाइम को कॉल करती है। अधिकांश Python उपकरण एक सतत dispatcher का उपयोग करते हैं जो भारी पुस्तकालयों (PIL, NumPy, MediaPipe, rembg) को पूर्व-आयात करता है ताकि बाद की कॉलें आयात ओवरहेड को छोड़ दें। OCR उस परिवर्तनशील साझा वातावरण से अलग है: `fast` मूल Tesseract को आमंत्रित करता है, जबकि `balanced` और `best` सक्रिय अपरिवर्तनीय RapidOCR/ONNX पीढ़ी पर पिन किए गए एक समर्पित लगातार JSONL dispatcher का उपयोग करते हैं। प्रत्येक अनुरोध में एक generation lease होता है। सक्रियण पहले एक उम्मीदवार पर smoke test चलाता है, फिर परमाणु रूप से इसके dispatcher पर स्विच करता है। पूर्ववर्ती dispatcher कचरा एकत्रित होने से पहले ही नष्ट हो जाता है।
**मॉडल पहले से लोड नहीं होते।** प्रत्येक टूल स्क्रिप्ट अनुरोध के समय डिस्क से अपने मॉडल वेट लोड करती है और अनुरोध समाप्त होने पर उन्हें त्याग देती है। पूर्ण मेमोरी प्रोफ़ाइल के लिए [Resource footprint](#resource-footprint) देखें।
Python स्क्रिप्ट `packages/ai/python/` में रहती हैं। बड़े वैकल्पिक मॉडल पैक लगातार `/data/ai` वॉल्यूम में मांग पर स्थापित किए जाते हैं। सटीक OCR हस्ताक्षरित, प्लेटफ़ॉर्म-विशिष्ट कलाकृतियों का उपयोग करता है; अंतर्निहित Tesseract टियर को किसी मॉडल-पैक डाउनलोड की आवश्यकता नहीं है।
साझा TypeScript प्रकार, स्थिरांक (जैसे `APP_VERSION` और टूल परिभाषाएँ), और i18n अनुवाद स्ट्रिंग्स जो फ़्रंटएंड और बैकएंड दोनों द्वारा उपयोग की जाती हैं।
## Applications {#applications}
### API (`apps/api`) {#api-apps-api}
एक Fastify v5 सर्वर जो पाँच मोडैलिटी (image, video, audio, PDF, file) में 241 टूल रूट प्रकट करता है, जो निम्न को संभालता है:
- फ़ाइल अपलोड, अस्थायी वर्कस्पेस प्रबंधन, और स्थायी फ़ाइल स्टोरेज
- संस्करण श्रृंखलाओं के साथ उपयोगकर्ता फ़ाइल लाइब्रेरी (`user_files` तालिका) - प्रत्येक प्रोसेस किया गया परिणाम अपनी स्रोत फ़ाइल से वापस लिंक होता है और रिकॉर्ड करता है कि कौन सा टूल लागू किया गया था, Files पेज के लिए स्वतः-जनरेट किए गए थंबनेल के साथ
- टूल निष्पादन (प्रत्येक टूल अनुरोध को इमेज इंजन या AI ब्रिज पर रूट करता है)
- पाइपलाइन ऑर्केस्ट्रेशन (कई टूल को क्रमिक रूप से श्रृंखलाबद्ध करना)
- BullMQ जॉब क्यू के माध्यम से समवर्तीता नियंत्रण के साथ बैच प्रोसेसिंग (पूल: image, media, ai, docs, system)
- उपयोगकर्ता प्रमाणीकरण, RBAC (पूर्ण अनुमति सेट के साथ admin/user भूमिकाएँ), API कुंजी प्रबंधन, और रेट लिमिटिंग
- Teams प्रबंधन - केवल-admin CRUD; उपयोगकर्ताओं को उनके प्रोफ़ाइल पर `team` फ़ील्ड के माध्यम से एक टीम को सौंपा जाता है
- रनटाइम सेटिंग्स - `settings` तालिका में एक key-value स्टोर जो पुनः परिनियोजन के बिना `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, और अन्य परिचालन नियंत्रण संभालता है
- डेटाबेस-समर्थित सेटिंग्स के माध्यम से कस्टम ब्रांडिंग और रनटाइम प्राथमिकताएँ
-`/api/docs` पर Scalar/OpenAPI दस्तावेज़ीकरण
- प्रोडक्शन में निर्मित फ़्रंटएंड को एक SPA के रूप में सर्व करना
मुख्य डिपेंडेंसी: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, सत्यापन के लिए Zod।
सर्वर SIGTERM/SIGINT पर सुशोभित शटडाउन संभालता है: यह HTTP कनेक्शन को खाली करता है, BullMQ वर्कर को रोकता है, Python डिस्पैचर को बंद करता है, और डेटाबेस कनेक्शन बंद करता है।
### Web (`apps/web`) {#web-apps-web}
Vite के साथ बनाया गया एक React 19 सिंगल-पेज ऐप। स्टेट प्रबंधन के लिए Zustand, स्टाइलिंग के लिए Tailwind CSS v4, और आइकन के लिए Lucide का उपयोग करता है। REST और SSE (प्रगति ट्रैकिंग के लिए) पर API के साथ संवाद करता है।
पेजों में एक टूल वर्कस्पेस, स्थायी अपलोड और परिणामों के प्रबंधन के लिए एक Files पेज, एक ऑटोमेशन/पाइपलाइन बिल्डर, और एक admin सेटिंग्स पैनल शामिल हैं।
निर्मित फ़्रंटएंड प्रोडक्शन में Fastify बैकएंड द्वारा सर्व किया जाता है, इसलिए Docker कंटेनर में कोई अलग वेब सर्वर नहीं है।
### Docs (`apps/docs`) {#docs-apps-docs}
यह VitePress साइट। `main` पर पुश होने पर स्वचालित रूप से Cloudflare Pages पर परिनियोजित।
## How a request flows {#how-a-request-flows}
1. उपयोगकर्ता वेब UI में एक टूल चुनता है और एक फ़ाइल अपलोड करता है।
2. फ़्रंटएंड फ़ाइल और सेटिंग्स के साथ `/api/v1/tools/:section/:toolId` पर एक multipart POST भेजता है।
3. API रूट इनपुट को Zod के साथ सत्यापित करता है, फिर प्रोसेसिंग डिस्पैच करता है।
4. मानक टूल के लिए, जॉब को उपयुक्त BullMQ पूल (मोडैलिटी के आधार पर image, media, या docs) में क्यू में डाला जाता है। इन-प्रोसेस BullMQ वर्कर EXIF मेटाडेटा के आधार पर इमेज को स्वतः-ओरिएंट करता है, टूल के प्रोसेस फ़ंक्शन को चलाता है, और परिणाम लौटाता है।
5. अधिकांश AI टूल के लिए, TypeScript ब्रिज लगातार Python dispatcher को एक अनुरोध भेजता है। तेज़ OCR इसके बजाय Tesseract को आमंत्रित करता है, और सटीक OCR सक्रिय अपरिवर्तनीय OCR पीढ़ी से पिन किए गए निष्पादन योग्य को प्रारंभ करता है। अनुरोधित OCR टियर प्रवेश पर तय किया गया है और निष्पादन के दौरान इसे कभी भी चुपचाप नहीं बदला जाता है।
6. जॉब प्रगति PostgreSQL में `jobs` तालिका में बनी रहती है ताकि स्टेट कंटेनर पुनरारंभ के दौरान बना रहे। वास्तविक समय अपडेट `/api/v1/jobs/:jobId/progress` पर SSE के माध्यम से वितरित किए जाते हैं।
7. API एक `jobId` और `downloadUrl` लौटाता है। उपयोगकर्ता `/api/v1/download/:jobId/:filename` से प्रोसेस की गई फ़ाइल डाउनलोड करता है।
पाइपलाइनों के लिए, API प्रत्येक चरण के आउटपुट को अगले के इनपुट के रूप में फ़ीड करता है, उन्हें क्रमिक रूप से चलाता है।
बैच प्रोसेसिंग के लिए, API प्रति-चरण चाइल्ड जॉब के साथ BullMQ फ़्लो का उपयोग करता है और सभी प्रोसेस की गई फ़ाइलों के साथ एक ZIP फ़ाइल लौटाता है।
## Resource footprint {#resource-footprint}
SnapOtter को कम निष्क्रिय मेमोरी उपयोग के लिए डिज़ाइन किया गया है। स्टार्टअप पर कुछ भी पहले से लोड या गर्म नहीं रखा जाता।
### At idle {#at-idle}
Node.js/Fastify प्रक्रिया, PostgreSQL, और Redis चल रहे हैं। सामान्य निष्क्रिय RAM तीनों कंटेनरों में **~200-300 MB** है (Node.js प्रक्रिया, Postgres, और Redis)। कोई Python प्रक्रिया नहीं, मेमोरी में कोई मॉडल वेट नहीं।
### What starts, and when {#what-starts-and-when}
| घटक | कब शुरू होता है | सक्रिय रहते समय मेमोरी |
|-----------|-------------|---------------------|
| Fastify सर्वर + Postgres + Redis | कंटेनर शुरू | कुल ~200-300 MB |
| BullMQ वर्कर | कंटेनर शुरू (इन-प्रोसेस) | प्रति पूल एक वर्कर (image, media, ai, docs, system) |
| Python डिस्पैचर | पहला AI टूल अनुरोध | Python इंटरप्रेटर + पूर्व-इम्पोर्ट की गई लाइब्रेरी (PIL, NumPy, MediaPipe, rembg) - कोई मॉडल वेट नहीं |
| AI मॉडल वेट | विशिष्ट टूल के अनुरोध के दौरान | डिस्क से लोड, अनुरोध समाप्त होने पर मुक्त |
### Model loading {#model-loading}
सभी मॉडल वेट फ़ाइलें (कुल मिलाकर कई GB) हर समय `/opt/models/` में डिस्क पर रहती हैं। प्रत्येक AI टूल स्क्रिप्ट केवल अपने स्वयं के मॉडल को एक अनुरोध की अवधि के लिए मेमोरी में लोड करती है, फिर उन्हें रिलीज़ कर देती है। कुछ स्क्रिप्ट इंफ़रेंस के बाद स्पष्ट रूप से `del model` और `torch.cuda.empty_cache()` को कॉल करती हैं ताकि यह सुनिश्चित हो सके कि मेमोरी तुरंत वापस दी जाए।
अनुरोधों के बीच कोई मॉडल कैश नहीं है। एक ही AI टूल को लगातार चलाने से हर बार मॉडल फिर से लोड होता है। यह हर AI अनुरोध पर एक मॉडल-लोड विलंब की कीमत पर निष्क्रिय मेमोरी को शून्य के करीब रखता है।
### First AI request cold start {#first-ai-request-cold-start}
जब कंटेनर शुरू होता है तो Python डिस्पैचर नहीं चल रहा होता। पहला AI अनुरोध समानांतर में दो चीज़ें ट्रिगर करता है: डिस्पैचर बैकग्राउंड में गर्म होना शुरू होता है, और अनुरोध स्वयं एक बार की Python सबप्रोसेस स्पॉन पर वापस लौट आता है। एक बार डिस्पैचर तैयार होने का संकेत देता है, तो बाद के सभी AI अनुरोध सीधे इसका उपयोग करते हैं और सबप्रोसेस स्पॉन लागत को छोड़ देते हैं।