Spaces:
Running
Running
Upload 9 files
Browse files- app.py +25 -5
- config/data.py +40 -29
- core/callbacks.py +40 -11
- 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.
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
# ---
|
| 16 |
-
demo = create_ui(visit_count_html)
|
| 17 |
|
| 18 |
-
# ---
|
| 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 |
-
|
| 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 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"""
|
| 66 |
-
|
|
|
|
|
|
|
| 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 |
-
#
|
| 79 |
-
|
| 80 |
-
|
| 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=
|
| 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 |
-
#
|
| 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 |
-
|
|
|
|
| 68 |
with gr.TabItem("🤖 AI 課程助教"):
|
| 69 |
-
gr.
|
| 70 |
-
gr.
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 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(
|