un2266 commited on
Commit
5c42d58
·
1 Parent(s): 29017e5

開發優化建議改為中性技術文件,移除第一人稱與第二人稱

Browse files

原文件以「我對業主提建議」的書信口吻寫成,全文 50 處「我」「您」。
這份文件的實際讀者是後續承接的工程資源,內容是技術評估與施工方向,
用人稱敘述會讓它讀起來像一封信而不是可依循的技術文件;且交接後提出
人不一定在場,「我」指涉誰會失去對象。

全文改為中性陳述,六個含人稱的章節標題一併改寫:
- 為什麼我把這一項排在第一位 → 列為最高優先的理由
- 請您確認的事項 → 待確認事項
- 我為什麼提出這件事 → 問題背景
- 我為什麼把它歸類為工程範疇⋯ → 歸類為工程範疇、不納入本次改版施工範圍的理由
- 我建議的實作順序 → 建議的實作順序
- 七、待您決策的項目 → 七、待決策的項目

「我測試時」改為「本次測試時」、「我建議」改為「建議」、「等您決定」
改為「待決定」,其餘依此類推。所有事實、數據、程式碼行號與章節結構
均未變動。順帶修掉一處以破折號作補充說明的寫法。

docs/UIUX優化成果與技術說明.md CHANGED
@@ -1,10 +1,10 @@
1
- # SpectraEngine(譯彩紛呈 II 分析引擎)UI/UX 優化成果與技術說明
2
 
3
  - 文件建立日期:2026-07-20
4
  - 最後更新日期:2026-08-06(全文重整為成果導向架構,對應 commit `f8aab79`)
5
- - 專案委託方:致理科技大學應日系 鄧老師
6
- - 專案執行:吳千卉
7
- - 用途:說明本次 UI/UX 優化執行了哪些工作項目,並保留設計規範與技術決策,供後續工程接手參考
8
 
9
  ---
10
 
 
1
+ # 譯彩紛呈 II 分析引擎 SpectraEngine UI/UX 優化成果與技術說明
2
 
3
  - 文件建立日期:2026-07-20
4
  - 最後更新日期:2026-08-06(全文重整為成果導向架構,對應 commit `f8aab79`)
5
+ - 專案委託方:致理科技大學應日系 鄧敏君老師
6
+ - 專案執行:利他設計工作室 吳千卉
7
+ - 用途:說明本次 UI/UX 優化執行的工作項目,並保留設計規範與技術決策,供後續工程接手參考
8
 
9
  ---
10
 
docs/開發優化建議.md CHANGED
@@ -3,26 +3,26 @@
3
  - 文件建立日期:2026-08-05(2026-08-06 全面改版,補入實測數據並重新校正程式碼行號)
4
  - 提出人:吳千卉(本次 UI/UX 改版執行者)
5
  - 對應版本:commit `f8aab79`(分支 `redesign/ui-refresh`),本文件所有 `app_agent.py:行號` 均以此版本為準
6
- - 用途:**供您後續自行規劃、排期與發包**
7
 
8
- 這份文件整理的是我在這次 UI/UX 改版過程中發現、但超出本次委託施工範圍(純視覺/版面調整)的項目。每一項我都寫清楚具體問題、程式碼位置、影響範圍與建議方向,讓您和後續承接的工程資源可以直接依此評估,不需要再回頭問我才能動工。
9
 
10
- > ### ⚠ 請先看這裡:我測試時換過 LLM 模型設定
11
  >
12
- > 我手上沒有您的 OpenRouter API Key,開發期間是用我自己申請的金鑰測試,並在本機 `.env` 把模型改成 `LLM1_MODEL=openrouter/free`/`LLM2_MODEL=openrouter/free`,**沒有使用專案預設的 `google/gemma-4-31b-it`/`google/gemma-4-26b-a4b-it`**。
13
  >
14
- > `.env` 已列在 `.gitignore`(第 35 行)不進版控,所以**我的設定不會隨程式碼交付,您本機與正式機的設定都不受影響**。但有兩件事請留意:
15
  >
16
- > 1. **本文件出現的所有耗時數據,都是在我的模型設定下量測的,不能當作正式機的效能基準**。實測差距不小:專案預設的 Gemma 模型單次回應約 21 秒,我測試用的自動路由則是 36.1 秒。
17
- > 2. **重建環境時(Hugging Face Spaces 的 Secrets、新工程師的本機設定),請確認 `LLM1_MODEL`/`LLM2_MODEL` 填的是您要用的模型代號**,不要沿用本文件範例中出現的 `openrouter/free`。
18
  >
19
  > 完整實測數據見第三節。
20
 
21
- ## 這份文件的檢視角度
22
 
23
- 我從四個角度重新檢視了整個系統,各自帶出的項目如下。同一個項目可能同時命中多個角度,我把它歸在最主要的那一類:
24
 
25
- | 角度 | 我問的問題 | 帶出的項目 |
26
  |---|---|---|
27
  | **機密性** | 使用者上傳的研究語料,會流到哪裡、留在哪裡、誰看得到? | 一、四、六 |
28
  | **可用性** | 使用者實際操作時,會不會卡住、會不會白做工? | 二 |
@@ -39,11 +39,11 @@
39
  |---|---|---|---|
40
  | 一、研究語料傳送至第三方 AI 服務的資料政策 | 機密性 | 高(已在正式機發生中,且資料送出不可逆)| 新發現,多數是帳號設定確認,未必需要工程 |
41
  | 二、持久化機制(存檔/讀取進度)| 可用性 | 高(直接影響長分析的可靠性)| 需求已定案,0% 實作 |
42
- | 三、LLM API 呼叫可靠性強化 | 完整性 | 高(這次實測直接命中)| 新發現,未評估 |
43
- | 四、Session 輸出目錄清理機制 | 機密性/維運 | 中(付費 Spaces,磁碟累積有實際成本,且語料無限期留存)| 原始需求已列出,等您決定 |
44
  | 五、單一檔案 4232 行、且完全沒有自動化測試 | 可維護性 | 中(不緊急,但會放大前面每一項的施工風險)| 新發現,未評估 |
45
  | 六、本機啟動預設開啟公開分享連結 | 機密性 | 低(不影響正式機,影響工程端地端開發)| 新發現,一行設定即可調整 |
46
- | 七、待您決策的項目 | 產品決策 | 低(皆��急迫)| 原始需求已列出,等您決定 |
47
 
48
  ---
49
 
@@ -51,45 +51,45 @@
51
 
52
  ### 現況
53
 
