un2266 Claude Opus 4.8 commited on
Commit
89ca669
·
1 Parent(s): 35486cb

確認框改置中 modal、移除統計比較分析手風琴、整包下載按鈕改名

Browse files

「開始新分析」確認框(千卉指定):
- 從 nav-col 內嵌卡片改成全螢幕半透明深灰遮罩 + 水平垂直置中的橫式對話框
- 元件搬到 workspace_row 外層是必要的,不是順手:nav-col 在手機寬度是
position:fixed + transform 的抽屜,被 transform 的祖先會讓子孫的
position:fixed 改以該祖先為定位基準,對話框會被關在抽屜裡
- overlay 刻意不設 display:Gradio 的 visible=False 就是靠 display:none
生效,寫 display:flex !important 會把隱藏本身蓋掉。改用「維持 Gradio 給的
flex 排版 + 面板 margin:auto」達成置中
- 深色模式另給 rgba(0,0,0,.62):頁面本身已是深色,同一階深灰疊上去看不出
被壓暗,遮罩形同不存在
- 補上點遮罩關閉與 Esc 關閉(沿用手機抽屜既有的互動慣例)
- 移除 8-E 的「收合狀態下點重置先自動展開 nav-col」變通,該變通存在的理由
(彈窗會擠在 60px 窄欄)已隨 modal 消失

移除「統計比較分析」手風琴(千卉指定:標題文字與收合效果都不要):
- 四個子頁改成跟其他導覽項目平列的一般項目
- 原本那一列兼任 stat 頁的 active 指示器,拿掉後改由使用者實際點的子項目
自己標 active:applyActivePage() 多收 navId 參數,結果記在 data-nav,
讓每 400ms 的 _reapplyActivePage()(不帶 navId)不會把 active 清掉
- 連帶移除 #nav_stat_accordion 與 .result-nav-accordion 整組死 CSS

整包下載按鈕改名「下載完整報告」→「下載完整分析資料」(zip 檔名同步),
原名與「下載研究報告」字面過於接近,分不出哪個才是報告本文。

修掉一個自己剛引入的 bug:Esc 關閉對話框原本用 offsetParent !== null 判斷
可見性,但 position:fixed 元素的 offsetParent 永遠是 null,會判成「沒開」,
Esc 永遠關不到。改讀 computed display。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (2) hide show
  1. app_agent.py +142 -112
  2. docs/需求彙整-UIUX改版.md +2 -2
app_agent.py CHANGED
@@ -1609,7 +1609,7 @@ _ZIP_GROUPS = [
1609
  ("idx", "指標數據"),
1610
  ("stat", "統計圖表"),
1611
  ("report", "研究報告"),
1612
- ("all", "完整分析結果"),
1613
  ]
1614
 
1615
 
@@ -2763,10 +2763,11 @@ img.button-icon { opacity: .82; }
2763
  不一樣)。 */
2764
  .nav-col.collapsed { flex: 0 0 60px !important; max-width: 60px !important; min-width: 60px !important; }
2765
  .nav-col.collapsed .nav-collapse-row { justify-content: center; }
 
 
 
2766
  .nav-col.collapsed .scenario-buttons,
2767
- .nav-col.collapsed .result-nav-accordion,
2768
- .nav-col.collapsed .result-nav-divider,
2769
- .nav-col.collapsed .reset-confirm { display: none !important; }
2770
  /* 選擇器裡的 #result_nav_col 是為了贏過下方 nav-underline 樣式的
2771
  `#result_nav_col .result-nav-btn`(1 個 id + 1 個 class = 1,1,0)。
2772
  兩邊都掛 !important 時由 specificity 決勝,純 class 的
@@ -2780,36 +2781,13 @@ img.button-icon { opacity: .82; }
2780
  }
2781
  .nav-col.collapsed .button-icon { width: 20px !important; height: 20px !important; }
2782
  .nav-col.collapsed .result-nav { padding-top: .8rem !important; }
2783
- .nav-col.collapsed #nav_stat_accordion { display: none !important; }
2784
- }
2785
-
2786
- /* 統計比較分析 Accordion 沒有 icon= 參數,改用 CSS ::before 掛圖示
2787
- (data-URI,同一份 chart-bar 線稿,色值同步 assets/icons/*.svg 的
2788
- #475569/深色模式同樣靠 filter 反相,寫法比照上面 img.button-icon)。 */
2789
- /* justify-content:Gradio 給 .label-wrap 的預設是 space-between(標題文字
2790
- 靠左、開合箭頭靠右)。這裡是三個 flex 子項(::before 圖示、標題文字、
2791
- 箭頭),space-between 會把可用空間平均塞進三者之間,標題文字被推到整
2792
- 列中間,跟旁邊其他導覽項目的文字對不齊。改成 flex-start 讓圖示與文字
2793
- 靠左並排(跟其他項目一致),箭頭再單獨用 margin-left:auto 推到最右。
2794
- gap 對齊其他項目:圖示 18px 之後空 10px 才是文字。 */
2795
- #nav_stat_accordion .label-wrap {
2796
- display: flex !important; align-items: center !important;
2797
- justify-content: flex-start !important; gap: 10px !important;
2798
- }
2799
- #nav_stat_accordion .label-wrap > .icon {
2800
- margin-left: auto !important;
2801
- }
2802
- #nav_stat_accordion .label-wrap::before {
2803
- content: "";
2804
- width: 18px; height: 18px; flex: none;
2805
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' y1='20' x2='20' y2='20'/%3E%3Crect x='5' y='12' width='3' height='8'/%3E%3Crect x='10.5' y='7' width='3' height='13'/%3E%3Crect x='16' y='14' width='3' height='6'/%3E%3C/svg%3E");
2806
- background-size: contain; background-repeat: no-repeat;
2807
- opacity: .82;
2808
- }
2809
- .dark #nav_stat_accordion .label-wrap::before {
2810
- filter: grayscale(1) invert(1) brightness(1.55) contrast(.85);
2811
  }
