Search K
Kiến trúc
SnapOtter là một monorepo được quản lý bằng pnpm workspaces và Turborepo. Nó triển khai như một ngăn xếp Docker Compose 3 container: image ứng dụng SnapOtter, PostgreSQL 17 và Redis 8.
Cấu trúc dự án
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 configCác gói
@snapotter/image-engine
Thư viện xử lý ảnh cốt lõi được xây dựng trên Sharp. Nó xử lý mọi thao tác không phải AI: thay đổi kích thước, cắt, xoay, lật, chuyển đổi, nén, tước metadata và điều chỉnh màu (độ sáng, độ tương phản, độ bão hòa, thang xám, sepia, đảo màu, kênh màu).
Gói này không có phụ thuộc mạng và chạy hoàn toàn trong tiến trình.
@snapotter/ai
Một lớp cầu nối gọi các script Python cho các thao tác ML. Ở lần dùng đầu tiên, cầu nối khởi động một tiến trình dispatcher Python bền vững, tiến trình này nạp trước các thư viện nặng (PIL, NumPy, MediaPipe, rembg) để các lệnh gọi AI sau đó bỏ qua chi phí nạp. Nếu dispatcher chưa sẵn sàng, cầu nối lùi về việc sinh ra một tiến trình con Python mới cho mỗi yêu cầu.
Các mô hình không được nạp trước. Mỗi script công cụ nạp trọng số mô hình của nó từ đĩa vào thời điểm yêu cầu và loại bỏ chúng khi yêu cầu hoàn tất. Xem Dấu chân tài nguyên để biết hồ sơ bộ nhớ đầy đủ.
Các thao tác được hỗ trợ: xóa nền (rembg/BiRefNet), phóng to (RealESRGAN), làm mờ khuôn mặt (MediaPipe), tăng cường khuôn mặt (GFPGAN/CodeFormer), xóa vật thể (LaMa ONNX), OCR (PaddleOCR/Tesseract), tô màu (DDColor), khử nhiễu, xóa mắt đỏ, phục chế ảnh, tạo ảnh hộ chiếu, sửa độ trong suốt (BiRefNet HR-matting), và thay đổi kích thước nhận biết nội dung (nhị phân Go caire).
Các script Python nằm trong packages/ai/python/. Image Docker tải trước tất cả trọng số mô hình trong quá trình build để container hoạt động hoàn toàn ngoại tuyến.
@snapotter/shared
Các kiểu TypeScript dùng chung, các hằng số (như APP_VERSION và định nghĩa công cụ), và các chuỗi dịch i18n được cả frontend và backend sử dụng.
Các ứng dụng
API (apps/api)
Một máy chủ Fastify v5 phơi bày 241 tuyến công cụ trên năm phương thức (image, video, audio, PDF, file) xử lý:
- Tải tệp lên, quản lý không gian làm việc tạm thời, và lưu trữ tệp bền vững
- Thư viện tệp người dùng với chuỗi phiên bản (bảng
user_files) - mỗi kết quả đã xử lý liên kết ngược về tệp nguồn của nó và ghi lại công cụ nào đã được áp dụng, cùng các ảnh thu nhỏ tự sinh cho trang Files - Thực thi công cụ (định tuyến mỗi yêu cầu công cụ đến image engine hoặc cầu nối AI)
- Điều phối pipeline (kết nối nhiều công cụ theo tuần tự)
- Xử lý hàng loạt với kiểm soát đồng thời qua các hàng đợi công việc BullMQ (nhóm: image, media, ai, docs, system)
- Xác thực người dùng, RBAC (vai trò admin/user với một bộ quyền đầy đủ), quản lý khóa API và giới hạn tốc độ
- Quản lý Teams - CRUD chỉ dành cho admin; người dùng được gán vào một nhóm qua trường
teamtrên hồ sơ của họ - Cài đặt runtime - một kho khóa-giá trị trong bảng
settingsđiều khiểndisabledTools,enableExperimentalTools,loginAttemptLimit, và các nút vận hành khác mà không cần triển khai lại - Thương hiệu tùy chỉnh và tùy chọn runtime qua các cài đặt được hậu thuẫn bởi cơ sở dữ liệu
- Tài liệu Scalar/OpenAPI tại
/api/docs - Phục vụ frontend đã build dưới dạng SPA trong sản xuất
Các phụ thuộc chính: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod để kiểm định.
Máy chủ xử lý việc tắt máy nhẹ nhàng khi nhận SIGTERM/SIGINT: nó rút cạn các kết nối HTTP, dừng các worker BullMQ, tắt dispatcher Python, và đóng kết nối cơ sở dữ liệu.
Web (apps/web)
Một ứng dụng đơn trang React 19 được build với Vite. Dùng Zustand để quản lý trạng thái, Tailwind CSS v4 để tạo kiểu, và Lucide cho biểu tượng. Giao tiếp với API qua REST và SSE (để theo dõi tiến trình).
Các trang bao gồm một không gian làm việc công cụ, một trang Files để quản lý các tệp tải lên và kết quả bền vững, một trình dựng tự động hóa/pipeline, và một bảng cài đặt admin.
Frontend đã build được phục vụ bởi backend Fastify trong sản xuất, nên không có máy chủ web riêng biệt trong container Docker.
Docs (apps/docs)
Trang VitePress này. Được triển khai lên Cloudflare Pages tự động khi đẩy lên main.
Cách một yêu cầu di chuyển
- Người dùng chọn một công cụ trong giao diện web và tải lên một tệp.
- Frontend gửi một POST đa phần đến
/api/v1/tools/:section/:toolIdvới tệp và các cài đặt. - Tuyến API kiểm định đầu vào bằng Zod, rồi điều phối việc xử lý.
- Đối với các công cụ tiêu chuẩn, công việc được đưa vào nhóm BullMQ phù hợp (image, media hoặc docs dựa trên phương thức). Worker BullMQ trong tiến trình tự định hướng ảnh dựa trên metadata EXIF, chạy hàm xử lý của công cụ, và trả về kết quả.
- Đối với các công cụ AI, cầu nối TypeScript gửi một yêu cầu đến dispatcher Python bền vững (hoặc sinh ra một tiến trình con mới như phương án dự phòng), chờ nó hoàn tất, và đọc tệp đầu ra.
- Tiến trình công việc được lưu bền vào bảng
jobstrong PostgreSQL nên trạng thái tồn tại qua các lần khởi động lại container. Các cập nhật thời gian thực được chuyển qua SSE tại/api/v1/jobs/:jobId/progress. - API trả về một
jobIdvàdownloadUrl. Người dùng tải tệp đã xử lý từ/api/v1/download/:jobId/:filename.
Đối với pipeline, API đưa đầu ra của mỗi bước làm đầu vào cho bước kế tiếp, chạy chúng theo tuần tự.
Đối với xử lý hàng loạt, API dùng các flow BullMQ với các công việc con theo từng bước và trả về một tệp ZIP với tất cả các tệp đã xử lý.
Dấu chân tài nguyên
SnapOtter được thiết kế để dùng bộ nhớ thấp khi rảnh. Không có gì được nạp trước hay giữ ấm khi khởi động.
Khi rảnh
Tiến trình Node.js/Fastify, PostgreSQL và Redis đang chạy. RAM rảnh điển hình là khoảng 200-300 MB trên cả ba container (tiến trình Node.js, Postgres và Redis). Không có tiến trình Python, không có trọng số mô hình trong bộ nhớ.
Cái gì khởi động, và khi nào
| Thành phần | Khởi động khi | Bộ nhớ khi hoạt động |
|---|---|---|
| Máy chủ Fastify + Postgres + Redis | Khởi động container | tổng khoảng 200-300 MB |
| Các worker BullMQ | Khởi động container (trong tiến trình) | Một worker cho mỗi nhóm (image, media, ai, docs, system) |
| Dispatcher Python | Yêu cầu công cụ AI đầu tiên | Trình thông dịch Python + các thư viện nạp trước (PIL, NumPy, MediaPipe, rembg) - không có trọng số mô hình |
| Trọng số mô hình AI | Trong yêu cầu của công cụ cụ thể | Nạp từ đĩa, giải phóng khi yêu cầu hoàn tất |
Nạp mô hình
Tất cả các tệp trọng số mô hình (tổng cộng vài GB) nằm trên đĩa trong /opt/models/ mọi lúc. Mỗi script công cụ AI chỉ nạp mô hình của riêng nó vào bộ nhớ trong khoảng thời gian của một yêu cầu, rồi giải phóng chúng. Một số script gọi rõ ràng del model và torch.cuda.empty_cache() sau khi suy luận để đảm bảo bộ nhớ được trả lại ngay lập tức.
Không có bộ nhớ đệm mô hình giữa các yêu cầu. Chạy cùng một công cụ AI liên tiếp sẽ nạp lại mô hình mỗi lần. Điều này giữ bộ nhớ khi rảnh gần bằng không với cái giá là độ trễ nạp mô hình ở mỗi yêu cầu AI.
Khởi động nguội của yêu cầu AI đầu tiên
Dispatcher Python không chạy khi container khởi động. Yêu cầu AI đầu tiên kích hoạt hai việc song song: dispatcher bắt đầu khởi động trong nền, và bản thân yêu cầu lùi về việc sinh một tiến trình con Python dùng một lần. Khi dispatcher báo hiệu sẵn sàng, tất cả các yêu cầu AI sau đó dùng nó trực tiếp và bỏ qua chi phí sinh tiến trình con.
