visionA/docs/autoflow/03-design/flows/flow-model-upload.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

18 KiB
Raw Permalink Blame History

模型上傳流程 — visionA Cloud

雲端版新增流程。使用者上傳 Kneron 編譯後的 .nef 模型檔到雲端 object storage之後才能選模型燒錄到遠端裝置。

技術背景(給 Design 協作者):模型檔可能達 100MB若走 tunnel / API server 會拖慢服務。Architect 決策走 presigned PUT:前端向後端要一組有期限的 PUT URL前端直接把檔案 PUT 到 object storageS3 / R2 / GCS不經過 API server 也不經過 local agent tunnel。進度 / 失敗偵測靠瀏覽器 XMLHttpRequest.upload.onprogress

對應頁面:/models(入口) + UploadModelDialog(模態框,或走獨立 /models/upload 頁,見 6 節決策)

配套文字版 wireframewireframes/wf-model-upload.md(本次一併建立概念,實作期補圖)


1. User Story

作為 一個 Kneron 開發者, 我想要 把本地編好的 .nef 模型檔上傳到雲端, 這樣 我就能在任何裝置、任何地方把這個模型燒錄到 Kneron 硬體上。

成功條件:

  • 100MB 雛形上限內的檔案可在 3G 以上網速下 60 秒內完成
  • 上傳失敗能明確告訴使用者原因(網路、過期、大小、格式)並可續傳 / 重試
  • 上傳中不阻塞其他頁面操作(背景上傳)

2. 範圍與限制Phase 0 雛形)

項目 Phase 0 Phase 1+
單檔最大 100 MB(前端硬限) 2 GB + 分段上傳
副檔名 .nef 限定(前端驗證) .nef / .onnx / 其他
同時上傳數 1雛形不支援佇列 N 個並行
中途暫停 不支援 支援
續傳 失敗後全檔重傳 分段續傳
取消 支援abort XHR
背景上傳 不支援(離開頁面 = 中斷) 支援Service Worker
病毒掃描進度 不顯示(後端非同步處理) 顯示掃描狀態

3. 完整流程

使用者在 /models 按「上傳模型」
       ↓
開啟 UploadModelDialog
       ↓
┌────────────────────────┐
│ 步驟 A · 選檔 + 填 meta │
│  - 檔案 (.nef)          │
│  - 名稱、版本、備註        │
└────────────────────────┘
       ↓  按「開始上傳」
       ↓
前端驗證:
  - 副檔名 .nef
  - 大小 ≤ 100 MB
  - 必填欄位
  驗證失敗 → 顯示 error不發 API
       ↓
POST /api/models/upload-url
  body: { filename, size, contentType, metadata }
  → 後端產 presigned PUT URLTTL 15 分鐘)
       ↓
前端 PUT file 直接到 storage URL不經 API server
  - XHR.upload.onprogress → 更新進度條
  - 可 abort
       ↓
  成功 (HTTP 200)                       失敗
       ↓                                 ↓
POST /api/models/confirm              顯示錯誤原因
  body: { uploadId, etag }            → 重試 / 重新取 URL
  → 後端驗證 checksum、寫入 DB
       ↓
Toast「✓ 模型 {name} 已上傳」
關閉 Dialog回到 /models 列表(新模型顯示在頂部)
       ↓
後端非同步掃毒 / 解析 metadata → 狀態更新WebSocket push

4. UI 設計

4.1 入口(/models 頁面)

┌────────────────────────────────────────────────┐
│ 模型庫                                           │
│ 管理雲端上的 Kneron 模型                         │
│                                                 │
│                              [📤 上傳模型]       │
├────────────────────────────────────────────────┤
│ ModelCard · ModelCard · ModelCard ...           │
└────────────────────────────────────────────────┘

「上傳模型」按鈕:

  • variant=default size=default
  • 右上角(既有版型)
  • 圖示:Upload (Lucide)

4.2 UploadModelDialog — 選檔階段

┌─────────────────────────────────────────────────┐
│ 上傳模型                              [✕]        │
├─────────────────────────────────────────────────┤
│                                                  │
│  ┌───────────────────────────────────────────┐  │
│  │                                           │  │
│  │          📁  拖曳 .nef 檔到此處            │  │
│  │                                           │  │
│  │              或 [選擇檔案]                 │  │
│  │                                           │  │
│  │      支援格式:.nef · 最大 100 MB          │  │
│  │                                           │  │
│  └───────────────────────────────────────────┘  │
│  (bg-muted/50 border-2 border-dashed, h-48)      │
│                                                  │
│  已選檔案:                                       │
│  ┌───────────────────────────────────────────┐  │
│  │ 📄 yolov5s_kl520.nef                       │  │
│  │    47.3 MB · 修改於 2026-04-21             │  │
│  │                                   [✕ 移除] │  │
│  └───────────────────────────────────────────┘  │
│  (選好後才出現hover 淡 bg-accent)               │
│                                                  │
│  模型名稱 *                                      │
│  ┌───────────────────────────────────────────┐  │
│  │ YOLOv5s KL520                             │  │
│  └───────────────────────────────────────────┘  │
│  (預設帶入檔名去副檔名,可編輯)                    │
│                                                  │
│  版本                                            │
│  ┌───────────────────────────────────────────┐  │
│  │ v1.0                                       │  │
│  └───────────────────────────────────────────┘  │
│                                                  │
│  備註(選填)                                     │
│  ┌───────────────────────────────────────────┐  │
│  │ 針對戶外停車場場景訓練                     │  │
│  └───────────────────────────────────────────┘  │
│  (Textarea, rows=3)                              │
│                                                  │
│                         [取消]   [開始上傳]       │
└─────────────────────────────────────────────────┘

