Starsoup Image Platform (SIP) — Master Document
版本: 0.3 日期: 2026-03-24 狀態: 準備開發
目錄
一、產品定義
名稱
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 / Placid | RendrKit | SIP |
|---|---|---|---|
| AI 素材生成 | ❌ | ⚠️ 全自動,無模版控制 | ✅ 畫布內生成,人工確認入模版 |
| 視覺化模版編輯 | ✅ 雲端 SaaS | ❌ | ✅ 自建(vue-konva) |
| 程式化渲染 API | ✅ | ✅ | ✅ 自建(Satori) |
| 自托管 / 私有部署 | ❌ | ❌ | ✅ openclaw |
| 費用 | $49/月起 + 渲染費 | 同上 | 僅服務器電費 + R2 流量 |
| 與自有 ERP 原生整合 | 需 Webhook | API | ✅ 直調 |
差異化
市場上目前沒有任何工具同時具備: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-backendRepo 結構
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 Client | WebSocket | MIT ✅ |
| Pinia | 狀態管理 | MIT ✅ |
| Tailwind CSS 4 | 樣式 | MIT ✅ |
| vue-i18n | 國際化 | MIT ✅ |
| vue-konva / Konva.js | Layer 1 zone 編輯器 | MIT ✅ |
後端(新建)
| 技術 | 用途 | 授權 |
|---|---|---|
| Hono | API server | MIT ✅ |
| Socket.IO Server | WebSocket 流式中繼 | MIT ✅ |
| Drizzle ORM | Postgres ORM | MIT ✅ |
| Satori(Vercel) | Layer 2 渲染引擎 | Apache 2.0 ✅ |
| @resvg/resvg-js | SVG → PNG | Apache 2.0 ✅ |
基礎設施
| 技術 | 用途 | 費用 |
|---|---|---|
| Postgres 16(Docker) | 主資料庫 | 免費(自托管) |
| Cloudflare R2 | 圖片儲存 | 免費層 10GB,超出按量 |
| Google Fonts / Noto TC | 中文字體(OFL 1.1) | 免費,可商用 |
版權結論
全棧技術棧均為 MIT / Apache 2.0 / OFL 授權,可自由商用、無需開源自有代碼、無隱性費用。唯一需要持續費用的是 Cloudflare R2 的流量(極低)。
五、後端詳細設計
後端的本質
Hono 後端是一個薄代理層,不包含任何 AI 業務邏輯。其三個核心職責:
- API Key 安全保管:所有供應商 Key 存在服務器
.env,前端不持有任何 Key - 流式輸出中繼:
LLM API → Hono → Socket.IO → 前端 - 文件持久化:畫布、資產、模版、生成圖片存 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 偽碼)
// 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 核心邏輯
// 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
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)
// 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 結構
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/canvasCRUD - [ ] 實現
/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/templatesCRUD - [ ] 導航欄新增 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 開始。