依 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)。
18 KiB
18 KiB
模型上傳流程 — visionA Cloud
雲端版新增流程。使用者上傳 Kneron 編譯後的
.nef模型檔到雲端 object storage,之後才能選模型燒錄到遠端裝置。技術背景(給 Design 協作者):模型檔可能達 100MB,若走 tunnel / API server 會拖慢服務。Architect 決策走 presigned PUT:前端向後端要一組有期限的 PUT URL,前端直接把檔案 PUT 到 object storage(S3 / R2 / GCS),不經過 API server 也不經過 local agent tunnel。進度 / 失敗偵測靠瀏覽器
XMLHttpRequest.upload.onprogress。對應頁面:
/models(入口) +UploadModelDialog(模態框,或走獨立/models/upload頁,見 6 節決策)配套文字版 wireframe:
wireframes/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 URL(TTL 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 zone:
h-48 border-2 border-dashed rounded-lg bg-muted/50,hover / 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.onprogress算loaded / 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) │
│ │
│ 模型即將進入安全掃描,完成後可燒錄 │
│ │
│ [完成] │
└─────────────────────────────────────────────────┘
- 按「完成」關 Dialog,toast「✓ 模型 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 MB(62%) │
│ │
│ [取消] [重試] │
└─────────────────────────────────────────────────┘
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 送出時顯示確認「已存在同名模型,要新增版本還是覆蓋?」
- 新增版本:送出時把版本欄位自動
+1(Phase 0 簡化為使用者自己改版本欄) - 覆蓋:後端支援後再做(Phase 1)
- 新增版本:送出時把版本欄位自動
5.4 模型卡片上的新狀態
ModelCard 新增狀態 badge(Phase 0 簡單版,Phase 1 強化):
| 狀態 | Badge | 可操作 |
|---|---|---|
| uploading(僅上傳期間) | 🔵 上傳中 | 不顯示於列表(Dialog 內) |
| scanning | 🟡 掃描中 | 不可燒錄 |
| ready | 🟢 可用 | 可燒錄 |
| rejected | 🔴 檢測失敗 | 可刪除、不可燒錄 |
6. 流程 vs 頁面 — Dialog vs Page 決策
採用:Dialog(在 /models 觸發)
理由:
- 雛形只支援單檔上傳,Dialog 夠用
- 保留使用者所在頁面上下文,上傳完可以立刻看到列表
- 獨立頁面
/models/upload留到 Phase 1 支援多檔 / 佇列時再做
URL 不變(不用 router);Dialog 的開關用 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 zone:
role="button" tabIndex={0},Enter / Space 開啟檔案選擇器 - Progress:
role="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. TODO(Phase 1+)
| 項目 | 時機 |
|---|---|
| 多檔佇列上傳 | Phase 1 |
| 分段上傳(resumable, > 100MB) | Phase 1 |
| 背景上傳(Service Worker 持續) | Phase 2 |
獨立 /models/upload 頁 |
Phase 1(有佇列時) |
| 上傳歷史 / 失敗重試列表 | Phase 2 |
| 拖曳排序上傳順序 | Phase 2 |
| 病毒掃描詳細進度 | Phase 1 |