Skip to content

Starsoup Image Platform (SIP) — Master Document

版本: 0.3 日期: 2026-03-24 狀態: 準備開發


目錄

  1. 產品定義
  2. 市場定位
  3. 系統架構
  4. 技術棧與授權
  5. 後端詳細設計
  6. 核心數據結構
  7. 基礎設施
  8. 開發計劃
  9. 待確認事項

一、產品定義

名稱

Starsoup Image Platform(代號:sip

一句話定義

一個自托管、AI 驅動的創意畫布 + 圖片自動化平台,前端從 Jaaz 遷移,後端重建為 Hono(TypeScript),並在此基礎上疊加模版編輯器與程式化渲染 API,最終替代 Bannerbear / Placid 等 SaaS 服務。

三個核心層

Layer 0:AI 創意畫布(從 Jaaz 遷移)
  ├── Fabric.js 無限畫布
  ├── AI 聊天面板(Socket.IO 流式輸出)
  ├── 多供應商模型管理(OpenAI、Anthropic、Ollama、ComfyUI 等)
  ├── 圖片生成、資產管理、知識庫
  └── Agent Studio

Layer 1:Template Studio(新增)
  ├── 在 AI 生成的背景圖上,視覺化定義 zones(vue-konva)
  ├── 文字 zone:字體、字號、顏色、對齊、最大行數
  ├── 圖片 zone:位置、尺寸、圓角
  ├── 即時預覽(所見即所得)
  └── 存入 Postgres,背景圖存 Cloudflare R2

Layer 2:Render API(新增)
  ├── POST /v1/render:接收 { templateId, data }
  ├── Satori 合成背景圖 + zones → 輸出 PNG
  ├── 生成圖片上傳 R2,返回永久 URL
  └── 供 @crystal/erp、@crystal/ec 及其他 Starsoup 項目直調

定位澄清

SIP 是 Starsoup 的獨立項目,不屬於 crystal-business-suite monorepo。@crystal/erp@crystal/ec 作為外部調用方,通過 Render API 使用 Layer 2 功能。


二、市場定位

競品對比

功能維度Bannerbear / PlacidRendrKitSIP
AI 素材生成⚠️ 全自動,無模版控制✅ 畫布內生成,人工確認入模版
視覺化模版編輯✅ 雲端 SaaS✅ 自建(vue-konva)
程式化渲染 API✅ 自建(Satori)
自托管 / 私有部署✅ openclaw
費用$49/月起 + 渲染費同上僅服務器電費 + R2 流量
與自有 ERP 原生整合需 WebhookAPI✅ 直調

差異化

市場上目前沒有任何工具同時具備:AI 素材生成層 + 視覺化 zone 編輯器 + 私有部署渲染 API + 原生整合自有業務系統。SIP 是唯一填補這個空缺的自托管方案,同時也是 Starsoup 未來對外提供服務的潛在 SaaS 產品。


三、系統架構

整體架構圖

用戶
  └─→ Cloudflare Zero Trust
        └─→ Nginx(openclaw 內,統一入口)
              ├── /        → sip-frontend(Vue 靜態)
              └── /api/    → sip-backend(Hono)
                  /ws/     → sip-backend(Socket.IO)

sip-backend(Hono)
  ├── LLM proxy → OpenAI / Anthropic / Ollama(外部)
  ├── 圖片生成 → Replicate / HuggingFace / ComfyUI(外部)
  ├── Canvas / Assets / Config → Postgres + 本地/R2
  ├── Templates → Postgres
  └── Render → Satori → R2

外部調用方(@crystal/erp、@crystal/ec)
  └─→ POST /api/render → sip-backend

Repo 結構

starsoup-image-platform/
  src/                         ← Vue 前端(現有,從 Jaaz 遷移)
    views/
      HomeView.vue
      CanvasView.vue
      AgentStudioView.vue
      AssetsView.vue
      KnowledgeView.vue
      TemplateListView.vue     ← Layer 1 新增
      TemplateEditorView.vue   ← Layer 1 新增(vue-konva)
    api/
    components/
    stores/
    ...
  server/                      ← Hono 後端(新建)
    src/
      routes/
        llm.ts                 ← LLM proxy + 流式輸出
        image.ts               ← 圖片生成
        canvas.ts              ← Canvas CRUD
        assets.ts              ← 資產管理
        config.ts              ← 供應商配置
        templates.ts           ← Layer 1 新增
        render.ts              ← Layer 2 新增(Satori)
      ws/
        chat.ts                ← Socket.IO 聊天中繼
      lib/
        satori.ts              ← Satori 渲染引擎
        r2.ts                  ← Cloudflare R2 client
        db.ts                  ← Postgres(Drizzle ORM)
        providers.ts           ← LLM 供應商配置管理
      index.ts                 ← Hono app entry
    package.json
    Dockerfile
  docker-compose.yml
  nginx.conf
  .env.example

四、技術棧與授權

前端(現有,從 Jaaz 遷移)

技術用途授權
Vue 3 + TypeScript框架MIT ✅
Vite 8構建MIT ✅
Fabric.js 7畫布引擎MIT ✅
Socket.IO ClientWebSocketMIT ✅
Pinia狀態管理MIT ✅
Tailwind CSS 4樣式MIT ✅
vue-i18n國際化MIT ✅
vue-konva / Konva.jsLayer 1 zone 編輯器MIT ✅

後端(新建)

技術用途授權
HonoAPI serverMIT ✅
Socket.IO ServerWebSocket 流式中繼MIT ✅
Drizzle ORMPostgres ORMMIT ✅
Satori(Vercel)Layer 2 渲染引擎Apache 2.0 ✅
@resvg/resvg-jsSVG → PNGApache 2.0 ✅

基礎設施

技術用途費用
Postgres 16(Docker)主資料庫免費(自托管)
Cloudflare R2圖片儲存免費層 10GB,超出按量
Google Fonts / Noto TC中文字體(OFL 1.1)免費,可商用

版權結論

全棧技術棧均為 MIT / Apache 2.0 / OFL 授權,可自由商用、無需開源自有代碼、無隱性費用。唯一需要持續費用的是 Cloudflare R2 的流量(極低)。


五、後端詳細設計

後端的本質

Hono 後端是一個薄代理層,不包含任何 AI 業務邏輯。其三個核心職責:

  1. API Key 安全保管:所有供應商 Key 存在服務器 .env,前端不持有任何 Key
  2. 流式輸出中繼LLM API → Hono → Socket.IO → 前端
  3. 文件持久化:畫布、資產、模版、生成圖片存 Postgres + R2

端點清單

# LLM & 圖片生成
POST   /api/llm/chat          → 轉發至對應供應商,pipe stream → Socket.IO
POST   /api/llm/image         → 轉發圖片生成請求,返回圖片 URL
GET    /api/models            → 返回各供應商可用模型列表

# 供應商配置
GET    /api/config            → 讀取所有供應商配置(不含 Key 明文)
POST   /api/config            → 寫入供應商配置(含 Key)
POST   /api/config/test       → 測試指定供應商連接

# Canvas
GET    /api/canvas            → 畫布列表
POST   /api/canvas            → 新建畫布
GET    /api/canvas/:id        → 取得畫布
PUT    /api/canvas/:id        → 更新畫布
DELETE /api/canvas/:id        → 刪除畫布

# 資產管理
GET    /api/assets            → 資產列表
POST   /api/upload            → 上傳圖片(存 R2)
DELETE /api/assets/:id        → 刪除資產

# Layer 1:Template CRUD
GET    /api/templates         → 模版列表
POST   /api/templates         → 新建模版
GET    /api/templates/:id     → 取得模版
PUT    /api/templates/:id     → 更新模版
DELETE /api/templates/:id     → 刪除模版

# Layer 2:Render API
POST   /api/render            → 渲染圖片(主端點)
GET    /api/render/:jobId     → 查詢渲染狀態(非同步時用)

# WebSocket
WS     /ws                    → Socket.IO,流式聊天中繼

LLM Proxy 核心邏輯(TypeScript 偽碼)

typescript
// server/src/routes/llm.ts
app.post('/api/llm/chat', async (c) => {
  const { provider, model, messages } = await c.req.json()
  const config = await getProviderConfig(provider)  // 從 DB/env 取 Key

  const stream = await fetchLLMStream(config, model, messages)
  // pipe → Socket.IO → 前端
  return streamSSE(c, stream)
})

所有複雜的 AI 邏輯(理解意圖、生成提示詞、多輪對話)都在 LLM API 那一側,Hono 只負責轉發。

Render API 核心邏輯

typescript
// server/src/routes/render.ts
app.post('/api/render', async (c) => {
  const { template_id, data } = await c.req.json()

  // 1. 從 Postgres 取 template JSON
  const template = await db.query.templates.findFirst(...)

  // 2. 從 R2 取背景圖(base64)
  const bgImage = await r2.getAsBase64(template.background_url)

  // 3. 商品圖片取得
  const productImage = data.product_image
    ? await fetchImageAsBase64(data.product_image)
    : null

  // 4. Satori 渲染
  const svg = await satori(
    buildJSX(template.zones, { ...data, bgImage, productImage }),
    { width: template.width, height: template.height, fonts: [...] }
  )

  // 5. SVG → PNG → 上傳 R2
  const png = new Resvg(svg).render().asPng()
  const imageUrl = await r2.upload(`generated/${uuid()}.png`, png)

  return c.json({ image_url: imageUrl, rendered_at: new Date() })
})

六、核心數據結構

Template JSON Schema

typescript
interface Template {
  id: string
  name: string
  width: number           // 例如 1080
  height: number          // 例如 1080
  background_url: string  // R2 public URL
  zones: Zone[]
  project_id?: string     // 多項目隔離(Phase 3 用)
  created_at: string
  updated_at: string
}

interface Zone {
  id: string              // 對應 Render API data 的 key 名稱
  type: 'text' | 'image'
  x: number
  y: number
  width: number
  height: number
  // text zone 專用
  font_family?: string    // 僅限 OFL 字體,例如 "Noto Serif TC"
  font_size?: number
  font_weight?: '400' | '700'
  color?: string          // hex,例如 "#2D1B4E"
  align?: 'left' | 'center' | 'right'
  line_height?: number
  max_lines?: number
  prefix?: string         // 例如 "¥"
  // image zone 專用
  border_radius?: number
  object_fit?: 'cover' | 'contain' | 'fill'
}

Postgres Schema(Drizzle)

typescript
// server/src/db/schema.ts

export const templates = pgTable('templates', {
  id:             text('id').primaryKey(),
  name:           text('name').notNull(),
  width:          integer('width').notNull(),
  height:         integer('height').notNull(),
  backgroundUrl:  text('background_url').notNull(),
  zones:          jsonb('zones').notNull().default([]),
  projectId:      text('project_id'),
  createdAt:      timestamp('created_at').defaultNow(),
  updatedAt:      timestamp('updated_at').defaultNow(),
})

export const renderLogs = pgTable('render_logs', {
  id:         uuid('id').defaultRandom().primaryKey(),
  templateId: text('template_id').references(() => templates.id),
  caller:     text('caller'),     // 例如 "crystal-erp"
  imageUrl:   text('image_url'),
  renderedAt: timestamp('rendered_at').defaultNow(),
})

export const canvases = pgTable('canvases', {
  id:        text('id').primaryKey(),
  name:      text('name').notNull(),
  data:      jsonb('data').notNull().default({}),  // Fabric.js JSON
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
})

export const assets = pgTable('assets', {
  id:        uuid('id').defaultRandom().primaryKey(),
  name:      text('name').notNull(),
  url:       text('url').notNull(),        // R2 URL
  type:      text('type').notNull(),       // image/video/audio
  size:      integer('size'),
  createdAt: timestamp('created_at').defaultNow(),
})

Render API Contract(對外調用方)

POST /api/render
Authorization: Bearer <SIP_API_KEY>
Content-Type: application/json

{
  "template_id": "crystal-detail-1x1",
  "data": {
    "product_image": "https://r2.../amethyst.jpg",
    "crystal_name": "天然紫水晶原石",
    "price": "3,800",
    "description": "來自烏拉圭深礦..."
  }
}

→ 200 OK
{
  "image_url": "https://r2.starsoup.co/generated/uuid.png",
  "template_id": "crystal-detail-1x1",
  "rendered_at": "2026-03-24T10:00:00Z"
}

七、基礎設施

Docker Compose 結構

yaml
services:
  sip-frontend:
    build:
      context: .
      dockerfile: Dockerfile.frontend
    depends_on: [sip-backend]

  sip-backend:
    build:
      context: ./server
    environment:
      DATABASE_URL: postgres://sip:${DB_PASSWORD}@postgres:5432/sip
      R2_ACCOUNT_ID: ${R2_ACCOUNT_ID}
      R2_ACCESS_KEY: ${R2_ACCESS_KEY}
      R2_SECRET_KEY: ${R2_SECRET_KEY}
      R2_BUCKET: starsoup-images
      SIP_API_KEY: ${SIP_API_KEY}
    depends_on: [postgres]
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: sip
      POSTGRES_USER: sip
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on: [sip-frontend, sip-backend]
    restart: unless-stopped

volumes:
  pgdata:

Cloudflare R2 Bucket 結構

starsoup-images/
  templates/
    backgrounds/     ← 畫布 AI 生成的背景底圖
    assets/          ← 其他模版素材
  generated/         ← Render API 輸出的合成圖
  uploads/           ← 用戶上傳的資產

Cloudflare Zero Trust

Nginx 對外只暴露 80/443 端口,通過 Cloudflare Tunnel 接入,後端所有服務在 Docker 內網通訊,不對外暴露。


八、開發計劃

Phase 0:移植驗證(優先)

目標:讓從 Jaaz 遷移過來的前端,連接自建的 Hono 後端後,所有現有功能完全正常。

方式:由 Claude Code 讀取 jaaz/server/ Python 源碼,分析所有端點,逐一用 Hono TypeScript 重新實現。不原封不動照搬,以「前端功能跑通」為驗收標準。

工作項

  • [ ] 初始化 server/(Hono + Drizzle + TypeScript)
  • [ ] 實現 /api/models(模型列表,修復模型名稱無法選擇)
  • [ ] 實現 /api/config(供應商配置 + 測試連接)
  • [ ] 實現 /api/llm/chat + Socket.IO(修復 WebSocket 和流式輸出)
  • [ ] 實現 /api/llm/image(圖片生成)
  • [ ] 實現 /api/canvas CRUD
  • [ ] 實現 /api/assets + /api/upload
  • [ ] 移除前端 Jaaz 設備授權流程,改為純 API Key 配置
  • [ ] Docker Compose 跑通全棧
  • [ ] .env.example 整理

驗收:所有現有 Jaaz 功能(畫布、AI 聊天、圖片生成、資產管理)在 openclaw 上正常運行。


Phase 1:Layer 1 — Template Studio

目標:在 SIP 內建視覺化模版編輯器。

  • [ ] 安裝 vue-konva
  • [ ] 新建 TemplateListView.vue(列表 + 新建)
  • [ ] 新建 TemplateEditorView.vue
    • [ ] 從 R2 載入背景圖至 Konva canvas
    • [ ] 拖拽新增 text zone / image zone
    • [ ] Zone 樣式面板(字體、顏色、字號、對齊)
    • [ ] 即時預覽
    • [ ] 存入 Postgres
  • [ ] 後端實現 /api/templates CRUD
  • [ ] 導航欄新增 Templates 入口

驗收:能在 SIP 內設計一個水晶詳情圖模版,儲存後可在列表查看。


Phase 2:Layer 2 — Render API

目標:程式化圖片生成,供 ERP/EC 調用。

  • [ ] 安裝 Satori、@resvg/resvg-js
  • [ ] 嵌入 Noto Serif TC / Noto Sans TC 字體文件
  • [ ] 實現 server/src/lib/satori.ts(渲染核心)
  • [ ] 實現 POST /api/render
  • [ ] 生成 PNG 上傳 R2,返回永久 URL
  • [ ] @crystal/erp 整合調用測試

驗收:從 @crystal/erp 傳入水晶資料,能正確生成詳情圖並返回 URL。


Phase 3:Starsoup 化(未來)

  • [ ] project_id 多項目隔離
  • [ ] API Key 管理(每個調用方獨立 Key)
  • [ ] 渲染統計後台
  • [ ] 懶人包長圖(動態高度模版)

九、待確認事項

以下問題在 Phase 0 開始前需要決定:

Q1(已確認):後端從 jaaz/server/ Python 源碼分析後,由 Claude Code 重寫為 Hono TypeScript,不原封不動移植 Python。✅

Q2(已確認):SIP 部署在 openclaw(Starsoup Server A),使用 Docker Compose,通過 Cloudflare Zero Trust 對外暴露。✅

Q3(已確認):SIP 是獨立項目,不屬於 crystal-business-suite@crystal/erp 作為外部調用方使用 Render API。✅

Q4(待確認):Template Studio(/templates)是獨立頁面,還是整合進 CanvasView 工作流(在畫布完成 AI 生圖後,直接有「存為模版」按鈕)?

Q5(待確認):Cloudflare R2 已有現成 bucket,還是需要新建?bucket 命名是否按上方結構來?


下一步:回答 Q4、Q5,然後開啟 Claude Code session,以本文件作為 context,從 Phase 0 開始。