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

架構

SnapOtter 是一個以 pnpm workspaces 與 Turborepo 管理的 monorepo。它以 3 容器的 Docker Compose 堆疊部署:SnapOtter app 映像檔、PostgreSQL 17 與 Redis 8。

專案結構

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

套件

@snapotter/image-engine

Sharp 為基礎建置的核心影像處理程式庫。它處理所有非 AI 的操作:resize、crop、rotate、flip、convert、compress、strip metadata,以及色彩調整(亮度、對比、飽和度、灰階、懷舊、反轉、色版)。

此套件沒有網路相依,完全在程序內執行。

@snapotter/ai

一個橋接層,呼叫 Python 指令碼進行 ML 操作。首次使用時,橋接層會啟動一個常駐的 Python 分派器程序,預先匯入沉重的程式庫(PIL、NumPy、MediaPipe、rembg),使後續的 AI 呼叫可略過匯入負擔。若分派器尚未就緒,橋接層會退回為每個請求產生一個全新的 Python 子程序。

模型不會預先載入。 每個工具指令碼會在請求時從磁碟載入其模型權重,並在請求結束時捨棄。完整的記憶體剖析請參閱資源占用

支援的操作:背景移除(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 翻譯字串。

應用程式

API(apps/api

一個 Fastify v5 伺服器,公開橫跨五種模態(image、video、audio、PDF、file)的 241 個工具路由,負責處理:

  • 檔案上傳、暫存工作區管理,以及持久化檔案儲存
  • 具版本鏈的使用者檔案資料庫(user_files 資料表)- 每個處理結果都連回其來源檔案並記錄套用了哪個工具,並為 Files 頁面自動產生縮圖
  • 工具執行(將每個工具請求路由至影像引擎或 AI 橋接層)
  • 管線協調(依序串接多個工具)
  • 透過 BullMQ 工作佇列(pools:image、media、ai、docs、system)進行具並行控制的批次處理
  • 使用者驗證、RBAC(admin/user 角色與完整權限集)、API 金鑰管理,以及速率限制
  • 團隊管理 - 僅限 admin 的 CRUD;使用者透過其個人檔案上的 team 欄位指派至團隊
  • 執行階段設定 - settings 資料表中的鍵值儲存,可控制 disabledToolsenableExperimentalToolsloginAttemptLimit 與其他運維旋鈕,無需重新部署
  • 透過資料庫支援的設定進行自訂品牌與執行階段偏好設定
  • 位於 /api/docs 的 Scalar/OpenAPI 文件
  • 在正式環境中以 SPA 形式提供已建置的前端

主要相依套件:Fastify、Drizzle ORM(pg-core、node-postgres)、Sharp、BullMQ、ioredis、以及用於驗證的 Zod。

伺服器會在 SIGTERM/SIGINT 時處理平順關機:排空 HTTP 連線、停止 BullMQ workers、關閉 Python 分派器,並關閉資料庫連線。

Web(apps/web

一個以 Vite 建置的 React 19 單頁應用程式。使用 Zustand 進行狀態管理、Tailwind CSS v4 進行樣式設計、Lucide 提供圖示。透過 REST 與 SSE(用於進度追蹤)與 API 溝通。

頁面包含工具工作區、用於管理持久化上傳與結果的 Files 頁面、自動化/管線建構器,以及管理員設定面板。

已建置的前端在正式環境中由 Fastify 後端提供,因此 Docker 容器中沒有獨立的 web 伺服器。

Docs(apps/docs

即此 VitePress 網站。在推送至 main 時自動部署至 Cloudflare Pages。

請求如何流動

  1. 使用者在 web UI 中選取工具並上傳檔案。
  2. 前端將含檔案與設定的 multipart POST 送往 /api/v1/tools/:section/:toolId
  3. API 路由以 Zod 驗證輸入,然後分派處理。
  4. 對於標準工具,工作會依模態排入適當的 BullMQ pool(image、media 或 docs)。程序內的 BullMQ worker 會根據 EXIF 中繼資料自動校正影像方向、執行工具的處理函式,並回傳結果。
  5. 對於 AI 工具,TypeScript 橋接層會將請求送往常駐的 Python 分派器(或退回產生一個全新的子程序),等待其完成,並讀取輸出檔案。
  6. 工作進度會持久化至 PostgreSQL 中的 jobs 資料表,因此狀態可在容器重新啟動後保留。即時更新透過位於 /api/v1/jobs/:jobId/progress 的 SSE 傳遞。
  7. API 回傳一個 jobIddownloadUrl。使用者從 /api/v1/download/:jobId/:filename 下載處理後的檔案。

對於管線,API 會將每個步驟的輸出作為下一步的輸入,依序執行。

對於批次處理,API 會使用具逐步驟子工作的 BullMQ flows,並回傳一個含所有處理後檔案的 ZIP 檔案。

資源占用

SnapOtter 的設計以低閒置記憶體使用為目標。啟動時不會預先載入或保持任何暖機狀態。

閒置時

Node.js/Fastify 程序、PostgreSQL 與 Redis 都在執行。三個容器(Node.js 程序、Postgres 與 Redis)的典型閒置 RAM 為 約 200-300 MB。沒有 Python 程序,記憶體中也沒有模型權重。

什麼會啟動,以及何時啟動

元件啟動時機作用時的記憶體
Fastify 伺服器 + Postgres + Redis容器啟動合計約 200-300 MB
BullMQ workers容器啟動(程序內)每個 pool 一個 worker(image、media、ai、docs、system)
Python 分派器首次 AI 工具請求Python 直譯器 + 預先匯入的程式庫(PIL、NumPy、MediaPipe、rembg)- 無模型權重
AI 模型權重特定工具的請求期間從磁碟載入,請求結束時釋放

模型載入

所有模型權重檔案(合計數 GB)始終位於 /opt/models/ 的磁碟上。每個 AI 工具指令碼僅在請求期間將自己的模型載入記憶體,之後即釋放。有些指令碼會在推論後明確呼叫 del modeltorch.cuda.empty_cache(),以確保記憶體立即歸還。

請求之間沒有模型快取。連續執行同一個 AI 工具時,每次都會重新載入模型。這使得閒置記憶體趨近於零,代價是每個 AI 請求都會有模型載入延遲。

首次 AI 請求的冷啟動

容器啟動時 Python 分派器並未執行。首次 AI 請求會並行觸發兩件事:分派器開始在背景暖機,而請求本身則退回為一次性的 Python 子程序產生。一旦分派器發出就緒信號,所有後續 AI 請求都會直接使用它,並略過子程序產生的成本。