# 跨系統使用者流程圖 — VisionA 終端使用者視角 > **視角**:VisionA 終端使用者(Edge AI 應用開發者)在 VisionA 平台內完成模型轉檔的完整體驗。 > > **重點**:從使用者能「感知到什麼」切入,而非技術細節。Token 種類、錯誤情境等只標示關鍵分支,詳細 API 規格見 `04-architecture/TDD.md`。 > > **涵蓋情境**: > - 情境 A:首次上傳模型並轉檔(主要 Happy Path) > - 情境 B:離開頁面後回來看未完成的 job(Recovery) > - 情境 C:Phase 2 使用者下載模型(阻塞中 — 僅供未來參考) > > **服務標記慣例**: > - 🧑 = VisionA 終端使用者(人類) > - 🖥️ = VisionA 前端(瀏覽器) > - ⚙️ = visionA-backend(Go 服務) > - 🔐 = Member Center(Auth 中心) > - 📦 = File Access Agent(檔案閘道) > - 🏭 = Kneron Converter API(本專案) > - 🗄️ = Converter Bucket(MinIO 暫存) > - 💾 = NAS Bucket(模型庫長期儲存) ## 變更歷程 | 日期 | 變更內容 | |------|---------| | 2026-04-25 | 原始模型上傳路徑改為 visionA-backend → Converter multipart 直連(不經過 File Access Agent)。`POST /api/v1/jobs` 改為 multipart/form-data,移除 `input_object_key` 欄位,`user_id` 改以 multipart field 帶入。Phase 1 Converter 不再從 File Access Agent 讀取原始模型,只在 promote 階段 PUT 結果檔。階段編號:原「階段 2(上傳 FAA)+ 階段 3(建 job)」合併為新「階段 2(建 job + 上傳)」,後續階段編號往前挪一格。| --- ## 情境 A:使用者上傳新模型並轉檔(Happy Path) ### A.1 使用者視角的流程摘要(非技術) ``` 🧑 使用者在 VisionA 平台 │ ├─ 1. 在「模型庫」按「新增模型」→ 選本機 ONNX 檔 │ ├─ 2. 填寫 model_id / version / 選擇 Kneron 晶片平台(520/720/...) │ ├─ 3. 按「上傳並轉檔」 │ ↓ │ [系統顯示進度條:ONNX 優化 0% → 33% → BIE 量化 33% → 66% → NEF 編譯 66% → 100%] │ ├─ 4. 轉檔完成!顯示成功訊息 + 「加進我的模型庫」按鈕 │ └─ 5. 按「加進我的模型庫」→ 短暫 loading → 顯示「已加入模型庫」 ↓ 使用者在模型庫看到新的已轉檔模型 ``` **使用者感知到的總體體驗**:從選檔到加進模型庫,一條流水線,使用者不需要知道背後有多個服務在協作(建 job 時涉及 visionA-backend / Member Center / Converter,promote 時才會再呼叫 File Access Agent)。 --- ### A.2 完整跨系統流程圖(Mermaid Sequence) ```mermaid sequenceDiagram autonumber participant U as 🧑 使用者 participant FE as 🖥️ VisionA 前端 participant BE as ⚙️ visionA-backend participant MC as 🔐 Member Center participant FA as 📦 File Access Agent participant CV as 🏭 Converter API participant CB as 🗄️ Converter Bucket participant NB as 💾 NAS Bucket Note over U,FE: 階段 1:選檔 + 填表 U->>FE: 選本機 ONNX 檔 U->>FE: 填 model_id / version / platform U->>FE: 按「上傳並轉檔」 FE->>BE: 送出 (multipart: 檔案 + 參數) Note over BE,CB: 階段 2:visionA-backend 呼叫 Converter 建 job(multipart 同時帶原始模型) BE->>MC: POST /oauth/token (client_credentials, scope=converter:job.write) MC-->>BE: service token (aud=kneron_converter_api) BE->>CV: POST /api/v1/jobs
Authorization: Bearer token
Content-Type: multipart/form-data
files: model (必填, ≤500MB), ref_images[] (optional, maxCount 100)
fields: user_id, model_id, version, platform, enable_evaluate, enable_sim_fp, enable_sim_fixed, enable_sim_hw CV->>MC: 驗 token (JWKS) MC-->>CV: 驗證通過 CV->>CV: 檢查:user_id 是否有 in-progress job?
(US-11 同使用者一個轉檔限制) alt 有 in-progress job CV-->>BE: 409 Conflict + {code: user_has_active_job, active_job_id, active_job_status, active_job_stage} BE-->>FE: 409 + active job 詳情 FE-->>U: 顯示「你有一個轉檔進行中,要切過去看嗎?」 Note over U: 走情境 B(Recovery) else 沒有 in-progress job CV->>CB: 寫入原始模型 (jobs/{job_id}/input/{filename})
(含 ref_images[] 如有) CV->>CV: 建 job 記錄,塞進 Redis
Enqueue 到 onnx-worker CV-->>BE: 201 Created + {job_id, status: created} BE-->>FE: 201 + job_id end Note over FE,CV: 階段 3:Polling 進度(每 2-5 秒一次) loop 直到 status=completed 或 failed FE->>BE: GET /api/jobs/{job_id}/status BE->>CV: GET /api/v1/jobs/{job_id}
Authorization: Bearer token CV-->>BE: {status, stage, progress, stage_timings, expires_at, ...} BE-->>FE: 轉譯後的進度 JSON FE->>U: 更新進度條 + 階段顯示 end Note over CV: Worker 背景處理:ONNX → BIE → NEF
(Crash 即 Reset 設計,不保證跨 Crash 存活) CV->>CB: 儲存各階段結果(out.onnx / out.bie / out.nef) Note over FE,U: 階段 4:轉檔完成,顯示「加進模型庫」按鈕 CV-->>BE: {status: completed, result_object_keys: [...], expires_at: "2026-05-02"} BE-->>FE: 已完成 + result 資訊 FE->>U: 顯示「轉檔完成!」
+「加進我的模型庫」按鈕
+「檔案將於 7 天後自動清除」提示 Note over U,NB: 階段 5:使用者按「加進模型庫」→ promote U->>FE: 按「加進我的模型庫」 FE->>BE: POST /api/models/{job_id}/add-to-library BE->>CV: POST /api/v1/jobs/{job_id}/promote
Authorization: Bearer token
Body: {target_object_key: "visionA/models/{user_id}/{model_id}/v{version}/out.nef", ...} CV->>MC: 取 files:upload.write token(若 cache 過期) MC-->>CV: service token CV->>CB: 讀結果檔 CV->>FA: PUT /files/{target_object_key}
Authorization: Bearer token
Body: out.nef FA->>MC: 驗 token MC-->>FA: 驗證通過 FA->>NB: 儲存到 NAS(使用者的模型庫區) FA-->>CV: 200 OK CV-->>BE: 200 OK + promoted_object_keys BE-->>FE: 成功 FE->>U: 顯示「已加入模型庫」 Note over U: 使用者在模型庫看到新模型 ``` ### A.3 使用者感知到什麼 vs 背後發生什麼 | 使用者看到 / 做的事 | 背後發生的事(簡述)| |------------------|------------------| | 選檔並按「上傳並轉檔」 | visionA-backend 以 multipart/form-data 直接把原始模型上傳到 Converter(經 Member Center auth 取 `converter:job.write` service token),Converter 收檔後暫存在自己的 Bucket | | 進度條跳動 | visionA-backend 每 2-5 秒 polling Converter,拿到新進度就更新 UI | | 「ONNX 優化 → BIE 量化 → NEF 編譯」階段切換 | Converter 的 Task Scheduler 在 Redis 中推進 job stage,Worker 消耗 stage queue | | 「檔案將於 7 天後自動清除」 | Converter Bucket 有 7 天 lifecycle,促使使用者做 promote | | 按「加進我的模型庫」 | visionA-backend 呼叫 Converter `/promote`,Converter 自己把檔案 PUT 到 File Access Agent,不經 visionA-backend(省流量)| | 模型出現在使用者的模型庫 | NAS Bucket 已經收到檔案(在 VisionA 使用者模型庫的 objectKey path 下)| ### A.4 關鍵時間點與可能的等待體驗 | 時間點 | 使用者感受 | 影響 UX 的因素 | |-------|---------|--------------| | 按「上傳並轉檔」後 | Loading / 上傳進度條 | 單一 multipart 上傳從 visionA-backend 到 Converter 的時間,**與檔案尺寸相關**(上限 500MB)。**建議 UI 顯示 upload progress bar**:multipart 上傳可追蹤 byte 進度(XHR upload progress event 或等效機制),不再像舊設計有「上傳 FAA + 拉檔入 Bucket」兩段式等待 | | 進度條階段切換 | 期待每階段的時間均等,但實際上 NEF 通常最久 | 建議 UI 顯示各階段預估時間(可從 `stage_timings` 歷史推算)| | 轉檔完成 → 按「加進模型庫」 | 短暫 loading | API p95 < 3s,使用者體感應該 OK | | 轉檔失敗 | 希望看到具體原因 | 錯誤碼要結構化,`error.details.stage` + 人類可讀的 `error.details.reason` | --- ## 情境 B:使用者離開頁面後回來(Recovery) ### B.1 使用者視角 ``` 🧑 使用者在轉檔進行中... │ ├─ 按上一頁 / 關閉瀏覽器 / 切到其他 app │ [Converter 背景繼續轉檔,不受影響] │ ├─ 15 分鐘後回來 VisionA,進入「模型庫」或「轉檔中心」 │ ├─ 系統自動偵測到「你有一個轉檔進行中」 │ └─ 顯示:「模型 XYZ 正在轉檔(BIE 階段 60%),要繼續追蹤嗎?」 │ └─ 使用者按「繼續追蹤」→ 回到進度頁,正常 polling 進度 ``` ### B.2 跨系統流程(Mermaid Sequence) ```mermaid sequenceDiagram autonumber participant U as 🧑 使用者 participant FE as 🖥️ VisionA 前端 participant BE as ⚙️ visionA-backend participant MC as 🔐 Member Center participant CV as 🏭 Converter API Note over U,FE: 使用者重新打開 VisionA 模型庫頁 U->>FE: 進入頁面 FE->>BE: GET /api/models/in-progress?user_id=... BE->>MC: 取 service token(若 cache 過期)
scope=converter:job.read MC-->>BE: service token BE->>CV: GET /api/v1/jobs?user_id=...&status=in_progress
Authorization: Bearer token CV->>MC: 驗 token MC-->>CV: 驗證通過 CV->>CV: 從 Redis 查 user_id 對應的所有 in-progress job CV-->>BE: [{job_id, status, stage, progress, created_at}, ...] alt 有 in-progress job BE-->>FE: job 列表 FE->>U: 顯示「你有一個轉檔進行中
模型 XYZ - BIE 階段 60%
[繼續追蹤] [放著不管]」 U->>FE: 按「繼續追蹤」 FE->>U: 進入進度頁,開始 polling
(同情境 A 階段 4) else 沒有 in-progress job BE-->>FE: 空陣列 FE->>U: 正常顯示模型庫頁,不打擾使用者 end Note over CV: ⚠️ 特別情境:Converter 在使用者離開期間 Crash Note over CV: Redis 被重置 → in-progress job 消失
(符合「Crash 即 Reset」設計) Note over U: 使用者回來看不到 job
需要重新上傳(US-12 已明示此限制) ``` ### B.3 Recovery 的 UX 考量 | 考量點 | 處理建議 | |-------|---------| | 使用者不記得自己有 job 在跑 | 進入頁面時主動查詢並顯示提示 | | 使用者有多個裝置(手機+電腦)都登入 | 每個裝置都會看到相同的 in-progress job(因為是 server-side 查詢)| | Converter Crash 導致 job 消失 | VisionA 前端顯示「上次的轉檔記錄已遺失,建議重新上傳」(需 visionA-backend 決定是否保留本地 cache)| | 回來時 job 已完成 | 可查 `GET /api/v1/jobs?user_id=...&status=completed` 看最近完成的 job(可選功能)| --- ## 情境 C:Phase 2 — 使用者直接下載模型庫檔案(阻塞中) > ⚠️ **重要提示**:本情境的 `POST /file-access/download-tokens` endpoint 在 Member Center **尚未實作**,Phase 1 **不做此情境**。 > > 以下流程圖僅供未來 Phase 2 啟動時參考。Phase 1 上線時使用者如何處理下載需求,見 `design-review.md` 第 6 節「議題 #1」。 ### C.1 Phase 2 目標使用者體驗 ``` 🧑 使用者在 VisionA 模型庫 │ ├─ 看到已轉檔的模型 │ ├─ 按「下載 .nef 檔」 │ └─ 瀏覽器直接下載(快速,因為直連 File Access Agent,不經 visionA-backend) ``` ### C.2 Phase 2 跨系統流程(Mermaid Sequence,尚未實作) ```mermaid sequenceDiagram autonumber participant U as 🧑 使用者 participant FE as 🖥️ VisionA 前端 participant BE as ⚙️ visionA-backend participant MC as 🔐 Member Center participant FA as 📦 File Access Agent participant NB as 💾 NAS Bucket Note over U,FE: 使用者按下載按鈕 U->>FE: 按「下載 .nef」 FE->>BE: POST /api/models/{model_id}/download-token Note over BE,MC: ⚠️ Phase 2 阻塞中:Member Center 尚未實作此 endpoint BE->>MC: POST /file-access/download-tokens
Body: {tenant_id, user_id, object_key, scope: "files:download.delegate"} MC-->>BE: {delegated_token (opaque, exp <= 5min), expires_at} BE-->>FE: delegated_token + File Access Agent 的 download URL Note over FE,FA: 瀏覽器直接下載(不經 visionA-backend,省流量) FE->>FA: GET /files/{object_key}?token= FA->>MC: POST /validate-delegated-token
Body: {token} MC-->>FA: {valid: true, user_id, object_key, tenant_id} FA->>NB: 讀檔 FA-->>FE: 檔案(stream 下載) FE->>U: 瀏覽器儲存檔案 ``` ### C.3 Phase 2 體驗 vs Phase 1 折衷方案的 UX 落差 | 項目 | Phase 1 折衷方案(任一)| Phase 2 完整方案 | |------|----------------------|----------------| | 下載速度 | 慢(大檔走 visionA-backend proxy)或無法下載(隱藏按鈕)| 快(瀏覽器直連 File Access Agent)| | 對 visionA-backend 的負擔 | 高(若 proxy)| 零 | | 檔案流量路徑 | 使用者 → visionA-backend → File Access Agent → 使用者 | 使用者 ↔ File Access Agent(直連)| | 使用者 UX | 有 spinner,可能 timeout | 瀏覽器原生下載體驗 | | 安全性 | 同 Phase 2(都走 Member Center auth)| 同 | --- ## 綜合:三個情境的狀態機 ```mermaid stateDiagram-v2 [*] --> 選擇模型: 使用者進入 VisionA 選擇模型 --> 上傳中: 選檔 + 填表 + 提交 上傳中 --> 建 job: multipart 直接上傳到 Converter(含原始模型 + ref_images) 建 job --> 有衝突: 檢查 user_id 已有 in-progress 有衝突 --> 繼續追蹤舊 job: 使用者選擇查看既有 job 建 job --> 轉檔中: 無衝突,Converter 收檔並建立 job 轉檔中 --> ONNX 階段 ONNX 階段 --> BIE 階段: 完成 BIE 階段 --> NEF 階段: 完成 NEF 階段 --> 轉檔完成: 完成 ONNX 階段 --> 轉檔失敗: 失敗 BIE 階段 --> 轉檔失敗: 失敗 NEF 階段 --> 轉檔失敗: 失敗 轉檔中 --> 使用者離開: 使用者關頁面 使用者離開 --> 繼續追蹤舊 job: 使用者回來(走情境 B Recovery) 使用者離開 --> Job 消失: Converter Crash(符合設計哲學) Job 消失 --> [*] 轉檔完成 --> 已 promote: 使用者按「加進模型庫」(Converter PUT 到 File Access Agent) 已 promote --> 下載: 使用者要用模型 下載 --> Phase 1 折衷: Phase 1(下載功能缺口) 下載 --> Phase 2 直連: Phase 2(等 Member Center 補完) 轉檔失敗 --> [*]: 使用者看錯誤訊息 Phase 1 折衷 --> [*] Phase 2 直連 --> [*] 繼續追蹤舊 job --> 轉檔中 ``` --- ## 附錄:Token 類型快速對照 | Token | 誰持有 | 誰簽發 | 誰驗 | 用途 | |-------|-------|-------|------|------| | visionA-backend service token(aud=kneron_converter_api, scope=converter:job.write / converter:job.read)| visionA-backend | Member Center(client_credentials grant)| Converter API(JWKS 驗簽)| visionA-backend 呼叫 Converter API(建 job / 查詢 / promote)| | Converter service token(aud=file_access_api, scope=files:upload.write)| Converter API | Member Center(client_credentials grant)| File Access Agent | Converter 在 promote 階段 PUT 結果檔到 NAS。**Phase 1 不再需要 `files:download.read` / `files:metadata.read`**,因為原始模型已改由 visionA-backend multipart 直接上傳到 Converter,Converter 不再從 FAA 拉檔 | | Delegated download token(Phase 2)| 瀏覽器 | Member Center(代 user 簽)| File Access Agent(線上驗)| 使用者瀏覽器直連 File Access Agent 下載 | --- ## 附錄:錯誤情境快速對照 | 使用者看到的訊息 | 技術原因 | 對應 HTTP 狀態 + error code | |---------------|---------|--------------------------| | 「你有一個轉檔進行中,要切過去看嗎?」| 同使用者同時一個轉檔限制觸發 | `POST /api/v1/jobs` 回 409 `user_has_active_job` | | 「轉檔失敗:BIE 量化階段 — [具體原因]」| Worker 內部失敗 | Job 的 `status=failed`, `error.details.stage=bie`, `error.details.reason=...` | | 「上傳失敗,請確認檔案格式與欄位」| multipart body 格式錯 / `model` 欄位缺失 / 必填 field 缺失(例如 `user_id`、`model_id`)| `POST /api/v1/jobs` 回 400 `invalid_multipart` | | 「檔案過大,請確認模型不超過 500MB」| 上傳檔案超過 Converter 的大小上限 | `POST /api/v1/jobs` 回 413 `file_too_large` | | 「模型庫服務暫時不可用,請稍後再試」| promote 時 File Access Agent 不可用 | `POST /promote` 回 502 `file_gateway_unavailable` | | 「轉檔還沒完成,請等進度條到 100% 再加進模型庫」| promote 時 job 還沒 `completed` | `POST /promote` 回 409 `job_not_ready_for_promote` | | 「你的登入已過期,請重新登入」| visionA-backend 的 user session 過期(visionA 自己的 auth,不是 Converter 的問題)| visionA-backend 自己處理 | | 「系統錯誤,請聯絡客服」| Member Center 或 File Access Agent 完全無法連線 | `POST /api/v1/jobs` 回 503 `auth_service_unavailable` 或類似(Phase 1 建 job 本身不依賴 FAA,FAA 錯誤集中在 promote)| --- ## 結語 這份跨系統流程圖揭露的關鍵 UX 訊息: 1. **使用者只看到「在 VisionA 平台內按幾下就轉完」**,背後有多個服務協作(visionA-backend / Member Center / Converter / File Access Agent)— 這是架構要盡力維持的體驗。值得注意的是 Phase 1 **建 job 階段只涉及 3 方**(visionA-backend / Member Center / Converter),File Access Agent 僅在 promote 階段參與,簡化了上傳時的故障面 2. **Phase 1 的 UX 閉環卡在「下載」**,需要 VisionA 產品團隊協調 messaging(見 `design-review.md` 議題 #1) 3. **Recovery 體驗是 VisionA 的 UX 優勢**,但受限於「Crash 即 Reset」設計,不保證跨 Crash 存活 — 要誠實告知使用者 4. **錯誤訊息的人類可讀性直接決定使用者對系統的信任**,Architect 在 TDD 中要重視 error code 的結構化設計 5. **multipart 直連上傳的 UX 優勢**:相較於舊設計「visionA-backend → FAA → Converter 拉檔」兩段式,新設計從使用者按下「上傳並轉檔」到 Converter 建 job 成功,只有一次網路傳輸;上傳時間可用 progress bar 精準呈現,不再有中間「看不見的 Converter 拉檔等待」