cwadayi commited on
Commit
8eb449d
·
verified ·
1 Parent(s): 76e6122

Upload 9 files

Browse files
Files changed (4) hide show
  1. app.py +25 -5
  2. config/data.py +40 -29
  3. core/callbacks.py +40 -11
  4. ui/layouts.py +18 -18
app.py CHANGED
@@ -1,9 +1,29 @@
1
  # app.py
 
2
  from core.visits import get_and_update_visits
3
  from ui.layouts import create_ui
4
 
5
- # --- 1. Update site visit count on startup ---
6
- # This part runs only once when the application starts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  try:
8
  count = get_and_update_visits()
9
  visit_count_html = f"🚀 **總載入次數:** {count}"
@@ -12,9 +32,9 @@ except Exception as e:
12
  visit_count_html = "🚀 **總載入次數:** N/A"
13
  print(f"Could not update visit count: {e}")
14
 
15
- # --- 2. Create the main UI by passing the dynamic content into the layout function ---
16
- demo = create_ui(visit_count_html)
17
 
18
- # --- 3. Launch the application ---
19
  if __name__ == "__main__":
20
  demo.launch()
 
1
  # app.py
2
+ import gradio as gr
3
  from core.visits import get_and_update_visits
4
  from ui.layouts import create_ui
5
 
6
+ # --- 1. Define a new custom theme for better aesthetics ---
7
+ custom_theme = gr.themes.Soft(
8
+ primary_hue="blue",
9
+ secondary_hue="cyan",
10
+ neutral_hue="slate",
11
+ spacing_size=gr.themes.sizes.spacing_md,
12
+ radius_size=gr.themes.sizes.radius_md,
13
+ ).set(
14
+ # Custom colors
15
+ body_background_fill="#F0F4F8", # A light blue-gray for the main background
16
+ block_background_fill="white", # White background for components
17
+ block_border_width="1px", # Thin border for components
18
+ block_shadow="*shadow_drop_lg", # Soft shadow for a floating effect
19
+
20
+ # Custom button style
21
+ button_primary_background_fill="*primary_500",
22
+ button_primary_background_fill_hover="*primary_400",
23
+ button_primary_text_color="white",
24
+ )
25
+
26
+ # --- 2. Update site visit count on startup ---
27
  try:
28
  count = get_and_update_visits()
29
  visit_count_html = f"🚀 **總載入次數:** {count}"
 
32
  visit_count_html = "🚀 **總載入次數:** N/A"
33
  print(f"Could not update visit count: {e}")
34
 
35
+ # --- 3. Create the main UI, passing both dynamic content and the new theme ---
36
+ demo = create_ui(visit_count_html, theme=custom_theme)
37
 
38
+ # --- 4. Launch the application ---
39
  if __name__ == "__main__":
40
  demo.launch()
config/data.py CHANGED
@@ -8,32 +8,43 @@ schedule_data = {
8
  }
9
  schedule_df = pd.DataFrame(schedule_data)
10
 
