Spaces:
Running
Running
新增開發優化建議文件,彙整超出本次改版範圍的工程項目給業主
Browse files- Phase 10 持久化機制(存檔/讀取進度):問題重現、原始需求、架構說明、三個實作選項優缺點
- LLM API 呼叫可靠性強化:無重試機制、180秒逾時未真正生效、免費模型自動路由浪費 reasoning token
- 本機啟動 share=True 預設開啟公開分享連結的安全性疑慮
- Session 輸出目錄清理機制
- 待業主決策項目:Metadata 補填、致理品牌識別、上傳檔案大小上限
- docs/開發優化建議.md +232 -0
docs/開發優化建議.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SpectraEngine 開發優化建議
|
| 2 |
+
|
| 3 |
+
- 文件建立日期:2026-08-05
|
| 4 |
+
- 提出人:吳千卉(本次 UI/UX 改版執行者)
|
| 5 |
+
- 對應 commit:`aa1f341`(分支 `redesign/ui-refresh`)
|
| 6 |
+
- 用途:彙整本次 UI/UX 改版過程中發現、但**超出本次委託施工範圍**(純視覺/版面調整)、需要業主方工程資源評估與實作的項目。每項都附具體問題描述、程式碼位置、與建議方向,供工程端規劃排期。
|
| 7 |
+
|
| 8 |
+
## 項目總覽
|
| 9 |
+
|
| 10 |
+
| 項目 | 類型 | 急迫性 | 狀態 |
|
| 11 |
+
|---|---|---|---|
|
| 12 |
+
| 一、持久化機制(存檔/讀取進度)| 新功能,工程 | 高——直接影響長分析的可靠性 | 需求已定案,0% 實作 |
|
| 13 |
+
| 二、LLM API 呼叫可靠性強化 | 可靠性強化,工程 | 高——今天實測直接命中 | 新發現,未評估 |
|
| 14 |
+
| 三、本機啟動預設開啟公開分享連結 | 安全性設定 | 中——地端測試/交接時有暴露風險 | 新發現,一行設定即可調整 |
|
| 15 |
+
| 四、Session 輸出目錄清理機制 | 維運 | 中——長期磁碟空間問題 | 原始需求已列出,待業主拍板 |
|
| 16 |
+
| 五、待業主決策項目(Metadata 補填/品牌識別/上傳大小上限)| 產品決策 | 低——皆非急迫 | 原始需求已列出,待業主拍板,拍板後由本次改版團隊實作 |
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## 一、持久化機制(存檔/讀取進度)
|
| 21 |
+
|
| 22 |
+
### 為什麼現在提出
|
| 23 |
+
|
| 24 |
+
2026-08-05 實測時重現了一個問題:一次完整分析(指標計算+統計分析+報告撰寫)耗時約 3.5–4 分鐘,過程中瀏覽器分頁若失去與伺服器的即時連線,**後端會照常把運算跑完、報告與下載檔案也正常產出在磁碟上,但前端畫面永遠停在「處理中」不會更新**;此時使用者若重新整理頁面試圖恢復,畫面只會回到全新首頁,剛才的分析結果雖然還在磁碟上(`sessions/<session_id>/output/`),但介面上沒有任何入口能找回它。
|
| 25 |
+
|
| 26 |
+
追查後確認:這不是這次改版動到的邏輯造成的迴歸,而是系統從第一天架構設計起就存在的缺口——**沒有任何形式的執行狀態持久化**。這正是原始需求彙整文件(`docs/需求彙整-UIUX改版.md` 第八節)裡列為「持久化機制(方案 D)」、且從專案第一天起進度就是 0% 的項目,一般在專案溝通中稱為 **Phase 10**。
|
| 27 |
+
|
| 28 |
+
### 為什麼歸類為工程範疇、不在本次 UI/UX 改版施工範圍內
|
| 29 |
+
|
| 30 |
+
需求彙整文件第二節「技術背景與限制」原本就把 `app_agent.py` 分成三個風險區,並明確界定本次改版的施工邊界:
|
| 31 |
+
|
| 32 |
+
| 區域 | 風險等級 | 本次改版是否可動 |
|
| 33 |
+
|---|---|---|
|
| 34 |
+
| `_CUSTOM_CSS` 純樣式 | 低風險 | 可 |
|
| 35 |
+
| `create_app()` 版面配置(勿動 `.click()`/`.change()` 綁定本身的邏輯) | 中風險 | 可(僅限版面調整) |
|
| 36 |
+
| **`handle_*` 流程函式與核心運算函式** | **高風險** | **改版不應觸碰** |
|
| 37 |
+
|
| 38 |
+
本次改版(Phase 1-9、多輪視覺打磨)完成的項目性質上都落在前兩區——CSS、版面配置、圖示、既有 handler 裡個別 `gr.update()` 輸出值的修正。
|
| 39 |
+
|
| 40 |
+
而「存檔/讀檔」在技術上無法迴避觸碰第三區:它需要在流程中途把執行狀態完整取出、並在讀檔後把狀態「倒帶」回中斷當下,這代表要重新設計 `handle_*` 系列函式與核心運算函式的進出點介面,不是在既有邏輯外面加一層存讀檔就能做到。因此判斷這項工作應交由業主方工程資源評估與實作,本次改版團隊不會嘗試觸碰這一區,以符合原始需求文件對施工範圍的界定。
|
| 41 |
+
|
| 42 |
+
### 原始需求(2026-07-20 訪談已定案,尚未實作)
|
| 43 |
+
|
| 44 |
+
摘自需求彙整文件第八節「持久化機制(方案 D:進度存檔/讀檔)」:
|
| 45 |
+
|
| 46 |
+
- **不做登入、不做伺服器端多 session 儲存**。理由:無登入代表任何人拿到 session 網址都能看到他人資料,有資安疑慮;且會與現有 Gradio session 架構(見下方說明)衝突。
|
| 47 |
+
- 防手滑(`beforeunload` 離開頁面前跳出確認):**已實作完成**(2026-07-24)。
|
| 48 |
+
- 中途下載已產出的部分成果:**已部分實作**(左側導覽列四顆下載捷徑,任一階段完成即可下載該階段成果)。
|
| 49 |
+
- **存檔/讀檔(核心機制,未實作)**:
|
| 50 |
+
- 存檔內容須含**原始上傳文本**(不只中繼結果),確保讀檔後完整還原、不需重新上傳
|
| 51 |
+
- 「讀取進度」入口在首頁,讀檔後**直接跳過首頁與情境選擇**,回到中斷當下的步驟與畫面狀態
|
| 52 |
+
- 存檔用自訂副檔名(如 `.spectra`)、次要按鈕樣式+警語,與「成果下載」(一般格式+醒目主色按鈕)在視覺與命名上明確區分,避免非技術使用者誤認「中繼存檔=最終成果」
|
| 53 |
+
- 多開分析:不需特別開發——開新分頁天生是獨立 session,只需在首次進站提示「建議同時只跑一個分析,以確保運算速度」
|
| 54 |
+
|
| 55 |
+
需求彙整文件第十三節「錯誤狀態設計」也已預先列出一個目前完全沒有現成邏輯的案例:**讀取進度檔案毀損或格式不符**,需要設計對應錯誤訊息與復原路徑。
|
| 56 |
+
|
| 57 |
+
### 現況缺口:連「執行到一半先匯出」都做不到
|
| 58 |
+
|
| 59 |
+
上面談的「讀檔還原」是持久化機制裡最複雜的一半;但目前系統**連更基本的「單向匯出、不要求能讀回來」都不存在**,這點值得跟工程端額外說明清楚:
|
| 60 |
+
|
| 61 |
+
- 現有的下載能力(左側導覽列四顆捷徑)**只在「分析運算」這個步驟裡、且至少一個子階段(指標計算/統計分析/報告撰寫)已產出結果後才會出現**(見 `_populate_tabs()` app_agent.py:5358 的觸發條件)
|
| 62 |
+
- 在此之前的三個步驟——**研究選擇、文本上傳、語料說明**——使用者可能已經填了不少東西(尤其語料 Metadata 表單,情境 3/4 有原文資訊+群組層+個體層共十幾個欄位),但這整段完全沒有任何「先把目前已輸入的內容存個檔」的入口。連線一斷、分頁一關,這幾步全部要重來,不是「找不回結果」,是「連過程都留不住」
|
| 63 |
+
- 這件事本質上比完整的「存檔+讀檔」小一步:**先做「匯出」,「讀回來」可以晚一點再做**,也能先驗證「把 `state` + 已上傳檔案 + 表單內容打包成一份可下載的檔案」這件事本身的可行性與格式設計,作為選項 A/C 共同的地基
|
| 64 |
+
- 但即使只做單向匯出,仍然需要串接到 `state` 機制與各步驟當下的即時資料(含尚未提交的表單暫存值),不是單純加一顆下載按鈕就能完成,同樣建議由工程端評估排入本節整體規劃
|
| 65 |
+
|
| 66 |
+
### 現有系統架構(工程端評估前必讀)
|
| 67 |
+
|
| 68 |
+
- **Session 識別**:`session_id = str(uuid.uuid4())[:8]`,在 `demo.load`(app_agent.py:5575)時產生,**純記憶體內、不寫入 cookie 或任何持久化媒介**。瀏覽器重新整理=拿到全新 `session_id`,舊的執行狀態與伺服器端連線一起消失,只有已寫入磁碟的檔案(`sessions/<session_id>/uploads/`、`sessions/<session_id>/output/`)還留著,但沒有任何介面能再指回它。
|
| 69 |
+
- **執行狀態容器**:`state`(`gr.State`,app_agent.py:4762)是一個純 Python dict,由 `make_initial_state()`(app_agent.py:1368-1413)建立,內容包含 `scenario`/`uploaded_files`/`cleaned_files`/`user_metadata`/`done_steps`/`current_step`/`failed_step`/`analysis`(分析運算三子階段狀態)/`upload`(上傳兩子階段狀態)/`output_files`/`zip_paths` 等欄位。
|
| 70 |
+
|
| 71 |
+
**這個 dict 只是狀態的一半**:對話串內容(`chatbot` 元件值)與技術執行紀錄(`exec_log` 元件值)是**獨立於 `state` 之外的 Gradio 元件值**,透過函式參數傳遞(例如 `on_scenario_pick(sc_num, chat_history, log_history, state)`,app_agent.py:5846),不在這個 dict 裡,存檔範圍必須額外涵蓋這兩者。
|
| 72 |
+
|
| 73 |
+
- **畫面還原不是「把資料塞回去」就好**:目前每個階段的畫面切換(`scenario_select_row`/`upload_row`/`metadata_row`/`canvas_idx_col`/`canvas_stat_col`/`canvas_report_col`/`result_nav_col` 等十餘個容器的 `visible` 狀態)是由對應的 handler 在使用者操作當下逐步 `yield` 出來的(可參考 `on_scenario_pick` app_agent.py:5846-5900、`_run_computation` app_agent.py:6124-6177)。讀檔要做到「回到中斷當下的畫面」,等同於要能夠**不靠使用者實際操作、直接重放這些 handler 原本會產生的一系列可見性狀態**,這版 Gradio 的容器可見性切換有多個非直覺的時序限制(本次改版一路踩坑下來已累積不少記錄),讀檔還原邏輯需要對這些限制有完整掌握才不會做出「資料回來了但畫面錯亂」的結果。
|
| 74 |
+
|
| 75 |
+
- **與第四節「Session 輸出目錄清理機制」高度相關**:存讀檔功能上線後,只會讓孤兒 session 目錄累積得更快(讀檔本質上就是把一個舊 session 目錄的內容接到新 session 上,舊的、沒被讀取的 session 目錄仍會一直留著)。**建議這兩項工程一併規劃**,不要分開處理。
|
| 76 |
+
|
| 77 |
+
### 實作選項與優缺點
|
| 78 |
+
|
| 79 |
+
**選項 A:純前端存讀檔(原方案 D 設計,2026-07-20 已定案方向)**
|
| 80 |
+
|
| 81 |
+
使用者手動點擊「儲存進度」,系統把 `state` + `chat_history` + `log_history` + 已上傳原始文本序列化成一個 `.spectra` 檔案供下載;之後在首頁點「讀取進度」上傳該檔案,還原上述狀態並重放對應的可見性 yield 邏輯。
|
| 82 |
+
|
| 83 |
+
- 優點:不需要伺服器端多使用者儲存、不需要登入,維持原始需求「無資安疑慮」的設計初衷;部署在 Hugging Face Spaces 免費層不需要額外的資料庫或儲存服務;與需求彙整文件已定案方向一致
|
| 84 |
+
- 缺點:`.spectra` 檔案體積可能不小,使用者要自行妥善保管檔案(研究語料外流的疑慮需要跟業主確認是否可接受);**無法解決本節重現的問題**——存檔動作本身通常也需要使��者手動觸發,若連線已經斷了,使用者根本按不到那顆按鈕;讀檔還原邏輯技術複雜度不低
|
| 85 |
+
|
| 86 |
+
**選項 B:伺服器端 session 儲存 + 恢復代碼/連結**
|
| 87 |
+
|
| 88 |
+
執行狀態存在伺服器磁碟或資料庫,使用者拿到一組代碼或短網址,之後憑代碼接回同一個 session。
|
| 89 |
+
|
| 90 |
+
- 優點:使用者體驗較友善,不需要自行保管檔案;若搭配輪詢(polling)較有機會做到「連線斷掉後自動接回」
|
| 91 |
+
- 缺點:**這正是 2026-07-20 需求訪談時業主與執行團隊已經明確討論並否決的方向**(無登入代表任何人拿到代碼/連結就能看到他人研究資料,資安疑慮;且與現有 Gradio session 架構有衝突)。若要重新考慮,需要業主重新拍板,並額外設計存取控制
|
| 92 |
+
|
| 93 |
+
**選項 C(本次實測後新增建議):先做「長任務容錯」,與完整存讀檔脫鉤分階段處理**
|
| 94 |
+
|
| 95 |
+
不等待完整的存讀檔功能,先把「分析運算」這個最耗時的階段,從**目前綁死在單一條前端-後端串流連線上的 generator 模式**,改成**後端非同步 job + 前端輪詢(polling)**:分析開始後立即回傳一個 job 識別碼,後端在背景把 job 進度寫入 session 目錄下的一個進度檔案,前端改成每隔幾秒輪詢一次進度。
|
| 96 |
+
|
| 97 |
+
- 優點:**直接解決本節重現的真實痛點**——只要 `session_id` 還在瀏覽器端(例如寫進 URL query string 或 localStorage),就算分頁重新整理或連線重連,前端都能重新輪詢到目前進度;施工範圍比完整存讀檔小很多;若進度檔案格式與未來 `.spectra` 格式相容,之後銜接完整存讀檔的邊際成本會降低
|
| 98 |
+
- 缺點:不等於完整的 Phase 10——沒有「關掉瀏覽器、隔天回來繼續」的能力;需要把 `run_computation_pipeline_streaming`(app_agent.py:1704)與 `_run_computation`(app_agent.py:6124)目前的 generator/yield 架構改成 job 佇列模式,同樣觸碰核心運算函式範圍
|
| 99 |
+
|
| 100 |
+
### 建議實作順序
|
| 101 |
+
|
| 102 |
+
1. **選項 C 優先**:風險與工作量相對可控,且直接解決今天實測到的既有問題,任何一次超過 1-2 分鐘的分析都會受益
|
| 103 |
+
2. **選項 A 其次**:完成需求文件已定案的「進度存檔/讀檔」
|
| 104 |
+
3. **選項 B 除非業主主動要求**:牽涉到已被否決過一次的資安權衡,需要重新拍板才建議投入
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## 二、LLM API 呼叫可靠性強化
|
| 109 |
+
|
| 110 |
+
同一次 2026-08-05 實測過程中,連帶用真實 API Key 直接測試了 `_call_openrouter()`(app_agent.py:377-401)的行為,發現三個目前程式碼裡沒有處理、會直接影響分析運算穩定性與速度的問題。這三點都集中在同一個函式,建議工程端一次評估、一次修改。
|
| 111 |
+
|
| 112 |
+
```python
|
| 113 |
+
def _call_openrouter(api_key, model, system_prompt, user_prompt, max_tokens=16000, temperature=0.5):
|
| 114 |
+
...
|
| 115 |
+
with urllib.request.urlopen(req, timeout=180) as resp:
|
| 116 |
+
result = json.loads(resp.read().decode("utf-8"))
|
| 117 |
+
return result["choices"][0]["message"]["content"]
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### 2.1 完全沒有重試機制
|
| 121 |
+
|
| 122 |
+
`call_main_agent()`(app_agent.py:408,LLM1 報告撰寫用)與 `call_worker_agent()`(app_agent.py:413,LLM2 上傳/格式驗證引導用)都是單次呼叫,全檔搜尋不到任何 retry/backoff 邏輯。只要遇到一次暫時性失敗(429 限流、網路瞬斷),該階段就直接標記失敗(見 `run_computation_pipeline_streaming` app_agent.py:1765 的例外處理),使用者要嘛整段報告缺失、要嘛得重新跑一次完整流程,沒有「自動重試一次就過了」的緩衝空間。
|
| 123 |
+
|
| 124 |
+
**建議**:至少對 429/連線逾時/5xx 這類暫時性錯誤加上 2-3 次的重試(搭配指數退避),永久性錯誤(如 401 金鑰錯誤)才直接失敗不重試。
|
| 125 |
+
|
| 126 |
+
### 2.2 180 秒逾時實際上不是有效的總時間上限
|
| 127 |
+
|
| 128 |
+
程式碼寫 `timeout=180`,字面上看像是「最多等 3 分鐘」,但今天實測**同一次呼叫耗時約 230 秒仍然成功回應、沒有觸發逾時例外**。原因是 `urllib.request.urlopen` 的 `timeout` 參數作用在底層 socket 的個別讀寫操作上,只要伺服器持續有資料流動(不是完全靜默超過 180 秒),單次呼叫的總耗時就可能遠超過這個數字。
|
| 129 |
+
|
| 130 |
+
這代表目前程式碼裡「180 秒」給的是一種**假的安全感**——工程端若以為這個數字就是使用者最長等待時間的上限,據此設計前端提示文案或逾時重試邏輯,會與實際行為不符。
|
| 131 |
+
|
| 132 |
+
**建議**:如果要有一個真正的總時間上限,需要額外用執行緒或 `signal` 搭配獨立計時器強制中斷(`urllib` 本身的 `timeout` 參數做不到),或改用支援總逾時設定的 HTTP client(例如 `httpx` 的 `timeout` 可以分別設定 connect/read/write/pool 各階段)。
|
| 133 |
+
|
| 134 |
+
### 2.3 免費模型走自動路由,行為不可預測,且「思考過程」的 token 完全被浪費
|
| 135 |
+
|
| 136 |
+
`.env` 目前設定 `LLM1_MODEL=openrouter/free`、`LLM2_MODEL=openrouter/free`。這不是���個固定模型,而是 OpenRouter 的**自動路由別名**——每次呼叫可能被導去不同的免費模型。今天實測這次被導去 `nvidia/nemotron-3-ultra-550b-a55b:free`,這個模型預設會做 reasoning(回應 JSON 裡有一段獨立的 `"reasoning"` 欄位,內容是模型的思考過程文字)。
|
| 137 |
+
|
| 138 |
+
問題是:`_call_openrouter()` 的回傳只取 `result["choices"][0]["message"]["content"]`(app_agent.py:399),**`reasoning` 欄位從頭到尾沒有被讀取或使用**。也就是說,這個思考過程佔掉的生成時間(對 `max_tokens=16000` 的報告撰寫來說可能是總耗時的一大部分)**完全是純浪費,對使用者看到的報告內容沒有任何貢獻**,這很可能是報告撰寫階段耗時 3-4 分鐘的主要原因之一。
|
| 139 |
+
|
| 140 |
+
另外,自動路由代表每次執行分配到的模型不保證一致,回應速度、輸出品質、是否觸發 reasoning 都會隨 OpenRouter 當下的免費額度供給而變動,難以做效能調校或問題重現。
|
| 141 |
+
|
| 142 |
+
**建議**:
|
| 143 |
+
- 改用固定的免費模型代號(例如 `.env.example` 裡原本建議的 `google/gemma-4-31b-it`/`google/gemma-4-26b-a4b-it`),而不是自動路由別名,取得可預期、可重現的效能表現
|
| 144 |
+
- 若選用的模型支援關閉 reasoning(部分 OpenRouter 模型可透過 request body 加 `reasoning: {"enabled": false}` 或類似參數關閉),應明確關閉,避免為了完全用不到的思考過程付出等待時間
|
| 145 |
+
- 中期可考慮改用 OpenRouter 的串流(streaming)API,讓報告內容逐字回傳、前端可即時顯示部分生成內容,即使模型本身較慢,使用者體感也會從「畫面死掉 3 分鐘」變成「看得到字在跑」
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## 三、本機啟動預設開啟公開分享連結
|
| 150 |
+
|
| 151 |
+
`app_agent.py` 最後的進入點(app_agent.py:6372-6380):
|
| 152 |
+
|
| 153 |
+
```python
|
| 154 |
+
app.launch(
|
| 155 |
+
server_name="0.0.0.0",
|
| 156 |
+
server_port=7860,
|
| 157 |
+
share=True,
|
| 158 |
+
...
|
| 159 |
+
)
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
`share=True` 是寫死的,不受任何環境變數控制。這代表**任何人只要執行 `python app_agent.py`**(例如工程交接時的地端測試、除錯),Gradio 都會自動額外開一條公開的 `https://xxxx.gradio.live` 通道,把當下這台機器上跑的服務(可能還在除錯、可能連著測試用的 API 金鑰)暴露到公開網際網路上,且這條連結預設可存活達 7 天,沒有任何存取控制。
|
| 163 |
+
|
| 164 |
+
正式部署在 Hugging Face Spaces 上時,`share=True` 本身是多餘的(Spaces 平台自己就是公開存取),問題主要在**地端執行**的情境。今天測試時就實際觀察到這個行為(啟動 log 會印出 `* Running on public URL: https://xxx.gradio.live`)。
|
| 165 |
+
|
| 166 |
+
**建議**:改成依環境變數控制(例如 `share=os.environ.get("GRADIO_SHARE", "false").lower() == "true"`),預設關閉,只有明確需要臨時對外展示時才手動開啟。
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## 四、Session 輸出目錄清理機制
|
| 171 |
+
|
| 172 |
+
原始需求彙整文件第十四節「待業主確認的範圍清單」第 3 項已列出:**Session 輸出目錄的清理機制(維運層面,非 UI/UX 範圍,但影響 Hugging Face Space 長期磁碟空間)**。`sessions/`(`SESSIONS_DIR`,app_agent.py:71-72)目前沒有任何過期清理邏輯,每次分析都會留下一個含原始上傳文本與全部產出檔案的資料夾,長期執行下磁碟用量只會持續增加。
|
| 173 |
+
|
| 174 |
+
如本文件第一節所述,這項工程與「持久化機制」高度相關——存讀檔上線後只會讓孤兒目錄累積得更快,建議兩者一併規劃排期,不要分開處理。
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## 五、待業主決策項目(拍板後由本次改版團隊實作,非工程範疇)
|
| 179 |
+
|
| 180 |
+
以下三項原始需求彙整文件第十四節「待業主確認的範圍清單」就已列出、當時業主尚未拍板,本次改版也都還沒做。跟第一至四節不同,這幾項**不需要業主的工程資源**,是產品/內容面的決定,拍板後回到本次改版團隊(吳千卉)施工即可,一併列在此處供業主後續思考。
|
| 181 |
+
|
| 182 |
+
### 5.1 語料 Metadata「略過」後可否事後補填
|
| 183 |
+
|
| 184 |
+
目前使用者在語料 Metadata 說明表單點「略過」後,系統標記為「使用者未提供」並**永久跳過,沒有任何入口可以事後回來編輯**。2026-07-20 訪談時的決定是「本次不做,只調整文案誠實告知略過的後果」(略過文案:「以下資訊會寫入報告開頭,您也可以先略過(略過後這次分析將不會用到這些補充說明)」)。若業主認為使用者略過後常常想回頭補,可以再評估是否要開一個「編輯語料說明」的入口+重新觸發報告生成,屬於全新功能,需要另外估工。
|
| 185 |
+
|
| 186 |
+
### 5.2 是否加入致理科技大學品牌/校徽識別
|
| 187 |
+
|
| 188 |
+
目前系統畫面上只有 `SpectraEngine` 自身的產品品牌,沒有委託單位(致理科技大學應日系)的識別。是否要加、加在哪裡(例如首頁或報告文件封面)、有沒有現成的校徽電子檔案可用,需要業主決定。
|
| 189 |
+
|
| 190 |
+
### 5.3 ��否設定上傳檔案大小上限
|
| 191 |
+
|
| 192 |
+
目前系統對上傳檔案**沒有任何大小限制**,異常巨大的檔案可能拖慢運算或超出 Gradio 本身的上傳限制。2026-07-20 訪談時判斷「不急迫,可延後決定」。若要做,可以與既有的檔名字首合法性、每組檔案數下限檢查(`run_format_check()`,app_agent.py:1579)合併在同一關驗證,實作成本不高。
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## 附錄:關鍵程式碼位置對照(截至 commit `aa1f341`)
|
| 197 |
+
|
| 198 |
+
| 項目 | 位置 |
|
| 199 |
+
|---|---|
|
| 200 |
+
| `SESSIONS_DIR` 定義 | app_agent.py:71-72 |
|
| 201 |
+
| `_call_openrouter()`(LLM API 呼叫核心)| app_agent.py:377-401 |
|
| 202 |
+
| `call_main_agent()` / `call_worker_agent()` | app_agent.py:408 / 413 |
|
| 203 |
+
| `make_initial_state()`(state dict 完整結構)| app_agent.py:1368-1413 |
|
| 204 |
+
| `_STEPS`(步驟列定義)| app_agent.py:1437 起 |
|
| 205 |
+
| `handle_scenario_select()` | app_agent.py:1510 |
|
| 206 |
+
| `run_format_check()` | app_agent.py:1579 |
|
| 207 |
+
| `run_computation_pipeline_streaming()`(指標計算/統計分析/報告撰寫三段 generator)| app_agent.py:1704 |
|
| 208 |
+
| `run_report_writing()`(LLM1 報告撰寫)| app_agent.py:1255 |
|
| 209 |
+
| `_populate_tabs()`(結果分頁與 zip 打包收尾)| app_agent.py:5358 |
|
| 210 |
+
| `on_scenario_pick()`(含多段 yield 可見性切換範例)| app_agent.py:5846 |
|
| 211 |
+
| `on_reset_confirm()` | app_agent.py:5962 |
|
| 212 |
+
| `on_upload_confirm()` | app_agent.py:6003 |
|
| 213 |
+
| `_run_computation()`(分析運算收尾 wrapper)| app_agent.py:6124 |
|
| 214 |
+
| `on_back_to_scenario()` | app_agent.py:6229 |
|
| 215 |
+
| `on_metadata_confirm()` | app_agent.py:6261 |
|
| 216 |
+
| `app.launch(share=True, ...)` | app_agent.py:6372-6380 |
|
| 217 |
+
|
| 218 |
+
## 附錄:2026-08-05 問題重現紀錄
|
| 219 |
+
|
| 220 |
+
- **首次觀察**:使用者於分析運算「報告撰寫」階段等候超過 180 秒,畫面持續顯示「處理中」無任何更新或錯誤訊息
|
| 221 |
+
- **診斷過程**:
|
| 222 |
+
1. 確認 OpenRouter API 金鑰額度正常(`usage: 0`,`limit: null`),排除額度問題
|
| 223 |
+
2. 直接呼叫 `.env` 設定的模型代號(`openrouter/free`)驗證為合法別名,0.5 秒內正常回應,排除模型代號無效問題,但發現該次回應路由到會做 reasoning 的模型(見第二節 2.3)
|
| 224 |
+
3. 檢查伺服器程序當下對外連線,**無任何連往 openrouter.ai 的 established 連線**,代表 LLM 呼叫已經結束(成功或失敗),並非仍在等待 API
|
| 225 |
+
4. 直接檢查該 session 輸出目錄,確認報告 docx/txt 與四包下載 zip **皆已完整產出**,證實後端已成功跑完整條流程,只是前端沒有反映
|
| 226 |
+
5. 使用者重新整理頁面後,畫面回到全新首頁(新 `session_id`),舊 session 的產出檔案「還在但找不到路回去」
|
| 227 |
+
- **重現測試**:以本機 `.venv` + Chrome 走一次完整流程(情境 1,測試樣本 `A_01.txt`/`B_01.txt`),**全程保持分頁在前景、電腦未休眠**,分析運算(含報告撰寫)於約 3 分 48 秒後正確顯示「分析完成!」,各階段狀態即時更新,未重現卡住現象;同一次測試也實際觀察到 `share=True` 產生的公開連結(見第三節)
|
| 228 |
+
- **結論**:長分析任務的可靠性問題與「分頁失去前景/連線中斷」高度相關,而非每次必然發生的邏輯錯誤;在目前的架構下,任何超過 1-2 分鐘的分析都暴露在同樣的風險下,這正是第一節選項 C 建議優先處理的原因
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
*本文件為本次 UI/UX 改版團隊(吳千卉)提出的技術評估建議,非最終實作規格。實際採用的方案、優先順序與工作量估計,請業主方工程資源進一步評估後定案。*
|