2812
 
 
 
 
 
 
2813
  /* 手機漢堡開關(mobile_nav_toggle)與抽屜遮罩(nav_backdrop):兩者都是
2814
  放在 workspace_row 外層、一開始就 visible=True 的獨立元件(理由見
2815
  create_app() 該處註解),CSS 預設整個不顯示,只在手機寬度且 nav-col
@@ -2952,14 +2930,50 @@ img.button-icon { opacity: .82; }
2952
  gap: .6rem !important;
2953
  }
2954
  .upload-actions button { border-radius: 10px !important; font-weight: 700 !important; }
2955
- /* 「開始新分析」按鈕與其確認彈窗:換情境/重新開始共用同一套機制 */
2956
  .reset-btn { margin-top: .8rem !important; width: 100% !important; }
2957
- .reset-confirm {
2958
- border: 1px solid rgba(220,38,38,.35) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2959
  border-radius: 14px !important;
2960
- background: rgba(220,38,38,.06) !important;
2961
- padding: 1rem 1.2rem !important;
2962
- margin-top: .6rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2963
  }
2964
  /* Phase 8-D:結果導覽(情境選定後顯示在開始新分析按鈕下方),純文字
2965
  標籤(暫不用圖示,等下一輪視覺打磨補 Heroicons SVG),維持跟
@@ -3013,27 +3027,9 @@ img.button-icon { opacity: .82; }
3013
  圖示、左緣齊平,這組樣式整個不再需要,連同 class 一起移除。
3014
  Gradio secondary 按鈕邊框那個 specificity 坑不會因此消失——它現在由
3015
  上方 `#result_nav_col .result-nav-btn` 那組共用規則一併處理。 */
3016
- /* padding 左右歸零:Gradio 幫 gr.Accordion 的外層 block 掛了 .padded
3017
- (水平 12px)。四個子頁改成一般項目、要跟其他導覽項目左緣齊平之後,
3018
- 這 12px 會讓它們整組往右位移,看起來像還有縮排沒拿乾淨。垂直方向的
3019
- padding 不動,那是手風琴自己的行距。 */
3020
- .result-nav-accordion {
3021
- margin: 0 !important;
3022
- border: none !important;
3023
- background: transparent !important;
3024
- box-shadow: none !important;
3025
- padding-left: 0 !important;
3026
- padding-right: 0 !important;
3027
- }
3028
- /* 手風琴的標題列(統計比較分析)也是一個導覽項目,字重/內距/左緣線
3029
- 都跟 .result-nav-btn 對齊,不然它會是這排裡唯一長得不一樣的一項 */
3030
- .result-nav-accordion .label-wrap {
3031
- padding: .5rem .75rem !important;
3032
- font-size: .88rem !important;
3033
- font-weight: 500 !important;
3034
- border-left: 2px solid transparent !important;
3035
- color: var(--c-text-soft) !important;
3036
- }
3037
  /* 2026-07-23:頁面切換模型——目前使用者所在的那一頁在導覽列高亮,
3038
  applyActivePage() JS 幫對應按鈕加/移除這個 class。視覺先求「看得出
3039
  選中誰」的基本可用,細部樣式(跟 Bootstrap 借來的分組/層級規範)留給
@@ -3044,11 +3040,6 @@ img.button-icon { opacity: .82; }
3044
  color: var(--c-accent-strong) !important;
3045
  font-weight: 700 !important;
3046
  }
3047
- .result-nav-accordion.active .label-wrap {
3048
- border-left-color: var(--c-accent-strong) !important;
3049
- color: var(--c-accent-strong) !important;
3050
- font-weight: 700 !important;
3051
- }
3052
  /* 技術執行紀錄:低優先度診斷工具,跟上面的分析結果用分隔線+降低視覺
3053
  權重(字級與顏色都比其他導覽項目再低一階)區隔開來 */
