Deva

文章佇列在三份草稿各自進行研究、生成、檢查與儲存時,只顯示一個不透明的 Generating 轉圈動畫。

那在技術上正確,但在實務上幾乎毫無幫助。伺服器知道的遠比介面顯示的多,因此我把轉圈動畫替換成由 Server-Sent Events 驅動的即時三槽步進器。

串流實際工作進度

POST /api/articles/drafts/queue 現在透過 SSE 串流進度。每個事件都會回報目前槽位的有意義狀態:Researching、Generating、Verifying links 或 Done。

儀表板透過 GenerationProgress 渲染這些事件。不再盯著轉圈動畫猜測是否正在進行,我可以看見研究時來源數量即時出現、生成後草稿標題送達、以及只有在草稿真正建立時才觸發的完成事件。

最後這點很重要。slot_done 會附帶真正的 draft_id,並在正確的時機發送。在持久化完成前就顯示愉快的 Done 狀態,並不是真正的進度回報,而是帶有載入指示器的虛構結果。

進度必須來自實際工作

在路由新增 SSE 很簡單,但有意義的進度必須源自生成流程更深處。

我把 progress_cb 貫穿 backfill 與 generate_article_payload,也就是實際執行各階段的地方。這能確保事件與正在進行的工作綁定,而不是由路由用計時器或裝飾性百分比來估計進度。

沒有「73% 完成」之類的虛假宣稱。文章生成並不提供乾淨的線性工作單位。具名的階段與具體成品更誠實:找到來源、生成標題、驗證連結、持久化草稿。

失敗時應保留已完成的工作

現在佇列填入一次只建立一個槽位的草稿。如果第三個槽位失敗,第一、第二個槽位仍會被持久化且可見。

先前把佇列填入視為單一不可分割的操作,雖然能讓介面變得簡單,但也會丟棄已完成的有用工作,或把它藏在後續失敗之後。每槽持久化讓部分成功擁有第一級地位。

錯誤會留在相關的步進器槽位內。使用者可以看到哪個草稿失敗,同時不會失去其他槽位的進度與結果。這比把整個佇列收攏成單一紅色橫幅的失敗模型好得多。

相容性權衡

SSE 讓瀏覽器體驗大幅改善,但並非每個用戶端都預期收到串流。我保留了 JSON 回退機制,供不使用 SSE 的用戶端。

這產生了兩條需要維護的回應路徑。雖然增加了表面積,但強迫所有現有用戶端改用串流,會把儀表板改進變成不必要的相容性中斷。只要兩種用戶端都存在,這個權衡就是值得的。

如果重來我會怎麼做

我會在把回呼函式貫穿整個堆疊之前,先定義好進度事件結構描述。階段、payload 欄位、錯誤形狀與完成語意才是真正的合約。從這裡開始,能讓後端與儀表板更快收斂,尤其是在決定槽位真正變成 Done 的精確時機。

更廣泛的教訓很簡單:如果伺服器知道正在發生什麼,介面就不應該假裝它只知道「有東西正在發生」。