* 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
18 KiB
description, i18n_output_hash, i18n_source_hash, i18n_provenance
| description | i18n_output_hash | i18n_source_hash | i18n_provenance |
|---|---|---|---|
| โครงสร้าง monorepo, สถาปัตยกรรมของแอปและแพ็กเกจ, วงจรชีวิตของคำขอ และรอยเท้าทรัพยากรของ SnapOtter | d1d73eea741b | 733cb3c10884 | human |
Architecture
SnapOtter เป็น monorepo ที่จัดการด้วย pnpm workspaces และ Turborepo ปรับใช้เป็นสแตก Docker Compose 3 คอนเทนเนอร์: อิมเมจแอป SnapOtter, PostgreSQL 17 และ Redis 8
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
Packages
@snapotter/image-engine
ไลบรารีประมวลผลรูปภาพหลักที่สร้างบน Sharp จัดการการดำเนินการที่ไม่ใช่ AI ทั้งหมด: ปรับขนาด, ครอบตัด, หมุน, พลิก, แปลง, บีบอัด, ลบเมทาดาทา และปรับสี (ความสว่าง, ความเปรียบต่าง, ความอิ่มตัว, ขาวดำ, ซีเปีย, กลับสี, ช่องสี)
แพ็กเกจนี้ไม่มี dependency ของเครือข่ายและทำงานภายในกระบวนการทั้งหมด
@snapotter/ai
เลเยอร์บริดจ์ที่เรียกเนทีฟและรันไทม์ Python ML เครื่องมือ Python ส่วนใหญ่ใช้ dispatcher แบบถาวรที่นำเข้าไลบรารีขนาดใหญ่ล่วงหน้า (PIL, NumPy, MediaPipe, rembg) ดังนั้นการโทรครั้งต่อไปจะข้ามค่าใช้จ่ายในการนำเข้า OCR ถูกแยกออกจากสภาพแวดล้อมที่ใช้ร่วมกันที่ไม่แน่นอน: fast เรียกใช้ Tesseract ดั้งเดิม ในขณะที่ balanced และ best ใช้ JSONL dispatcher ถาวรโดยเฉพาะที่ปักหมุดไว้กับรุ่น RapidOCR/ONNX ที่ไม่เปลี่ยนรูปแบบที่ใช้งานอยู่ แต่ละคำขอจะมี generation lease การเปิดใช้งานจะรัน smoke test บนตัวเลือกแรก จากนั้นจึงสลับไปที่ dispatcher แบบอะตอมมิก dispatcher รุ่นก่อนหน้าจะระบายออกก่อนที่จะมีการรวบรวมขยะ
โมเดลไม่ได้ถูกโหลดล่วงหน้า สคริปต์ของแต่ละเครื่องมือโหลดน้ำหนักโมเดลจากดิสก์ ณ เวลาที่ขอ และทิ้งเมื่อคำขอเสร็จสิ้น ดู Resource footprint สำหรับโปรไฟล์หน่วยความจำทั้งหมด
การดำเนินการที่รองรับ: การลบพื้นหลัง (rembg/BiRefNet), การลดขนาด (RealESRGAN), การเบลอใบหน้า (MediaPipe), การปรับปรุงใบหน้า (GFPGAN/CodeFormer), การลบวัตถุ (LaMa ONNX), OCR (Tesseract และ RapidOCR พร้อมรุ่น PP-OCR ONNX), การปรับสี (DDColor), การกำจัดสัญญาณรบกวน, การลบตาแดง, การฟื้นฟูภาพถ่าย, การสร้างภาพถ่ายหนังสือเดินทาง การแก้ไขความโปร่งใส (BiRefNet HR-matting) และการปรับขนาดการรับรู้เนื้อหา (Go caire binary)
สคริปต์ Python ใช้งานจริงใน packages/ai/python/ แพ็กแบบจำลองเสริมขนาดใหญ่ได้รับการติดตั้งตามความต้องการในไดรฟ์ข้อมูล /data/ai แบบถาวร OCR ที่แม่นยำใช้สิ่งประดิษฐ์เฉพาะแพลตฟอร์มที่มีการลงนาม ระดับ Tesseract ในตัวไม่จำเป็นต้องดาวน์โหลดแพ็คโมเดล
@snapotter/shared
ประเภท TypeScript ที่ใช้ร่วมกัน, ค่าคงที่ (เช่น APP_VERSION และการกำหนดเครื่องมือ) และสตริงการแปล i18n ที่ใช้ทั้งส่วนหน้าและส่วนหลัง
Applications
API (apps/api)
เซิร์ฟเวอร์ Fastify v5 ที่เปิดเผยเส้นทางเครื่องมือ 241 เส้นทางครอบคลุมห้ารูปแบบ (image, video, audio, PDF, file) ที่จัดการ:
- การอัปโหลดไฟล์, การจัดการพื้นที่ทำงานชั่วคราว และที่จัดเก็บไฟล์แบบถาวร
- คลังไฟล์ผู้ใช้พร้อมห่วงโซ่เวอร์ชัน (ตาราง
user_files) - ผลลัพธ์ที่ประมวลผลแล้วแต่ละรายการเชื่อมโยงกลับไปยังไฟล์ต้นทางและบันทึกว่าใช้เครื่องมือใด พร้อมภาพขนาดย่อที่สร้างอัตโนมัติสำหรับหน้า Files - การเรียกใช้เครื่องมือ (กำหนดเส้นทางคำขอเครื่องมือแต่ละรายการไปยังเอนจินรูปภาพหรือบริดจ์ AI)
- การประสานงานไปป์ไลน์ (เชื่อมโยงเครื่องมือหลายตัวตามลำดับ)
- การประมวลผลเป็นชุดพร้อมการควบคุมการทำงานพร้อมกันผ่านคิวงาน BullMQ (pool: image, media, ai, docs, system)
- การยืนยันตัวตนผู้ใช้, RBAC (บทบาท admin/user พร้อมชุดสิทธิ์เต็ม), การจัดการคีย์ API และการจำกัดอัตรา
- การจัดการทีม - CRUD เฉพาะ admin ผู้ใช้ถูกกำหนดให้อยู่ในทีมผ่านฟิลด์
teamบนโปรไฟล์ของพวกเขา - การตั้งค่ารันไทม์ - ที่จัดเก็บแบบคีย์-ค่าในตาราง
settingsที่ควบคุมdisabledTools,enableExperimentalTools,loginAttemptLimitและปุ่มปรับการทำงานอื่น ๆ โดยไม่ต้องปรับใช้ใหม่ - การสร้างแบรนด์กำหนดเองและการตั้งค่ารันไทม์ผ่านการตั้งค่าที่รองรับด้วยฐานข้อมูล
- เอกสาร Scalar/OpenAPI ที่
/api/docs - การเสิร์ฟส่วนหน้าที่สร้างแล้วเป็น SPA ในการใช้งานจริง
Dependency หลัก: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod สำหรับการตรวจสอบ
เซิร์ฟเวอร์จัดการการปิดตัวอย่างสง่างามเมื่อได้รับ SIGTERM/SIGINT: มันระบายการเชื่อมต่อ HTTP, หยุด worker ของ BullMQ, ปิด Python dispatcher และปิดการเชื่อมต่อฐานข้อมูล
Web (apps/web)
แอปหน้าเดียว React 19 ที่สร้างด้วย Vite ใช้ Zustand สำหรับการจัดการสถานะ, Tailwind CSS v4 สำหรับการจัดสไตล์ และ Lucide สำหรับไอคอน สื่อสารกับ API ผ่าน REST และ SSE (สำหรับการติดตามความคืบหน้า)
หน้าต่าง ๆ รวมถึงพื้นที่ทำงานเครื่องมือ, หน้า Files สำหรับจัดการการอัปโหลดและผลลัพธ์แบบถาวร, ตัวสร้างระบบอัตโนมัติ/ไปป์ไลน์ และแผงการตั้งค่าผู้ดูแลระบบ
ส่วนหน้าที่สร้างแล้วถูกเสิร์ฟโดยส่วนหลัง Fastify ในการใช้งานจริง จึงไม่มีเว็บเซิร์ฟเวอร์แยกต่างหากในคอนเทนเนอร์ Docker
Docs (apps/docs)
ไซต์ VitePress นี้ ปรับใช้ไปยัง Cloudflare Pages โดยอัตโนมัติเมื่อ push ไปยัง main
How a request flows
- ผู้ใช้เลือกเครื่องมือใน UI เว็บและอัปโหลดไฟล์
- ส่วนหน้าส่ง multipart POST ไปยัง
/api/v1/tools/:section/:toolIdพร้อมไฟล์และการตั้งค่า - เส้นทาง API ตรวจสอบอินพุตด้วย Zod จากนั้นส่งต่อการประมวลผล
- สำหรับเครื่องมือมาตรฐาน งานจะถูกจัดคิวไปยัง BullMQ pool ที่เหมาะสม (image, media หรือ docs ตามรูปแบบ) worker BullMQ ในกระบวนการจะปรับทิศทางภาพอัตโนมัติตามเมทาดาทา EXIF, รันฟังก์ชันการประมวลผลของเครื่องมือ และส่งคืนผลลัพธ์
- สำหรับเครื่องมือ AI ส่วนใหญ่ สะพาน TypeScript จะส่งคำขอไปยัง Python dispatcher แบบถาวร Fast OCR จะเรียกใช้ Tesseract แทน และ OCR ที่แม่นยำจะเริ่มต้นการดำเนินการที่ปักหมุดไว้จากรุ่น OCR ที่ไม่เปลี่ยนรูปแบบที่ใช้งานอยู่ ระดับ OCR ที่ร้องขอได้รับการแก้ไขที่ทางเข้าและจะไม่มีการเปลี่ยนแปลงอย่างเงียบๆ ในระหว่างการดำเนินการ
- ความคืบหน้าของงานจะถูกบันทึกลงในตาราง
jobsใน PostgreSQL เพื่อให้สถานะอยู่รอดจากการรีสตาร์ตคอนเทนเนอร์ การอัปเดตแบบเรียลไทม์ถูกส่งผ่าน SSE ที่/api/v1/jobs/:jobId/progress - API ส่งคืน
jobIdและdownloadUrlผู้ใช้ดาวน์โหลดไฟล์ที่ประมวลผลแล้วจาก/api/v1/download/:jobId/:filename
สำหรับไปป์ไลน์ API จะป้อนเอาต์พุตของแต่ละขั้นตอนเป็นอินพุตให้ขั้นตอนถัดไป โดยรันตามลำดับ
สำหรับการประมวลผลเป็นชุด API ใช้ BullMQ flow พร้อม child job ต่อขั้นตอน และส่งคืนไฟล์ ZIP พร้อมไฟล์ที่ประมวลผลแล้วทั้งหมด
Resource footprint
SnapOtter ออกแบบมาเพื่อการใช้หน่วยความจำขณะว่างต่ำ ไม่มีสิ่งใดถูกโหลดล่วงหน้าหรืออุ่นไว้ตอนเริ่มต้น
At idle
กระบวนการ Node.js/Fastify, PostgreSQL และ Redis กำลังทำงาน RAM ขณะว่างโดยทั่วไปอยู่ที่ ~200-300 MB รวมทั้งสามคอนเทนเนอร์ (กระบวนการ Node.js, Postgres และ Redis) ไม่มีกระบวนการ Python ไม่มีน้ำหนักโมเดลในหน่วยความจำ
What starts, and when
| Component | Starts when | Memory while active |
|---|---|---|
| เซิร์ฟเวอร์ Fastify + Postgres + Redis | เมื่อคอนเทนเนอร์เริ่ม | ~200-300 MB รวม |
| worker BullMQ | เมื่อคอนเทนเนอร์เริ่ม (ในกระบวนการ) | หนึ่ง worker ต่อ pool (image, media, ai, docs, system) |
| Python dispatcher | คำขอเครื่องมือ AI ครั้งแรก | ตัวแปล Python + ไลบรารีที่นำเข้าล่วงหน้า (PIL, NumPy, MediaPipe, rembg) - ไม่มีน้ำหนักโมเดล |
| น้ำหนักโมเดล AI | ระหว่างคำขอของเครื่องมือนั้น ๆ | โหลดจากดิสก์ ปล่อยเมื่อคำขอเสร็จสิ้น |
Model loading
ไฟล์น้ำหนักโมเดลทั้งหมด (รวมหลาย GB) อยู่บนดิสก์ใน /opt/models/ ตลอดเวลา สคริปต์เครื่องมือ AI แต่ละตัวโหลดเฉพาะโมเดลของตัวเองเข้าหน่วยความจำตลอดระยะเวลาของคำขอ แล้วปล่อยออก บางสคริปต์เรียก del model และ torch.cuda.empty_cache() อย่างชัดเจนหลังการอนุมานเพื่อให้แน่ใจว่าหน่วยความจำถูกคืนทันที
ไม่มีแคชโมเดลระหว่างคำขอ การรันเครื่องมือ AI เดียวกันติดต่อกันจะโหลดโมเดลใหม่ทุกครั้ง สิ่งนี้ทำให้หน่วยความจำขณะว่างเข้าใกล้ศูนย์ โดยแลกกับความล่าช้าในการโหลดโมเดลในทุกคำขอ AI
First AI request cold start
Python dispatcher ไม่ทำงานเมื่อคอนเทนเนอร์เริ่มต้น คำขอ AI ครั้งแรกกระตุ้นสองสิ่งพร้อมกัน: dispatcher เริ่มอุ่นเครื่องในเบื้องหลัง และคำขอนั้นเองจะย้อนกลับไปสร้าง Python subprocess แบบครั้งเดียว เมื่อ dispatcher ส่งสัญญาณว่าพร้อม คำขอ AI ที่ตามมาทั้งหมดจะใช้มันโดยตรงและข้ามค่าใช้จ่ายในการสร้าง subprocess