3054
  .result-nav-divider {
@@ -3528,15 +3519,26 @@ _APP_HEAD_SCRIPT = """
3528
  report: ['canvas_report'],
3529
  exec: ['canvas_exec']
3530
  };
 
 
 
 
 
 
3531
  var _PAGE_NAV_IDS = {
3532
- chat: 'nav_top_btn', idx: 'nav_idx_btn', stat: 'nav_stat_accordion',
3533
  report: 'nav_report_btn', exec: 'nav_exec_btn'
3534
  };
3535
- function applyActivePage(page) {
 
 
3536
  var mainRow = document.querySelector('.main-layout-row');
3537
  if (!mainRow) return;
3538
  mainRow.setAttribute('data-page', page);
3539
  mainRow.classList.toggle('chat-collapsed', page !== 'chat');
 
 
 
3540
  Object.keys(_PAGE_OWNED_IDS).forEach(function (key) {
3541
  _PAGE_OWNED_IDS[key].forEach(function (id) {
3542
  var el = document.getElementById(id);
@@ -3548,9 +3550,9 @@ _APP_HEAD_SCRIPT = """
3548
  }
3549
  });
3550
  });
3551
- Object.keys(_PAGE_NAV_IDS).forEach(function (key) {
3552
- var btn = document.getElementById(_PAGE_NAV_IDS[key]);
3553
- if (btn) btn.classList.toggle('active', key === page);
3554
  });
3555
  }
3556
  function _initActivePageIfNeeded() {
@@ -3594,13 +3596,12 @@ _APP_HEAD_SCRIPT = """
3594
  if (e.target.closest('#nav_exec_btn')) {
3595
  closeMobileDrawerIfOpen(); applyActivePage('exec'); scrollToId('canvas_exec'); return;
3596
  }
3597
- if (e.target.closest('#nav_stat_accordion .label-wrap')) {
3598
- closeMobileDrawerIfOpen(); applyActivePage('stat'); scrollToId('canvas_stat'); return;
3599
- }
3600
  for (var i = 0; i < 4; i++) {
3601
  if (e.target.closest('#nav_stat_sub_' + i)) {
3602
  closeMobileDrawerIfOpen();
3603
- applyActivePage('stat');
3604
  scrollToId('canvas_stat');
3605
  /* 注意:這版 Gradio 的巢狀分頁 DOM 結構是
3606
  .tabs > .tab-wrapper > .tab-container(兩個,
@@ -3688,14 +3689,21 @@ _APP_HEAD_SCRIPT = """
3688
  return;
3689
  }
3690
  if (e.target.closest('#reset_btn')) {
3691
- /* 收合狀態下點重置,先展開 nav-col 再讓 Gradio 原生
3692
- .click() 觸發確認彈窗,避免彈窗擠在 60px 窄欄裡;
 
 
3693
  這裡不 preventDefault/stopPropagation,事件照常
3694
- 冒泡給 Gradio。 */
3695
- var navCol2 = document.querySelector('.nav-col');
3696
- if (navCol2 && navCol2.classList.contains('collapsed')) setNavCollapsed(false);
3697
  closeMobileDrawerIfOpen();
3698
  }
 
 
 
 
 
 
 