11
- KNOWLEDGE_BASE = [
12
- {'keywords': ['你好', '哈囉', 'hello', 'hi'], 'answer': f"你好!我是課程 AI 助教,很高興為您服務。今天是 {pd.Timestamp.now(tz='Asia/Taipei').strftime('%Y年%m月%d日')},有什麼可以協助您的嗎?"},
13
- {'keywords': ['你是誰', '功能', '幹嘛', 'who are you'], 'answer': "我是本課程的 AI 助教,我的核心任務是根據內建的知識庫,回答您關於課程的各種問題,包含**地球物理概念**、**程式工具**與**課程安排**。我還可以在「互動體驗區」協助您執行與除錯程式碼!"},
14
- {'keywords': ['成績', '評分', '分數', 'grading'], 'answer': "課程的評分標準設計如下:\n* **作業 (50%)**: 包含了所有程式碼練習、數據分析報告等。\n* **期中考 (40%)**: 範圍涵蓋前八週的地球物理知識與資料處理技能。\n* **平時成績 (10%)**: 根據您的課堂參與度、提問與互動表現進行評估。\n您可以在「成績計算」分頁看到完整的說明。"},
15
- {'keywords': ['進度', '課綱', '課程表', 'schedule', 'syllabus', '第幾週'], 'answer': "完整的課程進度表都列在「課程進度」分頁中喔!您可以去那裡查看每一週的地球物理知識主題和資料處理技能目標。"},
16
- {'keywords': ['期末', '專題', '報告', 'final project'], 'answer': "關於期末專題,我們預計在第十五週進行發表。這是一個很好的機會,讓您可以整合本學期所學的地球物理知識和數據處理技能,選擇一個您感興趣的主題進行深入研究與展示。"},
17
- {'keywords': ['考試', '期中考', 'midterm'], 'answer': "期中考安排在第八週。考試範圍將涵蓋前七週所有教過的地球物理概念(如折射探勘、重力探勘)和資料處理技能(如 PyGMT, ObsPy, GitHub 等)。請務必複習課程內容和實作作業!"},
18
- {'keywords': ['地球物理', 'geophysics', '學什麼'], 'answer': "地球物理學是一門利用物理原理(如波動、重力、磁力、熱力)來研究地球的科學,範圍非常廣泛!主要可以分為:\n1. **固體地球物理學**: 研究地球內部,如地震學、重力學。\n2. **應用地球物理學**: 尋找石油、礦產等資源或應用於工程與環境探勘。\n這門課會帶您認識核心概念,並用程式實作來分析相關數據!"},
19
- {'keywords': ['折射', '震測'], 'answer': "折射震測是透過人為產生震波,並分析震波在地下不同速度地層間的折射路徑與時間,來反推地下構造的方法。我們在第三週會有校園野外實驗,讓大家親手操作!"},
20
- {'keywords': ['重力', 'gravimetry'], 'answer': "重力探勘是透過精密儀器測量地表重力值的微小差異,來推斷地下物質的密度分佈。例如,高密度的礦床會產生正重力異常。"},
21
- {'keywords': ['板塊', 'tectonics'], 'answer': "板塊構造學說描述了地球的岩石圈是由數個巨大板塊組成,板塊的移動、碰撞或分離造成了地震、火山和造山運動。這是現代地球科學的基石。"},
22
- {'keywords': ['pygmt', 'gmt', 'cartopy', '地圖', 'map'], 'answer': "`PyGMT` `Cartopy` 都是 Python 中非常強大的地理資訊繪圖函式庫。\n* **PyGMT**: GMT 的 Python 介面,指令簡潔,功能專業,適合發表高品質的學術圖件。\n* **Cartopy**: 與 `matplotlib` 整合度極高,可以輕鬆加入地圖投影、海岸線等特徵。「地圖繪製實驗室」使用的就是 `Cartopy`!"},
23
- {'keywords': ['obspy'], 'answer': "ObsPy 是一個專為地震學設計的開源 Python 函式庫,能方便地讀寫各種地震波形格式、進行訊號處理(如濾波)、儀器響應校正等,是地震學家的瑞士刀。"},
24
- {'keywords': ['gradio', 'streamlit'], 'answer': "Gradio Streamlit 都是能讓我們用純 Python 快速建立互動式網頁應用的工具!\n* **Gradio**: 特別適合為函式或機器學習模型打造 demo 介面,就像這個課程網頁。\n* **Streamlit**: 則常用於建立數據分析的儀表板 (Dashboard)。"},
25
- {'keywords': ['hugging face', 'hf', 'huggingface'], 'answer': "Hugging Face 是一個以 AI 為核心的社群與平台。它最棒的功能之一 **Spaces** 提供免費資源,讓我們可以輕鬆部署 Gradio 或 Streamlit 應用,分享給全世界!這個課程網頁就是部署在 Hugging Face Spaces 上。"},
26
- {'keywords': ['gemini', 'dify'], 'answer': "Gemini Google 開發的強大大型語言模型 (LLM)。Dify 則是一個 Low-code AI 應用開發平台,可以讓我們快速串接像 Gemini 這樣的語言模型,打造出有記憶能力的聊天機器人或 AI 應用。"},
27
- {'keywords': ['謝謝', '感謝', 'thank'], 'answer': "不客氣!能幫助到您我也很開心。若還有其他問題,隨時都可以再問我喔!"},
28
- {'keywords': ['笑話', '好玩', '有趣'], 'answer': "好的,給��說個地質學家的冷笑話:\n\n為什麼地質學家從來不賭博?\n\n...因為他們知道什麼叫做「斷層」(Fault) 😄"},
29
- {'keywords': ['安裝', '環境', 'anaconda', 'miniconda', 'setup', 'environment'], 'answer': "對於初學者,強烈推薦使用 **Anaconda** 或 **Miniconda** 來建置 Python 環境。\n* **Anaconda**: 包含了 Python、數百個常用科學計算套件以及 `conda` 管理器。\n* **Miniconda**: 僅包含 Python 和 `conda`,更輕量。\n使用 `conda` 可以輕鬆創建獨立的虛擬環境,避免套件版本衝突。"},
30
- {'keywords': ['colab', 'codespaces', '比較', '差別', 'difference'], 'answer': "`Colab` 和 `GitHub Codespaces` 都是優秀的雲端開發環境:\n\n| 特性 | Google Colab | GitHub Codespaces |\n| :--- | :--- | :--- |\n| **強項** | 數據分析、機器學習 | 完整的軟體開發 |\n| **免費額度** | 提供免費 GPU/TPU | 每月提供免費核心時數 |\n| **環境** | Jupyter Notebook | 完整的 VS Code 編輯器 |\n| **整合性** | Google Drive | GitHub Repo |\n| **適用情境** | 執行分析腳本、跑模型 | 開發完整專案、網頁應用 |"},
31
- {'keywords': ['git', 'github', '版本控制', '版控', 'version control', 'commit'], 'answer': "`Git` 是一個**版本控制系統**,`GitHub` 是一個**托管 Git 專案的平台**。\n使用版本控制有三大好處:\n1. **紀錄歷程**: 可隨時回溯到任何過去的版本。\n2. **團隊協作**: 多人可同時修改專案並合併工作。\n3. **分支開發**: 可安全地實驗新功能而不影響主線。"},
32
- {'keywords': ['api', '應用程式介面'], 'answer': "您可以將 **API (Application Programming Interface)** 想像成餐廳的**菜單**。\n您只需要看懂菜單(API 文件),向服務生(API 端點)下訂單(發送請求),服務生就會將做好的菜(數據或結果)送回給您,而您完全不需要知道廚房內部的運作細節。"},
33
- {'keywords': ['視覺化', '可視化', 'visualization', '好圖', '圖表'], 'answer': "一張好的科學圖表應具備**清晰性**、**準確性**、**簡潔性**和**故事性**。\n記住,圖表的目的是有效傳達資訊,而不是展示花俏的繪圖技巧。"},
34
- {'keywords': ['地磁', '古地磁', '磁場', 'geomagnetism', 'paleomagnetism'], 'answer': "地球本身就像一個巨大的磁鐵,擁有**地磁**。當火山熔岩冷卻時,其中的磁性礦物會將當時地球磁場的方向「鎖住」,這就是**古地磁**紀錄。海底擴張帶兩側對稱的地磁異常條帶,是板塊構造學說最經典的證據之一。"},
35
- {'keywords': ['地熱', '能源', 'geothermal'], 'answer': "地熱是來自地球內部的可再生能源。我們主要透過鑽井到地下的熱儲層,利用高溫蒸汽或熱水來發電。地熱發電的優點是**穩定**且**碳排放極低**,且通常集中在板塊邊界,台灣正好就位在這個絕佳的地理位置上!"},
36
- {'keywords': ['地震儀', '地震學', 'seismograph', 'seismology', '差別'], 'answer': "**地震學 (Seismology)** 是一門**科學**,研究地震與地球內部構造。\n**地震儀 (Seismograph)** 是一種**儀器**,是地震學家使用的工具,用來記錄地面振動。\n簡單來說:**地震學家使用地震儀來進行地震學研究。**"},
37
- {'keywords': ['野外', 'field work', '實驗', '準備', '穿什麼'], 'answer': "對於第三週的校園折射震測實驗,建議準備如下:\n* **服裝**: 穿著輕便、適合活動的長褲與不怕髒的鞋子。\n* **防曬/防雨**: 根據天氣預報,準備好帽子、防曬乳或雨具。\n* **其他**: 務必攜帶足夠的水和筆記本。"},
38
- {'keywords': ['專題', '題目', '想法', 'topic', 'idea'], 'answer': "這裡提供幾個期末專題的靈感方向:\n1. **特定區域地震活動分析**: 使用 `ObsPy` 下載台灣特定區域的地震資料,分析其時空分佈並用 `PyGMT` `Cartopy` 繪圖。\n2. **重力或磁力異常圖的解譯**: 尋找公開的重磁資料,繪製異常圖並嘗試解讀其對應的地質構造。\n3. **AI 應用於地球物理**: 訓練一個簡單的機器學習模型,用來自動區分 P 波和 S 波的到達時間。\n4. **互動式地球物理教學工具**: 使用 `Gradio` 或 `Streamlit` 製作一個互動小工具,用來展示特定概念。"}
39
- ]
 
 
 
 
 
 
 
 
 
 
 
 
8
  }