54
- 系統的報告撰寫階段(`run_report_writing()`,app_agent.py:1255)會把使用者上傳的譯文內容組進 prompt(app_agent.py:1273-1290),透過 `_call_openrouter()`(app_agent.py:377-401)送到 OpenRouter。OpenRouter 是一個**路由層**,它會再把請求轉給上游的實際模型供應商(我實測時看到的供應商包含 Google AI Studio)。
55
 
56
  也就是說,使用者上傳的研究語料,實際上會經過至少兩個第三方:OpenRouter 本身,以及它背後路由到的模型供應商。
57
 
58
- 我在程式碼中搜尋不到任何與資料政策、去識別化、或使用者告知相關的處理。
59
 
60
- ### 為什麼我把這一項排在第一位
61
 
62
  這套系統的使用對象是應用日語系的師生,處理的是**尚未發表的翻譯研究語料**。這類資料有兩個特性讓它比一般應用更敏感:
63
 
64
  - **預先揭露的風險**:研究資料在正式發表前流出,可能影響後續投稿與著作權主張
65
- - **不可逆**:前面第二至六節的問題都可以在發現後修好,但資料一旦送出第三方,無法收回
66
 
67
- 我要說清楚的是,**我沒有證據顯示已經發生任何外洩**。我提出來的是:這件事目前沒有任何人在管理,而管理它的成本很低。
68
 
69
- ### 請您確認的事項
70
 
71
- 以下多數不需要動程式碼,是帳號層級的設定確認:
72
 
73
- 1. **OpenRouter 帳號的資料政策設定**:OpenRouter 允許使用者設定是否同意 prompt 內容被保留或用於模型訓練。部分免費模型端點會**要求**開啟較寬鬆的政策才能使用。請確認正式機使用的帳號目前是哪一種設定。
74
  2. **上游供應商的保留政策**:即使 OpenRouter 本身不保留,實際處理請求的供應商(如 Google AI Studio)有自己的政策。若語料機密性要求高,可考慮在 OpenRouter 的 provider routing 設定中限定供應商。
75
- 3. **Hugging Face Space 的可見性**:正式機若設為 public,任何拿到網址的人都能上傳文本並執行分析,也就能消耗您的 API 額度。請確認 Space 目前是 public 還是 private,以及是否需要加上存取控制。
76
 
77
  ### 建議的工程項目
78
 
79
- 1. **在系統內對使用者揭露**:在文本上傳步驟加一段說明,告知「您上傳的內容會傳送至第三方 AI 服務進行分析」。這是成本最低、也最該做的一項,讓使用者能自己判斷哪些語料適合放進來。
80
- 2. **若語料含個人資料**(例如受訪者、學生作業),請一併確認是否符合學校的個資規範,必要時在上傳前加入去識別化步驟。
81
 
82
  ---
83
 
84
  ## 二、持久化機制(存檔/讀取進度)
85
 
86
- ### 我為什麼提出這件事
87
 
88
- 我在 2026-08-05 實測時重現了一個問題:一次完整分析(指標計算+統計分析+報告撰寫)耗時數分鐘,過程中瀏覽器分頁若失去與伺服器的即時連線,**後端會照常把運算跑完、報告與下載檔案也正常產出在磁碟上,但前端畫面永遠停在「處理中」不會更新**。此時使用者若重新整理頁面試圖恢復,畫面只會回到全新首頁;剛才的分析結果雖然還在磁碟上(`sessions/<session_id>/output/`),但介面上沒有任何入口能找回它。
89
 
90
- 我追查後確認,這個缺口從系統第一天架構設計起就存在,與這次改版動到的邏輯無關:系統沒有任何形式的執行狀態持久化。這正是《UIUX優化成果與技術說明》(`docs/UIUX優化成果與技術說明.md`)5.2 列為「持久化機制:存檔/讀取進度」、從專案第一天起進度就是 0% 的項目。
91
 
92
- ### 我為什麼把它歸類為工程範疇、不放進本次改版施工範圍
93
 
94
  《UIUX優化成果與技術說明》1.2「技術背景與施工限制」原本就把 `app_agent.py` 分成三個風險區,並明確界定本次改版的施工邊界:
95
 
@@ -99,9 +99,9 @@
99
  | `create_app()` 版面配置(勿動 `.click()`/`.change()` 綁定本身的邏輯)| 中風險 | 可(僅限版面調整)|
100
  | **`handle_*` 流程函式與核心運算函式** | **高風險** | **改版不應觸碰** |
101
 
102
- 我這次改版完成的項目,性質上都落在前兩區:CSS、版面配置、圖示、既有 handler 裡個別 `gr.update()` 輸出值的修正。
103
 
104
- 而「存檔/讀檔」在技術上無法迴避觸碰第三區。它需要在流程中途把執行狀態完整取出,並在讀檔後把狀態「倒帶」回中斷當下,這代表要重新設計 `handle_*` 系列函式與核心運算函式的進出點介面,在既有邏輯外面加一層存讀檔是做不到的。因此我建議把這項工作交給後續的工程資源評估與實作;為了守住原始需求文件界定的施工範圍,我沒有去動這一區。
105
 
106
  ### 原始需求(2026-07-20 訪談已定案,尚未實作)
107
 
@@ -120,10 +120,10 @@
120
 
121
  ### 現況缺口:連「執行到一半先匯出」都做不到
122
 
123
- 上面談的「讀檔還原」是持久化機制裡最複雜的一半。但目前系統**連更基本的「單向匯出、不要求能讀回來」都不存在**:
124
 
125
  - 現有的下載能力(左側導覽列四顆捷徑)**只在「分析運算」這個步驟裡、且至少一個子階段已產出結果後才會出現**(見 `_populate_tabs()` app_agent.py:3177 的觸發條件)
126
- - 在此之前的步驟——文本上傳、語料說明——使用者可能已經填了不少東西(尤其語料 Metadata 表單有十幾個欄位),但這整段完全沒有任何「先把目前已輸入的內容存個檔」的入口。連線一斷、分頁一關,這幾步全部要重來,不只是找不回結果,是連過程都留不住
127
  - 這件事本質上比完整的「存檔+讀檔」小一步:**先做「匯出」,「讀回來」可以晚一點再做**,也能先驗證「把 `state` + 已上傳檔案 + 表單內容打包成一份可下載的檔案」這件事本身的可行性與格式設計,作為選項 A/C 共同的地基
128
  - 但即使只做單向匯出,仍然需要串接到 `state` 機制與各步驟當下的即時資料(含尚未提交的表單暫存值),加一顆下載按鈕是不夠的
129
 
@@ -154,24 +154,24 @@
154
  - 優點:使用者體驗較友善,不需要自行保管檔案;若搭配輪詢較有機會做到「連線斷掉後自動接回」
