un2266 Claude Sonnet 5 commited on
Commit
f063fb7
·
1 Parent(s): b1088c8

Phase 8-C:結果內容從 tab2/tab3/tab6 遷移進 Canvas

Browse files

- 外層 gr.Tabs() 與 tab2/tab3/tab6(gr.Tab)拿掉分頁包裝,改成
canvas_idx_col/canvas_stat_col/canvas_report_col 三個 gr.Column,
搬進 canvas-scroll(upload_row/metadata_row 之後、
exec_log_accordion 之前);canvas_stat_col 內部維持原本巢狀
gr.Tabs()(敘述統計/差異性分析/監督式學習/非監督式學習 4 子頁不變)
- 首頁 gr.Tab("主頁") 包裝一併拿掉,改用純 with 區塊,首頁不再顯示
「主頁」分頁標籤
- _TAB_OUTPUTS/on_reset_confirm/reset_confirm_btn.click 的
tab2/tab3/tab6 引用改指向新 Column
- 修復兩個真實 CSS bug:
1. 巢狀子分頁字級規則原本靠 .tabitem 祖先跟全頁分頁列區分,外層
gr.Tabs() 拿掉後祖先消失、選擇器失效,改用 #canvas_stat 的
elem_id 精準定位
2. exec_log_accordion 搬進 canvas-scroll(flex column)後被壓縮成
只剩標題列高度:flexbox 規範規定 flex 子項只有 overflow:visible
時才用內容高度當自動最小高度下限,Gradio 幫 Accordion wrapper
加的 inline overflow:auto 觸發下限歸零。修法:
.canvas-scroll > * { flex-shrink:0 !important; }