3699
  if (e.target.closest('#reset_confirm_btn')) {
3700
  /* 2026-07-23:真的按下「確定重新開始」才重置頁面切換
3701
  狀態回 chat(不能在 #reset_btn 那支就做,那個只是
@@ -3708,7 +3716,20 @@ _APP_HEAD_SCRIPT = """
3708
  }
3709
  });
3710
  document.addEventListener('keydown', function (e) {
3711
- if (e.key === 'Escape') closeMobileDrawerIfOpen();
 
 
 
 
 
 
 
 
 
 
 
 
 
3712
  });
3713
  }
3714
  bindNavControls();
@@ -3961,6 +3982,25 @@ def create_app():
3961
  elem_id="nav_backdrop_wrap",
3962
  )
3963
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3964
  # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
3965
  # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/
3966
  # 執行紀錄,獨立捲動)。手機寬度見 CSS 的 media query 改上下堆疊。
@@ -4000,20 +4040,13 @@ def create_app():
4000
  "開始新分析", icon=str(BASE_DIR / "assets" / "icons" / "plus.svg"),
4001
  elem_id="reset_btn", elem_classes=["reset-btn"], variant="secondary",
4002
  )
4003
- with gr.Column(visible=False, elem_classes=["reset-confirm"]) as reset_confirm_row:
4004
- gr.Markdown("確定要開始新分析嗎?目前進度將會遺失。")
4005
- with gr.Row(elem_classes=["upload-actions"]):
4006
- reset_cancel_btn = gr.Button("取消", scale=1, variant="secondary")
4007
- reset_confirm_btn = gr.Button("確定重新開始", elem_id="reset_confirm_btn", scale=2, variant="primary")
4008
-
4009
  # Phase 8-D:結果導覽——情境選定後顯示,讓使用者不用手動
4010
  # 往下捲一大段就能跳到 Canvas 對應區塊。這幾顆按鈕刻意不接
4011
  # Python .click():純粹是「捲動+切換巢狀子分頁」的畫面
4012
  # 操作,不牽動任何後端狀態,改用跟首頁情境卡片同一套手法
4013
  # (_APP_HEAD_SCRIPT 的 JS 監聽),完全不用走 Gradio queue
4014
- # 來回,反應更快。下載捷徑/「下載完整報告」假按鈕留到
4015
- # 下一輪視覺打磨(Heroicons 圖示)一起補,這次先求導覽功能
4016
- # 正確可用。
4017
  with gr.Column(visible=False, elem_id="result_nav_col", elem_classes=["result-nav"]) as result_nav_col:
4018
  # 2026-07-23 改名「AI對話」+改變功能定位:原本純粹是
4019
  # 「捲回 Canvas 最上面」的畫面操作,現在身兼「切回流程頁」
@@ -4029,29 +4062,26 @@ def create_app():
4029
  "語言指標分析", icon=str(BASE_DIR / "assets" / "icons" / "trending-up.svg"),
4030
  elem_id="nav_idx_btn", elem_classes=["result-nav-btn"], variant="secondary",
4031
  )
4032
- # open=True(千卉 2026-07-23):收合狀態下這一項跟旁邊四個
4033
- # 主項目長得一樣,看不出底下還有四個子頁面,使用者不會知道
4034
- # 要去點開。導覽列的作用是把可去的地方攤開來給人看,預設收
4035
- # 起來等於把內容藏住。仍然保留可收合,只是不再預設收起。
4036
- with gr.Accordion("統計比較分析", open=True, elem_id="nav_stat_accordion",
4037
- elem_classes=["result-nav-accordion"]):
4038
- # 2026-07-23 千卉決定:這四頁改用跟其他導覽項目完全
4039
- # 一樣的樣式(elem_classes 從 result-nav-subbtn 換成
4040
- # result-nav-btn)並各自配圖示,縮排也拿掉、左緣齊平。
4041
- # 原本的「小一號字+灰一階+真縮排」子選單樣式(Phase 3
4042
- # 借 Bootstrap Sidebars 的那套)連同 CSS 一併移除。
4043
- nav_stat_sub_0 = gr.Button(
4044
- "敘述統計", icon=str(BASE_DIR / "assets" / "icons" / "table-cells.svg"),
4045
- elem_id="nav_stat_sub_0", elem_classes=["result-nav-btn"], variant="secondary")
4046
- nav_stat_sub_1 = gr.Button(
4047
- "差異性分析", icon=str(BASE_DIR / "assets" / "icons" / "scale.svg"),
4048
- elem_id="nav_stat_sub_1", elem_classes=["result-nav-btn"], variant="secondary")
4049
- nav_stat_sub_2 = gr.Button(
4050
- "監督式學習", icon=str(BASE_DIR / "assets" / "icons" / "academic-cap.svg"),
4051
- elem_id="nav_stat_sub_2", elem_classes=["result-nav-btn"], variant="secondary")
4052
- nav_stat_sub_3 = gr.Button(
4053
- "非監督式學習", icon=str(BASE_DIR / "assets" / "icons" / "squares-2x2.svg"),
4054
- elem_id="nav_stat_sub_3", elem_classes=["result-nav-btn"], variant="secondary")
4055
  nav_report_btn = gr.Button(
4056
  "LLM輔助分析", icon=str(BASE_DIR / "assets" / "icons" / "document.svg"),
4057
  elem_id="nav_report_btn", elem_classes=["result-nav-btn"], variant="secondary",
@@ -4080,7 +4110,7 @@ def create_app():
4080
  elem_classes=["result-nav-btn", "result-nav-dl"], variant="secondary",
4081
  )
4082
  nav_dl_all_btn = gr.DownloadButton(
4083
- "下載完整報告", icon=str(BASE_DIR / "assets" / "icons" / "download.svg"),
4084
  visible=False, elem_id="nav_dl_all_btn",
4085
  elem_classes=["result-nav-btn", "result-nav-dl"], variant="secondary",
4086
  )
 
1609
  ("idx", "指標數據"),
1610
  ("stat", "統計圖表"),
1611
  ("report", "研究報告"),
1612
+ ("all", "完整分析資料"),
1613
  ]
1614
 
1615
 
 
2763
  不一樣)。 */
2764
  .nav-col.collapsed { flex: 0 0 60px !important; max-width: 60px !important; min-width: 60px !important; }
2765
  .nav-col.collapsed .nav-collapse-row { justify-content: center; }
2766
+ /* .reset-confirm 已於 2026-07-24 搬出 nav-col 改成全螢幕 modal,
2767
+ 不再需要在收合狀態下隱藏;.result-nav-accordion 同日隨「統計比較
2768
+ 分析」手風琴一起移除。 */
2769
  .nav-col.collapsed .scenario-buttons,
2770
+ .nav-col.collapsed .result-nav-divider { display: none !important; }
 
 
2771
  /* 選擇器裡的 #result_nav_col 是為了贏過下方 nav-underline 樣式的
2772
  `#result_nav_col .result-nav-btn`(1 個 id + 1 個 class = 1,1,0)。
2773
  兩邊都掛 !important 時由 specificity 決勝,純 class 的
 
2781
  }
2782
  .nav-col.collapsed .button-icon { width: 20px !important; height: 20px !important; }
2783
  .nav-col.collapsed .result-nav { padding-top: .8rem !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2784
  }
2785
 
2786
+ /* 2026-07-24:這裡原本有一整組 #nav_stat_accordion 樣式(CSS ::before 掛
2787
+ chart-bar 圖示、label-wrap 的 justify-content 修正、深色模式 filter),
2788
+ 隨「統計比較分析」Accordion 一起移除。四個子頁現在是平列的一般導覽項目,
2789
+ 圖示走 gr.Button(icon=) 那條路,不需要這組特例。 */
2790
+
2791
  /* 手機漢堡開關(mobile_nav_toggle)與抽屜遮罩(nav_backdrop):兩者都是
2792
  放在 workspace_row 外層、一開始就 visible=True 的獨立元件(理由見
2793
  create_app() 該處註解),CSS 預設整個不顯示,只在手機寬度且 nav-col
 
2930
  gap: .6rem !important;
2931
  }
2932
  .upload-actions button { border-radius: 10px !important; font-weight: 700 !important; }
2933
+ /* 「開始新分析」按鈕與其確認對話框:換情境/重新開始共用同一套機制 */
2934
  .reset-btn { margin-top: .8rem !important; width: 100% !important; }
2935
+ /* 2026-07-24:確認框從 nav-col 內嵌卡片改成置中 modal。
2936
+ 刻意不對 overlay 設 display——Gradio 的 visible=False 就是靠 display:none
2937
+ 生效,這裡若寫 display:flex !important 會把「隱藏」本身蓋掉,對話框變成
2938
+ 永遠開著。改用「維持 Gradio 給的 flex 排版 + 面板自己 margin:auto」達成
2939
+ 水平垂直置中,完全不碰 display 這個屬性。 */
2940
+ .reset-confirm-overlay {
2941
+ position: fixed !important;
2942
+ inset: 0 !important;
2943
+ z-index: 120 !important;
2944
+ padding: 1.5rem !important;
2945
+ background: rgba(15, 23, 42, .55) !important;
2946
+ backdrop-filter: blur(2px);
2947
+ }
2948
+ .reset-confirm-panel {
2949
+ margin: auto !important; /* flex 容器內上下左右皆置中 */
2950
+ width: 100% !important;
2951
+ max-width: 400px !important;
2952
+ flex: none !important; /* 不被 overlay 的 flex 拉伸成滿版高 */
2953
+ gap: 1.2rem !important;
2954
+ padding: 1.5rem !important;
2955
+ border: 1px solid var(--c-border) !important;
2956
  border-radius: 14px !important;
2957
+ background: var(--c-surface) !important;
2958
+ box-shadow: 0 20px 50px rgba(15, 23, 42, .3) !important;
2959
+ }
2960
+ /* 深色模式:頁面本身已經是深色(--c-surface #0d1526),同一階深灰疊上去
2961
+ 幾乎看不出後面被壓暗,遮罩形同不存在。改用更黑的值才有「後面那層被關掉」
2962
+ 的分離感。兩種寫法都給:.dark 是 Gradio 切主題時掛的 class,媒體查詢則
2963
+ 對應本檔既有的 prefers-color-scheme token 機制(見 _DARK_MODE_HEAD_CSS)。 */
2964
+ .dark .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
2965
+ @media (prefers-color-scheme: dark) {
2966
+ :root:not(.light) .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
2967
+ }
2968
+ .reset-confirm-panel .reset-confirm-text p { margin: 0 !important; }
2969
+ /* 橫式:兩顆按鈕同寬並排,窄視窗也不換行 */
2970
+ .reset-confirm-actions {
2971
+ flex-wrap: nowrap !important;
2972
+ gap: .7rem !important;
2973
+ }
2974
+ .reset-confirm-actions button {
2975
+ border-radius: 10px !important;
2976
+ font-weight: 700 !important;
2977
  }
2978
  /* Phase 8-D:結果導覽(情境選定後顯示在開始新分析按鈕下方),純文字
2979
  標籤(暫不用圖示,等下一輪視覺打磨補 Heroicons SVG),維持跟
 
3027
  圖示、左緣齊平,這組樣式整個不再需要,連同 class 一起移除。
3028
  Gradio secondary 按鈕邊框那個 specificity 坑不會因此消失——它現在由
3029
  上方 `#result_nav_col .result-nav-btn` 那組共用規則一併處理。 */
3030
+ /* 2026-07-24:這裡原本有 .result-nav-accordion 與其 .label-wrap 的樣式
3031
+ (拿掉 Gradio .padded 的水平 12px、標題列對齊其他導覽項目),隨
3032
+ 「統計比較分析」Accordion 一起移除。 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3033
  /* 2026-07-23:頁面切換模型——目前使用者所在的那一頁在導覽列高亮,
3034
  applyActivePage() JS 幫對應按鈕加/移除這個 class。視覺先求「看得出
3035
  選中誰」的基本可用,細部樣式(跟 Bootstrap 借來的分組/層級規範)留給
 
3040
  color: var(--c-accent-strong) !important;
3041
  font-weight: 700 !important;
3042
  }
 
 
 
 
 
3043
  /* 技術執行紀錄:低優先度診斷工具,跟上面的分析結果用分隔線+降低視覺
3044
  權重(字級與顏色都比其他導覽項目再低一階)區隔開來 */
3045
  .result-nav-divider {
 
3519
  report: ['canvas_report'],
3520
  exec: ['canvas_exec']
3521
  };
3522
+ /* stat 頁沒有自己的導覽列項目——2026-07-24 拿掉「統計比較分析」
3523
+ 那層 Accordion 後,四個子頁是平列的獨立項目,active 要落在使用者
3524
+ 實際點的那一個,不能靠 page 名稱反查。因此 applyActivePage() 多收
3525
+ 一個 navId 參數,並把結果記在 .main-layout-row 的 data-nav 上:
3526
+ 每 400ms 跑一次的 _reapplyActivePage() 不帶 navId,靠這個記錄才
3527
+ 不會每個 tick 把 active 清掉。 */
3528
  var _PAGE_NAV_IDS = {
3529
+ chat: 'nav_top_btn', idx: 'nav_idx_btn',
3530
  report: 'nav_report_btn', exec: 'nav_exec_btn'
3531
  };
3532
+ var _ALL_NAV_IDS = ['nav_top_btn', 'nav_idx_btn', 'nav_report_btn', 'nav_exec_btn',
3533
+ 'nav_stat_sub_0', 'nav_stat_sub_1', 'nav_stat_sub_2', 'nav_stat_sub_3'];
3534
+ function applyActivePage(page, navId) {
3535
  var mainRow = document.querySelector('.main-layout-row');
3536
  if (!mainRow) return;
3537
  mainRow.setAttribute('data-page', page);
3538
  mainRow.classList.toggle('chat-collapsed', page !== 'chat');
3539
+ if (navId) mainRow.setAttribute('data-nav', navId);
3540
+ else if (_PAGE_NAV_IDS[page]) mainRow.setAttribute('data-nav', _PAGE_NAV_IDS[page]);
3541
+ var activeId = mainRow.getAttribute('data-nav');
3542
  Object.keys(_PAGE_OWNED_IDS).forEach(function (key) {
3543
  _PAGE_OWNED_IDS[key].forEach(function (id) {
3544
  var el = document.getElementById(id);
 
3550
  }
3551
  });
3552
  });
3553
+ _ALL_NAV_IDS.forEach(function (id) {
3554
+ var btn = document.getElementById(id);
3555
+ if (btn) btn.classList.toggle('active', id === activeId);
3556
  });
3557
  }
