mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
124 lines
16 KiB
Markdown
124 lines
16 KiB
Markdown
---
|
|||
|
|
description: "โครงสร้าง monorepo, สถาปัตยกรรมของแอปและแพ็กเกจ, วงจรชีวิตของคำขอ และรอยเท้าทรัพยากรของ SnapOtter"
|
||
|
|
i18n_source_hash: 9e8f80499a37
|
||
|
|
i18n_provenance: human
|
||
|
|
i18n_output_hash: d1d73eea741b
|
||
|
|
---
|
||
|
|
|
||
|
|
# Architecture {#architecture}
|
||
|
|
|
||
|
|
SnapOtter เป็น monorepo ที่จัดการด้วย pnpm workspaces และ Turborepo ปรับใช้เป็นสแตก Docker Compose 3 คอนเทนเนอร์: อิมเมจแอป SnapOtter, PostgreSQL 17 และ Redis 8
|
||
|
|
|
||
|
|
## Project structure {#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 {#packages}
|
||
|
|
|
||
|
|
### `@snapotter/image-engine` {#snapotter-image-engine}
|
||
|
|
|
||
|
|
ไลบรารีประมวลผลรูปภาพหลักที่สร้างบน [Sharp](https://sharp.pixelplumbing.com/) จัดการการดำเนินการที่ไม่ใช่ AI ทั้งหมด: ปรับขนาด, ครอบตัด, หมุน, พลิก, แปลง, บีบอัด, ลบเมทาดาทา และปรับสี (ความสว่าง, ความเปรียบต่าง, ความอิ่มตัว, ขาวดำ, ซีเปีย, กลับสี, ช่องสี)
|
||
|
|
|
||
|
|
แพ็กเกจนี้ไม่มี dependency ของเครือข่ายและทำงานภายในกระบวนการทั้งหมด
|
||
|
|
|
||
|
|
### `@snapotter/ai` {#snapotter-ai}
|
||
|
|
|
||
|
|
เลเยอร์เชื่อมต่อที่เรียกสคริปต์ Python สำหรับการดำเนินการ ML เมื่อใช้งานครั้งแรก บริดจ์จะเริ่มกระบวนการ Python dispatcher ที่คงอยู่ ซึ่งนำเข้าไลบรารีหนัก (PIL, NumPy, MediaPipe, rembg) ไว้ล่วงหน้า เพื่อให้การเรียก AI ครั้งต่อ ๆ ไปข้ามค่าใช้จ่ายการนำเข้า หาก dispatcher ยังไม่พร้อม บริดจ์จะย้อนกลับไปสร้าง subprocess ของ Python ใหม่ต่อคำขอ
|
||
|
|
|
||
|
|
**โมเดลไม่ได้ถูกโหลดล่วงหน้า** สคริปต์ของแต่ละเครื่องมือโหลดน้ำหนักโมเดลจากดิสก์ ณ เวลาที่ขอ และทิ้งเมื่อคำขอเสร็จสิ้น ดู [Resource footprint](#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` {#snapotter-shared}
|
||
|
|
|
||
|
|
ประเภท TypeScript ที่ใช้ร่วมกัน, ค่าคงที่ (เช่น `APP_VERSION` และการกำหนดเครื่องมือ) และสตริงการแปล i18n ที่ใช้ทั้งส่วนหน้าและส่วนหลัง
|
||
|
|
|
||
|
|
## Applications {#applications}
|
||
|
|
|
||
|
|
### API (`apps/api`) {#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`) {#web-apps-web}
|
||
|
|
|
||
|
|
แอปหน้าเดียว React 19 ที่สร้างด้วย Vite ใช้ Zustand สำหรับการจัดการสถานะ, Tailwind CSS v4 สำหรับการจัดสไตล์ และ Lucide สำหรับไอคอน สื่อสารกับ API ผ่าน REST และ SSE (สำหรับการติดตามความคืบหน้า)
|
||
|
|
|
||
|
|
หน้าต่าง ๆ รวมถึงพื้นที่ทำงานเครื่องมือ, หน้า Files สำหรับจัดการการอัปโหลดและผลลัพธ์แบบถาวร, ตัวสร้างระบบอัตโนมัติ/ไปป์ไลน์ และแผงการตั้งค่าผู้ดูแลระบบ
|
||
|
|
|
||
|
|
ส่วนหน้าที่สร้างแล้วถูกเสิร์ฟโดยส่วนหลัง Fastify ในการใช้งานจริง จึงไม่มีเว็บเซิร์ฟเวอร์แยกต่างหากในคอนเทนเนอร์ Docker
|
||
|
|
|
||
|
|
### Docs (`apps/docs`) {#docs-apps-docs}
|
||
|
|
|
||
|
|
ไซต์ VitePress นี้ ปรับใช้ไปยัง Cloudflare Pages โดยอัตโนมัติเมื่อ push ไปยัง `main`
|
||
|
|
|
||
|
|
## How a request flows {#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 {#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}
|
||
|
|
|
||
|
|
| 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 {#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 dispatcher ไม่ทำงานเมื่อคอนเทนเนอร์เริ่มต้น คำขอ AI ครั้งแรกกระตุ้นสองสิ่งพร้อมกัน: dispatcher เริ่มอุ่นเครื่องในเบื้องหลัง และคำขอนั้นเองจะย้อนกลับไปสร้าง Python subprocess แบบครั้งเดียว เมื่อ dispatcher ส่งสัญญาณว่าพร้อม คำขอ AI ที่ตามมาทั้งหมดจะใช้มันโดยตรงและข้ามค่าใช้จ่ายในการสร้าง subprocess
|