已用 Playwright 完整驗證:情境1 上傳→格式驗證→Metadata 略過→運算
完成,語言指標分析/統計比較分析 4 子頁(含監督式/非監督式學習下載
按鈕)皆在 Canvas 正確顯示+下載;LLM輔助分析區塊因本機無 API key
正確維持隱藏(非本次迴歸);開始新分析重置正確回到全新首頁。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app_agent.py +262 -241
app_agent.py CHANGED
@@ -2041,8 +2041,11 @@ _CUSTOM_CSS = """
2041
  background: rgba(var(--c-accent-rgb),.07) !important;
2042
  }
2043
  .tabitem { padding: 1.5rem 1.25rem !important; }
2044
- /* 巢狀子分頁(統計比較分析內)維持原字級 */
2045
- .tabitem .tabs > .tab-nav > button { font-size: 0.80rem !important; }
 
 
 
2046
 
2047
  /* ── 4. CHATBOT ────────────────────────────────────────────────── */
2048
  .chatbot {
@@ -2332,6 +2335,21 @@ div[role="log"][aria-label="chatbot conversation"],
2332
  overflow-x: hidden !important;
2333
  overflow-y: auto !important;
2334
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2335
 
2336
  /* ── 5c. SCENARIO BUTTONS(取代原本打字輸入研究情境的輸入列)────────── */
2337
  .scenario-buttons {
@@ -2510,6 +2528,10 @@ div[role="log"][aria-label="chatbot conversation"],
2510
  animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
2511
  }
2512
  .metadata-zone .gr-box, .metadata-zone .form { background: transparent !important; }
 
 
 
 
2513
  .upload-area, [data-testid="droparea"] {
2514
  border: 2px dashed rgba(var(--c-accent-rgb),.28) !important;
2515
  border-radius: 12px !important;
@@ -3014,257 +3036,255 @@ def create_app():
3014
 
3015
  session_state = gr.State(None)
3016
 
3017
- with gr.Tabs():
3018
- # ══════════════════════════════════════════════════════════════
3019
- # Tab 0: 主頁
3020
- # ══════════════════════════════════════════════════════════════
3021
- with gr.Tab("🏠 主頁"):
3022
- # Phase 8:首頁改成 4 張情境卡片(gr.HTML()+JS橋接,Gradio 無原生
3023
- # 卡片元件,點卡片=模擬點擊下方隱藏版 sc_btn_1..4,見
3024
- # _APP_HEAD_SCRIPT 的 bindScenarioCards())。情境選定後這塊隱藏,
3025
- # workspace_row(對話+操作區)才出現。
3026
- with gr.Column(visible=True) as homepage_col:
3027
- gr.HTML(_build_homepage_html())
3028
- # 隱藏版情境按鈕:純作 JS 橋接目標(.sc-bridge-btn,見 CSS/
3029
- # bindScenarioCards()),.click() 綁定邏輯完全不動。必須放在
3030
- # 一開始就 visible=True 的容器裡(homepage_col),因為 Gradio
3031
- # 對「建構時就 visible=False」的容器是整段不掛載進 DOM,不是
3032
- # 只用 CSS display:none 藏起來——放在 workspace_row 裡會導致
3033
- # 這幾顆按鈕在情境選定前根本不存在於 DOM,JS 找不到可點的對象。
3034
- sc_btn_1 = gr.Button("1 人工翻譯 vs 機器翻譯", variant="secondary",
3035
- elem_id="sc_btn_1", elem_classes=["sc-bridge-btn"])
3036
- sc_btn_2 = gr.Button("2 全譯 vs 編譯/節譯/改寫", variant="secondary",
3037
- elem_id="sc_btn_2", elem_classes=["sc-bridge-btn"])
3038
- sc_btn_3 = gr.Button("3 同期不同譯者譯本比較", variant="secondary",
3039
- elem_id="sc_btn_3", elem_classes=["sc-bridge-btn"])
3040
- sc_btn_4 = gr.Button("4 不同時期重譯本的歷時比較", variant="secondary",
3041
- elem_id="sc_btn_4", elem_classes=["sc-bridge-btn"])
3042
-
3043
- # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
3044
- # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/
3045
- # 執行紀錄,獨立捲動)。手機寬度見 CSS 的 media query 改上下堆疊。
3046
- with gr.Row(elem_classes=["main-layout-row"], visible=False) as workspace_row:
3047
- with gr.Column(scale=2, min_width=200, elem_classes=["nav-col"]) as nav_col:
3048
- # 研究情境是封閉式的固定四選一,改用可點擊按鈕取代打字輸入
3049
- # 數字再解析,選定後即隱藏,對話視窗回歸純輸出角色
3050
- # Phase 8:情境選擇按鈕已搬到 homepage_col(見上方,理由是
3051
- # Gradio 對建構時 visible=False 的容器不掛載進 DOM,見該處
3052
- # 註解)。這個 Column 目前是空的,只留來讓 on_scenario_pick/
3053
- # on_reset_confirm 既有的可見性輸出繼續運作,不動更多 wiring;
3054
- # Phase 8-F 全站清理時一併拿掉。
3055
- with gr.Column(elem_classes=["scenario-buttons"]) as scenario_btn_col:
3056
- pass
3057
-
3058
- # 「開始新分析」與其確認彈窗:換情境/重新開始都走這一套共用
3059
- # 機制。情境是封閉式按鈕選擇,不透過對話框打字,所以「換
3060
- # 情境」也統一走這顆按鈕,不恢復文字解析路徑。
3061
- reset_btn = gr.Button("🆕 開始新分析", elem_classes=["reset-btn"], variant="secondary")
3062
- with gr.Column(visible=False, elem_classes=["reset-confirm"]) as reset_confirm_row:
3063
- gr.Markdown("確定要開始新分析嗎?目前進度將會遺失。")
3064
- with gr.Row(elem_classes=["upload-actions"]):
3065
- reset_cancel_btn = gr.Button("取消", scale=1, variant="secondary")
3066
- reset_confirm_btn = gr.Button("確定重新開始", scale=2, variant="primary")
3067
- # 結果導覽(🏠對話/📊語言指標分析/📈統計比較分析/📝LLM
3068
- # 輔助分析)與下載區留給 Phase 8-D 一起做,這裡先不放。
3069
-
3070
- with gr.Column(scale=4, min_width=300, elem_classes=["chat-col"]):
3071
- chatbot = gr.Chatbot(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3072
  show_label=False,
3073
- height=400,
3074
- elem_classes=["main-chat"],
3075
  )
3076
- # Phase 8(千卉實測後回頭調整):對話輸入框改回「常駐顯示、
3077
- # 不負責上傳」——業主原本設計就是用拖曳/點擊上傳的卡片
3078
- # (見下方 upload_row),對話框目前也沒有其他打字用途
3079
- # (全站掃過沒有任何地方會解析使用者自由輸入文字),暫時
3080
- # 只留著當畫面元素,不接 sources=["upload"],避免顯示
3081
- # 附加檔案的迴紋針卻沒有作用。chat_upload.submit() 的
3082
- # wiring 已停用(見下方 on_chat_upload_submit 註解),
3083
- # 函式保留、之後如果對話框有真正用途要復活直接接回去即可。
3084
- with gr.Column(visible=True, elem_id="chat_upload_zone", elem_classes=["upload-zone-plain"]) as chat_upload_col:
3085
- chat_upload = gr.MultimodalTextbox(
3086
- file_types=[".txt"],
3087
- sources=[],
3088
- placeholder="對話輸入框(功能開發中)",
3089
- show_label=False,
3090
- )
3091
-
3092
- with gr.Column(scale=7, min_width=380, elem_classes=["canvas-col"]):
3093
- # 頂部步驟列+情境標籤:原本在全頁最上方(首頁也會看到),
3094
- # Phase 8-B 搬進 Canvas 內部,只在 workspace_row 顯示後
3095
- # 才出現,首頁不再看到。
3096
- # 注意:這裡不能靠 app_header_html 自己 position:sticky
3097
- # 達到「頂部固定」——它的直接 wrapper(Gradio 自動包的
3098
- # .block)高度只比它自己的內容高一點點,sticky 的可移動
3099
- # 範圍被那層 wrapper 框住,捲動一小段(wrapper 多出來的
3100
- # 高度)後就整個跟著捲走,不會真的貼在 Canvas 頂端不動
3101
- # (已實測踩坑)。正確做法:步驟列留在捲動區外面(跟著
3102
- # canvas-col 本身固定不動),下面另外包一層
3103
- # canvas-scroll 才是真正會捲動的區域,兩者高度各自獨立。
3104
- app_header_html = gr.HTML(_build_header_html())
3105
-
3106
- with gr.Column(elem_classes=["canvas-scroll"]):
3107
- # Phase 8(千卉實測後回頭調整):這張卡片改回正式使用
3108
- # 中的上傳入口(拖曳/點擊上傳),符合業主原本設計;
3109
- # Phase 7 曾經把它永久隱藏、改用對話框上傳,這次反
3110
- # 過來。程式邏輯完全沒動,只是把可見性開關從
3111
- # chat_upload_col 換回這裡。
3112
- with gr.Column(visible=False, elem_id="upload_zone", elem_classes=["upload-zone"]) as upload_row:
3113
- upload_hint_md = gr.Markdown(value="", elem_classes=["upload-hint"])
3114
- file_input = gr.File(
3115
- show_label=False,
3116
- file_types=[".txt"], file_count="multiple",
3117
- )
3118
- with gr.Row(elem_classes=["upload-actions"]):
3119
- cancel_btn = gr.Button("取消", scale=1, variant="secondary")
3120
- upload_btn = gr.Button("確認上傳", scale=2, variant="primary")
3121
-
3122
- # ── 語料 Metadata 說明表單(上傳後、分析前)───────
3123
- with gr.Column(visible=False, elem_id="metadata_zone", elem_classes=["metadata-zone"]) as metadata_row:
3124
- gr.Markdown("### 📑 語料 Metadata 說明\n請於正式分析前說明語料來源、範圍與比較結構,將寫入研究報告。")
3125
-
3126
- gr.Markdown("**原文資訊**")
3127
- with gr.Row():
3128
- meta_title = gr.Textbox(label="原文標題", scale=2)
3129
- meta_author = gr.Textbox(label="作者", scale=2)
3130
- meta_lang = gr.Textbox(label="語言", scale=1)
3131
- with gr.Row():
3132
- meta_pubyear = gr.Textbox(label="出版年", scale=1)
3133
- meta_version = gr.Textbox(label="版本", scale=2)
3134
-
3135
- # 比較結構(僅情境 3、4 需要;情境 1、2 固定二群)
3136
- with gr.Group(visible=False) as meta_compare_group:
3137
- gr.Markdown("**比較結構**")
3138
- with gr.Row():
3139
- meta_ngroups = gr.Radio(
3140
- ["二群", "三群"], label="群組數量", value="二群", scale=1,
3141
- )
3142
- meta_method = gr.Radio(
3143
- ["群組間比較", "個體間比較"], label="比較方式",
3144
- value="群組間比較", scale=1,
3145
- )
3146
-
3147
- gr.Markdown("**群組層資訊**(共通屬性)")
3148
- meta_group_df = gr.Dataframe(
3149
- headers=["群組標籤", "翻譯類型", "翻譯方向", "譯者背景", "目標讀者"],
3150
- datatype=["str"] * 5,
3151
- col_count=(5, "fixed"),
3152
- row_count=(1, "dynamic"),
3153
- interactive=True,
3154
- label="群組層",
3155
- )
3156
 
3157
- gr.Markdown("**個體層資訊**(每個譯本/檔案)")
3158
- meta_indiv_df = gr.Dataframe(
3159
- headers=["檔案", "譯者/系統名稱", "出版年", "譯者母語", "後編輯程度", "重譯序位"],
3160
- datatype=["str"] * 6,
3161
- col_count=(6, "fixed"),
3162
- row_count=(1, "dynamic"),
3163
- interactive=True,
3164
- label="個體層",
3165
- )
3166
 
3167
- gr.Markdown(
3168
- "以下資訊會寫入報告開頭,您也可以先略過(略過後這次分析將不會用到這些補充說明)",
3169
- elem_classes=["upload-hint"],
 
 
 
 
 
 
 
 
 
 
 
 
3170
  )
3171
- with gr.Row(elem_classes=["upload-actions"]):
3172
- meta_cancel_btn = gr.Button("略過", scale=1, variant="secondary")
3173
- meta_confirm_btn = gr.Button("確認並開始分析", scale=2, variant="primary")
3174
-
3175
- # 技術執行紀錄:Phase 8-B 從 chat-col 搬到 Canvas 底
3176
- # 部,跟中段內容同一塊可捲動捲軸,非 sticky,點擊
3177
- # 往下展開。
3178
- with gr.Accordion("技術執行紀錄", open=False, elem_id="exec_log_accordion") as exec_log_accordion:
3179
- exec_log = gr.Markdown(
3180
- value="",
3181
- elem_id="exec_log",
3182
- elem_classes=["exec-log"],
3183
  )
3184
 
3185
- # ══════════════════════════════════════════════════════════════
3186
- # 結果分頁:初始隱藏,分析完成後顯示
3187
- # ══════════════════════════════════════════════════════════════
3188
- with gr.Tab("📊 語言指標分析", visible=False) as tab2:
3189
- tab_idx_md = gr.Markdown(_EMPTY_HINT)
3190
- tab_idx_file = gr.File(
3191
- label="下載檔案",
3192
- file_count="multiple", interactive=False, visible=False,
3193
- )
3194
- tab_idx_df = gr.Dataframe(
3195
- label="指標計算結果",
3196
- interactive=False, visible=False,
3197
- )
3198
 
3199
- with gr.Tab("📈 統計比較分析", visible=False) as tab3:
3200
- with gr.Tabs():
3201
- # 子分頁 1:敘述統計
3202
- with gr.Tab("1. 敘述統計"):
3203
- tab_desc_md = gr.Markdown(_EMPTY_HINT)
3204
- tab_desc_df = gr.Dataframe(
3205
- label="敘述性統計",
3206
- interactive=False, visible=False,
3207
  )
3208
- tab_desc_file = gr.File(
3209
- label="下載敘述統計",
3210
- interactive=False, visible=False,
 
3211
  )
3212
- # 子分頁 2:差異性分析
3213
- with gr.Tab("2. 差異性分析"):
3214
- tab_diff_head = gr.Markdown(_EMPTY_HINT)
3215
- tab_box_img = gr.Image(label="顯著指標箱形圖", visible=False)
3216
- tab_diff_df = gr.Dataframe(
3217
- label="差異性分析",
3218
- interactive=False, visible=False,
 
 
 
 
 
 
 
3219
  )
3220
- tab_diff_file = gr.File(
3221
- label="下載差異性分析",
3222
  interactive=False, visible=False,
3223
  )
3224
- # 子分頁 3:監督式學習
3225
- with gr.Tab("3. 監督式學習"):
3226
- tab_rf_md = gr.Markdown(_EMPTY_HINT)
3227
- with gr.Row():
3228
- with gr.Column(scale=1):
3229
- tab_imp_df = gr.Dataframe(
3230
- label="特徵重要性 Top 20",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3231
  interactive=False, visible=False,
3232
  )
3233
- with gr.Column(scale=2):
3234
- tab_imp_img = gr.Image(label="特徵重要性圖", visible=False)
3235
- tab_imp_file = gr.File(
3236
- label="下載監督式學習",
3237
- interactive=False, visible=False,
3238
- )
3239
- # 子分頁 4:非監督式學習
3240
- with gr.Tab("4. 非監督式學習"):
3241
- tab_clust_md = gr.Markdown(_EMPTY_HINT)
3242
- with gr.Row():
3243
- with gr.Column(scale=1):
3244
- tab_dendro_img = gr.Image(label="層次分群樹狀圖", visible=False)
3245
- with gr.Column(scale=1):
3246
- tab_elbow_img = gr.Image(label="K-means Elbow Curve", visible=False)
3247
- tab_pca_img = gr.Image(label="分群視覺化(PCA 2D)", visible=False)
3248
- tab_cluster_df = gr.Dataframe(
3249
- label="分群結果",
3250
- interactive=False, visible=False,
3251
- )
3252
- tab_cluster_file = gr.File(
3253
- label="下載非監督式學習",
3254
- interactive=False, visible=False,
3255
- )
3256
- with gr.Tab("📝 LLM輔助分析", visible=False) as tab6:
3257
- tab_rpt_md = gr.Markdown(_EMPTY_HINT)
3258
- tab_rpt_file = gr.File(
3259
- label="下載報告",
3260
- file_count="multiple", interactive=False, visible=False,
3261
- )
3262
-
3263
 
3264
- # ── 分頁 Tab 元件(控制顯示/隱藏)─────────────────────────────────
3265
- _RESULT_TABS = [tab2, tab3, tab6]
 
 
 
 
3266
 
3267
- # ── 所有分頁內容輸出元件 + 3 個 Tab 可見性 ────────────────────────
 
 
 
 
 
 
 
 
 
 
 
3268
  _TAB_OUTPUTS = [
3269
  tab_idx_md, tab_idx_df, tab_idx_file, # 0-2 語言指標分析
3270
  tab_desc_md, tab_desc_df, tab_desc_file, # 3-5 敘述統計(含下載)
@@ -3273,7 +3293,7 @@ def create_app():
3273
  tab_clust_md, tab_dendro_img, tab_elbow_img, # 13-15 非監督式學習
3274
  tab_pca_img, tab_cluster_df, # 16-17
3275
  tab_rpt_md, tab_rpt_file, # 18-19 研究報告
3276
- tab2, tab3, tab6, # 20-22 (visibility)
3277
  tab_imp_file, tab_cluster_file, # 23-24 新增下載按鈕(僅能往尾端加,
3278
  # 不可插入既有 index 中間,見程式工作守則)
3279
  ]
@@ -3586,9 +3606,9 @@ def create_app():
3586
  gr.update(visible=False), # upload_row(Phase 8:正式上傳入口改這張卡片)
3587
  gr.update(value=None), # chat_upload(對話框仍清空一下,雖然目前不接上傳)
3588
  gr.update(visible=False), # metadata_row
3589
- gr.update(visible=False), # tab2 語言指標分析
3590
- gr.update(visible=False), # tab3 統計比較分析
3591
- gr.update(visible=False), # tab6 LLM輔助分析
3592
  gr.update(visible=False), # reset_confirm_row(關掉確認彈窗自己)
3593
  _accordion_update(state, new_log), # exec_log_accordion(收合並更新摘要文字)
3594
  gr.update(visible=True), # homepage_col(Phase 8:重新開始=回到首頁選情境)
@@ -3601,7 +3621,8 @@ def create_app():
3601
  reset_confirm_btn.click(
3602
  fn=on_reset_confirm,
3603
  outputs=[chatbot, exec_log, session_state, app_header_html,
3604
- scenario_btn_col, upload_row, chat_upload, metadata_row, tab2, tab3, tab6,
 
3605
  reset_confirm_row, exec_log_accordion, homepage_col, workspace_row, file_input],
3606
  )
3607
 
 
2041
  background: rgba(var(--c-accent-rgb),.07) !important;
2042
  }
2043
  .tabitem { padding: 1.5rem 1.25rem !important; }
2044
+ /* 巢狀子分頁(統計比較分析內,敘述統計/差異性分析/監督式學習/非監督式學習)
2045
+ 維持原字級。Phase 8-C 前這裡靠 .tabitem 祖先跟外層分頁列的 .tabs 區分;
2046
+ 外層 gr.Tabs() 拿掉後,這是全站唯一剩下的 .tabs,改用 canvas_stat_col
2047
+ 的 elem_id 精準定位,避免套用上面給「全頁分頁列」設計的 1.20rem 大字級 */
2048
+ #canvas_stat .tabs > .tab-nav > button { font-size: 0.80rem !important; }
2049
 
2050
  /* ── 4. CHATBOT ────────────────────────────────────────────────── */
2051
  .chatbot {
 
2335
  overflow-x: hidden !important;
2336
  overflow-y: auto !important;
2337
  }
2338
+ /* Phase 8-C 踩坑:結果內容搬進 canvas-scroll 後,內容總高度遠超過
2339
+ canvas-col 給的固定 height,理論上該讓 canvas-scroll 自己捲動就好;但
2340
+ exec_log_accordion(技術執行紀錄)被壓縮成只剩標題列高度(22px),內容
2341
+ 反而擠進一個內部小捲動框。原因:CSS flexbox 規範規定「flex 子項的
2342
+ automatic minimum size」只有在該子項 overflow:visible 時才會採用內容
2343
+ 高度當下限,一旦子項 overflow 不是 visible(Gradio 會自動幫 Accordion
2344
+ 這類元件的外層 wrapper 加上 inline overflow:auto,方便使用者拖曳調整
2345
+ 大小),下限就變成 0,於是在總內容超出容器高度、瀏覽器要收縮子項時,
2346
+ 這個子項就被壓到接近 0——同排的 upload_row/metadata_row/canvas_idx_col
2347
+ 等純 gr.Column 因為維持預設 overflow:visible 沒有這個下限問題,才沒被
2348
+ 壓縮。修法不是去動 Accordion 的 overflow(那是 Gradio 拖曳調整大小的
2349
+ 功能,不想關掉),而是直接讓這個捲動容器裡的所有子項都不參與收縮 */
2350
+ .canvas-scroll > * {
2351
+ flex-shrink: 0 !important;
2352
+ }
2353
 
2354
  /* ── 5c. SCENARIO BUTTONS(取代原本打字輸入研究情境的輸入列)────────── */
2355
  .scenario-buttons {
 
2528
  animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
2529
  }
2530
  .metadata-zone .gr-box, .metadata-zone .form { background: transparent !important; }
2531
+ /* Phase 8-C:結果內容區塊(語言指標分析/統計比較分析/LLM輔助分析),
2532
+ 從原本的 gr.Tab()(自帶 .tabitem padding)改成 gr.Column,這裡補上
2533
+ 跟上面 upload-zone/metadata-zone 一致的垂直間距,避免緊貼在一起 */
2534
+ .canvas-result { margin-top: 1.2rem !important; }
2535
  .upload-area, [data-testid="droparea"] {
2536
  border: 2px dashed rgba(var(--c-accent-rgb),.28) !important;
2537
  border-radius: 12px !important;
 
3036
 
3037
  session_state = gr.State(None)
3038
 
3039
+ # ══════════════════════════════════════════════════════════════
3040
+ # 主頁(Phase 8-C:不再包在 gr.Tabs()/gr.Tab() 裡,外層分頁列已拿掉)
3041
+ # ══════════════════════════════════════════════════════════════
3042
+ # Phase 8:首頁改成 4 張情境卡片(gr.HTML()+JS橋接,Gradio 無原生
3043
+ # 卡片元件,點卡片=模擬點擊下方隱藏版 sc_btn_1..4,見
3044
+ # _APP_HEAD_SCRIPT 的 bindScenarioCards())。情境選定後這塊隱藏,
3045
+ # workspace_row(對話+操作區)才出現。
3046
+ with gr.Column(visible=True) as homepage_col:
3047
+ gr.HTML(_build_homepage_html())
3048
+ # 隱藏版情境按鈕:純作 JS 橋接目標(.sc-bridge-btn,見 CSS/
3049
+ # bindScenarioCards()),.click() 綁定邏輯完全不動。必須放在
3050
+ # 一開始就 visible=True 的容器裡(homepage_col),因為 Gradio
3051
+ # 對「建構時就 visible=False」的容器是整段不掛載進 DOM,不是
3052
+ # 只用 CSS display:none 藏起來——放在 workspace_row 裡會導致
3053
+ # 這幾顆按鈕在情境選定前根本不存在於 DOM,JS 找不到可點的對象。
3054
+ sc_btn_1 = gr.Button("1 人工翻譯 vs 機器翻譯", variant="secondary",
3055
+ elem_id="sc_btn_1", elem_classes=["sc-bridge-btn"])
3056
+ sc_btn_2 = gr.Button("2 全譯 vs 編譯/節譯/改寫", variant="secondary",
3057
+ elem_id="sc_btn_2", elem_classes=["sc-bridge-btn"])
3058
+ sc_btn_3 = gr.Button("3 同期不同譯者譯本比較", variant="secondary",
3059
+ elem_id="sc_btn_3", elem_classes=["sc-bridge-btn"])
3060
+ sc_btn_4 = gr.Button("4 不同時期重譯本的歷時比較", variant="secondary",
3061
+ elem_id="sc_btn_4", elem_classes=["sc-bridge-btn"])
3062
+
3063
+ # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
3064
+ # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/
3065
+ # 執行紀錄,獨立捲動)。手機寬度見 CSS 的 media query 改上下堆疊。
3066
+ with gr.Row(elem_classes=["main-layout-row"], visible=False) as workspace_row:
3067
+ with gr.Column(scale=2, min_width=200, elem_classes=["nav-col"]) as nav_col:
3068
+ # 研究情境是封閉式的固定四選一,改用可點擊按鈕取代打字輸入
3069
+ # 數字再解析,選定後即隱藏,對話視窗回歸純輸出角色
3070
+ # Phase 8:情境選擇按鈕已搬到 homepage_col(見上方,理由是
3071
+ # Gradio 對建構時 visible=False 的容器不掛載進 DOM,見該處
3072
+ # 註解)。這個 Column 目前是空的,只留來讓 on_scenario_pick/
3073
+ # on_reset_confirm 既有的可見性輸出繼續運作,不動更多 wiring;
3074
+ # Phase 8-F 全站清理時一併拿掉。
3075
+ with gr.Column(elem_classes=["scenario-buttons"]) as scenario_btn_col:
3076
+ pass
3077
+
3078
+ # 「開始新分析」與其確認彈窗:換情境/重新開始都走這一套共用
3079
+ # 機制。情境是封閉式按鈕選擇,不透過對話框打字,所以「換
3080
+ # 情境」也統一走這顆按鈕,不恢復文字解析路徑。
3081
+ reset_btn = gr.Button("🆕 開始新分析", elem_classes=["reset-btn"], variant="secondary")
3082
+ with gr.Column(visible=False, elem_classes=["reset-confirm"]) as reset_confirm_row:
3083
+ gr.Markdown("確定要開始新分析嗎?目前進度將會遺失。")
3084
+ with gr.Row(elem_classes=["upload-actions"]):
3085
+ reset_cancel_btn = gr.Button("取消", scale=1, variant="secondary")
3086
+ reset_confirm_btn = gr.Button("確定重新開始", scale=2, variant="primary")
3087
+ # 結果導覽(🏠對話/📊語言指標分析/📈統計比較分析/📝LLM
3088
+ # 輔助分析)與下載區留給 Phase 8-D 一起做,這裡先不放。
3089
+
3090
+ with gr.Column(scale=4, min_width=300, elem_classes=["chat-col"]):
3091
+ chatbot = gr.Chatbot(
3092
+ show_label=False,
3093
+ height=400,
3094
+ elem_classes=["main-chat"],
3095
+ )
3096
+ # Phase 8(千卉實測後回頭調整):對話輸入框改回「常駐顯示、
3097
+ # 不負責上傳」——業主原本設計就是用拖曳/點擊上傳的卡片
3098
+ # (見下方 upload_row),對話框目前也沒有其他打字用途
3099
+ # (全站掃過沒有任何地方會解析使用者自由輸入文字),暫時
3100
+ # 只留著當畫面元素,不接 sources=["upload"],避免顯示
3101
+ # 附加檔案的迴紋針卻沒有作用。chat_upload.submit() 的
3102
+ # wiring 已停用(見下方 on_chat_upload_submit 註解),
3103
+ # 函式保留、之後如果對話框有真正用途要復活直接接回去即可。
3104
+ with gr.Column(visible=True, elem_id="chat_upload_zone", elem_classes=["upload-zone-plain"]) as chat_upload_col:
3105
+ chat_upload = gr.MultimodalTextbox(
3106
+ file_types=[".txt"],
3107
+ sources=[],
3108
+ placeholder="對話輸入框(功能開發中)",
3109
+ show_label=False,
3110
+ )
3111
+
3112
+ with gr.Column(scale=7, min_width=380, elem_classes=["canvas-col"]):
3113
+ # 頂部步驟列+情境標籤:原本在全頁最上方(首頁也會看到),
3114
+ # Phase 8-B 搬進 Canvas 內部,只在 workspace_row 顯示後
3115
+ # 才出現,首頁不再看到。
3116
+ # 注意:這裡不能靠 app_header_html 自己 position:sticky
3117
+ # 達到「頂部固定」——它的直接 wrapper(Gradio 自動包的
3118
+ # .block)高度只比它自己的內容高一點點,sticky 的可移動
3119
+ # 範圍被那層 wrapper 框住,捲動一小段(wrapper 多出來的
3120
+ # 高度)後就整個跟著捲走,不會真的貼在 Canvas 頂端不動
3121
+ # (已實測踩坑)。正確做法:步驟列留在捲動區外面(跟著
3122
+ # canvas-col 本身固定不動),下面另外包一層
3123
+ # canvas-scroll 才是真正會捲動的區域,兩者高度各自獨立。
3124
+ app_header_html = gr.HTML(_build_header_html())
3125
+
3126
+ with gr.Column(elem_classes=["canvas-scroll"]):
3127
+ # Phase 8(千卉實測後回頭調整):這張卡片改回正式使用
3128
+ # 中的上傳入口(拖曳/點擊上傳),符合業主原本設計;
3129
+ # Phase 7 曾經把它永久隱藏、改用對話框上傳,這次反
3130
+ # 過來。程式邏輯完全沒動,只是把可見性開關從
3131
+ # chat_upload_col 換回這裡。
3132
+ with gr.Column(visible=False, elem_id="upload_zone", elem_classes=["upload-zone"]) as upload_row:
3133
+ upload_hint_md = gr.Markdown(value="", elem_classes=["upload-hint"])
3134
+ file_input = gr.File(
3135
  show_label=False,
3136
+ file_types=[".txt"], file_count="multiple",
 
3137
  )
3138
+ with gr.Row(elem_classes=["upload-actions"]):
3139
+ cancel_btn = gr.Button("取消", scale=1, variant="secondary")
3140
+ upload_btn = gr.Button("確認上傳", scale=2, variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3141
 
3142
+ # ── 語料 Metadata 說明表單(上傳後、分析前)───────
3143
+ with gr.Column(visible=False, elem_id="metadata_zone", elem_classes=["metadata-zone"]) as metadata_row:
3144
+ gr.Markdown("### 📑 語料 Metadata 說明\n請於正式分析前說明語料來源、範圍與比較結構,將寫入研究報告。")
 
 
 
 
 
 
3145
 
3146
+ gr.Markdown("**原文資訊**")
3147
+ with gr.Row():
3148
+ meta_title = gr.Textbox(label="原文標題", scale=2)
3149
+ meta_author = gr.Textbox(label="作者", scale=2)
3150
+ meta_lang = gr.Textbox(label="語言", scale=1)
3151
+ with gr.Row():
3152
+ meta_pubyear = gr.Textbox(label="出版年", scale=1)
3153
+ meta_version = gr.Textbox(label="版本", scale=2)
3154
+
3155
+ # 比較結構(僅情境 3、4 需要;情境 1、2 固定二群)
3156
+ with gr.Group(visible=False) as meta_compare_group:
3157
+ gr.Markdown("**比較結構**")
3158
+ with gr.Row():
3159
+ meta_ngroups = gr.Radio(
3160
+ ["二群", "三群"], label="群組數量", value="二群", scale=1,
3161
  )
3162
+ meta_method = gr.Radio(
3163
+ ["群組間比較", "個體間比較"], label="比較方式",
3164
+ value="群組間比較", scale=1,
 
 
 
 
 
 
 
 
 
3165
  )
3166
 
3167
+ gr.Markdown("**群組層資訊**(共通屬性)")
3168
+ meta_group_df = gr.Dataframe(
3169
+ headers=["群組標籤", "翻譯類型", "翻譯方向", "譯者背景", "目標讀者"],
3170
+ datatype=["str"] * 5,
3171
+ col_count=(5, "fixed"),
3172
+ row_count=(1, "dynamic"),
3173
+ interactive=True,
3174
+ label="群組層",
3175
+ )
 
 
 
 
3176
 
3177
+ gr.Markdown("**個體層資訊**(每個譯本/檔案)")
3178
+ meta_indiv_df = gr.Dataframe(
3179
+ headers=["檔案", "譯者/系統名稱", "出版年", "譯者母語", "後編輯程度", "重譯序位"],
3180
+ datatype=["str"] * 6,
3181
+ col_count=(6, "fixed"),
3182
+ row_count=(1, "dynamic"),
3183
+ interactive=True,
3184
+ label="個體層",
3185
  )
3186
+
3187
+ gr.Markdown(
3188
+ "以下資訊會寫入報告開頭,您也可以先略過(略過後這次分析將不會用到這些補充說明)",
3189
+ elem_classes=["upload-hint"],
3190
  )
3191
+ with gr.Row(elem_classes=["upload-actions"]):
3192
+ meta_cancel_btn = gr.Button("略過", scale=1, variant="secondary")
3193
+ meta_confirm_btn = gr.Button("確認並開始分析", scale=2, variant="primary")
3194
+
3195
+ # Phase 8-C:結果內容從 tab2/tab3/tab6(原本 gr.Tabs() 分頁頁面)搬進
3196
+ # Canvas,改成三個互斥可見性的 gr.Column,取代整頁分頁切換。三塊
3197
+ # 目前皆用同一組 visible=True/False 語意(見 _populate_tabs 的
3198
+ # index 20-22),分析完成後會同時顯示、垂直堆疊;哪個結果先出現在
3199
+ # 畫面上由使用者往下捲決定,nav-col 的「結果導覽」切換是 8-D 待辦。
3200
+ with gr.Column(visible=False, elem_id="canvas_idx", elem_classes=["canvas-result"]) as canvas_idx_col:
3201
+ tab_idx_md = gr.Markdown(_EMPTY_HINT)
3202
+ tab_idx_file = gr.File(
3203
+ label="下載檔案",
3204
+ file_count="multiple", interactive=False, visible=False,
3205
  )
3206
+ tab_idx_df = gr.Dataframe(
3207
+ label="指標計算結果",
3208
  interactive=False, visible=False,
3209
  )
3210
+
3211
+ with gr.Column(visible=False, elem_id="canvas_stat", elem_classes=["canvas-result"]) as canvas_stat_col:
3212
+ with gr.Tabs():
3213
+ # 子分頁 1:敘述統計
3214
+ with gr.Tab("1. 敘述統計"):
3215
+ tab_desc_md = gr.Markdown(_EMPTY_HINT)
3216
+ tab_desc_df = gr.Dataframe(
3217
+ label="敘述性統計",
3218
+ interactive=False, visible=False,
3219
+ )
3220
+ tab_desc_file = gr.File(
3221
+ label="下載敘述統計",
3222
+ interactive=False, visible=False,
3223
+ )
3224
+ # 子分頁 2:差異性分析
3225
+ with gr.Tab("2. 差異性分析"):
3226
+ tab_diff_head = gr.Markdown(_EMPTY_HINT)
3227
+ tab_box_img = gr.Image(label="顯著指標箱形圖", visible=False)
3228
+ tab_diff_df = gr.Dataframe(
3229
+ label="差異性分析",
3230
+ interactive=False, visible=False,
3231
+ )
3232
+ tab_diff_file = gr.File(
3233
+ label="下載差異性分析",
3234
+ interactive=False, visible=False,
3235
+ )
3236
+ # 子分頁 3:監督式學習
3237
+ with gr.Tab("3. 監督式學習"):
3238
+ tab_rf_md = gr.Markdown(_EMPTY_HINT)
3239
+ with gr.Row():
3240
+ with gr.Column(scale=1):
3241
+ tab_imp_df = gr.Dataframe(
3242
+ label="特徵重要性 Top 20",
3243
+ interactive=False, visible=False,
3244
+ )
3245
+ with gr.Column(scale=2):
3246
+ tab_imp_img = gr.Image(label="特徵重要性圖", visible=False)
3247
+ tab_imp_file = gr.File(
3248
+ label="下載監督式學習",
3249
+ interactive=False, visible=False,
3250
+ )
3251
+ # 子分頁 4:非監督式學習
3252
+ with gr.Tab("4. 非監督式學習"):
3253
+ tab_clust_md = gr.Markdown(_EMPTY_HINT)
3254
+ with gr.Row():
3255
+ with gr.Column(scale=1):
3256
+ tab_dendro_img = gr.Image(label="層次分群樹狀圖", visible=False)
3257
+ with gr.Column(scale=1):
3258
+ tab_elbow_img = gr.Image(label="K-means Elbow Curve", visible=False)
3259
+ tab_pca_img = gr.Image(label="分群視覺化(PCA 2D)", visible=False)
3260
+ tab_cluster_df = gr.Dataframe(
3261
+ label="分群結果",
3262
+ interactive=False, visible=False,
3263
+ )
3264
+ tab_cluster_file = gr.File(
3265
+ label="下載非監督式學習",
3266
  interactive=False, visible=False,
3267
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3268
 
3269
+ with gr.Column(visible=False, elem_id="canvas_report", elem_classes=["canvas-result"]) as canvas_report_col:
3270
+ tab_rpt_md = gr.Markdown(_EMPTY_HINT)
3271
+ tab_rpt_file = gr.File(
3272
+ label="下載報告",
3273
+ file_count="multiple", interactive=False, visible=False,
3274
+ )
3275
 
3276
+ # 技術執行紀錄:Phase 8-B 從 chat-col 搬到 Canvas 底
3277
+ # 部,跟中段內容同一塊可捲動捲軸,非 sticky,點擊
3278
+ # 往下展開。
3279
+ with gr.Accordion("技術執行紀錄", open=False, elem_id="exec_log_accordion") as exec_log_accordion:
3280
+ exec_log = gr.Markdown(
3281
+ value="",
3282
+ elem_id="exec_log",
3283
+ elem_classes=["exec-log"],
3284
+ )
3285
+ # ── 結果內容輸出元件 + 3 個 Canvas 結果區塊可見性 ─────────────────
3286
+ # Phase 8-C:tab2/tab3/tab6(原 gr.Tab())已改成 canvas_idx_col/
3287
+ # canvas_stat_col/canvas_report_col(gr.Column()),可見性語意不變。
3288
  _TAB_OUTPUTS = [
3289
  tab_idx_md, tab_idx_df, tab_idx_file, # 0-2 語言指標分析
3290
  tab_desc_md, tab_desc_df, tab_desc_file, # 3-5 敘述統計(含下載)
 
3293
  tab_clust_md, tab_dendro_img, tab_elbow_img, # 13-15 非監督式學習
3294
  tab_pca_img, tab_cluster_df, # 16-17
3295
  tab_rpt_md, tab_rpt_file, # 18-19 研究報告
3296
+ canvas_idx_col, canvas_stat_col, canvas_report_col, # 20-22 (visibility)
3297
  tab_imp_file, tab_cluster_file, # 23-24 新增下載按鈕(僅能往尾端加,
3298
  # 不可插入既有 index 中間,見程式工作守則)
3299
  ]
 
3606
  gr.update(visible=False), # upload_row(Phase 8:正式上傳入口改這張卡片)
3607
  gr.update(value=None), # chat_upload(對話框仍清空一下,雖然目前不接上傳)
3608
  gr.update(visible=False), # metadata_row
3609
+ gr.update(visible=False), # canvas_idx_col 語言指標分析
3610
+ gr.update(visible=False), # canvas_stat_col 統計比較分析
3611
+ gr.update(visible=False), # canvas_report_col LLM輔助分析
3612
  gr.update(visible=False), # reset_confirm_row(關掉確認彈窗自己)
3613
  _accordion_update(state, new_log), # exec_log_accordion(收合並更新摘要文字)
3614
  gr.update(visible=True), # homepage_col(Phase 8:重新開始=回到首頁選情境)
 
3621
  reset_confirm_btn.click(
3622
  fn=on_reset_confirm,
3623
  outputs=[chatbot, exec_log, session_state, app_header_html,
3624
+ scenario_btn_col, upload_row, chat_upload, metadata_row,
3625
+ canvas_idx_col, canvas_stat_col, canvas_report_col,
3626
  reset_confirm_row, exec_log_accordion, homepage_col, workspace_row, file_input],
3627
  )
3628