規格:

  • Dialog max-w-lg
  • Drop zoneh-48 border-2 border-dashed rounded-lg bg-muted/50hover / drag over 時 border-primary bg-primary/5
  • <input type="file" accept=".nef" hidden>
  • 「開始上傳」:檔案未選 / 必填未填 → disabled

4.3 UploadModelDialog — 上傳中

┌─────────────────────────────────────────────────┐
│ 上傳中                                [取消]     │
├─────────────────────────────────────────────────┤
│                                                  │
│  📄 yolov5s_kl520.nef                            │
│                                                  │
│  ████████████████████░░░░░░░░░░  62%             │
│  (Progress 元件, h-2, bg-primary)                │
│                                                  │
│  28.5 MB / 47.3 MB · 3.4 MB/s · 剩餘約 6 秒      │
│  (text-sm text-muted-foreground)                 │
│                                                  │
│  ⓘ 請勿關閉此視窗或導航到其他頁面                  │
│                                                  │
└─────────────────────────────────────────────────┘

規格:

  • Progress bar既有 Progress 元件
  • 百分比:XHR.upload.onprogressloaded / total
  • 速度:滑動視窗(最近 3 秒的 loaded 差 / 時間差)
  • 剩餘時間:(total - loaded) / speed< 5 秒顯示「即將完成」
  • Dialog onOpenChange 被阻擋:正在上傳時 ESC / 點外面關 dialog防誤觸使用者只能按「取消」
  • 「取消」AlertDialog「確定要取消上傳已傳的資料會作廢。」→ 確認後 xhr.abort()

4.4 UploadModelDialog — 成功

┌─────────────────────────────────────────────────┐
│                                                  │
│                  ✓                                │
│           (CheckCircle2, green, h-16)            │
│                                                  │
│           上傳完成                                │
│                                                  │
│      yolov5s_kl520.nef (47.3 MB)                 │
│                                                  │
│      模型即將進入安全掃描,完成後可燒錄             │
│                                                  │
│                         [完成]                   │
└─────────────────────────────────────────────────┘
  • 按「完成」關 Dialogtoast「✓ 模型 YOLOv5s KL520 已上傳」
  • 列表頂部插入新模型,狀態 badge「掃描中」Phase 1 精緻化)

4.5 UploadModelDialog — 錯誤狀態

不同錯誤顯示不同文案與行動:

錯誤 文案 CTA
副檔名錯誤(前端擋) 「只支援 .nef 檔案,你選的是 .onnx」 [重新選檔]
超過大小(前端擋) 「檔案太大({size} MB最大允許 100 MB」 [重新選檔]
取不到 presigned URL 「伺服器忙碌,請稍後再試」 [重試] [取消]
Presigned URL 過期PUT 403 「上傳授權已過期,重新取得中...」→ 自動重拿 URL 重傳(僅 1 次) 自動
網路中斷 「網路中斷,上傳已暫停」 [繼續上傳](雛形=從頭重傳) [取消]
Storage 回 5xx 「儲存空間暫時無法回應,請稍後再試」 [重試] [取消]
Confirm API 失敗 「上傳完成,但伺服器確認失敗,請重新上傳」 [重新上傳]
取消(使用者觸發) 不顯示錯誤 關 Dialog

錯誤視覺:

┌─────────────────────────────────────────────────┐
│ ⚠  上傳失敗                                      │
│                                                  │
│  yolov5s_kl520.nef                               │
│                                                  │
│  網路中斷,上傳已暫停                              │
│                                                  │
│  已上傳 28.5 MB / 47.3 MB62%                  │
│                                                  │
│          [取消]         [重試]                   │
└─────────────────────────────────────────────────┘

5. 互動細節

5.1 拖曳上傳

  • Drop zone 接受 .nef其他格式drop 後立刻顯示錯誤
  • 整個視窗偵測 dragenter 顯示 drop zone 放大提示(ring-2 ring-primary
  • dragleave / drop 後移除 highlight

5.2 離開頁面的防護

  • 上傳中 window.onbeforeunload 提示「上傳進行中,確定要離開嗎?」
  • 使用者同意 → abort XHR資料作廢

5.3 重複檔名

  • 若同名模型已存在Dialog 送出時顯示確認「已存在同名模型,要新增版本還是覆蓋?」
    • 新增版本:送出時把版本欄位自動 +1Phase 0 簡化為使用者自己改版本欄)
    • 覆蓋後端支援後再做Phase 1

5.4 模型卡片上的新狀態

ModelCard 新增狀態 badgePhase 0 簡單版Phase 1 強化):

