visionA/docs/autoflow/03-design/wireframes/wireframe-conversion.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

64 KiB
Raw Permalink Blame History

Wireframe — 轉檔(/conversion

文字版 wireframe。Phase 0.8 雲端版新增頁面 — 把使用者「ONNX → NEF」的轉檔旅程留在 visionA Cloud 內完成,不必再跳到 converter 站台。

對應流程文件:flows/flow-conversion.md 對應 Feature spec02-prd/features/feature-converter-integration.md


0. 設計對齊備註

  • 版型:沿用既有 AppShellSidebar + Header + main。轉檔頁 /conversion(單一 route內以 state 機切四個畫面 — idle / uploading / processing / completed(含 success / failed 兩支線),不開新分頁。
  • Sidebar 加位:在「裝置 / 模型 / 工作區」之後、「叢集 / 設定」之前與「Models」相鄰視覺上把「上傳模型」與「轉檔產生模型」放成兩個並排入口符合心智模型。Icon 採 Lucide Wand2 — 「魔法棒」隱喻「把一個格式變成另一個」,比 Workflow(流程感過重)/ Cpu(已被裝置語義佔用)/ RefreshCw(暗示同步、不是轉換)更貼切。最終決定見 §10「icon 替代方案」
  • 元件複用:上傳區塊抄一份 ModelUploadDialog 改名 ConversionUploadDialog(不直接共用,因為需求差太多 — 轉檔有 chip 必填、ref images 多檔、500 MB 上限)。其餘 Dialog / Card / Button / Progress / Badge / EmptyState / Sonner toast 全部直接複用。
  • Design Tokens:不新增任何 token。chip 選擇器底色用 --accent,進度條 --primaryerror 用 --destructive,警示 banner 沿用 bg-amber-50 / dark:bg-amber-950/30 + border-amber-300
  • i18n:所有文案都會走 i18nzh-TW + enkey 命名空間 conversion.*,整理在 §11。

1. Sidebar 與進入點

1.1 Sidebar 變更(追加項目)

┌────────────────────────┐
│ [vA] visionA Cloud     │   h-14, border-b
├────────────────────────┤
│  ▸ 儀表板               │
│  ▸ 裝置                 │
│  ▸ 模型庫               │
│  ▸ 工作區               │
│  ▸ 轉檔        ← new    │   ← Wand2 icon
│  ▸ 叢集                 │
│  ▸ 設定                 │
│                        │
│  (flex-1)              │
├────────────────────────┤
│  v0.1.0 · Phase 0.8     │
└────────────────────────┘

新增的 NavItem

{ href: "/conversion", labelKey: "nav.conversion", icon: Wand2 }

i18n

  • nav.conversion → 繁中「轉檔」/ English「Convert」

放置位置決策:模型庫之後。理由:使用者心智「我有一個外部模型,想讓它能在我的 KL 裝置上跑」的下一步通常是「我已經知道有 Models 頁可以管模型,那 Convert 應該就在它附近」。

1.2 入口

  • 主入口Sidebar 「轉檔」tab → 進 /conversion
  • 次要入口Phase 0.8 不做但保留思考):/models 上傳 Dialog 內加一個「我有 ONNX需要先轉檔 →」連結;先不做避免分支太多。

2. 頁面狀態總覽

/conversion 是單頁,內部依 state 機切四種畫面。state 由前端 store 決定(useConversionStore,等 frontend 時再實作),不靠 URL query。

┌──────────────────────────────────────────────────────────────────┐
│           idle           ┌───────►  processing  ─────┐            │
│  (無進行中 job、         │            polling      │            │
│   顯示空狀態 + CTA       │                            ▼            │
│            │             │                       completed         │
│            ▼             │                       ├─ success         │
│        uploading  ───────┘                       └─ failed          │
│      XHR 進度條 0100%                          (兩種分支)        │
└──────────────────────────────────────────────────────────────────┘
狀態 觸發進入 觸發離開
idle 預設 / 完成後按「開始新轉檔」/ 失敗後按「重新開始」 點「開始轉檔」開 Upload Dialog
uploading Upload Dialog 內按「開始上傳」 XHR 完成(→ processing/ 失敗(→ idle + toast/ 取消
processing upload 完成、收到 job_id poll 到 succeeded / failed
completed.success poll 到 status: succeeded 「開始新轉檔」回 idle
completed.failed poll 到 status: failed 或拿到非 200 「重新開始」回 idle

3. State Aidle — 無進行中 Job預設畫面

3.1 整體版型Desktop, ≥ 1024px

┌──────────────────────────────────────────────────────────────────┐
│ [Sidebar]  [Header]                                                │
│ ──────────────────────────────────────────────────────────────────  │
│  mx-auto max-w-7xl px-6 py-8 space-y-6                              │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  轉檔             text-2xl font-bold                          │  │
│  │  把 ONNX / TFLite 模型轉成 .nef跑在 Kneron 邊緣裝置上          │  │
│  │  text-muted-foreground                                        │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  EmptyState  (rounded-xl border bg-card py-16 text-center)    │  │
│  │                                                                │  │
│  │              ✨   (Wand2, h-12 w-12, text-muted-foreground)   │  │
│  │                                                                │  │
│  │              還沒有進行中的轉檔         text-lg font-semibold  │  │
│  │                                                                │  │
│  │   上傳一個 ONNX / TFLite 模型,選擇目標 Kneron 晶片,          │  │
│  │   我們幫你產出可直接燒錄的 .nef 檔案                            │  │
│  │   (max-w-md mx-auto text-sm text-muted-foreground)             │  │
│  │                                                                │  │
│  │              ┌────────────────────────────┐                    │  │
│  │              │   [✨ 開始轉檔]              │                    │  │
│  │              │   variant=default size=lg   │                    │  │
│  │              └────────────────────────────┘                    │  │
│  │                                                                │  │
│  │              支援 .onnx / .tflite · 最大 500 MB                 │  │
│  │              text-xs text-muted-foreground                     │  │
│  │                                                                │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │    關於轉檔                                                    │  │
│  │  text-sm text-muted-foreground (折疊區,預設展開)               │  │
│  │  • 一次只能跑一個轉檔任務(包含其他分頁)                        │  │
│  │  • 完成後 7 天內可下載結果,過期自動清除                         │  │
│  │  • 轉檔約耗時 110 分鐘,依模型大小而定                          │  │
│  └──────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

3.2 互動

元素 互動 行為
「開始轉檔」CTA click / Enter / Space 開啟 ConversionUploadDialog(見 §4

3.3 邊界 — 已有 active job

進入 /conversion 時前端先打一次 GET /api/conversion/activevisionA backend 提供,回傳該 user 是否有進行中的 job

回應 UI 行為
{ active: false } 顯示 idle 畫面(如上)
{ active: true, jobId, status, ... } 直接跳 processing 畫面(見 §6banner 加註「您離開前的轉檔仍在進行中」

這個檢查也涵蓋「使用者開了第二個分頁」「重新整理」「離開後再回來」三種情境,不需要前端額外狀態保存


4. Upload DialogConversionUploadDialog

複用 DialogInputLabelSelectButtonProgress 元件;參考既有 ModelUploadDialog 改寫。

4.1 階段 A — 選檔與設定(select

┌────────────────────────────────────────────────────────────┐
│ 開始轉檔                                          [✕]       │
│ DialogHeader · DialogTitle · DialogDescription              │
│ 上傳模型、選擇目標晶片,可選擇加上 reference images 提升精度    │
├────────────────────────────────────────────────────────────┤
│  space-y-5  px-6 py-4                                       │
│                                                            │
│  Label: 來源模型 *                                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                                                       │  │
│  │       📁  拖曳 .onnx / .tflite 到此處                  │  │
│  │                                                       │  │
│  │           或 [選擇檔案]                                │  │
│  │                                                       │  │
│  │       支援格式:.onnx · .tflite · 最大 500 MB           │  │
│  │                                                       │  │
│  └──────────────────────────────────────────────────────┘  │
│  border-2 border-dashed rounded-md bg-muted/50 h-32         │
│  選了之後變成:                                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 📄 yolov5s.onnx · 28.4 MB                  [✕ 移除]   │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                            │
│  Label: 任務名稱(選填)                                      │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ yolov5s預設帶檔名 stem可改                        │  │
│  └──────────────────────────────────────────────────────┘  │
│  hint: text-xs text-muted-foreground                        │
│  顯示用,不影響輸出檔名                                       │
│                                                            │
│  Label: 目標晶片 *                                           │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐               │  │
│  │  │KL520 │  │KL630 │  │KL720 │  │KL730 │               │  │
│  │  │  ●   │  │      │  │      │  │      │               │  │
│  │  └──────┘  └──────┘  └──────┘  └──────┘               │  │
│  │  4 個 ChipPill (RadioGroup),單選                       │  │
│  │  active: bg-primary text-primary-foreground             │  │
│  │  hover: bg-accent                                       │  │
│  │  border rounded-md px-4 py-3 cursor-pointer min-w-20    │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                            │
│  Label: Reference images選填                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  📁  拖曳圖片到此處(選填)                             │  │
│  │      或 [選擇檔案] · 最多 100 張,每張 ≤ 10 MB           │  │
│  │  border-2 border-dashed rounded-md bg-muted/30 h-20    │  │
│  └──────────────────────────────────────────────────────┘  │
│  選了之後:                                                   │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 已選 12 張 ref images共 24.3 MB       [移除全部]    │  │
│  │ ▸ 縮圖 gridhover 顯示 ✕ 個別移除)                   │  │
│  └──────────────────────────────────────────────────────┘  │
│  hint: 加上 ref images 可提升量化後精度(可選)                │
│                                                            │
├────────────────────────────────────────────────────────────┤
│ DialogFooter                                                │
│                       [取消]  [開始轉檔]                     │
│                       variant=outline · variant=default     │
│                                                            │
│ 「開始轉檔」disabled 條件:未選檔 / 未選 chip / 任一檔超大     │
└────────────────────────────────────────────────────────────┘

前端驗證(按下「開始轉檔」前):

驗證 失敗訊息
必須選檔 「請選擇 .onnx 或 .tflite 檔案」
副檔名為 .onnx.tflite 「不支援的格式,請改用 ONNX 或 TFLite」
模型 ≤ 500 MB 「模型超過 500 MB 上限,請改用較小的模型」
必須選 chip 「請選擇目標晶片」
每張 ref image ≤ 10 MB 「{filename} 超過 10 MB請移除或壓縮後再試」
ref images 總數 ≤ 100 張 「Reference images 上限 100 張」

驗證失敗error 顯示在對應欄位下方(text-sm text-destructive),不發 API。

4.2 階段 B — 上傳中(uploading

按下「開始轉檔」後 Dialog 內容切換(不關 Dialog,使用者要看到進度):

┌────────────────────────────────────────────────────────────┐
│ 上傳中                                          [✕*]       │
├────────────────────────────────────────────────────────────┤
│                                                            │
│       📤  正在上傳到 visionA…                                │
│       text-base font-medium                                 │
│                                                            │
│       yolov5s.onnx · 28.4 MB                                │
│       text-sm text-muted-foreground                         │
│                                                            │
│       ┌──────────────────────────────────────────────┐     │
│       │ ████████████████████░░░░░░░░░░░░░░░░  42%      │     │
│       └──────────────────────────────────────────────┘     │
│       Progress bar (h-2, --primary)                         │
│                                                            │
│       已上傳 11.9 / 28.4 MB · 預估剩餘 0:24                  │
│       text-xs text-muted-foreground                         │
│                                                            │
│       ⚠ 請勿關閉此分頁,否則上傳會中斷                        │
│       text-xs text-amber-700 dark:text-amber-300            │
│                                                            │
├────────────────────────────────────────────────────────────┤
│                              [取消上傳]                      │
│                              variant=outline                 │
└────────────────────────────────────────────────────────────┘
  • 進度由 XHR upload.onprogressloaded / total)計算
  • 預估剩餘 = 用最近 3 秒移動平均速度算 ETA不足 1 秒顯示「即將完成…」
  • [✕*] Dialog 右上角關閉按鈕在此階段保留可點,但點擊會問 AlertDialog「上傳尚未完成確定取消
  • 「取消上傳」:xhr.abort() → 回 idle 畫面 + toast「已取消上傳」
  • beforeunload上傳中觸發瀏覽器原生離開警告visionA backend 已 cancel converter job

4.3 階段 C — 上傳完成、轉檔啟動

XHR 200 後visionA backend 已 forward 到 converter 並拿到 job_id

  • Dialog 自動關閉
  • 主畫面切到 §6 processing 狀態
  • toast「已開始轉檔任務 #{shortJobId})」

4.4 階段 D — 上傳失敗

錯誤 UI
409 已有 active job Dialog 關閉,主畫面切 processing 並 banner 提示「您已有一個轉檔正在進行中,已切換至該任務」
4xx檔案被拒 Dialog 內顯示錯誤紅字 + 「重試」按鈕
5xx / 網路 Dialog 內顯示「上傳失敗:{訊息}」+ 「重試」

5. Stateuploading(在 Dialog 內,非全頁)

實際上 uploading 是 Dialog 內的階段§4.2),主畫面背後仍是 idle主畫面不顯示進度條,避免使用者以為要看兩處。

瀏覽器分頁標題會更新:

visionA Cloud · 上傳中 (42%)

讓使用者就算切到別的分頁也能看到進度。


6. Stateprocessing — 轉檔進行中

┌──────────────────────────────────────────────────────────────────┐
│ [Sidebar]  [Header]                                                │
│ ──────────────────────────────────────────────────────────────────  │
│  mx-auto max-w-7xl px-6 py-8 space-y-6                              │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  轉檔                                                         │  │
│  │  text-2xl font-bold                                           │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  Card: 進行中  (border, rounded-xl, p-6)                      │  │
│  │                                                                │  │
│  │  ┌──────────────────────────────────────────────────────┐    │  │
│  │  │ 📄 yolov5s.onnx → KL720      🔵 轉檔中                │    │  │
│  │  │ Badge: bg-blue-500 text-white animate-pulse            │    │  │
│  │  │ 任務 #a1b2c3d4 · 開始於 5 分鐘前                        │    │  │
│  │  │ text-sm text-muted-foreground                          │    │  │
│  │  └──────────────────────────────────────────────────────┘    │  │
│  │                                                                │  │
│  │  進度stage indicator不可點                               │  │
│  │  ┌──────┐    ┌──────┐    ┌──────┐                            │  │
│  │  │  ✓   │────│  ●   │────│  3   │                            │  │
│  │  │  1   │    │  2   │    │      │                            │  │
│  │  └──────┘    └──────┘    └──────┘                            │  │
│  │  上傳完成   解析模型     編譯 NEF                              │  │
│  │  text-sm text-foreground       text-muted-foreground            │  │
│  │                                                                │  │
│  │  ┌──────────────────────────────────────────────────────┐    │  │
│  │  │ 處理中…                                                │    │  │
│  │  │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ animate-pulse              │    │  │
│  │  └──────────────────────────────────────────────────────┘    │  │
│  │  Progress (indeterminate, h-1.5, --primary)                    │  │
│  │  hint: text-xs text-muted-foreground                           │  │
│  │  通常需要 110 分鐘 · 你可以離開此頁面,回來時會自動更新進度    │  │
│  │                                                                │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │    你可以放著不管                                             │  │
│  │  text-sm text-muted-foreground                                 │  │
│  │  • 我們會在背景持續查詢進度(每 510 秒一次)                   │  │
│  │  • 完成後分頁標題會通知你                                       │  │
│  │  • 此頁面關掉也沒關係,回來時會自動恢復                         │  │
│  └──────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

6.1 Stage indicator 規格

三段式 stepper不是 ONNX → BIE → NEF 那種編譯內部階段,因為 converter Phase 1 不暴露細階段;簡化成使用者能感知的三段):

Stage 完成條件 converter 狀態對應
1. 上傳完成 XHR 200 回到 visionA backend、拿到 job_id (前端自己標完成)
2. 解析模型 converter status 變 running poll status running 即標完成 stage 1 + 開始 stage 2
3. 編譯 NEF converter status 變 succeeded poll status succeeded 標 stage 3 完成

注意converter Phase 1 只回 queued / running / succeeded / failed,沒有 sub-stage。前端的「解析模型 / 編譯 NEF」純粹是 UI 上把 running 切成兩段視覺,不代表真實內部階段。如果 converter 後續加 progress 比例(已在 N4 後續規劃),可改成單一 progress bar 顯示百分比。

stage 視覺:

完成:
┌──┐  bg-primary text-primary-foreground rounded-full h-8 w-8
│✓ │  flex items-center justify-center
└──┘
 文字text-sm font-medium

當前:
┌──┐  ring-2 ring-primary bg-background rounded-full h-8 w-8
│● │  text-primary
└──┘
 文字text-sm font-medium

未完成:
┌──┐  bg-muted text-muted-foreground rounded-full h-8 w-8
│ 3│
└──┘
 文字text-sm text-muted-foreground

連線h-0.5(已完成段 bg-primary、未完成段 bg-muted

6.2 進度條策略

  • Phase 0.8 converter 不給 progress 比例 → 用 indeterminate progressshadcn Progress 不帶 value 屬性 + animate-pulse
  • 文字:處理中…(不要謊報百分比)
  • 不要顯示「預估剩餘時間」,因為沒有可靠資料

Phase 1 待 converter 提供 progress 後升級:改成 <Progress value={pct} />,文字改成 處理中({pct}%

6.3 Polling 行為

項目 規格
間隔 每 5 秒一次(前 60 秒)→ 每 10 秒(之後)
端點 GET /api/conversion/{job_id}visionA backend 中繼)
暫停 分頁不可見(document.visibilityState !== 'visible')時暫停;回到可見立即補打一次
失敗重試 指數退避 1s / 2s / 4s / 8s / 上限 30s連 5 次失敗顯示「無法取得轉檔狀態,請重試」+ retry 按鈕
終止 收到 succeeded / failed 或使用者離開頁面

6.4 邊界情境

情境 UI 反應
使用者關掉分頁、過 10 分鐘回來 重進 /conversion → §3.3 active job 檢查命中 → 直接落 processing 畫面
使用者開了第二個分頁 兩個分頁各自 polling 同一個 job狀態同步無需跨分頁通訊
Polling 一直拿到 queued 超過 5 分鐘 banner 提示「目前排隊較久,你可以離開此頁稍後再回」
跑超過 15 分鐘還沒完 不主動終止banner 加註「轉檔耗時較長,仍在進行中」

7. Statecompleted.success — 轉檔成功

┌──────────────────────────────────────────────────────────────────┐
│ [Sidebar]  [Header]                                                │
│ ──────────────────────────────────────────────────────────────────  │
│  mx-auto max-w-7xl px-6 py-8 space-y-6                              │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  轉檔                                                         │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  Card: 完成 (border-green-300 bg-green-50/40                  │  │
│  │           dark:bg-green-950/20 dark:border-green-800)         │  │
│  │                                                                │  │
│  │   ✓  轉檔完成                                                  │  │
│  │   CheckCircle2, h-6 w-6, text-green-600                        │  │
│  │   text-lg font-semibold                                        │  │
│  │                                                                │  │
│  │  ┌──────────────────────────────────────────────────────┐    │  │
│  │  │  yolov5s.onnx  →  yolov5s_kl720.nef                   │    │  │
│  │  │  text-sm font-mono                                      │    │  │
│  │  │                                                        │    │  │
│  │  │  目標晶片KL720       輸出大小4.2 MB                  │    │  │
│  │  │  耗時3 分 14 秒      checksumsha256:a1b2…            │    │  │
│  │  │  text-sm text-muted-foreground                          │    │  │
│  │  │  任務 #{shortJobId}                                     │    │  │
│  │  └──────────────────────────────────────────────────────┘    │  │
│  │                                                                │  │
│  │  接下來要做什麼?                                              │  │
│  │  text-sm font-medium                                           │  │
│  │                                                                │  │
│  │  ┌────────────────────────┐  ┌────────────────────────┐      │  │
│  │  │ 📚 加到模型庫           │  │ ⬇  下載 .nef            │      │  │
│  │  │ 之後可以從模型庫部署     │  │ 存到本機自行使用          │      │  │
│  │  │ 到任何 KL720 裝置        │  │                          │      │  │
│  │  │                         │  │                          │      │  │
│  │  │ [加到模型庫]             │  │ [下載]                    │      │  │
│  │  │ variant=default         │  │ variant=outline           │      │  │
│  │  └────────────────────────┘  └────────────────────────┘      │  │
│  │  Card 內兩格 grid (md: grid-cols-2 gap-4)                     │  │
│  │                                                                │  │
│  │  ⏳ 此轉檔結果將在 6 天 21 小時後自動清除,請在期限內完成處理     │  │
│  │  text-xs text-amber-700 dark:text-amber-300                    │  │
│  │                                                                │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │                            [✨ 開始新轉檔]                      │  │
│  │                            variant=outline w-full              │  │
│  └──────────────────────────────────────────────────────────────┘  │
│  完成 + 還沒按過任何按鈕也允許開新轉檔converter 不再有 active job │
└──────────────────────────────────────────────────────────────────┘

7.1 「加到模型庫」流程(按鈕)

1. 點擊 → 開 AlertDialog 確認 + 輸入欄位(複用 visionA 既有 import flow
   ┌────────────────────────────────────────────────┐
   │ 加到模型庫                                      │
   │                                                 │
   │ Label: 模型名稱(預設帶 job 任務名)              │
   │ ┌──────────────────────────────────────────┐   │
   │ │ yolov5s_kl720                            │   │
   │ └──────────────────────────────────────────┘   │
   │                                                 │
   │ Label: 描述(選填)                              │
   │ ┌──────────────────────────────────────────┐   │
   │ │                                          │   │
   │ └──────────────────────────────────────────┘   │
   │ Textarea (rows=3)                              │
   │                                                 │
   │ ▸ 來源轉檔job #{shortJobId}text-xs        │
   │ ▸ 目標晶片KL720自動帶入                    │
   │                                                 │
   │              [取消]   [加到模型庫]                │
   └────────────────────────────────────────────────┘
2. 確認後呼叫 POST /api/conversion/{job_id}/promote-to-models
3. Loading按鈕變 spinner + 「處理中…」disabled
4. 200 OK
   - toast「已加入模型庫」+ action「前往模型庫 →」連結到 /models/{model_id}
   - 「加到模型庫」按鈕變綠勾 + 副標「✓ 已加入(前往查看 →)」(仍可點再加,但會 409
5. 409 already imported
   - toast「此任務已加入過模型庫」+ 連結「查看現有模型 →」
6. 其他錯誤toast 顯示錯誤訊息 + 「重試」

為何不直接彈兩個按鈕沒有確認 dialog 也 OK 因為 PRD §F4 的決策是「半自動 = user 顯式選擇」,但「加到模型庫」會建立永久資源(出現在 /models),讓使用者確認名稱比靜默 import 更符合心智 — 跟 ModelUploadDialog 的做法一致。

👉 給 PM上面的「模型名稱 / 描述」要不要做進 Phase 0.8 Dialog如果你想最簡可以直接用 job.name 自動填、不問使用者,省一個 Dialog。我這邊建議保留這個小 Dialog 但只給「名稱」一個欄位(描述放 Phase 1理由使用者「轉檔任務名」≠「模型庫名稱」的心智差異很常見。

7.2 「下載」流程(按鈕)

1. 點擊 → 按鈕短暫進 loadingspinner + 「準備下載…」)
2. 觸發 navigationwindow.location.href = '/api/conversion/{job_id}/download'
   (或用 anchor tag <a href="/api/conversion/{job_id}/download" download>,效果等價)
3. visionA backend 收到後 server-side 跟 MC 換 delegated token
   直接回 HTTP 302 Redirect → Location: <FAA-URL>/files/{key}?access_token=...
4. 瀏覽器自動跟著 302 跳轉到 FAA內建下載管理器接管下載
   - 按鈕回到原狀態(不變灰,使用者可重複下載 → 重新換新 token
   - toast「下載已開始」+ 副標「若沒看到下載提示,請檢查瀏覽器設定」
5. 4xx / 5xxbackend 還沒到 redirect 階段就 fail
   - 因為已 navigation瀏覽器會顯示 backend 的錯誤頁;使用者按 back 即可
   - 或前端可選用 fetch + 偵測 status 後才 navigate避免 navigate 到錯誤頁)

重點token 不暴露給 frontend JS,整個換 token 流程在 backend 內完成,前端只看得到 /api/conversion/{job_id}/download 這個 URL。

Phase 0.8 短期方案FAA 還沒加 CORS 前):靠 navigation download + 302 redirect瀏覽器內建下載管理器接手無自訂進度條。 FAA 加 CORS 後(升級):可改用 fetch('/api/conversion/{job_id}/download', { redirect: 'follow' }) + ReadableStream + 自訂進度條Dialog 內顯示下載進度),仍維持 server-side 換 token 設計。本檔保留視覺位置,實作時再補。

7.3 「開始新轉檔」按鈕

直接重置 store state 回 idle不清除舊 job 紀錄(仍可從 §3.3 active job 機制判斷,但因為剛剛已 completed不會有 active舊 job 結果如果使用者沒下載 / 沒 import過 7 天自動 GC

⚠️ 邊界:使用者按「開始新轉檔」之前「加到模型庫」「下載」按鈕仍應保持可用(不在使用者按「開始新轉檔」時消失)— 因為使用者可能想兩個都做。「開始新轉檔」應該被視為離開這個結果頁的明確動作。


8. Statecompleted.failed — 轉檔失敗

┌──────────────────────────────────────────────────────────────────┐
│ [Sidebar]  [Header]                                                │
│ ──────────────────────────────────────────────────────────────────  │
│  mx-auto max-w-7xl px-6 py-8 space-y-6                              │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │  Card: 失敗 (border-destructive/40 bg-destructive/5            │  │
│  │            dark:bg-destructive/10)                             │  │
│  │                                                                │  │
│  │   ⚠  轉檔失敗                                                   │  │
│  │   AlertCircle, h-6 w-6, text-destructive                       │  │
│  │   text-lg font-semibold                                        │  │
│  │                                                                │  │
│  │   {translatedErrorMessage}                                     │  │
│  │   text-sm text-foreground                                      │  │
│  │   例:「模型內含不支援的運算子,無法量化到目標晶片」                │  │
│  │                                                                │  │
│  │  ┌──────────────────────────────────────────────────────┐    │  │
│  │  │  yolov5s.onnx  →  KL720失敗                       │    │  │
│  │  │  text-sm                                              │    │  │
│  │  │  錯誤代碼QUANTIZATION_FAILED                          │    │  │
│  │  │  任務 #{shortJobId}                                    │    │  │
│  │  │  text-xs font-mono text-muted-foreground               │    │  │
│  │  └──────────────────────────────────────────────────────┘    │  │
│  │                                                                │  │
│  │  💡 你可以試試:                                                │  │
│  │  text-sm font-medium                                           │  │
│  │  • 確認模型已用標準 PyTorch / TensorFlow export              │  │
│  │  • 簡化模型結構(移除 Custom Op                              │  │
│  │  • 改用較小的 batch size 或 input shape                        │  │
│  │  text-sm text-muted-foreground                                  │  │
│  │  suggestions 依 error code 切換)                             │  │
│  │                                                                │  │
│  │  ┌────────────────────────┐  ┌────────────────────────┐      │  │
│  │  │ [重新開始]               │  │ [回模型庫]               │      │  │
│  │  │ variant=default         │  │ variant=outline           │      │  │
│  │  └────────────────────────┘  └────────────────────────┘      │  │
│  │                                                                │  │
│  │  若持續發生,請複製任務 ID #{fullJobId} 聯絡支援團隊            │  │
│  │  [📋 複製任務 ID]                                               │  │
│  │  text-xs text-muted-foreground · variant=ghost size=xs         │  │
│  │                                                                │  │
│  └──────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

8.1 錯誤訊息對照(依 PRD §F5

converter error code 顯示文案zh-TW suggestions 顯示
UNSUPPORTED_FORMAT 此模型格式目前不支援,請改用 ONNX / TFLite 確認檔案副檔名、用標準 export 工具
INVALID_CHECKSUM 檔案傳輸過程毀損,請重新上傳 重新開始上傳
QUANTIZATION_FAILED 模型內含不支援的運算子,無法量化到目標晶片 簡化模型、移除 Custom Op、改 input shape
MODEL_TOO_LARGE 模型超過 500 MB 上限 改用較小模型 / Pruning
QUOTA_EXCEEDED 系統暫時繁忙,請稍後再試 等 5 分鐘重試
其他 / unknown 轉檔失敗,請稍後重試。若持續發生請聯絡支援團隊 複製 job ID 回報

i18n keyconversion.error.{code}.message / conversion.error.{code}.suggestions(見 §11

8.2 「Job 已過期」特殊狀態

當使用者重新整理頁面、active job 檢查回 404converter 7 天 GC 已清除):

┌──────────────────────────────────────────────────────────────────┐
│  Card: 已過期 (border-muted bg-muted/30)                          │
│                                                                  │
│   ⏰  此轉檔結果已過期                                              │
│   Clock, h-6 w-6, text-muted-foreground                          │
│                                                                  │
│   轉檔結果保留期為 7 天,目前已超過保留期限並自動清除。              │
│                                                                  │
│              [✨ 開始新轉檔]                                       │
└──────────────────────────────────────────────────────────────────┘

9. 響應式

沿用 design-spec.md §6 整體策略,逐狀態調整:

斷點 idle uploading(在 Dialog 內) processing completed.success
Mobile (< 640px) EmptyState 縮窄 max-w-full、CTA 全寬;「關於轉檔」摺疊 Dialog 改 fullscreen-on-mobileshadcn 預設行為) stage indicator 改縱向堆疊icon 圓點 + 標籤一行Card padding p-4 兩個 action card 改縱向堆疊grid-cols-1
Tablet (6401024) 居中、max-w-2xl Dialog 寬 max-w-lg stage indicator 橫排但縮短連線 grid-cols-2
Desktop (≥ 1024) 完整呈現 完整呈現 完整呈現 grid-cols-2

/conversion 在 Mobile 不顯示「請使用桌面版」警告(不像 /workspace 那麼依賴大螢幕,可用)。但500 MB 檔案在 Mobile 上傳體驗極差,給一個 hint banner

 Mobile 設備上傳大型模型可能不穩定,建議使用桌面版瀏覽器
(只在 Mobile 顯示)

10. icon 替代方案(給設計討論)

任務指定 Wand2 / Workflow / Cpu / RefreshCw 四選一,本 wireframe 採 Wand2。理由與替代方案:

icon 隱喻 採用? 原因
Wand2 「魔法轉換」一個東西變成另一個 採用 直覺、與既有 Boxes / Cable / LayoutDashboard 視覺密度相近
Workflow 流程 / 多步驟 太流程感、容易跟 CI/CD 混淆
Cpu 晶片 / 硬體 已被裝置語義佔據(Devices tab 概念上更該用這個)
RefreshCw 同步 / 重整 暗示週期性同步,不是一次性轉換

備案:如果使用者覺得 Wand2 太可愛不夠工程感,可改 FileCogWand2 的工程版)或 Replace更精準的「A→B」隱喻— 待 review 確認。


11. i18n key 規劃

11.1 Sidebar / 導航

nav.conversion → 轉檔 / Convert

11.2 頁面標題

conversion.title             → 轉檔
conversion.subtitle          → 把 ONNX / TFLite 模型轉成 .nef跑在 Kneron 邊緣裝置上

11.3 idle 空狀態

conversion.idle.heading      → 還沒有進行中的轉檔
conversion.idle.description  → 上傳一個 ONNX / TFLite 模型,選擇目標 Kneron 晶片,我們幫你產出可直接燒錄的 .nef 檔案
conversion.idle.cta          → 開始轉檔
conversion.idle.formats      → 支援 .onnx / .tflite · 最大 500 MB
conversion.idle.about.title  → 關於轉檔
conversion.idle.about.line1  → 一次只能跑一個轉檔任務(包含其他分頁)
conversion.idle.about.line2  → 完成後 7 天內可下載結果,過期自動清除
conversion.idle.about.line3  → 轉檔約耗時 110 分鐘,依模型大小而定

11.4 Upload Dialog

conversion.upload.title              → 開始轉檔
conversion.upload.description        → 上傳模型、選擇目標晶片,可選擇加上 reference images 提升精度
conversion.upload.source.label       → 來源模型
conversion.upload.source.dropzone    → 拖曳 .onnx / .tflite 到此處
conversion.upload.source.or          → 或
conversion.upload.source.browse      → 選擇檔案
conversion.upload.source.formatHint  → 支援格式:.onnx · .tflite · 最大 500 MB
conversion.upload.source.remove      → 移除
conversion.upload.name.label         → 任務名稱(選填)
conversion.upload.name.hint          → 顯示用,不影響輸出檔名
conversion.upload.chip.label         → 目標晶片
conversion.upload.refImages.label    → Reference images選填
conversion.upload.refImages.dropzone → 拖曳圖片到此處(選填)
conversion.upload.refImages.hint     → 加上 ref images 可提升量化後精度(最多 100 張,每張 ≤ 10 MB
conversion.upload.refImages.summary  → 已選 {count} 張 ref images共 {totalSize}
conversion.upload.refImages.removeAll → 移除全部
conversion.upload.cancel             → 取消
conversion.upload.start              → 開始轉檔

conversion.upload.error.noFile       → 請選擇 .onnx 或 .tflite 檔案
conversion.upload.error.unsupported  → 不支援的格式,請改用 ONNX 或 TFLite
conversion.upload.error.modelTooLarge → 模型超過 500 MB 上限,請改用較小的模型
conversion.upload.error.noChip       → 請選擇目標晶片
conversion.upload.error.refTooLarge  → {filename} 超過 10 MB請移除或壓縮後再試
conversion.upload.error.refTooMany   → Reference images 上限 100 張

11.5 Uploading 階段

conversion.uploading.title            → 上傳中
conversion.uploading.heading          → 正在上傳到 visionA…
conversion.uploading.progress         → 已上傳 {loaded} / {total} · 預估剩餘 {eta}
conversion.uploading.almostDone       → 即將完成…
conversion.uploading.warning          → 請勿關閉此分頁,否則上傳會中斷
conversion.uploading.cancel           → 取消上傳
conversion.uploading.cancelConfirm    → 上傳尚未完成,確定取消?
conversion.uploading.tabTitle         → visionA Cloud · 上傳中 ({pct}%)
conversion.uploading.toastCanceled    → 已取消上傳
conversion.uploading.toastFailed      → 上傳失敗:{reason}
conversion.uploading.toastStarted     → 已開始轉檔(任務 #{shortJobId}

11.6 Processing 階段

conversion.processing.title          → 轉檔
conversion.processing.cardHeading    → 進行中
conversion.processing.statusBadge    → 轉檔中
conversion.processing.startedAgo     → 開始於 {time}
conversion.processing.stage1         → 上傳完成
conversion.processing.stage2         → 解析模型
conversion.processing.stage3         → 編譯 NEF
conversion.processing.processing     → 處理中…
conversion.processing.hint           → 通常需要 110 分鐘 · 你可以離開此頁面,回來時會自動更新進度
conversion.processing.background.title → 你可以放著不管
conversion.processing.background.l1  → 我們會在背景持續查詢進度(每 510 秒一次)
conversion.processing.background.l2  → 完成後分頁標題會通知你
conversion.processing.background.l3  → 此頁面關掉也沒關係,回來時會自動恢復
conversion.processing.queueLong      → 目前排隊較久,你可以離開此頁稍後再回
conversion.processing.runLong        → 轉檔耗時較長,仍在進行中
conversion.processing.pollFailed     → 無法取得轉檔狀態,請重試
conversion.processing.bannerActive   → 您離開前的轉檔仍在進行中
conversion.processing.bannerExisting → 您已有一個轉檔正在進行中,已切換至該任務

11.7 已有 active job 提示idle 頁也會用到)

conversion.busy.title                → 您已有一個轉檔正在進行中
conversion.busy.cta                  → 查看進度

11.8 Success 結果

conversion.success.heading           → 轉檔完成
conversion.success.summary.chip      → 目標晶片
conversion.success.summary.size      → 輸出大小
conversion.success.summary.duration  → 耗時
conversion.success.summary.checksum  → checksum
conversion.success.summary.jobId     → 任務
conversion.success.nextStep          → 接下來要做什麼?

conversion.success.import.title      → 加到模型庫
conversion.success.import.description → 之後可以從模型庫部署到任何 {chip} 裝置
conversion.success.import.cta        → 加到模型庫
conversion.success.import.dialog.title       → 加到模型庫
conversion.success.import.dialog.nameLabel   → 模型名稱
conversion.success.import.dialog.descLabel   → 描述(選填)
conversion.success.import.dialog.sourceLabel → 來源
conversion.success.import.dialog.sourceValue → 轉檔job #{shortJobId}
conversion.success.import.dialog.confirm     → 加到模型庫
conversion.success.import.dialog.cancel      → 取消
conversion.success.import.processing → 處理中…
conversion.success.import.toastDone  → 已加入模型庫
conversion.success.import.toastDoneAction → 前往模型庫 →
conversion.success.import.toastDup   → 此任務已加入過模型庫
conversion.success.import.toastDupAction  → 查看現有模型 →
conversion.success.import.statusDone → ✓ 已加入(前往查看 →)

conversion.success.download.title    → 下載 .nef
conversion.success.download.description → 存到本機自行使用
conversion.success.download.cta      → 下載
conversion.success.download.preparing → 準備下載…
conversion.success.download.toastStart → 下載已開始
conversion.success.download.toastHint  → 若沒看到下載提示,請檢查瀏覽器設定
conversion.success.download.toastFail  → 下載連結取得失敗

conversion.success.expiry            → 此轉檔結果將在 {time} 後自動清除,請在期限內完成處理
conversion.success.startNew          → 開始新轉檔

11.9 Failed 結果

conversion.failed.heading            → 轉檔失敗
conversion.failed.errorCode          → 錯誤代碼
conversion.failed.suggestionsTitle   → 你可以試試:
conversion.failed.retry              → 重新開始
conversion.failed.backToModels       → 回模型庫
conversion.failed.contactSupport     → 若持續發生,請複製任務 ID 聯絡支援團隊
conversion.failed.copyJobId          → 複製任務 ID
conversion.failed.toastJobIdCopied   → 已複製任務 ID

conversion.error.UNSUPPORTED_FORMAT.message    → 此模型格式目前不支援,請改用 ONNX / TFLite
conversion.error.UNSUPPORTED_FORMAT.suggestions → ["確認檔案副檔名","用標準 export 工具"]
conversion.error.INVALID_CHECKSUM.message      → 檔案傳輸過程毀損,請重新上傳
conversion.error.INVALID_CHECKSUM.suggestions  → ["重新開始上傳"]
conversion.error.QUANTIZATION_FAILED.message   → 模型內含不支援的運算子,無法量化到目標晶片
conversion.error.QUANTIZATION_FAILED.suggestions → ["簡化模型結構","移除 Custom Op","改用較小的 input shape"]
conversion.error.MODEL_TOO_LARGE.message       → 模型超過 500 MB 上限
conversion.error.MODEL_TOO_LARGE.suggestions   → ["改用較小模型","嘗試 Pruning / Quantization"]
conversion.error.QUOTA_EXCEEDED.message        → 系統暫時繁忙,請稍後再試
conversion.error.QUOTA_EXCEEDED.suggestions    → ["等 5 分鐘後重試"]
conversion.error.unknown.message               → 轉檔失敗,請稍後重試。若持續發生請聯絡支援團隊
conversion.error.unknown.suggestions           → ["複製任務 ID 回報給支援團隊"]

11.10 已過期 / 找不到

conversion.expired.heading            → 此轉檔結果已過期
conversion.expired.description        → 轉檔結果保留期為 7 天,目前已超過保留期限並自動清除。
conversion.expired.startNew           → 開始新轉檔

12. 無障礙

區塊 規格
Sidebar 「轉檔」項目 aria-current="page"(沿用 sidebar 規格)
EmptyState role="status"(無互動結構,純宣告)
Upload Dialog shadcn Dialog 內建 focus trap、ESC 關閉
Drop zone role="button" + tabIndex=0 + aria-label="拖曳或選擇模型檔案"Enter / Space 觸發 file picker
Chip RadioGroup role="radiogroup" + aria-label="目標晶片";每個 chip role="radio" + aria-checked
上傳進度 role="progressbar" + aria-valuenow={pct}uploadingaria-busy="true"processing indeterminate
Stage indicator <ol role="list"> + 每 stage <li role="listitem"> + 當前 aria-current="step" + 完成 aria-label="{name}(已完成)"
Status banner / 錯誤 role="alert" + aria-live="assertive"completed.failed
成功 toast sonner 已自帶 role="status" aria-live="polite"
Tab 標題更新 document.title 變動時 SR 不會主動朗讀,但對視覺使用者足夠
觸控目標 兩個主要 action 按鈕高度 h-1040px符合 32px 桌面門檻
Dark Mode 所有色塊都用 token / Tailwind dark variant無 hardcoded hex

色彩對比驗證(手動 sample

  • success cardbg-green-50 text-foreground → > 12:1OK
  • failed cardbg-destructive/5 text-foreground → > 10:1OK
  • amber expiry hinttext-amber-700 on bg-card → 5.7:1≥ AA 4.5:1

13. Phase 0.8 不做(明確列出)

對齊 PRD §5 Non-Goals本 wireframe 不設計以下元素:

  • 轉檔歷史清單(/conversion/history
  • 取消正在跑的 job UIprocessing 畫面沒有 取消按鈕,只有 hint「離開沒關係」
  • 多 chip 同時轉檔chip RadioGroup 是單選,不是 Checkbox
  • SSE / WebSocket 進度推送(純 polling
  • 進階參數FP16、自訂量化、batch size
  • 模型版本管理 / A/B 比較
  • 配額計費 UI

14. 對 PM / Architect 的補充建議

(也整理在交付回報中,這裡放完整版)

給 PM

  1. 「加到模型庫」是否需要 Dialog 確認名稱? 我的建議是保留單欄位 Dialog只問模型名稱、預設帶 job name描述放 Phase 1。如果你想最簡可以直接靜默 import — 但這樣使用者沒辦法控制 /models 頁顯示的名稱。請決定。
  2. 「開始新轉檔」的位置:我把它放在 success 結果卡片外面下方,避免被誤點而離開結果頁;舊 job 的 import / download 按鈕仍在 result card 內可重複使用。建議 confirm。
  3. active job 檢查端點/conversion 載入時要打 GET /api/conversion/active(或類似),這個端點 visionA backend 是否已規劃?如果沒有,可以用「使用者按開始時才打」的方式 fallback但體驗會差一點使用者切回頁面要先按按鈕才知道有 job
  4. 錯誤訊息對照表§8.1 的對照沿用 PRD §F5。如果 converter 未來新增 error code需要同步更新這張表建議在 backend 加 i18n fallback未知 code 一律用 conversion.error.unknown.*

給 Architect

  1. Active job 檢查 API:建議加 GET /api/conversion/active,回 { active: bool, jobId?, status?, source_filename?, target_chip?, started_at? }。沒這個 API 就要靠前端 store 或 localStorage 記 jobId會讓「換瀏覽器 / 換分頁」這個情境壞掉。
  2. Polling 對 backend 的負擔visionA backend 中繼 polling 時510 秒/次/user記得 cache 個 23 秒避免 hammer converter。如果預期 10+ concurrent users這層 cache 必要。
  3. Upload streaming proxy 的進度XMLHttpRequest.upload.onprogress 算的是 browser → visionA backend 的進度。如果 backend 是把 stream forward 到 converter前端進度條 100% 不代表 converter 收到 100% — 之間有 backend buffering 延遲。如果這個延遲明顯,建議 backend 在 forward 完成後才 200而不是 browser 上傳完就 200。請確認語意。
  4. Job 7 天 GC 提醒success card 的「6 天 21 小時後清除」需要 backend 從 job.expires_at 算。如果 converter 不直接給 expires_at要 visionA backend 自行從 created_at + 7d 推算並回 frontend。
  5. 延伸未來如果要支援「使用者主動取消轉檔」Phase 1UI 位置我會放在 processing 畫面右上角的 menukebab— 但這個 wireframe 不畫,避免雛形階段引入複雜度。

給 Frontend

  1. 「上傳中」分頁標題更新(conversion.uploading.tabTitle)需要 document.title = ... 動態改;上傳完成或頁面卸載要還原。建議寫個小 hook usePageTitle(title)
  2. Indeterminate progress barshadcn Progress 沒有 indeterminate 樣式,需要自己加 CSS animationanimate-pulse 或 stripe shimmer
  3. prefers-reduced-motionindeterminate progress 與 spinner 都要尊重reduced-motion 下改用靜態 dot pattern 或純文字。

15. 對應的 Mermaid 流程圖

stateDiagram-v2
    [*] --> idle
    idle --> uploading : 點「開始轉檔」+ 選檔/選 chip + 送出
    uploading --> idle : 取消 / 上傳失敗
    uploading --> processing : XHR 200 + 拿到 job_id
    processing --> success : poll 到 status=succeeded
    processing --> failed : poll 到 status=failed
    success --> idle : 點「開始新轉檔」
    failed --> idle : 點「重新開始」
    note right of processing
        polling /api/conversion/{job_id}
        每 510 秒一次
        分頁不可見時暫停
    end note
    note right of idle
        進入頁面先打 GET /jobs/active
        若有 active 直接落 processing
    end note