Skip to content

IanLin419/byteplus-ai-gen-platform

Repository files navigation

BytePlus AI Gen Platform

支援多模態參考輸入(圖片 / 影片 / 音訊)、asset:// 數位人素材引用、[Image N] / [Video N] / [Audio N] 提示詞編號、任務匯出 / 匯入 JSON、本地 mp4 快取、TOS pre-signed URL 安全上傳,以及可重整恢復的併發任務追蹤(sessionStorage 持久化 + 背景輪詢 + 可設定的任務 TTL)。


前置需求

執行環境

  • Node.js 20.19+ 或 22.12+(Vite 8 / vitest 4 的官方需求;測試在 Node 20–25 通過)
  • npm 9 以上

BytePlus 帳號需要準備好的東西

項目 用途 取得方式
ModelArk API Key 影片生成驗證 BytePlus ModelArk console
Seedance 2.0 Endpoint ID 推論部署 自行依 BytePlus 官方文件 在 ModelArk 部署 Seedance 2.0 endpoint,部署後拿到 ID
TOS Bucket 影片 / 音訊參考素材的安全託管 BytePlus TOS console 自行建立
兩組 AccessKey / SecretKey(見下) 一組給 TOS、一組給 ARK Asset 庫 BytePlus IAM

憑證輸入方式(BYO 模式)

所有憑證都從 UI 輸入,.env 不需要填 AKSK。 Header 右上角會看到三顆膠囊(推論 / 素材庫 / TOS);新使用者打開時三顆都是灰色。

兩個入口都會打開右側憑證 Drawer:

  • 點任一膠囊:drawer 開啟並自動展開、聚焦對應憑證的第一個欄位
  • 點齒輪 / 按 ⌘,(Mac)或 Ctrl+,(Windows/Linux):drawer 開啟,三個區塊都收合,自己選要編輯哪個

Drawer 內三個區塊:

  • 推論憑證 — ModelArk API Key + Endpoint ID。輸入即時驗證(regex 比對 BytePlus 真實格式 ark-{UUID}-{5字元} / 純 UUID 對 API key;ep-YYYYMMDDHHMMSS-xxxxx 對 endpoint),不打網路驗證;沒有「測試連線」按鈕。
  • 私有素材庫憑證 — ARK AccessKey / SecretKey + Project Name(預設 default)。按「測試連線」打 /local-api/asset/verify
  • 物件儲存憑證 — TOS AccessKey / SecretKey + Region(下拉選單:ap-southeast-1 / ap-southeast-3 / cn-beijing / cn-shanghai / cn-guangzhou / cn-hongkong)+ Bucket(接受純 bucket 名稱或 bucket/prefix 格式 — 省略 prefix 會用預設 seedance-2-0/)。Endpoint 自動由 Region 推導tos-{region}.bytepluses.com),UI 不再顯示。測試連線會打 /local-api/tos/verify,順便 bootstrap bucket CORS。

每個區塊改任何欄位 → 對應 Header 膠囊立刻變灰(pend),需重新測試才會變綠。

所有 secret 欄位(5 個 AKSK)右側有眼睛 icon — 預設 type=password,點一下切到明文檢查。drawer 關閉時自動回到隱藏。

填入後存在瀏覽器 sessionStorage(per-tab)不寫進 .env.local。同一個 tab 內 refresh 會保留欄位與驗證狀態;關掉 tab 或開新 tab 就清空 — 公用機器上的下一個使用者不會看到上一個人的金鑰。

.env 中的 AKSK 欄位(TOS_ACCESS_KEYTOS_SECRET_KEYBYTEPLUS_AKBYTEPLUS_SK)僅供 npm run tos:bootstrap 這個 operator-only 的 CLI script 使用,執行中的 server 不再讀取這些變數

兩組 AKSK 的差別(很重要)

本專案需要兩組分離的 AccessKey / SecretKey,分別承擔不同職責,請不要共用同一組:

AKSK 在哪裡輸入 需要的權限 用途
TOS 那組 Drawer →「物件儲存憑證」(或點 Header TOS 膠囊) TOS Full Access 簽 PUT / GET pre-signed URL;驗證時自動 bootstrap bucket CORS
ARK 那組 Drawer →「私有素材庫憑證」(或點 Header 素材庫 膠囊) ARK Full Access 呼叫 ARK Asset Open API(建立 / 列出 / 刪除私有素材)

