File size: 12,350 Bytes
2dcae4b
1b65622
a024ea5
 
1b65622
 
2dcae4b
 
 
 
 
 
 
 
a024ea5
2dcae4b
a024ea5
 
 
2dcae4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f036fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b0b8733
1f036fb
 
 
b0b8733
 
1f036fb
b0b8733
1f036fb
8fe26ab
 
 
 
 
 
 
 
 
 
 
 
 
 
2dcae4b
1f036fb
 
 
f72f5ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2dcae4b
a024ea5
 
 
ef3829e
a024ea5
2dcae4b
a024ea5
 
8fe26ab
 
 
 
ebf8ad6
8f70f76
ebf8ad6
2dcae4b
ebf8ad6
 
 
2dcae4b
ebf8ad6
 
a024ea5
ebf8ad6
 
 
 
a024ea5
ebf8ad6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f036fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b0b8733
1f036fb
 
 
 
 
 
 
a024ea5
ebf8ad6
 
1f036fb
 
 
ebf8ad6
db91bf7
ebf8ad6
 
 
2dcae4b
ebf8ad6
 
1f036fb
 
 
ebf8ad6
 
a024ea5
2dcae4b
 
 
 
 
 
 
 
 
 
 
d584c36
1b65622
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# ui/layouts.py (最終完整版)
import gradio as gr
from config import content, data, defaults
from core import callbacks

def create_ui(visit_count_html: str, theme: gr.Theme):
    """
    Creates and returns the Gradio UI Blocks.
    
    Args:
        visit_count_html: The Markdown string to display the visit count.
        theme: The Gradio theme object to apply to the UI.
    """
    
    with gr.Blocks(theme=theme, title="地球物理學") as demo:
        # --- 自訂頂部 Header ---
        gr.Markdown(
            """
            <style>
            .custom-header {
                background: linear-gradient(135deg, #005f73, #0a9396);
                color: #fff;
                padding: 4rem 2rem;
                text-align: center;
                border-radius: 0 0 20px 20px;
                margin-bottom: 2rem;
                box-shadow: 0 8px 15px rgba(0,0,0,0.1);
            }
            .custom-header h1 {
                font-size: 3.5rem;
                font-weight: 700;
                margin-bottom: 0.8rem;
                line-height: 1.2;
            }
            .custom-header p {
                font-size: 1.6rem;
                font-weight: 400;
                opacity: 0.9;
                margin-top: 0;
            }
            /* Style for instruction cards */
            .instruction-card {
                background-color: #ffffff;
                border-left: 5px solid #0a9396;
                padding: 20px 25px;
                margin: 20px 0;
                border-radius: 8px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.08);
            }
            .instruction-card h3 {
                margin-top: 0;
                color: #005f73;
                font-size: 1.5rem;
            }
            .instruction-card p, .instruction-card ul {
                font-size: 1.1rem;
                color: #34495e;
                line-height: 1.8;
            }
            .instruction-card code {
                background-color: #e9ecef;
                padding: 4px 8px;
                border-radius: 5px;
                font-size: 1rem;
                color: #c7254e;
            }
            /* Style for the homepage image */
            .homepage-image-container {
                margin-top: 1rem; /* 圖片與上方 Header 的間距 */
                margin-bottom: 2rem; /* 圖片與下方 Tabs 的間距 */
                border-radius: 12px;
                overflow: hidden; /* 確保圖片圓角 */
                box-shadow: 0 6px 20px rgba(0,0,0,0.2);
            }
            .homepage-image-container img {
                width: 100%;
                height: auto;
                display: block;
            }

            @media (max-width: 768px) {
                .custom-header { padding: 3rem 1rem; border-radius: 0; }
                .custom-header h1 { font-size: 2.5rem; }
                .custom-header p { font-size: 1.2rem; }

                /* ✨ 為手機顯示調整 Tabs 樣式,強制換行並平均分佈 */
                /* 目標 Gradio 的 Tab 容器類別為 .gradio-tabs */
                .gradio-tabs .tabs-nav {
                    display: flex; /* 使用 flexbox 佈局 */
                    flex-wrap: wrap; /* 允許項目換行 */
                    justify-content: space-around; /* 均勻分佈項目 */
                    gap: 5px; /* 項目間距 */
                    padding: 5px; /* 內邊距 */
                }

                /* 目標 Gradio 的單個 Tab 項目類別為 .gradio-tabs > button */
                .gradio-tabs .tabs-nav > button {
                    flex: 1 1 auto; /* 允許項目彈性增長和縮小,並有基礎寬度 */
                    min-width: calc(33% - 10px); /* 每行顯示3個,考慮到 gap */
                    max-width: calc(50% - 10px); /* 小螢幕時至少顯示2個 */
                    box-sizing: border-box; /* 確保 padding 和 border 包含在寬度內 */
                    text-align: center;
                    white-space: normal; /* 允許文字換行 */
                    padding: 8px 5px; /* 調整 padding */
                    font-size: 0.85rem; /* 調整字體大小 */
                }
            }
            </style>
            <div class="custom-header">
                <h1>地球物理概論</h1>
                <p>在這裡,您不僅能學習理論,更能透過「互動實驗室」親手編寫程式碼、視覺化地震資料,並隨時向「AI 助教」提問,獲取課程解答與即時的全球地震資訊。立即開始您的地球物理探索之旅吧!</p>
            </div>
            """
        )

        with gr.Row(elem_classes="homepage-image-container"):
            gr.Image(value="homepage_image.png", show_label=False, interactive=False, container=False)

        with gr.Tabs():
        
            with gr.TabItem("🎯 課程目標", id="goals_tab"):
                gr.Markdown(content.course_goals_md)

            with gr.TabItem("🗓️ 課程進度"):
                gr.Markdown("### 每週課程安排")
                gr.DataFrame(data.schedule_df, wrap=True)

            with gr.TabItem("💯 成績計算"):
                gr.Markdown(content.grading_policy_md)

            with gr.TabItem("🚀 互動體驗區 (程式碼實驗室)"):
                gr.Markdown("## 🚀 互動程式碼實驗室")
                gr.Markdown("歡迎來到這裡!直接修改下方的 Python 程式碼,點擊「執行」,即可在右側看到成果。這是學習程式與地球物理最直接的方式!")
                gr.Info("注意:執行環境已受限,僅支援資料視覺化相關操作。請勿嘗試檔案讀寫或網路請求。")

                with gr.Accordion("🌍 地圖繪製實驗室 (PyGMT/Cartopy 概念)", open=True):
                    with gr.Row():
                        with gr.Column(scale=2):
                            gr.Markdown("### 說明\n這段程式碼使用 `cartopy` 和 `matplotlib` 函式庫來繪製地理地圖。\n\n**您可以試著:**\n1. 修改 `center_lon`, `center_lat` 來改變地圖中心。\n2. 調整 `extent_lon`, `extent_lat` 來縮放地圖。\n3. 將 `coastline_color` 改成 'red' 或其他顏色。\n4. **在 `symbols` 列表中新增或修改字典,來繪製自訂的符號(例如:標示您所在的城市)。**")
                            map_code = gr.Code(label="可編輯的 Python 程式碼", value=defaults.DEFAULT_MAP_CODE, language="python", lines=25)
                            map_run_button = gr.Button("執行程式碼", variant="primary")
                        with gr.Column(scale=3):
                            map_plot_output = gr.Plot(label="地圖輸出")
                            map_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
                
                with gr.Accordion("📈 震波圖繪製實驗室 (ObsPy 概念)", open=False):
                    with gr.Row():
                        with gr.Column(scale=2):
                            gr.Markdown("### 說明\n這段程式碼使用 `numpy` 產生模擬的地震波數據,並用 `matplotlib` 將其視覺化。\n\n**您可以試著:**\n1. 修改 `p_wave_arrival` 和 `s_wave_arrival` 來改變 P/S 波的抵達時間。\n2. 調整 `main_freq` 來改變地震波的頻率(數值越大,波形越密集)。\n3. 將 `decay_rate` 調小,觀察振幅衰減變慢的效果。")
                            seismo_code = gr.Code(label="可編輯的 Python 程式碼", value=defaults.DEFAULT_SEISMO_CODE, language="python", lines=25)
                            seismo_run_button = gr.Button("執行程式碼", variant="primary")
                        with gr.Column(scale=3):
                            seismo_plot_output = gr.Plot(label="震波圖輸出")
                            seismo_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
            
            with gr.TabItem("❓ AI 助教說明"):
                gr.Markdown("## 🤖 AI 課程助教使用指南")
                gr.Markdown("歡迎使用課程 AI 助教!我整合了課程知識庫與多種即時資訊來源,您可以透過多種方式與我互動。")
                
                with gr.Blocks():
                    gr.Markdown(
                        """
                        <div class="instruction-card">
                            <h3>📚 課程知識庫</h3>
                            <p>我可以回答關於本課程的各種靜態問題。您可以隨意提問,例如:</p>
                            <ul>
                                <li><code>這門課的評分標準是什麼?</code></li>
                                <li><code>Anaconda 和 Colab 有什麼不同?</code></li>
                                <li><code>如何安裝 anaconda?</code></li>
                            </ul>
                        </div>
                        
                        <div class="instruction-card">
                            <h3>⚡ 即時資訊查詢</h3>
                            <p>我能幫您查詢最新的動態資訊,您可以直接輸入下方的<b>快捷指令</b>或對應的<b>文字問題</b>:</p>
                            <ul>
                                <li>快捷指令 <code>#1</code> 或輸入 <code>今天有什麼重要新聞?</code></li>
                                <li>快捷指令 <code>#2</code> 或輸入 <code>最新的CWA顯著地震報告</code></li>
                                <li>快捷指令 <code>#3</code> 或輸入 <code>現在有地震預警嗎?</code></li>
                                <li>快捷指令 <code>#4</code> 或輸入 <code>最近全球有哪些大地震?</code></li>
                                <li>快捷指令 <code>#5</code> 或輸入 <code>pws info</code></li>
                                <li>快捷指令 <code>#6</code> 或輸入 <code>最新的pws地震警報</code></li>
                            </ul>
                        </div>

                        <div class="instruction-card">
                            <h3>🔎 進階地震查詢</h3>
                            <p>您可以指定<b>日期範圍</b>與<b>規模</b>來查詢全球地震紀錄。輸入快捷指令 <code>#7</code> 可以查看格式說明,或直接依照以下格式提問:</p>
                            <ul>
                                <li><code>查詢 2024-04-01 到 2024-04-07 規模 6.0 以上地震</code></li>
                            </ul>
                            <p>您可以自由更換其中的日期與規模數值。</p>
                        </div>
                        """
                    )

            with gr.TabItem("🤖 AI 課程助教"):
                with gr.Group():
                    gr.Markdown("### 🤖 AI 課程助教")
                    gr.Markdown("歡迎使用 AI 助教!您可以直接在下方對話框提問,或參考「❓ **AI 助教說明**」分頁了解所有支援的功能與快捷指令。")
                        
                    custom_textbox = gr.Textbox(
                        placeholder="對課程有什麼問題嗎?或輸入 #1、#2... 查詢即時資訊",
                        show_label=False,
                        container=False,
                    )

                    gr.ChatInterface(
                        callbacks.ai_chatbot_with_kb,
                        chatbot=gr.Chatbot(height=500, type="messages", avatar_images=(None, "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")),
                        title=None,
                        description=None,
                        textbox=custom_textbox,
                    )

        # --- 連接按鈕與後端執行函式 ---
        map_run_button.click(
            fn=lambda code: callbacks.execute_user_code(code, "地圖繪製"),
            inputs=[map_code],
            outputs=[map_plot_output, map_console_output]
        )
        seismo_run_button.click(
            fn=lambda code: callbacks.execute_user_code(code, "震波圖"),
            inputs=[seismo_code],
            outputs=[seismo_plot_output, seismo_console_output]
        )
            
    return demo