狀態 Badge 可操作
uploading僅上傳期間 🔵 上傳中 不顯示於列表Dialog 內)
scanning 🟡 掃描中 不可燒錄
ready 🟢 可用 可燒錄
rejected 🔴 檢測失敗 可刪除、不可燒錄

6. 流程 vs 頁面 — Dialog vs Page 決策

採用Dialog(在 /models 觸發)

理由:

  • 雛形只支援單檔上傳Dialog 夠用
  • 保留使用者所在頁面上下文,上傳完可以立刻看到列表
  • 獨立頁面 /models/upload 留到 Phase 1 支援多檔 / 佇列時再做

URL 不變(不用 routerDialog 的開關用 Zustand store 狀態管理(uploadStore),方便未來移到全域 FAB。


7. API 契約(給 Backend / Architect

7.1 POST /api/models/upload-url

Request:
{
  "filename": "yolov5s_kl520.nef",
  "size": 49573888,
  "contentType": "application/octet-stream",
  "metadata": {
    "name": "YOLOv5s KL520",
    "version": "v1.0",
    "notes": "針對戶外停車場場景訓練"
  }
}

Response 200:
{
  "uploadId": "upl_01HXXXX",
  "putUrl": "https://storage.visiona.ai/models/upl_01HXXXX?X-Amz-Signature=...",
  "expiresAt": "2026-04-21T14:45:00Z",
  "headers": {
    "Content-Type": "application/octet-stream",
    "x-amz-meta-model-name": "YOLOv5s KL520"
  }
}

7.2 前端直送 storage

PUT {putUrl}
Content-Type: application/octet-stream
body: (file binary)

7.3 POST /api/models/confirm

Request:
{
  "uploadId": "upl_01HXXXX",
  "etag": "<storage 回的 ETag>"
}

Response 200:
{
  "model": {
    "id": "mdl_...",
    "name": "YOLOv5s KL520",
    "status": "scanning",
    ...
  }
}

8. 無障礙

  • Dialog既有 shadcn 已處理焦點陷阱與 ESC上傳中 ESC 被攔截)
  • Drop zonerole="button" tabIndex={0}Enter / Space 開啟檔案選擇器
  • Progressrole="progressbar" aria-valuenow aria-valuemin aria-valuemax
  • 錯誤訊息:role="alert"
  • 成功:role="status" aria-live="polite"
  • 不只靠顏色Progress 旁顯示百分比文字,錯誤有圖示

9. i18n key

models.upload.button → 上傳模型
models.upload.dialog.title → 上傳模型
models.upload.dropzone.label → 拖曳 .nef 檔到此處
models.upload.dropzone.or → 或
models.upload.dropzone.browse → 選擇檔案
models.upload.dropzone.hint → 支援格式:.nef · 最大 100 MB
models.upload.selectedFile → 已選檔案
models.upload.field.name → 模型名稱
models.upload.field.version → 版本
models.upload.field.notes → 備註(選填)
models.upload.action.remove → 移除
models.upload.action.cancel → 取消
models.upload.action.start → 開始上傳
models.upload.uploading.title → 上傳中
models.upload.uploading.hint → 請勿關閉此視窗或導航到其他頁面
models.upload.uploading.stats → {uploaded} / {total} · {speed} · 剩餘約 {eta}
models.upload.uploading.almostDone → 即將完成
models.upload.cancelConfirm.title → 確定要取消上傳?
models.upload.cancelConfirm.desc → 已傳的資料會作廢
models.upload.success.title → 上傳完成
models.upload.success.scanHint → 模型即將進入安全掃描,完成後可燒錄
models.upload.error.invalidType → 只支援 .nef 檔案,你選的是 {type}
models.upload.error.tooLarge → 檔案太大({size}),最大允許 100 MB
models.upload.error.requiredField → {field} 為必填
models.upload.error.urlFailed → 伺服器忙碌,請稍後再試
models.upload.error.urlExpired → 上傳授權已過期,重新取得中...
models.upload.error.networkLost → 網路中斷,上傳已暫停
models.upload.error.storage5xx → 儲存空間暫時無法回應,請稍後再試
models.upload.error.confirmFailed → 上傳完成,但伺服器確認失敗,請重新上傳
models.upload.error.retry → 重試
models.upload.error.resume → 繼續上傳
models.upload.toast.uploaded → ✓ 模型 {name} 已上傳
models.upload.leaveWarning → 上傳進行中,確定要離開嗎?

10. 響應式

斷點 調整
Mobile (< 640px) Dialog 改 max-w-full h-full rounded-none近似全螢幕Drop zone h-36
Tablet / Desktop max-w-lg 居中

11. TODOPhase 1+

項目 時機
多檔佇列上傳 Phase 1
分段上傳resumable, > 100MB Phase 1
背景上傳Service Worker 持續) Phase 2
獨立 /models/upload Phase 1有佇列時
上傳歷史 / 失敗重試列表 Phase 2
拖曳排序上傳順序 Phase 2
病毒掃描詳細進度 Phase 1