155
  - 缺點:**這正是 2026-07-20 需求訪談時明確討論並否決的方向**(無登入代表任何人拿到代碼/連結就能看到他人研究資料)。若要重新考慮,需要重新拍板,並額外設計存取控制
156
 
157
- **選項 C(我這次實測後新增的建議):先做「長任務容錯」,與完整存讀檔脫鉤分階段處理**
158
 
159
  不等待完整的存讀檔功能,先把「分析運算」這個最耗時的階段,從目前綁死在單一條前端-後端串流連線上的 generator 模式,改成**後端非同步 job + 前端輪詢**:分析開始後立即回傳一個 job 識別碼,後端在背景把進度寫入 session 目錄下的進度檔案,前端每隔幾秒輪詢一次。
160
 
161
  - 優點:**直接解決本節重現的真實痛點**,只要 `session_id` 還在瀏覽器端(例如寫進 URL query string 或 localStorage),就算分頁重新整理或連線重連,前端都能重新輪詢到目前進度;施工範圍比完整存讀檔小很多;若進度檔案格式與未來 `.spectra` 格式相容,之後銜接完整存讀檔的邊際成本會降低
162
  - 缺點:不等於完整的持久化機制,沒有「關掉瀏覽器、隔天回來繼續」的能力;需要把 `run_computation_pipeline_streaming`(app_agent.py:1704)與 `_run_computation`(app_agent.py:3975)目前的 generator/yield 架構改成 job 佇列模式,同樣觸碰核心運算函式範圍
163
 
164
- ### 我建議的實作順序
165
 
166
  1. **選項 C 優先**:風險與工作量相對可控,且直接解決實測到的既有問題,任何一次超過 1-2 分鐘的分析都會受益
167
- 2. **選項 A 其次**:完成需求文件已定案的「進度存檔/讀檔」,但請一併考量 `.spectra` 檔案本身的機密性
168
  3. **選項 B 除非主動要求**:牽涉到已被否決過一次的資安權衡,需要重新拍板才建議投入
169
 
170
  ---
171
 
172
  ## 三、LLM API 呼叫可靠性強化
173
 
174
- 2026-08-05 實測過程中,我用自己申請的 OpenRouter API Key 直接測試了 `_call_openrouter()`(app_agent.py:377-401)的行為,發現兩個目前程式碼裡沒有處理、會直接影響分析穩定性的問題(3.1、3.2)。這兩點集中在同一個函式,建議一次評估、一次修改。
175
 
176
  ```python
177
  def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=16000, temperature=0.5):
@@ -197,36 +197,36 @@ def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=1600
197
 
198
  **建議**:如果要有一個真正的總時間上限,需要額外用執行緒或 `signal` 搭配獨立計時器強制中斷(`urllib` 本身的 `timeout` 參數做不到),或改用支援總逾時設定的 HTTP client(例如 `httpx` 可分別設定 connect/read/write/pool 各階段)。
199
 
200
- ### 3.3 未來換模型時的注意事項(我的測試環境限制,非目前設定的問題)
201
 
202
- 先說結論:**這一節描述的現象在專案預設模型下不會發生**,我記錄下來是給未來換模型時參考的。
203
 
204
- 我測試時把模型設成 `openrouter/free`。這個代號並非固定模型,它是 OpenRouter 的**自動路由別名**,每次呼叫可能被導去不同的免費模型。我用同一個代號連續測兩次,分別被導去 `nvidia/nemotron-3-nano-30b-a3b:free` 與 `openai/gpt-oss-20b:free`,證實路由結果確實每次不同。這兩個模型都預設會做 reasoning,回應 JSON 裡有一段獨立的 `"reasoning"` 欄位。
205
 
206
  而 `_call_openrouter()` 的回傳只取 `result["choices"][0]["message"]["content"]`(app_agent.py:399���,**`reasoning` 欄位從頭到尾沒有被讀取或使用**,這段思考過程佔掉的生成時間對使用者看到的報告內容沒有任何貢獻。
207
 
208
- 我用同一段測試 prompt(`max_tokens=800`)比較兩種設定:
209
 
210
- | | 專案預設 `google/gemma-4-31b-it` | 我測試用的 `openrouter/free` |
211
  |---|---|---|
212
  | 實際路由模型 | 固定為指定模型 | 每次不同(實測兩次得到兩個不同模型)|
213
  | `reasoning` 欄位 | 空(`None`)| 1056 字元 |
214
  | `reasoning_tokens` | **0** | **239**(佔 800 個生成 token 的 30%)|
215
  | 單次回應耗時 | **21.0 秒** | 36.1 秒 |
216
 
217
- **專案預設的 Gemma 模型完全沒有 reasoning 浪費的問題,而且比我測試用的設定更快。** 我先前觀察到報告撰寫偏慢,有相當比例來自我自己的模型設定。
218
 
219
  **建議**:
220
- - 專案預設模型的實際耗時,請由使用正式設定的環境重新量測一次,我這邊的數字不適合當基準
221
- - 未來換模型時請直接指定固定的模型代號,避免使用 `openrouter/free` 這類自動路由別名
222
  - 若換到的模型會做 reasoning,建議在 `_call_openrouter()` 明確關閉(部分模型可透過 request body 加 `reasoning: {"enabled": false}` 或類似參數)
223
  - 若報告撰寫階段仍然偏慢,中期可考慮改用 OpenRouter 的串流 API,讓報告逐字回傳、前端即時顯示,使用者體感會從「畫面死掉好幾分鐘」變成「看得到字在跑」
224
 
225
  ### 3.4 模型選用的兩點提醒
226
 
227
- **第一,專案預設模型是計費的。** 我用一段 60 個 prompt token、生成 800 個 token 的測試請求呼叫 `google/gemma-4-31b-it`,OpenRouter 回報 `cost: 0.0002774`。單次成本很低,但實際報告撰寫是 `max_tokens=16000`,量級差 20 倍,且每位使用者每次分析都會呼叫。請確認 OpenRouter 帳號的額度與付款方式已設定妥當,避免額度用罄導致服務全面中斷。
228
 
229
- **第二,請不要把模型代號改成加 `:free` 後綴的版本。** 我實測 `google/gemma-4-31b-it:free` 直接回 HTTP 429,錯誤訊息為 `is temporarily rate-limited upstream`,`limit_source` 標示為 `upstream_provider_shared_pool`,也就是這個免費端點是所有 OpenRouter 使用者共用的池子,隨時可能被限流。若為了節省成本改用 `:free` 變體,會讓分析在尖峰時段大量失敗,而目前又沒有重試機制(見 3.1),使用者會直接看到該階段失敗。
230
 
231
  ---
232
 
@@ -236,18 +236,18 @@ def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=1600
236
 
237
  這一項同時命中兩個角度:
238
 
239
- - **機密性**:每一份使用者上傳過的研究語料,都會無限期留存在正式機的磁碟上,沒有任何刪除機制,也沒有告知使用者這件事。若某位老師只是想試跑一下,他的語料就永久留在那台機器上了。
240
  - **維運成本**:正式機部署在付費的 Hugging Face Spaces 上,磁碟累積會直接反映在儲存空間的採購規格與費用上。
241
 
242
  **建議**:先確認目前 `sessions/` 已累積的用量,再決定清理策略(例如保留 N 天後自動刪除),並在介面上告知使用者資料的保存期限。
243
 
244
- 如同第二節所述,這項工程與持久化機制高度相關,存讀檔上線後只會讓孤兒目錄累積得更快,建議兩者一併規劃排期。
245
 
246
  ---
247
 
248
  ## 五、單一檔案 4232 行、且完全沒有自動化測試
249
 
250
- 這一項不緊急,但它會放大前面每一項的施工風險,所以我把它獨立列出來。
251
 
252
  ### 現況
253
 
@@ -260,13 +260,13 @@ def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=1600
260
 
261
  第二節的持久化機制、第三節的重試邏輯,動刀的位置都在這個檔案的核心運算區。在沒有任何自動化測試的情況下,每一次改動都只能靠人工重跑完整流程來確認沒弄壞既有功能,而完整流程本身要跑好幾分鐘。這會讓工程估時裡的「驗證」成本遠高於「實作」成本,也讓後續維護者不敢動。
262
 
263
- 我這次改版有做兩個小幅改善,可以當作後續整理的起點:CSS 已從主檔抽出為 `assets/app.css`(commit `7b42168`),輸出清單已拆成具名子清單(commit `54e74f4`)。這兩個改動都沒有改變任何行為。
264
 
265
  ### 建議
266
 
267
  1. **先加回歸測試,再談重構**。順序反過來會很危險。優先涵蓋純函式部分:格式檢查(`run_format_check()` app_agent.py:1579)、狀態建立(`make_initial_state()` app_agent.py:1368)、指標計算等不依賴 Gradio 的邏輯,這些最容易測、也最值得測。
268
  2. **把散落的設定集中**到檔案頂端的設定區塊或獨立的 `config.py`,讓「要調哪個數字」不需要在 4000 行裡找。
269
- 3. **把 handler 從 `create_app()` 內部搬到模組層級**,改用參數傳遞取得所需元件。這一步能讓 handler 變成可測試的普通函式,但它會大量觸碰事件綁定,建議等測試就位後再做。
270
 
271
  這三步可以分次進行,不需要一次到位。
272
 
@@ -285,9 +285,9 @@ app.launch(
285
  )
286
  ```
