Spaces:
Running
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>
- app_agent.py +262 -241
|
@@ -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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3018 |
-
|
| 3019 |
-
|
| 3020 |
-
|
| 3021 |
-
|
| 3022 |
-
|
| 3023 |
-
|
| 3024 |
-
|
| 3025 |
-
|
| 3026 |
-
|
| 3027 |
-
|
| 3028 |
-
|
| 3029 |
-
|
| 3030 |
-
|
| 3031 |
-
|
| 3032 |
-
|
| 3033 |
-
|
| 3034 |
-
|
| 3035 |
-
|
| 3036 |
-
|
| 3037 |
-
|
| 3038 |
-
|
| 3039 |
-
|
| 3040 |
-
|
| 3041 |
-
|
| 3042 |
-
|
| 3043 |
-
|
| 3044 |
-
|
| 3045 |
-
|
| 3046 |
-
|
| 3047 |
-
|
| 3048 |
-
|
| 3049 |
-
|
| 3050 |
-
|
| 3051 |
-
|
| 3052 |
-
|
| 3053 |
-
|
| 3054 |
-
|
| 3055 |
-
|
| 3056 |
-
|
| 3057 |
-
|
| 3058 |
-
|
| 3059 |
-
|
| 3060 |
-
|
| 3061 |
-
|
| 3062 |
-
|
| 3063 |
-
|
| 3064 |
-
|
| 3065 |
-
|
| 3066 |
-
|
| 3067 |
-
|
| 3068 |
-
|
| 3069 |
-
|
| 3070 |
-
|
| 3071 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3072 |
show_label=False,
|
| 3073 |
-
|
| 3074 |
-
elem_classes=["main-chat"],
|
| 3075 |
)
|
| 3076 |
-
|
| 3077 |
-
|
| 3078 |
-
|
| 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 |
-
|
| 3158 |
-
|
| 3159 |
-
|
| 3160 |
-
datatype=["str"] * 6,
|
| 3161 |
-
col_count=(6, "fixed"),
|
| 3162 |
-
row_count=(1, "dynamic"),
|
| 3163 |
-
interactive=True,
|
| 3164 |
-
label="個體層",
|
| 3165 |
-
)
|
| 3166 |
|
| 3167 |
-
|
| 3168 |
-
|
| 3169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3170 |
)
|
| 3171 |
-
|
| 3172 |
-
|
| 3173 |
-
|
| 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 |
-
|
| 3189 |
-
|
| 3190 |
-
|
| 3191 |
-
|
| 3192 |
-
|
| 3193 |
-
|
| 3194 |
-
tab_idx_df = gr.Dataframe(
|
| 3195 |
-
label="指標計算結果",
|
| 3196 |
-
interactive=False, visible=False,
|
| 3197 |
-
)
|
| 3198 |
|
| 3199 |
-
|
| 3200 |
-
|
| 3201 |
-
|
| 3202 |
-
|
| 3203 |
-
|
| 3204 |
-
|
| 3205 |
-
|
| 3206 |
-
|
| 3207 |
)
|
| 3208 |
-
|
| 3209 |
-
|
| 3210 |
-
|
|
|
|
| 3211 |
)
|
| 3212 |
-
|
| 3213 |
-
|
| 3214 |
-
|
| 3215 |
-
|
| 3216 |
-
|
| 3217 |
-
|
| 3218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3219 |
)
|
| 3220 |
-
|
| 3221 |
-
label="
|
| 3222 |
interactive=False, visible=False,
|
| 3223 |
)
|
| 3224 |
-
|
| 3225 |
-
with gr.
|
| 3226 |
-
|
| 3227 |
-
|
| 3228 |
-
with gr.
|
| 3229 |
-
|
| 3230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3266 |
|
| 3267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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), #
|
| 3590 |
-
gr.update(visible=False), #
|
| 3591 |
-
gr.update(visible=False), #
|
| 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,
|
|
|
|
| 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 |
|