關鍵是 TOS 與 ARK 各用一把獨立的 AKSK、權限只給對應 service(不要直接給 Admin / 全權限 key)。它們可以是同一個 BytePlus 帳號下的兩把 key,只要權限分開即可。

⚠️ ep / API key / Asset 必須在同一個 BytePlus 帳號與 project 下

要在影片生成時透過 asset://asset-xxx 引用私有素材庫的 asset,三項憑證必須處在同一個 BytePlus 帳號 + 同一個 project

  • 推論端:Seedance Endpoint ID(ep-...)+ ModelArk API Key — 隸屬於某個 BytePlus 帳號 / project
  • 素材端:ARK AKSK + Project Name — 也隸屬於某個 BytePlus 帳號 / project

如果推論用 A 帳號 / project-X 的 ep + API key,但 asset 是在 B 帳號 / project-Y 建立的,Seedance 推論時會找不到 asset,會失敗。**三邊都對齊到同一個 BytePlus 帳號 + 同一個 project(多數人就是 default)**才能 cross-reference。

TOS 不受此限制 — bucket 可以在不同 BytePlus 帳號或不同 project(pre-signed URL 是純 HTTPS GET,不依賴帳號 / project 對齊)。


一次性設定

1. 安裝相依

cd byteplus-ai-gen-platform
npm install

2. 在 BytePlus console 建立 TOS Bucket(首次必做)

到 BytePlus TOS console 建立一個 bucket:

  • Region 自選(例如 ap-southeast-1),之後在 Drawer →「物件儲存憑證」下拉選單選 region;Endpoint 由 Region 自動推導(tos-<region>.bytepluses.com),不用手動填
  • 不需要先設 CORS — 在 Drawer 填入 TOS 憑證並按「測試連線」時會自動套上

預設所有素材會丟在 seedance-2-0/ 前綴下,避免污染既有資料。要分流可在 Bucket 欄位輸入 mybucket/myproject/,前綴就會用 myproject/

3. 設定環境變數(選用)

.env.local 對於一般使用是非必要的——所有 AKSK 憑證都從 Header 膠囊 / Drawer 填入,TOS bucket 的 CORS 也會在 UI 驗證憑證時自動套上。

只有在需要覆寫 server runtime 選項(PORTEXPORTS_DIR 等)時才需要建立 .env.local

cp .env.example .env.local
# 僅填 server runtime 選項(PORT、EXPORTS_DIR 等)

.env.local 已被 .gitignore 排除,不會進 git。

4. 啟動 dev server

npm run dev

npm run dev 會用 concurrently 同時跑兩個 process:

開啟 **http://localhost:5173**,會自動導向影片生成頁。

如果只想跑前端(後端已經單獨啟動),用 npm run dev:vite-only

5.(建議)Smoke test:確認設定都吃進去了

npm test                 # 全套單元測試

第一次使用流程

  1. 點 Header 右上角任一灰色膠囊(或按 ⌘, / Ctrl+, 開齒輪 drawer)設定憑證:

    • 推論憑證:填入 ModelArk API Key(純 UUID 或 ark-{UUID}-{5字元} 格式)和 Endpoint ID(格式如 ep-20260327154051-xxxxx)。輸入即時驗證,格式正確 pill 立刻變綠。
    • 私有素材庫憑證:填入 ARK AccessKey / SecretKey;Project Name 預設已是 default(多數 tenant 自動建好的 project),按「測試連線」確認後端能用該 AKSK 通過 ARK
    • 物件儲存憑證:下拉選 Region、填 TOS AccessKey / SecretKey、Bucket(純名稱或 mybucket/prefix),按「測試連線」確認連線(驗證時會自動設定 bucket CORS)

    三顆膠囊全綠(已驗證)後就可以開始用。憑證存在 sessionStorage,這個 tab 內有效。

  2. 在「提示詞」欄位描述你想要的影片內容;可在文字中以 [Image 1] / [Video 1] / [Audio 1] 引用素材(編號規則見下方「提示詞素材編號」說明,非必填,不寫也能正常生成)。

  3. (可選)上傳參考圖片 / 影片 / 音訊(影片與音訊會自動上傳到 TOS 並轉成短效 pre-signed URL)。

  4. (可選)在「Asset 參考」加入 asset-xxxxx ID、asset:// URI 或 https://... URL(會被解析成正確的引用形式)。

  5. (可選)調整 Seed(預設 -1 = 每次隨機;指定整數可在相同提示詞下取得相似輸出;🎲 按鈕產生隨機固定值方便重現)。

  6. 調整畫面比例(預設 Adaptive,模型自選)、影片長度(4–15 秒,或 Auto)。

  7. 點擊「🎬 生成」按鈕;任務會送出後自動輪詢,影片完成後出現在中間預覽區。

  8. 完成後可在右側「📋 任務紀錄」匯出 JSON 或下載本地 mp4;下次啟動 dev server 點「📥 匯入」即可載入歷史任務並回放。


