Files
SnapOtter/apps/docs/th/guide/architecture.md
T

125 lines
18 KiB
Markdown
Raw Normal View History

---
description: "โครงสร้าง monorepo, สถาปัตยกรรมของแอปและแพ็กเกจ, วงจรชีวิตของคำขอ และรอยเท้าทรัพยากรของ SnapOtter"
i18n_source_hash: 50e076925c4b
i18n_provenance: human
i18n_output_hash: b3755300060a
i18n_hash_version: 2
---
# 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) ดังนั้นการโทรครั้งต่อไปจะข้ามค่าใช้จ่ายในการนำเข้า OCR ถูกแยกออกจากสภาพแวดล้อมที่ใช้ร่วมกันที่ไม่แน่นอน: `fast` เรียกใช้ Tesseract ดั้งเดิม ในขณะที่ `balanced` และ `best` ใช้ JSONL dispatcher ถาวรโดยเฉพาะที่ปักหมุดไว้กับรุ่น RapidOCR/ONNX ที่ไม่เปลี่ยนรูปแบบที่ใช้งานอยู่ แต่ละคำขอจะมี generation lease การเปิดใช้งานจะรัน smoke test บนตัวเลือกแรก จากนั้นจึงสลับไปที่ dispatcher แบบอะตอมมิก dispatcher รุ่นก่อนหน้าจะระบายออกก่อนที่จะมีการรวบรวมขยะ
**โมเดลไม่ได้ถูกโหลดล่วงหน้า** สคริปต์ของแต่ละเครื่องมือโหลดน้ำหนักโมเดลจากดิสก์ ณ เวลาที่ขอ และทิ้งเมื่อคำขอเสร็จสิ้น ดู [Resource footprint](#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` {#snapotter-shared}
ประเภท TypeScript ที่ใช้ร่วมกัน, ค่าคงที่ (เช่น `APP_VERSION` และการกำหนดเครื่องมือ) และสตริงการแปล i18n ที่ใช้ทั้งส่วนหน้าและส่วนหลัง
## Applications {#applications}
### API (`apps/api`) {#api-apps-api}
เซิร์ฟเวอร์ Fastify v5 ที่เปิดเผยเส้นทางเครื่องมือ 243 เส้นทางครอบคลุมห้ารูปแบบ (image, video, audio, PDF, file) ที่จัดการ:
- การอัปโหลดไฟล์, การจัดการพื้นที่ทำงานชั่วคราว และที่จัดเก็บไฟล์แบบถาวร
- คลังไฟล์ผู้ใช้ (ตาราง `user_files`): โดยค่าเริ่มต้น การแก้ไขที่บันทึกไว้จะถูกจัดเก็บเป็นไฟล์ใหม่อิสระ หรือเป็นเวอร์ชันที่เชื่อมโยงกับแถวแม่เมื่อคุณเขียนทับไฟล์ต้นฉบับ โดยจะบันทึกว่าใช้เครื่องมือใดบ้าง (`toolChain`) และได้ภาพขนาดย่อที่สร้างอัตโนมัติสำหรับหน้า 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 แบบถาวร Fast OCR จะเรียกใช้ Tesseract แทน และ OCR ที่แม่นยำจะเริ่มต้นการดำเนินการที่ปักหมุดไว้จากรุ่น OCR ที่ไม่เปลี่ยนรูปแบบที่ใช้งานอยู่ ระดับ OCR ที่ร้องขอได้รับการแก้ไขที่ทางเข้าและจะไม่มีการเปลี่ยนแปลงอย่างเงียบๆ ในระหว่างการดำเนินการ
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