This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

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) ไว้ล่วงหน้า เพื่อให้การเรียก AI ครั้งต่อ ๆ ไปข้ามค่าใช้จ่ายการนำเข้า หาก dispatcher ยังไม่พร้อม บริดจ์จะย้อนกลับไปสร้าง subprocess ของ Python ใหม่ต่อคำขอ

โมเดลไม่ได้ถูกโหลดล่วงหน้า สคริปต์ของแต่ละเครื่องมือโหลดน้ำหนักโมเดลจากดิสก์ ณ เวลาที่ขอ และทิ้งเมื่อคำขอเสร็จสิ้น ดู Resource footprint สำหรับโปรไฟล์หน่วยความจำทั้งหมด

การดำเนินการที่รองรับ: การลบพื้นหลัง (rembg/BiRefNet), การขยายภาพ (RealESRGAN), การเบลอใบหน้า (MediaPipe), การเพิ่มความคมชัดใบหน้า (GFPGAN/CodeFormer), การลบวัตถุ (LaMa ONNX), OCR (PaddleOCR/Tesseract), การลงสี (DDColor), การลบสัญญาณรบกวน, การลบตาแดง, การฟื้นฟูภาพถ่าย, การสร้างรูปถ่ายหนังสือเดินทาง, การแก้ความโปร่งใส (BiRefNet HR-matting) และการปรับขนาดที่คำนึงถึงเนื้อหา (ไบนารี Go caire)

สคริปต์ Python อยู่ใน packages/ai/python/ อิมเมจ Docker ดาวน์โหลดน้ำหนักโมเดลทั้งหมดล่วงหน้าระหว่างการสร้าง เพื่อให้คอนเทนเนอร์ทำงานได้อย่างสมบูรณ์แบบออฟไลน์

@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

  1. ผู้ใช้เลือกเครื่องมือใน UI เว็บและอัปโหลดไฟล์
  2. ส่วนหน้าส่ง multipart POST ไปยัง /api/v1/tools/:section/:toolId พร้อมไฟล์และการตั้งค่า
  3. เส้นทาง API ตรวจสอบอินพุตด้วย Zod จากนั้นส่งต่อการประมวลผล
  4. สำหรับเครื่องมือมาตรฐาน งานจะถูกจัดคิวไปยัง BullMQ pool ที่เหมาะสม (image, media หรือ docs ตามรูปแบบ) worker BullMQ ในกระบวนการจะปรับทิศทางภาพอัตโนมัติตามเมทาดาทา EXIF, รันฟังก์ชันการประมวลผลของเครื่องมือ และส่งคืนผลลัพธ์
  5. สำหรับเครื่องมือ AI บริดจ์ TypeScript จะส่งคำขอไปยัง Python dispatcher ที่คงอยู่ (หรือสร้าง subprocess ใหม่เป็นทางเลือกสำรอง) รอให้เสร็จ แล้วอ่านไฟล์เอาต์พุต
  6. ความคืบหน้าของงานจะถูกบันทึกลงในตาราง jobs ใน PostgreSQL เพื่อให้สถานะอยู่รอดจากการรีสตาร์ตคอนเทนเนอร์ การอัปเดตแบบเรียลไทม์ถูกส่งผ่าน SSE ที่ /api/v1/jobs/:jobId/progress
  7. 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

ComponentStarts whenMemory 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