3558
  function _initActivePageIfNeeded() {
 
3596
  if (e.target.closest('#nav_exec_btn')) {
3597
  closeMobileDrawerIfOpen(); applyActivePage('exec'); scrollToId('canvas_exec'); return;
3598
  }
3599
+ /* 2026-07-24:原本這裡還有一支處理「統計比較分析」Accordion
3600
+ 標題列點擊的分支,該層已移除,四個子項目自己就是入口。 */
 
3601
  for (var i = 0; i < 4; i++) {
3602
  if (e.target.closest('#nav_stat_sub_' + i)) {
3603
  closeMobileDrawerIfOpen();
3604
+ applyActivePage('stat', 'nav_stat_sub_' + i);
3605
  scrollToId('canvas_stat');
3606
  /* 注意:這版 Gradio 的巢狀分頁 DOM 結構是
3607
  .tabs > .tab-wrapper > .tab-container(兩個,
 
3689
  return;
3690
  }
3691
  if (e.target.closest('#reset_btn')) {
3692
+ /* 2026-07-24:確認框改成全螢幕置中 modal 後,原本
3693
+ 「收合狀態下先自動展開 nav-col,免得彈窗擠在 60px
3694
+ 窄欄裡」的變通已無必要,移除;手機抽屜仍要收起,
3695
+ 否則對話框會被抽屜遮住一半。
3696
  這裡不 preventDefault/stopPropagation,事件照常
3697
+ 冒泡給 Gradio 原生 .click()。 */
 
 
3698
  closeMobileDrawerIfOpen();
3699
  }
3700
+ /* 點遮罩空白處=取消(面板本身的點擊不算),沿用抽屜遮罩
3701
+ 同一套互動慣例 */
3702
+ if (e.target.id === 'reset_confirm_overlay') {
3703
+ var cancelBtn = document.getElementById('reset_cancel_btn');
3704
+ if (cancelBtn) cancelBtn.click();
3705
+ return;
3706
+ }
3707
  if (e.target.closest('#reset_confirm_btn')) {
3708
  /* 2026-07-23:真的按下「確定重新開始」才重置頁面切換
3709
  狀態回 chat(不能在 #reset_btn 那支就做,那個只是
 
3716
  }
3717
  });
3718
  document.addEventListener('keydown', function (e) {
3719
+ if (e.key !== 'Escape') return;
3720
+ /* 對話框開著時 Esc 優先關對話框(modal 慣例:只關最上層的
3721
+ 那一個),沒開著才輪到手機抽屜 */
3722
+ /* 不能用 offsetParent !== null 判斷可見:position:fixed 的
3723
+ 元素 offsetParent 永遠是 null,會判成「沒開」,Esc 就永遠
3724
+ 關不到對話框(改 modal 前這個寫法是對的,改成 fixed 之後
3725
+ 就不成立了)。改讀 computed display。 */
3726
+ var overlay = document.getElementById('reset_confirm_overlay');
3727
+ if (overlay && getComputedStyle(overlay).display !== 'none') {
3728
+ var cancelBtn = document.getElementById('reset_cancel_btn');
3729
+ if (cancelBtn) cancelBtn.click();
3730
+ return;
3731
+ }
3732
+ closeMobileDrawerIfOpen();
3733
  });
3734
  }
3735
  bindNavControls();
 
3982
  elem_id="nav_backdrop_wrap",
3983
  )
3984
 
3985
+ # 「開始新分析」確認對話框(2026-07-24 改成置中 modal):原本是
3986
+ # nav-col 裡的一張內嵌卡片,擠在 220px 窄欄裡,收合成 60px icon bar
3987
+ # 時更不成人形(8-E 當時還為此加了「收合狀態下點重置先自動展開
3988
+ # nav-col」的變通,改成 modal 後那個理由消失,該變通已一併移除)。
3989
+ # 放在 workspace_row 外層是必要的,不是順手:nav-col 在手機寬度是
3990
+ # position:fixed+transform 的抽屜,而被 transform 的祖先會讓子孫的
3991
+ # position:fixed 改以該祖先為定位基準,對話框會被關在抽屜裡而不是
3992
+ # 覆蓋整個視窗。
3993
+ with gr.Column(visible=False, elem_id="reset_confirm_overlay",
3994
+ elem_classes=["reset-confirm-overlay"]) as reset_confirm_row:
3995
+ with gr.Column(elem_id="reset_confirm_panel", elem_classes=["reset-confirm-panel"]):
3996
+ gr.Markdown("確定要開始新分析嗎?目前進度將會遺失。",
3997
+ elem_classes=["reset-confirm-text"])
3998
+ with gr.Row(elem_classes=["reset-confirm-actions"]):
3999
+ reset_cancel_btn = gr.Button("取消", elem_id="reset_cancel_btn",
4000
+ scale=1, variant="secondary")
4001
+ reset_confirm_btn = gr.Button("確定重新開始", elem_id="reset_confirm_btn",
4002
+ scale=1, variant="primary")
4003
+
4004
  # Phase 8-B:三欄式工作區——nav-col(窄,導覽/重置)+chat-col
4005
  # (對話視窗+輸入框,獨立捲動)+canvas-col(步驟列+上傳/表單/
4006
  # 執行紀錄,獨立捲動)。手機寬度見 CSS 的 media query 改上下堆疊。
 
4040
  "開始新分析", icon=str(BASE_DIR / "assets" / "icons" / "plus.svg"),
4041
  elem_id="reset_btn", elem_classes=["reset-btn"], variant="secondary",
4042
  )
 
 
 
 
 
 
4043
  # Phase 8-D:結果導覽——情境選定後顯示,讓使用者不用手動
4044
  # 往下捲一大段就能跳到 Canvas 對應區塊。這幾顆按鈕刻意不接
4045
  # Python .click():純粹是「捲動+切換巢狀子分頁」的畫面
4046
  # 操作,不牽動任何後端狀態,改用跟首頁情境卡片同一套手法
4047
  # (_APP_HEAD_SCRIPT 的 JS 監聽),完全不用走 Gradio queue
4048
+ # 來回,反應更快。(下載捷徑當初規劃留到下一輪補,已於
4049
+ # 2026-07-24 補齊,見下方 nav_dl_* 那四顆。)
 
4050
  with gr.Column(visible=False, elem_id="result_nav_col", elem_classes=["result-nav"]) as result_nav_col:
4051
  # 2026-07-23 改名「AI對話」+改變功能定位:原本純粹是
4052
  # 「捲回 Canvas 最上面」的畫面操作,現在身兼「切回流程頁」
 
4062
  "語言指標分析", icon=str(BASE_DIR / "assets" / "icons" / "trending-up.svg"),
4063
  elem_id="nav_idx_btn", elem_classes=["result-nav-btn"], variant="secondary",
4064
  )
4065
+ # 2026-07-24 千卉決定:拿掉「統計比較分析」這層 Accordion
4066
+ # (標題文字與收合效果都不要),四個子頁直接跟其他導覽項目
4067
+ # 平列。它原本存在的理由(2026-07-23 改 open=True 時的說法)
4068
+ # 是「讓人看得出底下還有四個子頁面」,但四頁攤開後這層標題
4069
+ # 只剩下多一列文字跟一個不會有人去點的箭頭。
4070
+ # 副作用:原本「統計比較分析」那一列兼任 stat 頁的 active
4071
+ # 指示器,拿掉後改由使用者實際點的那個子項目自己標 active,
4072
+ # 見 _APP_HEAD_SCRIPT 的 applyActivePage()。
4073
+ nav_stat_sub_0 = gr.Button(
4074
+ "敘述統計", icon=str(BASE_DIR / "assets" / "icons" / "table-cells.svg"),
4075
+ elem_id="nav_stat_sub_0", elem_classes=["result-nav-btn"], variant="secondary")
4076
+ nav_stat_sub_1 = gr.Button(
4077
+ "差異性分析", icon=str(BASE_DIR / "assets" / "icons" / "scale.svg"),
4078
+ elem_id="nav_stat_sub_1", elem_classes=["result-nav-btn"], variant="secondary")
4079
+ nav_stat_sub_2 = gr.Button(
4080
+ "監督式學習", icon=str(BASE_DIR / "assets" / "icons" / "academic-cap.svg"),
4081
+ elem_id="nav_stat_sub_2", elem_classes=["result-nav-btn"], variant="secondary")
4082
+ nav_stat_sub_3 = gr.Button(
4083
+ "非監督式學習", icon=str(BASE_DIR / "assets" / "icons" / "squares-2x2.svg"),
4084
+ elem_id="nav_stat_sub_3", elem_classes=["result-nav-btn"], variant="secondary")
 
 
 
4085
  nav_report_btn = gr.Button(
4086
  "LLM輔助分析", icon=str(BASE_DIR / "assets" / "icons" / "document.svg"),
4087
  elem_id="nav_report_btn", elem_classes=["result-nav-btn"], variant="secondary",
 
4110
  elem_classes=["result-nav-btn", "result-nav-dl"], variant="secondary",
4111
  )
4112
  nav_dl_all_btn = gr.DownloadButton(
4113
+ "下載完整分析資料", icon=str(BASE_DIR / "assets" / "icons" / "download.svg"),
4114
  visible=False, elem_id="nav_dl_all_btn",
4115
  elem_classes=["result-nav-btn", "result-nav-dl"], variant="secondary",
4116
  )
docs/需求彙整-UIUX改版.md CHANGED
@@ -210,14 +210,14 @@
210
  | 下載指標數據 | 75 項指標原始數值(工作底稿):csv + xlsx | 指標數據.zip |
211
  | 下載統計圖表 | 敘述統計、差異性分析、特徵重要性、分群樹狀圖/手肘圖/PCA 圖、分群結果 | 統計圖表.zip |
212
  | 下載研究報告 | 統計技術報告與 LLM 生成研究報告本文(docx) | 研究報告.zip |
213
- | 下載完整報告 | 以上全部,去重後打成一包 | 完整分析結果.zip |
214
 
215
  規則:
216
 
217
  - 內容為空的那一包不會產生,對應按鈕就不出現。例如 LLM 報告產製失敗時,「下載研究報告」不會亮出來
218
  - 只有一組有內容時不另外產生「完整分析結果」,避免兩顆按鈕內容一模一樣讓使用者猶豫該點哪個
219
  - zip 內容與各結果頁提供的下載檔案完全一致(含檔名改名規則),不含僅供畫面顯示的中間產物
220
- - ⚠️「下載研究報告」與「下載完整報告」字面接近,非技術使用者可能分不清哪個才是報告本文,命名待實機檢視後確認
221
 
222
  ## 十、Footer/設定/API 處理
223
 
 
210
  | 下載指標數據 | 75 項指標原始數值(工作底稿):csv + xlsx | 指標數據.zip |
211
  | 下載統計圖表 | 敘述統計、差異性分析、特徵重要性、分群樹狀圖/手肘圖/PCA 圖、分群結果 | 統計圖表.zip |
212
  | 下載研究報告 | 統計技術報告與 LLM 生成研究報告本文(docx) | 研究報告.zip |
213
+ | 下載完整分析資料 | 以上全部,去重後打成一包 | 完整分析資料.zip |
214
 
215
  規則:
216
 
217
  - 內容為空的那一包不會產生,對應按鈕就不出現。例如 LLM 報告產製失敗時,「下載研究報告」不會亮出來
218
  - 只有一組有內容時不另外產生「完整分析結果」,避免兩顆按鈕內容一模一樣讓使用者猶豫該點哪個
219
  - zip 內容與各結果頁提供的下載檔案完全一致(含檔名改名規則),不含僅供畫面顯示的中間產物
220
+ - 命名沿革:整包那顆原先叫「下載完整報告」,與「下載研究報告」字面過於接近,非技術使用者分不清哪個才是報告本文,2026-07-24 實機檢視後改為「下載完整分析資料」
221
 
222
  ## 十、Footer/設定/API 處理
223