Files
SnapOtter/apps/docs/th/guide/architecture.md
T
SnapOtterandGitHub 4963ab3bbd feat(docs-i18n): translate all documentation into 20 languages
All 181 docs markdown files translated into 20 languages (apps/docs/<locale>/**). Companion to the i18n code PR; admin-merged because the file count exceeds GitHub's per-PR CI trigger limit. Validated by pnpm i18n:check (all surfaces, 0 stale/missing) and a clean all-locale docs build.
2026-07-11 13:52:47 +08:00

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