9
  schedule_df = pd.DataFrame(schedule_data)
10
 
11
+ KNOWLEDGE_BASE = {
12
+ "course": [
13
+ {"keywords": ["成績", "評分", "分數", "grading"], "answer": "課程的評分標準設計如下:\n* **作業 (50%)**: 包含程式練習、數據分析報告等。\n* **期中考 (40%)**: 前八週地球物理知識與資料處理技能。\n* **平時成績 (10%)**: 課堂參與度、提問與互動。"},
14
+ {"keywords": ["進度", "課綱", "課程表", "schedule", "syllabus", "第幾週"], "answer": "完整課程進度表請參考「課程進度」分頁,可查看每週主題與技能目標。"},
15
+ {"keywords": ["期末", "專題", "報告", "final project"], "answer": "期末專題於第十五週發表,是整合所學知識與技能的重要展示機會。"},
16
+ {"keywords": ["考試", "期中考", "midterm"], "answer": "期中考在第八週,範圍涵蓋前七週的地球物理概念(如折射探勘、重力探勘)與資料處理技能(如 PyGMT, ObsPy, GitHub)。"},
17
+ {"keywords": ["專題", "題目", "想法", "topic", "idea"], "answer": "期末專題靈感:\n1. 區域地震活動分析 (ObsPy + PyGMT/Cartopy)\n2. 重力或磁力異常圖解譯\n3. AI 協助 P 波/S 波自動分類\n4. 互動式教學工具 (Gradio/Streamlit)"},
18
+ {"keywords": ["野外", "field work", "實驗", "準備", "穿什麼"], "answer": "第三週折射震測野外實驗建議:\n* **服裝**: 輕便長褲、不怕髒的鞋子\n* **防護**: 帽子、防曬乳或雨具\n* **其他**: 水、筆記本"}
19
+ ],
20
+
21
+ "geophysics": [
22
+ {"keywords": ["地球物理", "geophysics", "學什麼"], "answer": "地球物理學利用物理原理研究地球:\n1. **固體地球物理**:地震學、重力學。\n2. **應用地球物理**:資源探勘、工程與環境應用。"},
23
+ {"keywords": ["折射", "震測"], "answer": "折射震測透過震波在地下不同地層的折射路徑與時間來推測地下構造。第三週會有實驗。"},
24
+ {"keywords": ["重力", "gravimetry"], "answer": "重力探勘測量地表重力的微小差異,推斷地下物質密度分布,例如高密度礦床 正重力異常。"},
25
+ {"keywords": ["板塊", "tectonics"], "answer": "板塊構造學說認為地球岩石圈由數個板塊組成,移動造成地震、火山與造山運動。"},
26
+ {"keywords": ["地磁", "古地磁", "磁場", "geomagnetism", "paleomagnetism"], "answer": "地球像一顆巨大磁鐵。岩漿冷卻時磁性礦物記錄當時磁場方向 古地磁。海底擴張帶對稱條帶是板塊構造證據。"},
27
+ {"keywords": ["地熱", "能源", "geothermal"], "answer": "地熱能源來自地球內部,透過鑽井取得熱水/蒸氣發電。優點:**穩定、碳排低**,集中於板塊邊界,台灣具有優勢。"},
28
+ {"keywords": ["地震儀", "地震學", "seismograph", "seismology", "差別"], "answer": "**地震學 (Seismology)**: 研究地震與地球內部。\n**地震儀 (Seismograph)**: 儀器,用來記錄地面振動。"}
29
+ ],
30
+
31
+ "tools": [
32
+ {"keywords": ["pygmt", "gmt", "cartopy", "地圖", "map"], "answer": "`PyGMT`: 高品質學術圖件。`Cartopy`: matplotlib 整合度高,適合地圖投影與特徵繪製。"},
33
+ {"keywords": ["obspy"], "answer": "ObsPy: 專為地震學設計的 Python 函式庫,可讀寫地震波形、濾波、儀器校正。"},
34
+ {"keywords": ["gradio", "streamlit"], "answer": "Gradio: 快速 demo 介面。Streamlit: 數據分析儀表板。"},
35
+ {"keywords": ["hugging face", "hf", "huggingface"], "answer": "Hugging Face: AI 社群與平台。Spaces 提供免費資源部署 Gradio/Streamlit。"},
36
+ {"keywords": ["gemini", "dify"], "answer": "Gemini: Google 開發的 LLM。Dify: Low-code 平台,可快速串接 LLM 打造 AI 應用。"},
37
+ {"keywords": ["安裝", "環境", "anaconda", "miniconda", "setup", "environment"], "answer": "**Anaconda**: 全套科學計算套件。**Miniconda**: 輕量,僅含 Python conda。可建立虛擬環境避免衝突。"},
38
+ {"keywords": ["colab", "codespaces", "比較", "差別", "difference"], "answer": "Colab: 適合數據分析/機器學習,免費 GPU。\nCodespaces: 完整 VS Code 環境,適合軟體開發。"},
39
+ {"keywords": ["git", "github", "版本控制", "版控", "version control", "commit"], "answer": "Git: 版本控制系統。GitHub: 托管平台。好處:歷程紀錄、團隊協作、分支開發。"},
40
+ {"keywords": ["api", "應用程式介面"], "answer": "API 就像餐廳菜單,點菜 (請求) → 廚房 (系統) → 送餐 (結果)。"},
41
+ {"keywords": ["視覺化", "可視化", "visualization", "好圖", "圖表"], "answer": "好圖表需具備:**清晰、準確、簡潔、故事性**。目的在有效傳達資訊。"}
42
+ ],
43
+
44
+ "general": [
45
+ {"keywords": ["你好", "哈囉", "hello", "hi"], "answer": f"你好!我是課程 AI 助教,很高興為您服務。今天是 {pd.Timestamp.now(tz='Asia/Taipei').strftime('%Y年%m月%d日')}。"},
46
+ {"keywords": ["你是誰", "功能", "幹嘛", "who are you"], "answer": "我是本課程的 AI 助教,能回答地球物理、程式工具與課程安排相關問題,也能協助程式除錯。"},
47
+ {"keywords": ["謝謝", "感謝", "thank"], "answer": "不客氣!隨時歡迎再來提問。"},
48
+ {"keywords": ["笑話", "好玩", "有趣"], "answer": "為什麼地質學家不賭博?因為他們知道什麼叫做『斷層』!😄"}
49
+ ]
50
+ }
core/callbacks.py CHANGED
@@ -4,11 +4,13 @@ import contextlib
4
  import traceback