287
 
288
- `share=True` 是寫死的,不受任何環境變數控制。這代表**任何人只要執行 `python app_agent.py`**,Gradio 都會自動額外開一條公開的 `https://xxxx.gradio.live` 通道,把當下這台機器上跑的服務暴露到公開網際網路上,沒有任何存取控制。我在本機測試時就實際觀察到這個行為(啟動 log 會印出 `* Running on public URL: https://xxx.gradio.live`)。
289
 
290
- **這一項不影響您已經部署好的 Hugging Face Spaces 正式機**,Spaces 平台本身就是公開存取,多這條通道在效果上沒有差別。我提出來是針對**地端執行**的情境,也就是未來工程端接手維護時,在自己電腦上開發與除錯的那些時候:那台機器上可能跑著還沒測完的版本,也連著開發者自己的 OpenRouter 金鑰,卻在不見得會注意到的情況下對外開了一個入口(那行 log 混在一堆啟動訊息裡很容易被忽略)。
291
 
292
  **建議**:改成依環境變數控制,預設關閉,只有明確需要臨時對外展示時才手動開啟。
293
 
@@ -295,11 +295,11 @@ app.launch(
295
  share=os.environ.get("GRADIO_SHARE", "false").lower() == "true"
296
  ```
297
 
298
- 另外建議順手確認一次:正式機在 Hugging Face Spaces 上啟動時,log 裡是否也會印出 `gradio.live` 連結。若會,代表正式機同時存在第二條不受平台管理的對外入口,那就不只是開發環境的問題,值得一併關掉。
299
 
300
  ---
301
 
302
- ## 七、待您決策的項目
303
 
304
  ### 7.1 語料 Metadata「略過」後可否事後補填
305
 
@@ -311,13 +311,13 @@ share=os.environ.get("GRADIO_SHARE", "false").lower() == "true"
311
 
312
  目前系統對上傳檔案**沒有任何大小限制**,異常巨大的檔案可能拖慢運算或超出 Gradio 本身的上傳限制。
313
 
314
- 若要實作,可以與既有的檔名字首合法性、每組檔案數下限檢查(`run_format_check()`,app_agent.py:1579)合併在同一關驗證。
315
 
316
  ---
317
 
318
  ## 附錄:關鍵程式碼位置對照(截至 commit `f8aab79`)
319
 
320
- > 注意:本表行號對應 commit `f8aab79`。本專案主檔改動頻繁(例如 `7b42168` 將 CSS 抽出後,全檔行號位移近兩千行),若您手上的版本不同,請以函式名稱搜尋為準。
321
 
322
  | 項目 | 位置 |
323
  |---|---|
@@ -346,14 +346,14 @@ share=os.environ.get("GRADIO_SHARE", "false").lower() == "true"
346
  - **首次觀察**:分析運算「報告撰寫」階段等候超過 180 秒,畫面持續顯示「處理中」,沒有任何更新或錯誤訊息
347
  - **診斷過程**:
348
  1. 確認 OpenRouter API 金鑰額度正常(`usage: 0`,`limit: null`),排除額度問題
349
- 2. 直接呼叫測試環境 `.env` 設定的模型代號(`openrouter/free`,我自行覆寫,非專案預設)驗證為合法別名,0.5 秒內正常回應,排除模型代號無效問題,但發現該次回應路由到會做 reasoning 的模型(見 3.3)
350
  3. 檢查伺服器程序當下對外連線,**無任何連往 openrouter.ai 的 established 連線**,代表 LLM 呼叫已經結束(成功或失敗),並非仍在等待 API
351
  4. 直接檢查該 session 輸出目錄,確認報告 docx/txt 與四包下載 zip **皆已完整產出**,證實後端已成功跑完整條流程,只是前端沒有反映
352
  5. 重新整理頁面後,畫面回到全新首頁(新 `session_id`),舊 session 的產出檔案「還在但找不到路回去」
353
  - **重現測試**:以本機 `.venv` + Chrome 走一次完整流程,**全程保持分頁在前景、電腦未休眠**,分析運算正確完成、各階段狀態即時更新,未重現卡住現象;同一次測試也實際觀察到 `share=True` 產生的公開連結(見第六節)
354
  - **2026-08-06 補測**:為釐清耗時來自架構還是模型設定,對四個模型代號各發一次最小請求比對,確認專案預設的 `google/gemma-4-31b-it`/`google/gemma-4-26b-a4b-it` 可正常呼叫、`reasoning_tokens` 為 0、單次回應 21 秒;`openrouter/free` 兩次呼叫路由到兩個不同模型、`reasoning_tokens` 為 239、單次回應 36.1 秒;`google/gemma-4-31b-it:free` 則直接回 HTTP 429。完整數據見 3.3、3.4
355
- - **結論**:長分析任務的可靠性問題與「分頁失去前景/連線中斷」高度相關,並非每次必然發生的邏輯錯誤。但在目前的架構下,任何超過 1-2 分鐘的分析都暴露在同樣的風險下,這是第二節把選項 C 列為優先處理的原因。至於耗時本身,補測後確認有相當比例來自我的模型設定,不是架構問題
356
 
357
  ---
358
 
359
- *以上是我在這次 UI/UX 改版過程中的技術評估建議,不是最終實作規格,也不包含工作量估計。實際採用哪些項目、優先順序與施工方式,請您與後續承接的工程資源評估後定案。*
 
3
  - 文件建立日期:2026-08-05(2026-08-06 全面改版,補入實測數據並重新校正程式碼行號)
4
  - 提出人:吳千卉(本次 UI/UX 改版執行者)
5
  - 對應版本:commit `f8aab79`(分支 `redesign/ui-refresh`),本文件所有 `app_agent.py:行號` 均以此版本為準
6
+ - 用途:供後續規劃、排期與發包
7
 
8
+ 本文件整理的是本次 UI/UX 改版過程中發現、但超出本次委託施工範圍(純視覺/版面調整)的項目。每一項均載明具體問題、程式碼位置、影響範圍與建議方向,可直接作為評估與施工的依據。
9
 
10
+ > ### ⚠ 測試環境使用了非專案預設的 LLM 模型
11
  >
12
+ > 本次改版期間未取得專案正式的 OpenRouter API Key,測試改用另一組金鑰,並在本機 `.env` 將模型覆寫為 `LLM1_MODEL=openrouter/free`/`LLM2_MODEL=openrouter/free`,**未使用專案預設的 `google/gemma-4-31b-it`/`google/gemma-4-26b-a4b-it`**。
13
  >
14
+ > `.env` 已列在 `.gitignore`(第 35 行)不進版控,該覆寫設定不會隨程式碼交付,本機與正式機的設定均不受影響。但有兩點需留意:
15
  >
16
+ > 1. **本文件出現的所有耗時數據,均在上述覆寫設定下量測,不可作為正式機的效能基準**。實測差距不小:專案預設的 Gemma 模型單次回應約 21 秒,覆寫用的自動路由則是 36.1 秒。
17
+ > 2. **重建環境時(Hugging Face Spaces 的 Secrets、新工程師的本機設定),須確認 `LLM1_MODEL`/`LLM2_MODEL` 填的是正式要用的模型代號**,勿沿用本文件範例中出現的 `openrouter/free`。
18
  >
19
  > 完整實測數據見第三節。
20
 
21
+ ## 檢視角度
22
 
23
+ 本文件從四個角度檢視整個系統。同一個項目可能同時命中多個角度,歸類以最主要者為準:
24
 
25
+ | 角度 | 檢視的問題 | 帶出的項目 |
26
  |---|---|---|
27
  | **機密性** | 使用者上傳的研究語料,會流到哪裡、留在哪裡、誰看得到? | 一、四、六 |
28
  | **可用性** | 使用者實際操作時,會不會卡住、會不會白做工? | 二 |
 
39
  |---|---|---|---|
40
  | 一、研究語料傳送至第三方 AI 服務的資料政策 | 機密性 | 高(已在正式機發生中,且資料送出不可逆)| 新發現,多數是帳號設定確認,未必需要工程 |
41
  | 二、持久化機制(存檔/讀取進度)| 可用性 | 高(直接影響長分析的可靠性)| 需求已定案,0% 實作 |
42
+ | 三、LLM API 呼叫可靠性強化 | 完整性 | 高(本次實測直接命中)| 新發現,未評估 |
43
+ | 四、Session 輸出目錄清理機制 | 機密性/維運 | 中(付費 Spaces,磁碟累積有實際成本,且語料無限期留存)| 原始需求已列出,待決定 |
44
  | 五、單一檔案 4232 行、且完全沒有自動化測試 | 可維護性 | 中(不緊急,但會放大前面每一項的施工風險)| 新發現,未評估 |
45
  | 六、本機啟動預設開啟公開分享連結 | 機密性 | 低(不影響正式機,影響工程端地端開發)| 新發現,一行設定即可調整 |
46
+ | 七、待決策的項目 | 產品決策 | 低(皆非急迫)| 原始需求已列出,待決定 |
47
 
48
  ---
49
 
 
51
 
52
  ### 現況
53
 
54
+ 系統的報告撰寫階段(`run_report_writing()`,app_agent.py:1255)會把使用者上傳的譯文內容組進 prompt(app_agent.py:1273-1290),透過 `_call_openrouter()`(app_agent.py:377-401)送到 OpenRouter。OpenRouter 是一個**路由層**,會再把請求轉給上游的實際模型供應商(實測時觀察到的供應商包含 Google AI Studio)。
55
 
56
  也就是說,使用者上傳的研究語料,實際上會經過至少兩個第三方:OpenRouter 本身,以及它背後路由到的模型供應商。
57
 
58
+ 程式碼中搜尋不到任何與資料政策、去識別化、或使用者告知相關的處理。
59
 
60
+ ### 列為最高優先的理由
61
 
62
  這套系統的使用對象是應用日語系的師生,處理的是**尚未發表的翻譯研究語料**。這類資料有兩個特性讓它比一般應用更敏感:
63
 
64
  - **預先揭露的風險**:研究資料在正式發表前流出,可能影響後續投稿與著作權主張
65
+ - **不可逆**:第二至六節的問題都可以在發現後修好,但資料一旦送出第三方,無法收回
66
 
67
+ 需要說明的是,**目前沒有證據顯示已經發生任何外洩**。本節指出的是:這件事目前沒有任何管理機制,而建立管理的成本很低。
68
 
69
+ ### 待確認事項
70
 
71
+ 以下多數不需要動程式碼,屬帳號層級的設定確認:
72
 
73
+ 1. **OpenRouter 帳號的資料政策設定**:OpenRouter 允許設定是否同意 prompt 內容被保留或用於模型訓練。部分免費模型端點會**要求**開啟較寬鬆的政策才能使用。須確認正式機使用的帳號目前是哪一種設定。
74
  2. **上游供應商的保留政策**:即使 OpenRouter 本身不保留,實際處理請求的供應商(如 Google AI Studio)有自己的政策。若語料機密性要求高,可考慮在 OpenRouter 的 provider routing 設定中限定供應商。
75
+ 3. **Hugging Face Space 的可見性**:正式機若設為 public,任何拿到網址的人都能上傳文本並執行分析,也就能消耗帳號的 API 額度。須確認 Space 目前是 public 還是 private,以及是否需要加上存取控制。
76
 
77
  ### 建議的工程項目
78
 
79
+ 1. **在系統內對使用者揭露**:在文本上傳步驟加一段說明,告知「上傳的內容會傳送至第三方 AI 服務進行分析」。這是成本最低、也最該做的一項,讓使用者能自行判斷哪些語料適合放進來。
80
+ 2. **若語料含個人資料**(例如受訪者、學生作業),須一併確認是否符合學校的個資規範,必要時在上傳前加入去識別化步驟。
81
 
82
  ---
83
 
84
  ## 二、持久化機制(存檔/讀取進度)
85
 
86
+ ### 問題背景
87
 
88
+ 2026-08-05 實測時重現了一個問題:一次完整分析(指標計算+統計分析+報告撰寫)耗時數分鐘,過程中瀏覽器分頁若失去與伺服器的即時連線,**後端會照常把運算跑完、報告與下載檔案也正常產出在磁碟上,但前端畫面永遠停在「處理中」不會更新**。此時使用者若重新整理頁面試圖恢復,畫面只會回到全新首頁;剛才的分析結果雖然還在磁碟上(`sessions/<session_id>/output/`),但介面上沒有任何入口能找回它。
89
 
90
+ 追查後確認,這個缺口從系統第一天架構設計起就存在,與本次改版動到的邏輯無關:系統沒有任何形式的執行狀態持久化。這正是《UIUX優化成果與技術說明》(`docs/UIUX優化成果與技術說明.md`)5.2 列為「持久化機制:存檔/讀取進度」、從專案第一天起進度就是 0% 的項目。
91
 
92
+ ### 歸類為工程範疇、不納入本次改版施工範圍的理由
93
 
94
  《UIUX優化成果與技術說明》1.2「技術背景與施工限制」原本就把 `app_agent.py` 分成三個風險區,並明確界定本次改版的施工邊界:
95
 
 
99
  | `create_app()` 版面配置(勿動 `.click()`/`.change()` 綁定本身的邏輯)| 中風險 | 可(僅限版面調整)|
100
  | **`handle_*` 流程函式與核心運算函式** | **高風險** | **改版不應觸碰** |
101
 
102
+ 本次改版完成的項目,性質上都落在前兩區:CSS、版面配置、圖示、既有 handler 裡個別 `gr.update()` 輸出值的修正。
103
 
104
+ 而「存檔/讀檔」在技術上無法迴避觸碰第三區。它需要在流程中途把執行狀態完整取出,並在讀檔後把狀態「倒帶」回中斷當下,這代表要重新設計 `handle_*` 系列函式與核心運算函式的進出點介面,在既有邏輯外面加一層存讀檔是做不到的。因此這項工作建議交由後續的工程資源評估與實作;為守住原始需求文件界定的施工範圍,本次改版未觸碰這一區。
105
 
106
  ### 原始需求(2026-07-20 訪談已定案,尚未實作)
107
 
 
120
 
121
  ### 現況缺口:連「執行到一半先匯出」都做不到
122
 
123
+ 上述的「讀檔還原」是持久化機制裡最複雜的一半。但目前系統**連更基本的「單向匯出、不要求能讀回來」都不存在**:
124
 
125
  - 現有的下載能力(左側導覽列四顆捷徑)**只在「分析運算」這個步驟裡、且至少一個子階段已產出結果後才會出現**(見 `_populate_tabs()` app_agent.py:3177 的觸發條件)
126
+ - 在此之前的步驟(文本上傳、語料說明)使用者可能已經填了不少東西,尤其語料 Metadata 表單有十幾個欄位,但這整段完全沒有任何「先把目前已輸入的內容存個檔」的入口。連線一斷、分頁一關,這幾步全部要重來,不只是找不回結果,是連過程都留不住
127
  - 這件事本質上比完整的「存檔+讀檔」小一步:**先做「匯出」,「讀回來」可以晚一點再做**,也能先驗證「把 `state` + 已上傳檔案 + 表單內容打包成一份可下載的檔案」這件事本身的可行性與格式設計,作為選項 A/C 共同的地基
128
  - 但即使只做單向匯出,仍然需要串接到 `state` 機制與各步驟當下的即時資料(含尚未提交的表單暫存值),加一顆下載按鈕是不夠的
129
 
 
154
  - 優點:使用者體驗較友善,不需要自行保管檔案;若搭配輪詢較有機會做到「連線斷掉後自動接回」
155
  - 缺點:**這正是 2026-07-20 需求訪談時明確討論並否決的方向**(無登入代表任何人拿到代碼/連結就能看到他人研究資料)。若要重新考慮,需要重新拍板,並額外設計存取控制
156
 
157
+ **選項 C(本次實測後新增的建議):先做「長任務容錯」,與完整存讀檔脫鉤分階段處理**
158
 
159
  不等待完整的存讀檔功能,先把「分析運算」這個最耗時的階段,從目前綁死在單一條前端-後端串流連線上的 generator 模式,改成**後端非同步 job + 前端輪詢**:分析開始後立即回傳一個 job 識別碼,後端在背景把進度寫入 session 目錄下的進度檔案,前端每隔幾秒輪詢一次。
160
 
161
  - 優點:**直接解決本節重現的真實痛點**,只要 `session_id` 還在瀏覽器端(例如寫進 URL query string 或 localStorage),就算分頁重新整理或連線重連,前端都能重新輪詢到目前進度;施工範圍比完整存讀檔小很多;若進度檔案格式與未來 `.spectra` 格式相容,之後銜接完整存讀檔的邊際成本會降低
162
  - 缺點:不等於完整的持久化機制,沒有「關掉瀏覽器、隔天回來繼續」的能力;需要把 `run_computation_pipeline_streaming`(app_agent.py:1704)與 `_run_computation`(app_agent.py:3975)目前的 generator/yield 架構改成 job 佇列模式,同樣觸碰核心運算函式範圍
163
 
164
+ ### 建議的實作順序
165
 
166
  1. **選項 C 優先**:風險與工作量相對可控,且直接解決實測到的既有問題,任何一次超過 1-2 分鐘的分析都會受益
167
+ 2. **選項 A 其次**:完成需求文件已定案的「進度存檔/讀檔」,但須一併考量 `.spectra` 檔案本身的機密性
168
  3. **選項 B 除非主動要求**:牽涉到已被否決過一次的資安權衡,需要重新拍板才建議投入
169
 
170
  ---
171
 
172
  ## 三、LLM API 呼叫可靠性強化
173
 
174
+ 2026-08-05 實測過程中,以測試用的 OpenRouter API Key 直接呼叫 `_call_openrouter()`(app_agent.py:377-401),發現兩個目前程式碼裡沒有處理、會直接影響分析穩定性的問題(3.1、3.2)。這兩點集中在同一個函式,建議一次評估、一次修改。
175
 
176
  ```python
177
  def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=16000, temperature=0.5):
 