主要功能

私有素材庫管理頁(/assets

從頂部 tab「私有素材庫管理」進入,可以:

  • 列出 / 搜尋既有 Asset Group 與 Asset(Image / Video / Audio)
  • 建立 Asset Group(首次使用可以先建一個 default 試試)
  • 上傳檔案(先進 TOS pre-signed PUT,再以 12 小時 GET URL 給 ARK 的 CreateAsset,背景每 5 秒輪詢 GetAsset 直到 Active / Failed)
  • 狀態視覺化:卡片右上角依 Asset 狀態顯示 — Active 綠色小點(最低調)、Processing 黃色「處理中」膠囊 + 旋轉中 spinner、Failed 紅色「失敗」膠囊 + ⚠️ 預覽 placeholder
  • 狀態過濾 chips:在類型過濾旁邊有第二列 chips(全部 / Active / Processing / Failed),server-side 過濾
  • 背景輪詢:列表上的 Processing 資產每 5 秒自動 poll → 終態時自動翻成 Active / Failed,不用手動 refresh
  • 重新命名 Asset / Asset Group;級聯刪除 Asset Group(需輸入完整名稱二次確認)
  • 點縮圖開預覽抽屜(影片用 preload=metadata,按 play 才下載);抽屜的「基本資訊」區塊也有狀態欄位
  • 一鍵複製 asset://asset-xxx URI,回到「影片生成」頁貼到「Asset 參考」即可使用

注意事項:

  • ARK Asset API 採 AK/SK 簽章server/routes/asset.ts 在 server 端做 HMAC-SHA256 v4);瀏覽器只打 /local-api/asset/*,密鑰不進前端。
  • 上傳的 12 小時 GET URL 是給 ARK 用的內部 URL;UI 預覽用的 URL 來自 ListAssets / GetAsset,同樣 12 小時,每次重整頁面會重抓。
  • DeleteAssetGroup 會級聯刪除組內所有資產,不可復原。
  • CreateAsset 一律送 Moderation = { Strategy: "Skip" }(先在 console 關閉 Secure Mode)。

多模態參考生成(Seedance 2.0 規格)

  • 參考圖片 0–9 張(1 張=圖生影片;多張=多模態 / 角色參考)
  • 參考影片 0–3 段,總長 ≤ 15 秒(mp4 / mov)
  • 參考音訊 0–3 段,總長 ≤ 15 秒(wav / mp3)
  • Asset 參考 任意數量,引用 ModelArk 私有素材庫的素材。欄位接受三種輸入:純 asset-xxx ID、asset://asset-xxx URI、或 https://... URL(可串接前一段任務的 last_frame_url / video_url

影片任務生命週期管理

  • 可設定的任務最長等待時間:左側「進階設定」下拉,1 / 2 / 4 / 8 / 24 / 48 / 72 小時(預設 1 小時)。對應 ARK 的 execution_expires_after,超過 server 端會自動標 expired,不會卡在排隊狀態無限期算配額。
  • 狀態 + 即時計時:每張卡顯示 排隊中 / 處理中 / 完成 / 失敗 / 已取消 / 已過期,queued/running 卡片即時跳秒(mm:ss),終態凍結在最終時長。orphaned chip(⚠ 無法查詢)出現代表此任務由其他金鑰建立、現在這把金鑰沒權限查詢。
  • 取消 / 刪除按鈕queued 狀態的「取消任務」呼叫 ARK DELETE → 立即停扣費;終態的「刪除記錄」清前端記錄(ARK 後端記錄 7 天後自動清)。
  • 背景輪詢:App 層 useBackgroundPolleractiveTaskIds,每個任務一個獨立 loop。間隔採 3 段 backoff(0–30s 用 3s、30s–5min 用 10s、5min+ 用 20s),status 變化時 reset。沒 API key 時暫停(不移除);401/403 時標 orphan 並停止。
  • 重整恢復videoStore 用 sessionStorage 持久化(per-tab)— 同 tab refresh 後 history、進行中任務、設定都還在;polling 自動恢復。關 tab / 新 tab 起就乾淨,不會洩漏給共用瀏覽器的下一個使用者。
  • 參考媒體失效提示:reload 後參考媒體因 File 物件無法序列化變成 stale chip(📎+filename+已失效);submit 時會 toast 擋下,要使用者移除後重新上傳。

提示詞素材編號

模型會解析 prompt 中的 [Image 1] / [Video 1] / [Audio 1] 等記號,數字依該類型在 content 陣列中的出現順序遞增。前端 UI 會即時在每個縮圖右下角顯示對應編號(例如已上傳 2 張圖、再加 1 個 image-type asset → asset 行右側顯示 [Image 3]),不需要手動數。

一鍵插入:縮圖右下角的徽章和 Asset 參考 row 右側的 [Type N] 都是按鈕 — 點下去會把對應字串插入到提示詞 textarea 的游標位置(前一個字非空白會自動補空白),免手動打或複製貼上。Asset 參考的按鈕在欄位內容是有效形式時(asset-xxx / asset://... / http(s)://...)才會啟用。

任務匯出 / 匯入

  • 每個成功的任務都可以匯出成 JSON(含完整 request body 與結果)
  • 匯出檔存在 exports/ 下;本地 mp4 存在 exports/mp4/
  • 「📥 匯入」可選多份 JSON 載入,並自動把最後一個有 video 的任務帶到中間預覽區(不會自動播放)
  • 匯入項目顯示「📥 已匯入」標籤以區別現場生成的任務

安全的影片 / 音訊上傳

不使用 base64 inline(Seedance 影片不支援;音訊 base64 上限 64 MB),改走 TOS pre-signed URL

Browser ──① POST /local-api/tos/sign-put──▶ Express server(瀏覽器隨請求帶 TOS creds)
        ◀── PUT pre-signed URL(15 分鐘有效)─
        ──② PUT file ────────────────────────▶ TOS bucket
        ──③ POST /local-api/tos/sign-get ───▶ Express server
        ◀── GET pre-signed URL(3 小時有效)─
        ──④ POST /api/v3/contents/...──────▶ Seedance(用 GET URL 拉影片)

AKSK 由瀏覽器隨請求帶到 Express server 做簽章,server 簽完即丟、不持久化。GET URL 過期後物件不再具備 public access。


可用指令

# 開發 / 建置
npm run dev              # concurrently 跑 Vite (5173) + Express (3000)
npm run dev:vite-only    # 僅 Vite,假設 Express 已另外啟動
npm run server:dev       # 只跑 Express dev server (tsx watch)
npm run server:start     # production: NODE_ENV=production tsx server/index.ts
npm run build            # tsc -b && vite build
npm run preview          # 預覽生產版本
npm run lint             # ESLint 檢查

# 測試(vitest + jsdom + Testing Library)
npm test                 # 跑全部測試
npm run test:watch       # watch 模式

# TOS bucket 設定(通常不需要手動跑 — UI 驗證 TOS 憑證時會自動 bootstrap CORS)
npm run tos:bootstrap    # 手動套用 CORS rule(idempotent;需要 .env.local 中的 TOS AKSK)
npm run tos:cors:show    # 印出目前 bucket 的 CORS 設定(純讀;需要 .env.local 中的 TOS AKSK)

專案結構

byteplus-ai-gen-platform/
├── scripts/
│   ├── tos-bootstrap.ts            # 透過 TOS AKSK 設定 bucket CORS(npm run tos:bootstrap)
│   └── loadDotenv.ts               # 共用 .env / .env.local 載入器
├── server/                         # Express 後端(取代舊的 vite-plugin-*)
│   ├── index.ts                    # entry: load env + listen
│   ├── app.ts                      # createApp() 工廠 — 掛載所有 router
│   ├── config/env.ts               # PORT / NODE_ENV 載入與驗證
│   ├── signers/
│   │   ├── tos.ts                  # TOS pre-signed URL 純邏輯(dev + prod 共用)
│   │   ├── tosCors.ts              # TOS bucket CORS merge / 驗證邏輯
│   │   └── asset.ts                # ARK Asset HMAC v4 簽章純邏輯
│   └── routes/
│       ├── tos.ts                  # POST /local-api/tos/sign-put | /sign-get
│       ├── tosVerify.ts            # POST /local-api/tos/verify(BYO 憑證驗證 + CORS bootstrap)
│       ├── asset.ts                # POST /local-api/asset/*(catch-all dispatch)
│       ├── assetVerify.ts          # POST /local-api/asset/verify(ARK 憑證驗證)
│       ├── localApi.ts             # 匯出 / 匯入 / 下載 mp4 / asset / frame
│       ├── exportsStatic.ts        # GET /exports/* 靜態檔
│       ├── arkProxy.ts             # /api/* → ark.ap-southeast.bytepluses.com
│       └── spaFallback.ts          # production: 服務 dist/ + index.html fallback
├── vite.config.ts                  # Vite 設定 + /local-api、/exports、/api proxy 到 Express (3000)
├── docs/
│   └── superpowers/                # 開發計畫 / 規格文件(plans/, specs/)
├── src/
│   ├── api/
│   │   ├── client.ts               # axios 實例 + 自動帶 API Key
│   │   ├── video.ts                # Seedance 任務 CRUD + 輪詢
│   │   ├── asset.ts                # ARK Asset 庫前端 client(走 server/routes/asset.ts)
│   │   ├── tos.ts                  # 前端 TOS client(走 server/routes/tos.ts)
│   │   ├── verify.ts               # 前端憑證驗證 client(走 tosVerify + assetVerify)
│   │   ├── local.ts                # 前端對 server/routes/localApi.ts 的 client
│   │   └── fileUtils.ts            # base64 轉換(圖片用)
│   ├── components/
│   │   ├── layout/
│   │   │   ├── AppLayout.tsx
│   │   │   └── Header.tsx
│   │   ├── common/
│   │   │   └── ResizeHandle.tsx    # 三欄佈局的拖拉縮放 handle
│   │   ├── credentials/             # Header pills + 右側 Drawer 憑證 UI
│   │   │   ├── schema.ts            # CREDENTIALS 常數(推論 / 素材庫 / TOS 三組定義)
│   │   │   ├── uiStore.ts           # drawer open + target + expandedSection(無持久化)
│   │   │   ├── HeaderStatusPills.tsx # Header 三顆狀態膠囊
│   │   │   ├── CredentialsDrawer.tsx # 右側 drawer 容器(accordion)
│   │   │   ├── CredentialSection.tsx # 單一憑證區塊(展開 / 收合 + 驗證狀態 pill)
│   │   │   └── CredentialForm.tsx   # schema-driven 欄位渲染
│   │   ├── video/
│   │   │   ├── VideoGenPage.tsx    # 三欄佈局
│   │   │   ├── VideoParams.tsx     # 左側參數面板(含 seed 輸入、可點 [Type N] 插入 prompt)
│   │   │   ├── VideoPreview.tsx    # 中間預覽(無 autoplay)
│   │   │   ├── VideoHistory.tsx    # 右側任務紀錄 + 匯入 / 匯出
│   │   │   └── MediaUploader.tsx   # 拖拽上傳 + 縮圖 + 可點 [Type N] 徽章(onLabelClick prop)
│   │   └── assets/                 # 私有素材庫管理頁(/assets)
│   │       ├── AssetLibraryPage.tsx     # 整頁佈局 + 過濾 chips + 背景輪詢 mount
│   │       ├── AssetGroupSidebar.tsx
│   │       ├── AssetGrid.tsx
│   │       ├── AssetCard.tsx            # 含狀態膠囊 / Active 綠點 / Failed ⚠️ placeholder
│   │       ├── AssetTypeFilterChips.tsx
│   │       ├── AssetStatusFilterChips.tsx # 全部 / Active / Processing / Failed(server-side 過濾)
│   │       ├── AssetPreviewDrawer.tsx   # 含狀態欄位 / Failed / Processing 預覽分支
│   │       ├── AssetUploadDialog.tsx
│   │       ├── AudioWaveformDecoration.tsx
│   │       └── UploadProgressPanel.tsx
│   ├── hooks/
│   │   ├── useVideoGeneration.ts   # 建任務 + 寫 history(不 await polling,由 useBackgroundPoller 接手)
│   │   ├── useBackgroundPoller.ts  # App 層常駐:掃 activeTaskIds、3-tier backoff、auth-pause、orphan 偵測
│   │   ├── useNow.ts               # 1 秒 ticker(live mm:ss elapsed 計時用;intervalMs=0 可停用)
│   │   ├── useReferenceUpload.ts   # 影片 / 音訊收檔即上傳 TOS(id-based 容錯)
│   │   ├── useAssetUpload.ts       # /assets 頁的並行上傳(最多 8 條)+ ARK CreateAsset 5s 輪詢(含 429 退避)
│   │   ├── useAssetStatusPoller.ts # 背景輪詢 Processing 資產(5s);跳過上傳中的 id 避免雙 poller race
│   │   └── useResizableWidth.ts    # 欄寬拖拉持久化 hook
│   ├── stores/
│   │   ├── authStore.ts            # API Key / Endpoint / AssetCreds / TosCreds + verifyState(sessionStorage 持久化,per-tab)
│   │   ├── videoStore.ts           # 影片參數 / 參考素材 / 任務狀態 / 歷史(sessionStorage 持久化;參考媒體 reload 後保留 filename 並標 stale)
│   │   └── assetStore.ts           # /assets 頁的 group / asset 列表狀態
│   ├── types/
│   │   ├── index.ts                # 影片生成相關型別(API + UI store)
│   │   └── asset.ts                # ARK Asset 庫型別
│   ├── utils/
│   │   ├── contentLabels.ts        # [Image N] / [Video N] / [Audio N] 編號計算
│   │   ├── mediaValidation.ts      # 影片 / 音訊長度與格式驗證
│   │   ├── panelScale.ts           # 三欄佈局縮放邏輯
│   │   ├── tosRegions.ts           # TOS region 列表 + region → endpoint 轉換
│   │   ├── tosBucket.ts            # parseBucketAndPrefix(接受純 bucket 或 bucket/prefix)
│   │   └── clipboard.ts            # asset:// URI 複製
│   ├── __tests__/                  # vitest + jsdom 測試(執行 `npm test` 看當前數量)
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css                   # 深色主題樣式
├── .env.example                    # 環境變數範本(commit)
├── .env.local                      # 真實值(git 排除)
└── exports/                        # 匯出的 JSON 與 mp4(git 排除)

系統設計重點

server/routes/tos.ts + tosVerify.ts(簽章邏輯在 server/signers/tos.ts,CORS 邏輯在 server/signers/tosCors.ts

Express 上掛 /local-api/tos/* 三個 endpoint:

  • POST /sign-put 接收 { filename, contentType, creds },回 PUT pre-signed URL(15 分鐘有效)+ 物件 key
  • POST /sign-get 接收 { key, expiresSec?, creds },回 GET pre-signed URL(預設 3 小時,上限 7 天)
  • POST /verify 接收 { creds },驗證 TOS 憑證是否有效(headBucket),並自動將當前 origin merge 進 bucket 的 CORS 規則(idempotent)

物件 key 格式:${keyPrefix}${YYYY}/${MM}/${uuid}-${sanitized-filename},由 server 端組(client 不能任意指定 key)。keyPrefix 由前端 parseBucketAndPrefix 從 Bucket 欄位拆出(純 bucket → seedance-2-0/ 預設;mybucket/foofoo/)。所有 endpoint 的 AKSK 都由瀏覽器隨請求帶入(BYO 模式),server 不持久化;endpoint 也在 server 端透過 withDerivedEndpoint/deriveEndpoint 從 region 推導,不必由 client 帶。

server/routes/asset.ts(簽章邏輯在 server/signers/asset.ts

Express 上掛 /local-api/asset/*,把瀏覽器請求轉發到 ark.ap-southeast-1.byteplusapi.com,並用 ARK 那組 AKSK 在 server 端做 HMAC-SHA256 v4 簽章。瀏覽器只看到 /local-api/asset/*,AKSK 與 ProjectName 不會進前端。涵蓋的 ARK Asset Open API:ListAssetGroups / CreateAssetGroup / DeleteAssetGroup / ListAssets / CreateAsset / GetAsset / DeleteAsset,以及一個 download-asset 代理(避免 CORS 直下載)。

server/routes/localApi.ts

Express 上掛 /local-api/* 提供本機 file system 操作:

  • POST /export 寫入任務 JSON 到 exports/<task_id>.json
  • POST /download-mp4 下載遠端影片 → exports/mp4/<task_id>.mp4
  • GET /list-exportsGET /import/:taskIdGET /check-mp4/:taskId

useReferenceUpload

影片 / 音訊上傳採 id-based 索引,每個項目進 store 時帶一個 crypto.randomUUID()。當 promise 完成時用 id 找回對應項目,使用者中途移除 / 新增不會污染其他項目。

useVideoGeneration

  • Create-only 流程:建任務 + push taskIdactiveTaskIds + 寫 queued history entry,不 await polling(由 useBackgroundPoller 接手)
  • execution_expires_after 給 ARK(從 videoStore.executionExpiresAfter 讀取)
  • 影片 / 音訊一律使用 media.uploadedUrl(不再 fallback 到 base64,因 Seedance 影片不支援)
  • 參考素材尚未完成上傳、或重整後變 stale 時拒送並 toast 提示
  • content 陣列順序固定為 [text, …images, …videos, …audios, …assets],與 [Type N] 編號規則對齊

useBackgroundPoller

App 層 <App> 內常駐 mount 一次(無 cleanup,移到 route 元件會 leak loops)。掃 videoStore.activeTaskIds,每個 taskId 一個獨立 polling loop,用 useRef<Set<string>> dedupe 避免 re-render 重複起 loop。

  • 3-tier backoff:0–30s 間隔 3s、30s–5min 間隔 10s、5min+ 間隔 20s;status 變化 reset 計時。
  • Auth-pause:偵測到 authStore.apiKey 為空 → sleep 5s 重試(不移除任務,等使用者填回金鑰)。
  • Orphan 偵測apiClient interceptor 把 HTTP status 黏到 Error 物件上;poller 檢查 err.status === 401 || 403 → 標 orphaned: true + removeActiveTask
  • 取消 race 防護getVideoTask resolve 後再檢查 activeTaskIds.includes(taskId),使用者中途取消 → 直接 return,不寫陳舊 history。
  • 終態處理succeeded / failed / cancelled / expired 都會寫 updatedAt + 對應欄位(succeeded 寫 videoUrl/lastFrameUrl/seed/resolution/fps;failed 寫 error),然後 removeActiveTask

測試

執行 npm test 看當前測試結果(檔案數與 case 數會隨開發增加,以實際輸出為準)。主要涵蓋範圍(非完整列表):

影片生成模組

檔案 範圍
videoApi.test.ts Seedance 任務 CRUD、3 段 polling backoff (nextPollInterval)、timeout 後最後一次 sync 不 throw
videoGeneration.test.ts Create-only 流程:寫入 history + 進 activeTaskIds、發 execution_expires_after、validation、stale ref 阻擋
videoStore.test.ts store actions、resetForNewTask、預設值、removeHistoryexecutionExpiresAfterEXECUTION_EXPIRES_OPTIONS 範圍
videoStorePersist.test.ts sessionStorage 持久化 round-trip:prompt、設定、history、refs flatten 為 filename stub、rehydrate 為 stale
videoParams.test.tsx 左側面板:layout 順序、新任務、duration/ratio 選項、label 顯示、任務最長等待時間 下拉
videoHistory.test.tsx 右側面板:所有項目都顯示、imported tag、匯入流程、auto preview、已過期 / 無法查詢 狀態、live duration、取消任務、刪除記錄
videoPreview.test.tsx 中間預覽:無 autoplay、有 controls、source 優先順序
useBackgroundPoller.test.tsx App 層 polling:終態移除、dedupe、401 → orphaned、expired 寫入、無金鑰暫停、failed 帶 updatedAt
useNow.test.ts 1 秒 ticker:初值、推進、unmount cleanup、intervalMs=0 不啟動 setInterval
referenceUpload.test.ts 上傳 hook:成功 / 失敗 / 移除中 / 多筆併發
mediaUploader.test.tsx 拖拽上傳 UI:accept rule、[Type N] badge、stale rehydrated chips(📎+filename+已失效)+ 移除按鈕
mediaValidation.test.ts 影片 / 音訊長度與格式驗證
contentLabels.test.ts [Image N] / [Video N] / [Audio N] 編號計算
exportImport.test.ts 匯出 payload 結構、匯入解析邏輯

私有素材庫模組

檔案 範圍
assetApi.test.ts ARK Asset Open API client
assetStore.test.ts group / asset 列表 store actions
assetTypes.test.ts image/video/audio 型別與 mime 判斷
assetLibraryPage.test.tsx 整頁佈局與互動
assetGroupSidebar.test.tsx 左側 group 列表:建立 / 重新命名 / 級聯刪除
assetCard.test.tsx card v2:type badge、media branches
assetTypeFilterChips.test.tsx 型別過濾 chips + 計數
assetStatusFilterChips.test.tsx 狀態過濾 chips(全部/Active/Processing/Failed)+ aria-pressed
useAssetStatusPoller.test.ts 背景輪詢 hook:每 Processing id 一個 poller、不重複註冊、跳過 terminal、跳過上傳中
assetUploadDialog.test.tsx 上傳對話框:file picker、name cap、moderation skip
useAssetUpload.test.ts 並行上傳 hook(最多 8 條)+ ARK CreateAsset 輪詢(含 429 退避)
uploadProgressPanel.test.tsx 上傳進度面板:成功 / 失敗 / 進行中
audioWaveform.test.tsx 音訊縮圖視覺化
vitePluginAsset.test.ts plugin 純函式:HMAC v4 簽章、canonical request、URL 組裝

憑證 / 認證模組

檔案 範圍
headerStatusPills.test.tsx Header 三顆狀態膠囊:data-state、aria-label、click → openDrawer(key)
credentialsDrawer.test.tsx Drawer 容器:open class、ESC、backdrop、accordion 一次只開一個、target highlight、inert
credentialSection.test.tsx 單一 accordion section:title/hint、toggle、target autoFocus、測試連線、pending disable
credentialForm.test.tsx Schema-driven 表單:欄位渲染、secret type、setField 寫入、warn banner
authStoreVerify.test.ts verifyState slice:setField reset、verify 三條 path(local 格式 / asset / tos)
authStoreMigration.test.ts zustand auth store 版本遷移(v1→v2→v3→v4→v5)+ localStorage 清理

TOS / 本機 API 共用

檔案 範圍
tosClient.test.ts 前端 TOS client(mock fetch;含 bucket/prefix 解析驗證)
tosBucket.test.ts parseBucketAndPrefix:純 bucket / bucket+prefix 各種輸入
tosRegions.test.ts TOS_REGIONS list + regionToEndpoint helper
vitePluginTos.test.ts plugin 純函式:env 載入、key 組裝、簽名邏輯、TTL 邊界
tosBootstrap.test.ts bootstrap script:env 校驗、CORS payload、showOnly
tosCorsMerge.test.ts CORS merge 邏輯:idempotent rule 合併、wildcard 處理
localApi.test.ts 本機 export / import / mp4 client

Server 端路由(Express + supertest)

檔案 範圍
serverTosRoutes.test.ts TOS sign-put / sign-get 路由
serverTosVerify.test.ts TOS verify 路由:憑證驗證 + CORS bootstrap
serverAssetRoutes.test.ts ARK Asset catch-all dispatch 路由
serverAssetVerify.test.ts ARK Asset verify 路由
serverArkProxy.test.ts /api/* → ARK Open API proxy
serverExportsStatic.test.ts /exports/* 靜態檔服務
serverLocalApiRoutes.test.ts 本機 file system 操作路由
serverSpaFallback.test.ts production SPA fallback

src/__tests__/setup.ts 內含 in-memory localStoragesessionStorage polyfill,確保在 Node 25 + jsdom 環境下 zustand persist middleware(現在用 sessionStorage)不會掛掉。


部署到生產環境

後端已抽出為獨立 Express server(server/ 目錄)。生產部署完整教程:

  • docs/deployment-guide.md — 從 BytePlus VM 0→1(Phase 0 後端抽取已完成;Phase 1 起:VM provisioning、Nginx 反向代理、Let's Encrypt、systemd service、備份)

產品環境啟動:

npm run build
NODE_ENV=production PORT=3000 npm run server:start

Express 同時負責 API 路由與 dist/ 的 SPA 靜態服務,無需另外跑 Vite。


後續開發

  • 圖片生成(Seedream 模型)
  • Chat / LLM
  • 虛擬人模型
  • 語音模型
  • TOS bucket lifecycle 自動清理
  • 上傳前 client-side 影片時長檢查(防 > 15s)

About

BytePlus Seedance 2.0 video generation web app with BYO credentials and a TOS-backed asset library. React + Vite + TypeScript + Express.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages