Spaces:
Running
確認框改置中 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>
- app_agent.py +142 -112
- docs/需求彙整-UIUX改版.md +2 -2
|
@@ -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-
|
| 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 |
-
|
| 2958 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2959 |
border-radius: 14px !important;
|
| 2960 |
-
background:
|
| 2961 |
-
|
| 2962 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
/*
|
| 3017 |
-
(水平 12px
|
| 3018 |
-
|
| 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',
|
| 3533 |
report: 'nav_report_btn', exec: 'nav_exec_btn'
|
| 3534 |
};
|
| 3535 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 3552 |
-
var btn = document.getElementById(
|
| 3553 |
-
if (btn) btn.classList.toggle('active',
|
| 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 |
-
|
| 3598 |
-
|
| 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 |
-
/*
|
| 3692 |
-
|
|
|
|
|
|
|
| 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 ==
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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 |
-
#
|
| 4033 |
-
#
|
| 4034 |
-
#
|
| 4035 |
-
#
|
| 4036 |
-
|
| 4037 |
-
|
| 4038 |
-
|
| 4039 |
-
|
| 4040 |
-
|
| 4041 |
-
|
| 4042 |
-
|
| 4043 |
-
|
| 4044 |
-
|
| 4045 |
-
|
| 4046 |
-
|
| 4047 |
-
|
| 4048 |
-
|
| 4049 |
-
|
| 4050 |
-
|
| 4051 |
-
|
| 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 |
-
"下載完整
|
| 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 |
)
|
|
@@ -210,14 +210,14 @@
|
|
| 210 |
| 下載指標數據 | 75 項指標原始數值(工作底稿):csv + xlsx | 指標數據.zip |
|
| 211 |
| 下載統計圖表 | 敘述統計、差異性分析、特徵重要性、分群樹狀圖/手肘圖/PCA 圖、分群結果 | 統計圖表.zip |
|
| 212 |
| 下載研究報告 | 統計技術報告與 LLM 生成研究報告本文(docx) | 研究報告.zip |
|
| 213 |
-
| 下載完整
|
| 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 |
|