197
 
198
  **建議**:如果要有一個真正的總時間上限,需要額外用執行緒或 `signal` 搭配獨立計時器強制中斷(`urllib` 本身的 `timeout` 參數做不到),或改用支援總逾時設定的 HTTP client(例如 `httpx` 可分別設定 connect/read/write/pool 各階段)。
199
 
200
+ ### 3.3 未來換模型時的注意事項(源自測試環境設定,非目前設定的問題)
201
 
202
+ 先說結論:**本節描述的現象在專案預設模型下不會發生**,記錄下來是供未來換模型時參考。
203
 
204
+ 測試時模型設為 `openrouter/free`。這個代號並非固定模型,它是 OpenRouter 的**自動路由別名**,每次呼叫可能被導去不同的免費模型。以同一個代號連續測兩次,分別被導去 `nvidia/nemotron-3-nano-30b-a3b:free` 與 `openai/gpt-oss-20b:free`,證實路由結果確實每次不同。這兩個模型都預設會做 reasoning,回應 JSON 裡有一段獨立的 `"reasoning"` 欄位。
205
 
206
  而 `_call_openrouter()` 的回傳只取 `result["choices"][0]["message"]["content"]`(app_agent.py:399���,**`reasoning` 欄位從頭到尾沒有被讀取或使用**,這段思考過程佔掉的生成時間對使用者看到的報告內容沒有任何貢獻。
207
 
208
+ 以同一段測試 prompt(`max_tokens=800`)比較兩種設定:
209
 
210
+ | | 專案預設 `google/gemma-4-31b-it` | 測試用的 `openrouter/free` |
211
  |---|---|---|
212
  | 實際路由模型 | 固定為指定模型 | 每次不同(實測兩次得到兩個不同模型)|
213
  | `reasoning` 欄位 | 空(`None`)| 1056 字元 |
214
  | `reasoning_tokens` | **0** | **239**(佔 800 個生成 token 的 30%)|
215
  | 單次回應耗時 | **21.0 秒** | 36.1 秒 |
216
 
217
+ **專案預設的 Gemma 模型完全沒有 reasoning 浪費的問題,而且比測試用的設定更快。** 先前觀察到報告撰寫偏慢,有相當比例來自測試環境的模型設定。
218
 
219
  **建議**:
220
+ - 專案預設模型的實際耗時,應由使用正式設定的環境重新量測一次,本文件的數字不適合當基準
221
+ - 未來換模型時直接指定固定的模型代號,避免使用 `openrouter/free` 這類自動路由別名
222
  - 若換到的模型會做 reasoning,建議在 `_call_openrouter()` 明確關閉(部分模型可透過 request body 加 `reasoning: {"enabled": false}` 或類似參數)
223
  - 若報告撰寫階段仍然偏慢,中期可考慮改用 OpenRouter 的串流 API,讓報告逐字回傳、前端即時顯示,使用者體感會從「畫面死掉好幾分鐘」變成「看得到字在跑」
224
 
225
  ### 3.4 模型選用的兩點提醒
226
 
227
+ **第一,專案預設模型是計費的。** 以一段 60 個 prompt token、生成 800 個 token 的測試請求呼叫 `google/gemma-4-31b-it`,OpenRouter 回報 `cost: 0.0002774`。單次成本很低,但實際報告撰寫是 `max_tokens=16000`,量級差 20 倍,且每位使用者每次分析都會呼叫。須確認 OpenRouter 帳號的額度與付款方式已設定妥當,避免額度用罄導致服務全面中斷。
228
 
229
+ **第二,勿將模型代號改成加 `:free` 後綴的版本。** 實測 `google/gemma-4-31b-it:free` 直接回 HTTP 429,錯誤訊息為 `is temporarily rate-limited upstream`,`limit_source` 標示為 `upstream_provider_shared_pool`,也就是這個免費端點是所有 OpenRouter 使用者共用的池子,隨時可能被限流。若為了節省成本改用 `:free` 變體,會讓分析在尖峰時段大量失敗,而目前又沒有重試機制(見 3.1),使用者會直接看到該階段失敗。
230
 
231
  ---
232
 
 
236
 
237
  這一項同時命中兩個角度:
238
 
239
+ - **機密性**:每一份使用者上傳過的研究語料,都會無限期留存在正式機的磁碟上,沒有任何刪除機制,也沒有告知使用者這件事。若某位老師只是想試跑一下,語料就永久留在那台機器上了。
240
  - **維運成本**:正式機部署在付費的 Hugging Face Spaces 上,磁碟累積會直接反映在儲存空間的採購規格與費用上。
241
 
242
  **建議**:先確認目前 `sessions/` 已累積的用量,再決定清理策略(例如保留 N 天後自動刪除),並在介面上告知使用者資料的保存期限。
243
 
244
+ 如第二節所述,這項工程與持久化機制高度相關,存讀檔上線後只會讓孤兒目錄累積得更快,建議兩者一併規劃排期。
245
 
246
  ---
247
 
248
  ## 五、單一檔案 4232 行、且完全沒有自動化測試
249
 
250
+ 這一項不緊急,但會放大前面每一項的施工風險,因此獨立列出。
251
 
252
  ### 現況
253
 
 
260
 
261
  第二節的持久化機制、第三節的重試邏輯,動刀的位置都在這個檔案的核心運算區。在沒有任何自動化測試的情況下,每一次改動都只能靠人工重跑完整流程來確認沒弄壞既有功能,而完整流程本身要跑好幾分鐘。這會讓工程估時裡的「驗證」成本遠高於「實作」成本,也讓後續維護者不敢動。
262
 
263
+ 本次改版已做兩個小幅改善,可作為後續整理的起點:CSS 已從主檔抽出為 `assets/app.css`(commit `7b42168`),輸出清單已拆成具名子清單(commit `54e74f4`)。這兩個改動都沒有改變任何行為。
264
 
265
  ### 建議
266
 
267
  1. **先加回歸測試,再談重構**。順序反過來會很危險。優先涵蓋純函式部分:格式檢查(`run_format_check()` app_agent.py:1579)、狀態建立(`make_initial_state()` app_agent.py:1368)、指標計算等不依賴 Gradio 的邏輯,這些最容易測、也最值得測。
268
  2. **把散落的設定集中**到檔案頂端的設定區塊或獨立的 `config.py`,讓「要調哪個數字」不需要在 4000 行裡找。
269
+ 3. **把 handler 從 `create_app()` 內部搬到模組層級**,改用參數傳遞取得所需元件。這一步能讓 handler 變成可測試的普通函式,但會大量觸碰事件綁定,建議等測試就位後再做。
270
 
271
  這三步可以分次進行,不需要一次到位。
272
 
 
285
  )
286
  ```
287
 
288
+ `share=True` 是寫死的,不受任何環境變數控制。這代表**任何人只要執行 `python app_agent.py`**,Gradio 都會自動額外開一條公開的 `https://xxxx.gradio.live` 通道,把當下這台機器上跑的服務暴露到公開網際網路上,沒有任何存取控制。本機測試時已實際觀察到這個行為(啟動 log 會印出 `* Running on public URL: https://xxx.gradio.live`)。
289
 
290
+ **這一項不影響已部署的 Hugging Face Spaces 正式機**,Spaces 平台本身就是公開存取,多這條通道在效果上沒有差別。本節針對的是**地端執行**的情境,也就是未來工程端接手維護時,在自己電腦上開發與除錯的那些時候:那台機器上可能跑著還沒測完的版本,也連著開發者自己的 OpenRouter 金鑰,卻在不見得會注意到的情況下對外開了一個入口(那行 log 混在一堆啟動訊息裡很容易被忽略)。
291
 
292
  **建議**:改成依環境變數控制,預設關閉,只有明確需要臨時對外展示時才手動開啟。
293
 
 
295
  share=os.environ.get("GRADIO_SHARE", "false").lower() == "true"
296
  ```
297
 
298
+ 另建議順手確認一次:正式機在 Hugging Face Spaces 上啟動時,log 裡是否也會印出 `gradio.live` 連結。若會,代表正式機同時存在第二條���受平台管理的對外入口,那就不只是開發環境的問題,值得一併關掉。
299
 
300
  ---
301
 
302
+ ## 七、待決策的項目
303
 
304
  ### 7.1 語料 Metadata「略過」後可否事後補填
305
 
 
311
 
312
  目前系統對上傳檔案**沒有任何大小限制**,異常巨大的檔案可能拖慢運算或超出 Gradio 本身的上傳限制。
313
 
314
+ 若要實作,可與既有的檔名字首合法性、每組檔案數下限檢查(`run_format_check()`,app_agent.py:1579)合併在同一關驗證。
315
 
316
  ---
317
 
318
  ## 附錄:關鍵程式碼位置對照(截至 commit `f8aab79`)
319
 
320
+ > 注意:本表行號對應 commit `f8aab79`。本專案主檔改動頻繁(例如 `7b42168` 將 CSS 抽出後,全檔行號位移近兩千行),若手上的版本不同,請以函式名稱搜尋為準。
321
 
322
  | 項目 | 位置 |
323
  |---|---|
 
346
  - **首次觀察**:分析運算「報告撰寫」階段等候超過 180 秒,畫面持續顯示「處理中」,沒有任何更新或錯誤訊息
347
  - **診斷過程**:
348
  1. 確認 OpenRouter API 金鑰額度正常(`usage: 0`,`limit: null`),排除額度問題
349
+ 2. 直接呼叫測試環境 `.env` 設定的模型代號(`openrouter/free`,為測試環境自行覆寫,非專案預設)驗證為合法別名,0.5 秒內正常回應,排除模型代號無效問題,但發現該次回應路由到會做 reasoning 的模型(見 3.3)
350
  3. 檢查伺服器程序當下對外連線,**無任何連往 openrouter.ai 的 established 連線**,代表 LLM 呼叫已經結束(成功或失敗),並非仍在等待 API
351
  4. 直接檢查該 session 輸出目錄,確認報告 docx/txt 與四包下載 zip **皆已完整產出**,證實後端已成功跑完整條流程,只是前端沒有反映
352
  5. 重新整理頁面後,畫面回到全新首頁(新 `session_id`),舊 session 的產出檔案「還在但找不到路回去」
353
  - **重現測試**:以本機 `.venv` + Chrome 走一次完整流程,**全程保持分頁在前景、電腦未休眠**,分析運算正確完成、各階段狀態即時更新,未重現卡住現象;同一次測試也實際觀察到 `share=True` 產生的公開連結(見第六節)
354
  - **2026-08-06 補測**:為釐清耗時來自架構還是模型設定,對四個模型代號各發一次最小請求比對,確認專案預設的 `google/gemma-4-31b-it`/`google/gemma-4-26b-a4b-it` 可正常呼叫、`reasoning_tokens` 為 0、單次回應 21 秒;`openrouter/free` 兩次呼叫路由到兩個不同模型、`reasoning_tokens` 為 239、單次回應 36.1 秒;`google/gemma-4-31b-it:free` 則直接回 HTTP 429。完整數據見 3.3、3.4
355
+ - **結論**:長分析任務的可靠性問題與「分頁失去前景/連線中斷」高度相關,並非每次必然發生的邏輯錯誤。但在目前的架構下,任何超過 1-2 分鐘的分析都暴露在同樣的風險下,這是第二節將選項 C 列為優先處理的原因。至於耗時本身,補測後確認有相當比例來自測試環境的模型設定,不是架構問題
356
 
357
  ---
358
 
359
+ *本文件為技術評估建議,不是最終實作規格,也不包含工作量估計。實際採用哪些項目、優先順序與施工方式,由後續承接的工程資源評估後定案。*