5
  from datetime import datetime
6
  import pytz
 
7
 
8
  from core.visits import get_current_visit_count
9
  from core.notifications import send_line_notification_in_background
10
  from config.data import KNOWLEDGE_BASE
11
 
 
12
  def execute_user_code(code_string, source_lab):
13
  """Executes user-provided code in a restricted environment and sends a notification."""
14
  string_io = io.StringIO()
@@ -61,9 +63,42 @@ def execute_user_code(code_string, source_lab):
61
  send_line_notification_in_background(notification_text)
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  def ai_chatbot_with_kb(message, history):
65
- """Handles chatbot interaction, queries the knowledge base, and sends a notification."""
66
- # Send notification in the background
 
 
67
  tz = pytz.timezone('Asia/Taipei')
68
  current_time = datetime.now(tz).strftime('%H:%M:%S')
69
  visit_count = get_current_visit_count()
@@ -75,12 +110,6 @@ def ai_chatbot_with_kb(message, history):
75
  )
76
  send_line_notification_in_background(notification_text)
77
 
78
- # Perform knowledge base lookup
79
- user_message = message.lower().strip()
80
- for item in KNOWLEDGE_BASE:
81
- for keyword in item['keywords']:
82
- if keyword in user_message:
83
- return item['answer']
84
-
85
- # Default response if no keyword is matched
86
- return "這個問題很有趣,不過我的知識庫目前還沒有收錄相關的答案。您可以試著問我關於**課程評分、Anaconda安裝、Colab與Codespaces的差別、什麼是API,或者期末專題的靈感**等問題!"
 
4
  import traceback
5
  from datetime import datetime
6
  import pytz
7
+ import difflib # ✨ 1. 導入 difflib 函式庫
8
 
9
  from core.visits import get_current_visit_count
10
  from core.notifications import send_line_notification_in_background
11
  from config.data import KNOWLEDGE_BASE
12
 
13
+ # (execute_user_code 函式維持不變,此處省略以節省篇幅)
14
  def execute_user_code(code_string, source_lab):
15
  """Executes user-provided code in a restricted environment and sends a notification."""
16
  string_io = io.StringIO()
 
63
  send_line_notification_in_background(notification_text)
64
 
65
 
66
+ # --- ✨ 2. 加入新的模糊比對函式 ---
67
+ def find_best_match(user_input, knowledge_base, threshold=0.6):
68
+ """
69
+ Finds the best matching answer from the knowledge base using fuzzy string matching.
70
+ """
71
+ best_score = 0
72
+ best_answer = "這個問題很有趣,不過我的知識庫目前還沒有收錄相關的答案。您可以試著問我關於**課程評分、Anaconda安裝、Colab與Codespaces的差別**等問題!"
73
+ best_match_keyword = None
74
+
75
+ # Iterate through all keywords in the knowledge base
76
+ for category, entries in knowledge_base.items():
77
+ for entry in entries:
78
+ for keyword in entry['keywords']:
79
+ # Calculate similarity score
80
+ score = difflib.SequenceMatcher(None, user_input.lower(), keyword.lower()).ratio()
81
+ if score > best_score:
82
+ best_score = score
83
+ best_match_keyword = keyword
84
+ best_answer = entry['answer']
85
+
86
+ # If the best score is above the threshold, return the answer directly.
87
+ if best_score >= threshold:
88
+ return best_answer
89
+ # If the score is too low, but we found a potential match, ask for confirmation.
90
+ elif best_match_keyword:
91
+ return f"這個問題我不是很確定,您是指 **「{best_match_keyword}」** 嗎?\n\n我目前找到的相關資料如下:\n\n{best_answer}"
92
+ # If the knowledge base was empty or no match was found at all.
93
+ else:
94
+ return best_answer
95
+
96
+
97
  def ai_chatbot_with_kb(message, history):
98
+ """
99
+ Handles chatbot interaction by calling the fuzzy matching function and sends a notification.
100
+ """
101
+ # Notification logic (remains the same)
102
  tz = pytz.timezone('Asia/Taipei')
103
  current_time = datetime.now(tz).strftime('%H:%M:%S')
104
  visit_count = get_current_visit_count()
 
110
  )
111
  send_line_notification_in_background(notification_text)
112
 
113
+ # --- 3. 使用新的模糊比對函式取代舊的搜尋邏輯 ---
114
+ # The old exact-match logic is now replaced by a single call to our new function.
115
+ return find_best_match(message.strip(), KNOWLEDGE_BASE)
 
 
 
 
 
 
ui/layouts.py CHANGED
@@ -3,16 +3,17 @@ import gradio as gr
3
  from config import content, data, defaults
4
  from core import callbacks
5
 
6
- def create_ui(visit_count_html: str):
7
  """
8
  Creates and returns the Gradio UI Blocks.
9
 
10
  Args:
11
  visit_count_html: The Markdown string to display the visit count.
 
12
  """
13
 
14
  # --- Main UI Layout ---
15
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange"), title="地球物理學與AI應用") as demo:
16
  # --- Display Visit Counter at the top ---
17
  gr.Markdown(visit_count_html)
18
 
@@ -20,30 +21,26 @@ def create_ui(visit_count_html: str):
20
  gr.Markdown(content.course_introduction_md)
21
 
22
  # --- Main Tabs ---
23
- # ✨ --- UPDATED: Added emojis to tab labels for better UI --- ✨
24
  with gr.Tabs():
25
 
26
- # --- Tab 1: Course Goals ---
27
  with gr.TabItem("🎯 課程目標"):
28
  gr.Markdown(content.course_goals_md)
29
 
30
- # --- Tab 2: Course Schedule ---
31
  with gr.TabItem("🗓️ 課程進度"):
32
  gr.Markdown("### 每週課程安排")
33
  gr.DataFrame(data.schedule_df, wrap=True)
34
 
35
- # --- Tab 3: Grading Policy ---
36
  with gr.TabItem("💯 成績計算"):
37
  gr.Markdown(content.grading_policy_md)
38
 
39
- # --- Tab 4: Interactive Labs ---
40
  with gr.TabItem("🚀 互動體驗區 (程式碼實驗室)"):
41
  gr.Markdown("## 🚀 互動程式碼實驗室")
42
  gr.Markdown("歡迎來到這裡!直接修改下方的 Python 程式碼,點擊「執行」,即可在右側看到成果。這是學習程式與地球物理最直接的方式!")
43
  gr.Info("注意:執行環境已受限,僅支援資料視覺化相關操作。請勿嘗試檔案讀寫或網路請求。")
44
 
45
- # --- Lab 1: Mapping ---
46
  with gr.Accordion("🌍 地圖繪製實驗室 (PyGMT/Cartopy 概念)", open=True):
 
47
  with gr.Row():
48
  with gr.Column(scale=2):
49
  gr.Markdown("### 說明\n這段程式碼使用 `cartopy` 和 `matplotlib` 函式庫來繪製地理地圖。\n\n**您可以試著:**\n1. 修改 `center_lon`, `center_lat` 來改變地圖中心。\n2. 調整 `extent_lon`, `extent_lat` 來縮放地圖。\n3. 將 `coastline_color` 改成 'red' 或其他顏色。\n4. **在 `symbols` 列表中新增或修改字典,來繪製自訂的符號(例如:標示您所在的城市)。**")
@@ -53,8 +50,8 @@ def create_ui(visit_count_html: str):
53
  map_plot_output = gr.Plot(label="地圖輸出")
54
  map_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
55
 
56
- # --- Lab 2: Seismology ---
57
  with gr.Accordion("📈 震波圖繪製實驗室 (ObsPy 概念)", open=False):
 
58
  with gr.Row():
59
  with gr.Column(scale=2):
60
  gr.Markdown("### 說明\n這段程式碼使用 `numpy` 產生模擬的地震波數據,並用 `matplotlib` 將其視覺化。\n\n**您可以試著:**\n1. 修改 `p_wave_arrival` 和 `s_wave_arrival` 來改變 P/S 波的抵達時間。\n2. 調整 `main_freq` 來改變地震波的頻率(數值越大,波形越密集)。\n3. 將 `decay_rate` 調小,觀察振幅衰減變慢的效果。")
@@ -64,16 +61,19 @@ def create_ui(visit_count_html: str):
64
  seismo_plot_output = gr.Plot(label="震波圖輸出")
65
  seismo_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
66
 
67
- # --- Tab 5: AI Chatbot ---
 
68
  with gr.TabItem("🤖 AI 課程助教"):
69
- gr.Markdown("### 🤖 AI 課程助教 (知識庫強化版)")
70
- gr.Markdown("我內建了豐富的課程知識庫,試著問我 **「如何安裝Python環境?」**、**「什麼是版本控制?」** 或 **「給我一些期末專題的靈感」**")
71
- gr.ChatInterface(
72
- callbacks.ai_chatbot_with_kb,
73
- chatbot=gr.Chatbot(height=450, type="messages", avatar_images=(None, "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")),
74
- title="課程AI助教",
75
- description="由結構化知識庫驅動的問答機器人"
76
- )
 
 
77
 
78
  # --- Connect Buttons to Backend Functions ---
79
  map_run_button.click(
 
3
  from config import content, data, defaults
4
  from core import callbacks
5
 
6
+ def create_ui(visit_count_html: str, theme: gr.Theme):
7
  """
8
  Creates and returns the Gradio UI Blocks.
9
 
10
  Args:
11
  visit_count_html: The Markdown string to display the visit count.
12
+ theme: The Gradio theme object to apply to the UI.
13
  """
14
 
15
  # --- Main UI Layout ---
16
+ with gr.Blocks(theme=theme, title="地球物理學與AI應用") as demo:
17
  # --- Display Visit Counter at the top ---
18
  gr.Markdown(visit_count_html)
19
 
 
21
  gr.Markdown(content.course_introduction_md)
22
 
23
  # --- Main Tabs ---
 
24
  with gr.Tabs():
25
 
26
+ # (其他 TabItem 維持不變)
27
  with gr.TabItem("🎯 課程目標"):
28
  gr.Markdown(content.course_goals_md)
29
 
 
30
  with gr.TabItem("🗓️ 課程進度"):
31
  gr.Markdown("### 每週課程安排")
32
  gr.DataFrame(data.schedule_df, wrap=True)
33
 
 
34
  with gr.TabItem("💯 成績計算"):
35
  gr.Markdown(content.grading_policy_md)
36
 
 
37
  with gr.TabItem("🚀 互動體驗區 (程式碼實驗室)"):
38
  gr.Markdown("## 🚀 互動程式碼實驗室")
39
  gr.Markdown("歡迎來到這裡!直接修改下方的 Python 程式碼,點擊「執行」,即可在右側看到成果。這是學習程式與地球物理最直接的方式!")
40
  gr.Info("注意:執行環境已受限,僅支援資料視覺化相關操作。請勿嘗試檔案讀寫或網路請求。")
41
 
 
42
  with gr.Accordion("🌍 地圖繪製實驗室 (PyGMT/Cartopy 概念)", open=True):
43
+ # ... (此處內容不變)
44
  with gr.Row():
45
  with gr.Column(scale=2):
46
  gr.Markdown("### 說明\n這段程式碼使用 `cartopy` 和 `matplotlib` 函式庫來繪製地理地圖。\n\n**您可以試著:**\n1. 修改 `center_lon`, `center_lat` 來改變地圖中心。\n2. 調整 `extent_lon`, `extent_lat` 來縮放地圖。\n3. 將 `coastline_color` 改成 'red' 或其他顏色。\n4. **在 `symbols` 列表中新增或修改字典,來繪製自訂的符號(例如:標示您所在的城市)。**")
 
50
  map_plot_output = gr.Plot(label="地圖輸出")
51
  map_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
52
 
 
53
  with gr.Accordion("📈 震波圖繪製實驗室 (ObsPy 概念)", open=False):
54
+ # ... (此處內容不變)
55
  with gr.Row():
56
  with gr.Column(scale=2):
57
  gr.Markdown("### 說明\n這段程式碼使用 `numpy` 產生模擬的地震波數據,並用 `matplotlib` 將其視覺化。\n\n**您可以試著:**\n1. 修改 `p_wave_arrival` 和 `s_wave_arrival` 來改變 P/S 波的抵達時間。\n2. 調整 `main_freq` 來改變地震波的頻率(數值越大,波形越密集)。\n3. 將 `decay_rate` 調小,觀察振幅衰減變慢的效果。")
 
61
  seismo_plot_output = gr.Plot(label="震波圖輸出")
62
  seismo_console_output = gr.Textbox(label="執行結果 / 錯誤訊息", lines=8, interactive=False)
63
 
64
+
65
+ # --- ✨ Tab 5: AI Chatbot (with improved layout) --- ✨
66
  with gr.TabItem("🤖 AI 課程助教"):
67
+ # Use a gr.Box to create a visually distinct container
68
+ with gr.Box():
69
+ gr.Markdown("### 🤖 AI 課程助教 (知識庫強化版)")
70
+ gr.Markdown("我內建了豐富的課程知識庫,試著問我 **「如何安裝Python環境?」**、**「什麼是版本控制?」** 或 **「給我一些期末專題的靈感」**")
71
+ gr.ChatInterface(
72
+ callbacks.ai_chatbot_with_kb,
73
+ chatbot=gr.Chatbot(height=450, type="messages", avatar_images=(None, "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")),
74
+ title="課程AI助教",
75
+ description="由結構化知識庫驅動的問答機器人"
76
+ )
77
 
78
  # --- Connect Buttons to Backend Functions ---
79
  map_run_button.click(