Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import random | |
| import textwrap | |
| import folium | |
| from obspy.clients.fdsn import Client | |
| from obspy import UTCDateTime | |
| import ast # 用於安全地解析字典 | |
| # ------------------------------------------------------------------- | |
| # 範例 1 (基礎) 的預設資料 | |
| # ------------------------------------------------------------------- | |
| TUTORIAL_BASIC_INITIAL_DICT = { | |
| "板塊構造 (Plate Tectonics)": "地球的岩石圈被分成許多稱為「板塊」的巨大板塊,這些板塊在軟流圈上緩慢移動,彼此碰撞或分離,形成了火山、地震和山脈。", | |
| "地震波 (Seismic Wave)": "地震時從震源向四面八方傳播的能量波。主要分為 P 波(壓縮波,速度快)和 S 波(剪力波,速度慢),它們是我們研究地球內部的主要工具。", | |
| "海嘯 (Tsunami)": "由海底地震、火山爆發或山崩引起的巨大海浪。它在深海中傳播速度極快(可達時速 800 公里),但波高很低;當它靠近淺海岸時,速度減慢,波高則急遽增加。" | |
| } | |
| TUTORIAL_CODE_BASIC = textwrap.dedent(f""" | |
| # --- 這是 Level 1 範例的完整程式碼 --- | |
| # 試試看: | |
| # 1. 在 geo_dict 中新增一個你自己的詞條。 | |
| # 2. 修改 "海嘯" 的解釋。 | |
| # 3. 按下方的 "套用變更" 按鈕。 | |
| # 4. 回到 "Live Demo" 分頁看看你的修改! | |
| # 1. 知識庫 (你的地科字典) | |
| geo_dict = {TUTORIAL_BASIC_INITIAL_DICT!r} | |
| # 2. 定義介面函式 | |
| def get_definition(term): | |
| # geo_dict 會從這個程式碼的 "全域" 範圍內被找到 | |
| return geo_dict.get(term, "查無此名詞") | |
| """) | |
| # ------------------------------------------------------------------- | |
| # 範例 1 (基礎) 的動態執行 Wrapper | |
| # ------------------------------------------------------------------- | |
| def run_basic_code(term, code_string): | |
| # [***** 修復 *****] | |
| # 建立一個同時作為 global 和 local 範圍的 scope | |
| # 這樣在 `exec` 中定義的函式才能找到在同一個 scope 中定義的全域變數 | |
| execution_scope = {"__builtins__": {}} | |
| try: | |
| # [修復] 將 global 和 local 都設置為同一個 execution_scope | |
| exec(code_string, execution_scope, execution_scope) | |
| get_definition_func = execution_scope.get("get_definition") | |
| if not callable(get_definition_func): | |
| return "程式碼錯誤:找不到 `get_definition` 函式。" | |
| # 呼叫學生程式碼中定義的函式 | |
| return get_definition_func(term) | |
| except Exception as e: | |
| return f"執行時發生錯誤:\n{e}" | |
| # ------------------------------------------------------------------- | |
| # 範例 2 (進階) 的預設程式碼 | |
| # ------------------------------------------------------------------- | |
| TUTORIAL_CODE_ADVANCED = textwrap.dedent(""" | |
| # --- 這是 Level 2 範例的完整程式碼 --- | |
| # 試試看: | |
| # 1. 找到 "color = 'green'",把它改成 "color = 'blue'"。 | |
| # 2. 找到 "radius=mag * 1.5",把它改成 "radius=mag * 3"。 | |
| # 3. 按下方的 "套用變更" 按鈕。 | |
| # 4. 回到 "Live Demo" 分頁,搜尋地震看看你的修改! | |
| # 1. 定義核心函式:搜尋並繪圖 | |
| def fetch_and_plot_events(min_mag, days_ago): | |
| try: | |
| # Folium, Client, UTCDateTime 已經被安全地匯入 | |
| client = Client("IRIS") | |
| endtime = UTCDateTime.now() | |
| starttime = endtime - (days_ago * 24 * 3600) | |
| catalog = client.get_events( | |
| starttime=starttime, | |
| endtime=endtime, | |
| minmagnitude=min_mag | |
| ) | |
| m = folium.Map(location=[0, 0], zoom_start=2) | |
| if len(catalog) == 0: | |
| return "<p>在此條件下查無地震資料,請嘗試放寬搜尋條件。</p>" | |
| for event in catalog: | |
| origin = event.preferred_origin() | |
| mag = event.preferred_magnitude().mag | |
| lat = origin.latitude | |
| lon = origin.longitude | |
| depth_km = origin.depth / 1000.0 | |
| if depth_km < 70: | |
| color = "green" # <-- 試著修改這裡! | |
| elif depth_km < 300: | |
| color = "orange" | |
| else: | |
| color = "red" | |
| popup_html = f''' | |
| <b>時間:</b> {origin.time.strftime('%Y-%m-%d %H:%M:%S')}<br> | |
| <b>規模 (M):</b> {mag:.1f}<br> | |
| <b>深度 (km):</b> {depth_km:.1f}<br> | |
| <b>位置:</b> ({lat:.2f}, {lon:.2f}) | |
| ''' | |
| folium.CircleMarker( | |
| location=[lat, lon], | |
| radius=mag * 1.5, # <-- 試著修改這裡! | |
| popup=folium.Popup(popup_html, max_width=300), | |
| color=color, | |
| fill=True, | |
| fill_color=color, | |
| fill_opacity=0.6 | |
| ).add_to(m) | |
| return m._repr_html_() | |
| except Exception as e: | |
| return f"<p>發生錯誤:{e}</p>" | |
| """) | |
| # ------------------------------------------------------------------- | |
| # 範例 2 (進階) 的安全沙盒與動態執行 Wrapper | |
| # ------------------------------------------------------------------- | |
| def run_advanced_code(min_mag, days_ago, code_string): | |
| # 建立一個安全的 "全域" 環境,只允許必要的模組 | |
| # 這個設計是正確的,因為函式(fetch_and_plot_events)需要存取 | |
| # 這些 "預先匯入" 的全域模組。 | |
| safe_globals = { | |
| "__builtins__": { | |
| "print": print, "Exception": Exception, "len": len, "str": str, | |
| "float": float, "int": int, "list": list, "dict": dict, "range": range, | |
| "True": True, "False": False, "None": None | |
| }, | |
| "folium": folium, | |
| "Client": Client, | |
| "UTCDateTime": UTCDateTime | |
| } | |
| local_scope = {} | |
| try: | |
| # 在安全沙盒中執行學生程式碼 | |
| exec(code_string, safe_globals, local_scope) | |
| fetch_func = local_scope.get("fetch_and_plot_events") | |
| if not callable(fetch_func): | |
| return "<p>程式碼錯誤:找不到 `fetch_and_plot_events` 函式。</p>" | |
| # 呼叫學生程式碼中定義的函式 | |
| return fetch_func(min_mag, days_ago) | |
| except Exception as e: | |
| return f"<p>執行時發生錯誤:\n{e}</p>" | |
| # ------------------------------------------------------------------- | |
| # 專案總覽的資料 | |
| # ------------------------------------------------------------------- | |
| projects_data = { | |
| "🌊 地震與波形展示": [ | |
| {"title": "📡 地震目錄搜尋 API", "url": "https://cwadayi-python-app.hf.space/"}, | |
| {"title": "⚠️ 地震預警訊息顯示", "url": "https://huggingface.co/spaces/cwadayi/streamlit_alarm_Taiwan"}, | |
| {"title": "🗺️ PyGMT 震央繪圖展示", "url": "https://huggingface.co/spaces/cwadayi/streamlit_gmt_demo_new"}, | |
| {"title": "📈 ObsPy 地震資料處理", "url": "https://huggingface.co/spaces/cwadayi/streamlit_obspy"} | |
| ], | |
| "🛰️ 地震監測與資料應用": [ | |
| {"title": "📍 Geiger 法地震定位展示", "url": "https://huggingface.co/spaces/cwadayi/streamlit_earthquake_location"}, | |
| {"title": "🔍 地震監測流程 (擷取-撿拾-定位)", "url": "https://huggingface.co/spaces/cwadayi/earthquake_monitoring"}, | |
| {"title": "📊 Google Sheet 資料顯示 (類Grafana)", "url": "https://huggingface.co/spaces/cwadayi/Grafana_like_2"}, | |
| {"title": "🏠 PWS 地震預警顯示", "url": "https://cwadayi-app-show-pws.hf.space/cwa-earthquakes"}, | |
| {"title": "🌍 全球地震顯示 (Streamlit)", "url": "https://huggingface.co/spaces/cwadayi/streamlit"} | |
| ], | |
| "💻 資料擷取與 API 應用": [ | |
| {"title": "💬 LINE ROBOT 伺服器", "url": "https://huggingface.co/spaces/cwadayi/LINE-ROBOT"}, | |
| {"title": "🗺️ 震央分佈圖 (MCP-2)", "url": "https://huggingface.co/spaces/cwadayi/MCP-2"}, | |
| {"title": "📥 PWS 資訊擷取", "url": "https://huggingface.co/spaces/cwadayi/MCP-pws"}, | |
| {"title": "🏛️ USGS 地震資料 API", "url": "https://huggingface.co/spaces/cwadayi/Usgs_api_gemini"}, | |
| {"title": "🇹🇼 CWA 地震資料 API", "url": "https://huggingface.co/spaces/cwadayi/CWA_API_chatgpt5_1"} | |
| ], | |
| "🏫 地球物理課程與教材": [ | |
| {"title": "🎓 地球物理課程 (總覽)", "url": "https://huggingface.co/spaces/cwadayi/Geophysics_class"}, | |
| {"title": "📗 地物課程網站 (Day1)", "url": "https://huggingface.co/spaces/cwadayi/Geophysics_day1"}, | |
| {"title": "📘 地物課程網站 (Day2)", "url": "https://huggingface.co/spaces/cwadayi/Geophysics_day2"}, | |
| {"title": "📏 折射震測展示 (SDD)", "url": "https://huggingface.co/spaces/cwadayi/Refraction_sdd"}, | |
| {"title": "📐 折射震測展示 (Refraction 3)", "url": "https://huggingface.co/spaces/cwadayi/Refraction_3"}, | |
| {"title": "⚖️ 重力異常展示 (Bouguer & Free-air)", "url": "https://huggingface.co/spaces/cwadayi/Gravity"} | |
| ] | |
| } | |
| def recommend_project(): | |
| all_projects = [item for sublist in projects_data.values() for item in sublist] | |
| recommendation = random.choice(all_projects) | |
| return f""" | |
| ### {recommendation['title']} | |
| [**點此立即前往 🚀**]({recommendation['url']}) | |
| """ | |
| TUTORIAL_REQUIREMENTS = textwrap.dedent(""" | |
| gradio | |
| obspy | |
| folium | |
| """) | |
| # ------------------------------------------------------------------- | |
| # 自訂 CSS | |
| # ------------------------------------------------------------------- | |
| css = """ | |
| /* 標頭樣式 */ | |
| #main-header { | |
| background: linear-gradient(135deg, #2c3e50, #34495e); | |
| padding: 2rem 1.5rem; | |
| border-bottom: 5px solid #1abc9c; | |
| } | |
| #main-header h1 { | |
| font-size: 2.8rem; | |
| font-weight: 700; | |
| color: #ffffff; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.2); | |
| margin-bottom: 0.5rem; | |
| text-align: center; | |
| } | |
| #main-header p { | |
| font-size: 1.2rem; | |
| color: #ecf0f1; | |
| max-width: 600px; | |
| margin: 0 auto; | |
| text-align: center; | |
| } | |
| #header-image { | |
| width: 100%; | |
| max-height: 250px; | |
| object-fit: cover; | |
| border-radius: 12px; | |
| margin-bottom: 1rem; | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
| } | |
| /* Tab 按鈕美化 */ | |
| #main-tabs > .tab-buttons > button { | |
| background-color: #f0f2f5; | |
| color: #555; | |
| font-size: 1.05rem !important; | |
| font-weight: 600 !important; | |
| border: 2px solid #e0e0e0 !important; | |
| border-radius: 10px !important; | |
| margin: 0 5px !important; | |
| padding: 10px 20px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| #main-tabs > .tab-buttons > button:hover:not(.selected) { | |
| background-color: #e8e8e8; | |
| border-color: #ccc; | |
| transform: translateY(-2px); | |
| } | |
| #main-tabs > .tab-buttons > button.selected { | |
| background-color: #1abc9c !important; | |
| color: white !important; | |
| border-color: #1abc9c !important; | |
| box-shadow: 0 4px 10px rgba(22, 160, 133, 0.3); | |
| } | |
| /* 推薦按鈕 */ | |
| #recommend-button { | |
| background: linear-gradient(135deg, #1abc9c, #16a085); | |
| color: white !important; | |
| font-size: 1.1rem !important; | |
| font-weight: bold !important; | |
| border: none !important; | |
| border-radius: 10px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| #recommend-button:hover { | |
| background: linear-gradient(135deg, #16a085, #1abc9c); | |
| transform: scale(1.02); | |
| box-shadow: 0 5px 15px rgba(22, 160, 133, 0.4); | |
| } | |
| /* 推薦輸出框 */ | |
| #recommend-output { | |
| border: 2px dashed #1abc9c; | |
| border-radius: 10px; | |
| padding: 1.5rem; | |
| text-align: center; | |
| background-color: #f8fdfc; | |
| } | |
| #recommend-output h3 { margin-top: 0.5rem; margin-bottom: 1.2rem; color: #2c3e50; font-size: 1.4rem; } | |
| #recommend-output a { font-size: 1.1rem; font-weight: bold; color: #2980b9; text-decoration: none; transition: color 0.3s ease; } | |
| #recommend-output a:hover { color: #3498db; text-decoration: underline; } | |
| /* 專案標題 */ | |
| .project-title h3 { margin: 0; padding: 0.5rem 0; font-size: 1.2rem; color: #2c3e50; } | |
| /* 專案按鈕 */ | |
| .project-button a { | |
| background-color: #3498db !important; | |
| color: #ffffff !important; | |
| border-radius: 8px !important; | |
| transition: background-color 0.3s ease, transform 0.2s ease !important; | |
| } | |
| .project-button a:hover { background-color: #2980b9 !important; transform: scale(1.03); } | |
| /* 頁腳 */ | |
| #footer { text-align: center; color: #777; font-size: 0.9rem; margin-top: 2rem; } | |
| """ | |
| # ------------------------------------------------------------------- | |
| # Gradio 應用程式主體 | |
| # ------------------------------------------------------------------- | |
| with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as demo: | |
| # 標頭區 | |
| with gr.Row(elem_id="main-header"): | |
| gr.Markdown( | |
| """ | |
| <img id="header-image" src="https://images.pexels.com/photos/220201/pexels-photo-220201.jpeg" alt="Abstract seismic waves banner"> | |
| <h1>地球物理 x Hugging Face 專案展示</h1> | |
| <p>一個地球物理學家的數位百寶箱:探索一系列由 cwadayi 打造的地震與地科應用!</p> | |
| """ | |
| ) | |
| # 主內容區:Tab 介面 | |
| with gr.Tabs(elem_id="main-tabs") as tabs: | |
| # --- Tab 1: 專案總覽 --- | |
| with gr.TabItem("📚 專案總覽", id=0): | |
| # 幸運推薦區 | |
| gr.Markdown("---") | |
| with gr.Blocks(): | |
| gr.Markdown("## ✨ 幸運推薦 ✨") | |
| recommend_btn = gr.Button("不知道從何逛起? 點我隨機推薦一個專案!", elem_id="recommend-button") | |
| recommend_out = gr.Markdown("點擊上方按鈕,讓我為您挑選一個有趣的專案!", elem_id="recommend-output") | |
| gr.Markdown("---") | |
| # 專案列表區 | |
| gr.Markdown("## 🗂️ 專案列表") | |
| for category, items in projects_data.items(): | |
| with gr.Accordion(category, open=True): | |
| for item in items: | |
| with gr.Row(variant="panel", equal_height=True): | |
| with gr.Column(scale=4): | |
| gr.Markdown(f"### {item['title']}", elem_classes="project-title") | |
| with gr.Column(scale=1, min_width=150): | |
| gr.Button( | |
| "前往應用 ↗", | |
| link=item['url'], | |
| elem_classes="project-button" | |
| ) | |
| # --- Tab 2: 範例 1 (即時編輯) --- | |
| with gr.TabItem("📖 範例 1: 地科字典", id=1): | |
| gr.Markdown("# 🚀 範例 1: 地科字典 (Live Demo & Editor)") | |
| basic_code_state = gr.State(value=TUTORIAL_CODE_BASIC) | |
| with gr.Tabs(): | |
| with gr.TabItem("🚀 Live Demo"): | |
| gr.Markdown("在這裡測試你的程式碼!修改「Edit Code」分頁中的內容,按下「套用變更」,然後在這裡查看結果。") | |
| dropdown = gr.Dropdown( | |
| label="請選擇一個地科名詞", | |
| choices=list(TUTORIAL_BASIC_INITIAL_DICT.keys()) # 從預設字典載入 | |
| ) | |
| output_textbox = gr.Textbox(label="名詞解釋", lines=5, interactive=False) | |
| with gr.TabItem("✏️ Edit Code"): | |
| gr.Markdown("在這裡修改 Python 程式碼,然後按下「套用變更」儲存。") | |
| basic_code_editor = gr.Code( | |
| label="app.py (Level 1 範例)", | |
| value=TUTORIAL_CODE_BASIC, | |
| language="python", | |
| interactive=True, | |
| lines=20 | |
| ) | |
| apply_btn_basic = gr.Button("套用變更", variant="primary") | |
| apply_msg_basic = gr.Markdown("") | |
| # --- 範例 1 的邏輯綁定 --- | |
| def update_basic_code(new_code): | |
| local_scope = {} | |
| try: | |
| parsed_code = ast.parse(new_code) | |
| exec(compile(parsed_code, "<string>", "exec"), {"__builtins__": {}}, local_scope) | |
| new_dict = local_scope.get("geo_dict") | |
| if not isinstance(new_dict, dict): | |
| raise ValueError("找不到 `geo_dict` 變數或其不是一個字典。") | |
| new_choices = list(new_dict.keys()) | |
| return { | |
| basic_code_state: new_code, | |
| dropdown: gr.Dropdown(choices=new_choices, label="請選擇一個地科名詞"), | |
| apply_msg_basic: gr.Markdown("✅ 變更已套用!請回到 'Live Demo' 分頁查看。") | |
| } | |
| except Exception as e: | |
| # 保持原狀,並顯示錯誤 | |
| return { | |
| basic_code_state: basic_code_state, | |
| dropdown: dropdown, | |
| apply_msg_basic: gr.Markdown(f"❌ 程式碼錯誤,未套用:\n```\n{e}\n```") | |
| } | |
| apply_btn_basic.click( | |
| fn=update_basic_code, | |
| inputs=[basic_code_editor], | |
| outputs=[basic_code_state, dropdown, apply_msg_basic] | |
| ) | |
| dropdown.change( | |
| fn=run_basic_code, | |
| inputs=[dropdown, basic_code_state], | |
| outputs=output_textbox | |
| ) | |
| # --- Tab 3: 範例 2 (即時編輯) --- | |
| with gr.TabItem("🌍 範例 2: 地震地圖", id=2): | |
| gr.Markdown("# 🚀 範例 2: 進階地震地圖 (Live Demo & Editor)") | |
| advanced_code_state = gr.State(value=TUTORIAL_CODE_ADVANCED) | |
| with gr.Tabs(): | |
| with gr.TabItem("🚀 Live Demo"): | |
| gr.Markdown("在這裡測試你的程式碼!修改「Edit Code」分頁中的內容,按下「套用變更」,然後在這裡點擊「搜尋」。") | |
| with gr.Row(): | |
| mag_slider = gr.Slider(minimum=4.0, maximum=8.0, value=5.0, step=0.1, label="最小地震規模 (M)") | |
| days_slider = gr.Slider(minimum=1, maximum=30, value=7, step=1, label="搜尋天數 (過去幾天)") | |
| search_button = gr.Button("搜尋並繪製地圖 🌍", variant="primary") | |
| map_output = gr.HTML(label="地震分佈圖") | |
| with gr.TabItem("✏️ Edit Code"): | |
| gr.Markdown("在這裡修改 Python 程式碼,然後按下「套用變更」儲存。") | |
| advanced_code_editor = gr.Code( | |
| label="app.py (Level 2 範例)", | |
| value=TUTORIAL_CODE_ADVANCED, | |
| language="python", | |
| interactive=True, | |
| lines=40 | |
| ) | |
| apply_btn_advanced = gr.Button("套用變更", variant="primary") | |
| apply_msg_advanced = gr.Markdown("") | |
| # --- 範例 2 的邏輯綁定 --- | |
| def update_advanced_code(new_code): | |
| # 僅儲存,我們在點擊 "Search" 時才做完整測試 | |
| return new_code, "✅ 變更已儲存!請回到 'Live Demo' 分頁並點擊 '搜尋' 來執行新程式碼。" | |
| apply_btn_advanced.click( | |
| fn=update_advanced_code, | |
| inputs=[advanced_code_editor], | |
| outputs=[advanced_code_state, apply_msg_advanced] | |
| ) | |
| search_button.click( | |
| fn=run_advanced_code, | |
| inputs=[mag_slider, days_slider, advanced_code_state], | |
| outputs=map_output | |
| ) | |
| # --- Tab 4: AI 創客空間 (教學) --- | |
| with gr.TabItem("🎓 AI 創客空間", id=3): | |
| gr.Markdown( | |
| """ | |
| <div style="padding: 1rem 0;"> | |
| <h2>🚀 打造你自己的地科 App!</h2> | |
| <p>這是一個教你如何「跟 AI (例如 Gemini) 協作」來快速打造並部署應用程式的SOP:</p> | |
| <p><strong>最好的學習方式就是動手修改!</strong> 請前往「範例 1」和「範例 2」分頁中的 <strong>"✏️ Edit Code"</strong> 標籤,直接修改程式碼並查看即時結果!</p> | |
| </div> | |
| """ | |
| ) | |
| with gr.Accordion("Level 1: 基礎字典 App 📖 (點我展開)", open=False): | |
| gr.Markdown("### 部署你自己的版本:") | |
| gr.Markdown( | |
| """ | |
| <ol> | |
| <li>到 Hugging Face 網站,點選你的頭像,選擇 <strong>"New Space"</strong>。</li> | |
| <li>為你的 Space 命名 (例如: <code>my-geo-dictionary</code>)。</li> | |
| <li>在 "Space SDK" 中選擇 <strong>"Gradio"</strong> (很重要!)。</li> | |
| <li>點擊 "Create Space"。</li> | |
| <li>在你的 Space 頁面,點選 "Files and versions" -> "Add file" -> "Create new file"。</li> | |
| <li>將檔案命名為 <strong><code>app.py</code></strong> (必須是這個名字!)。</li> | |
| <li><strong>將下面的整段程式碼,完整貼到 `app.py` 的編輯器中。</strong></li> | |
| <li>點擊 "Commit new file" (提交檔案)。</li> | |
| </ol> | |
| """ | |
| ) | |
| # [修復] 修正教學程式碼,使其可以獨立運作 | |
| standalone_basic_code = ( | |
| "import gradio as gr\n\n" + | |
| TUTORIAL_CODE_BASIC + | |
| "\n\nwith gr.Blocks() as demo:\n" + | |
| " gr.Markdown('# 🚀 我的第一個地科 App (Level 1)')\n" + | |
| " dropdown = gr.Dropdown(label='請選擇一個地科名詞', choices=list(geo_dict.keys()))\n" + | |
| " output_textbox = gr.Textbox(label='名詞解釋', lines=5, interactive=False)\n" + | |
| " dropdown.change(fn=get_definition, inputs=dropdown, outputs=output_textbox)\n\n" + | |
| "demo.launch()\n" | |
| ) | |
| gr.Code( | |
| label="app.py (Level 1 範例)", | |
| value=standalone_basic_code, | |
| language="python", | |
| interactive=False | |
| ) | |
| with gr.Accordion("Level 2: 進階地震地圖 App 🌍 (點我展開)", open=False): | |
| gr.Markdown("### 部署你自己的版本:") | |
| gr.Markdown( | |
| """ | |
| <p>這次你需要上傳 <strong>2 個檔案</strong>到你自己的 Hugging Face Space:</p> | |
| <h4>1. <code>requirements.txt</code></h4> | |
| <p>建立這個檔案並貼上下面的內容,告訴 Hugging Face 你需要 `obspy` 和 `folium`。</p> | |
| """ | |
| ) | |
| gr.Code( | |
| label="requirements.txt (必備)", | |
| value=TUTORIAL_REQUIREMENTS, | |
| interactive=False | |
| ) | |
| gr.Markdown("<h4>2. <code>app.py</code></h4><p>建立 `app.py` 檔案並貼上下面的內容。</p>") | |
| # [修復] 修正教學程式碼,使其可以獨立運作 | |
| standalone_advanced_code = ( | |
| "import gradio as gr\nimport folium\nfrom obspy.clients.fdsn import Client\nfrom obspy import UTCDateTime\n\n" + | |
| TUTORIAL_CODE_ADVANCED + | |
| "\n\nwith gr.Blocks() as demo:\n" + | |
| " gr.Markdown('# 🚀 我的進階地科 App (Level 2)')\n" + | |
| " gr.Markdown('使用 ObsPy 從 IRIS FDSN 搜尋即時地震資料,並用 Folium 繪製互動式地圖。')\n" + | |
| " with gr.Row():\n" + | |
| " mag_slider = gr.Slider(minimum=4.0, maximum=8.0, value=5.0, step=0.1, label='最小地震規模 (M)')\n" + | |
| " days_slider = gr.Slider(minimum=1, maximum=30, value=7, step=1, label='搜尋天數 (過去幾天)')\n" + | |
| " search_button = gr.Button('搜尋並繪製地圖 🌍', variant='primary')\n" + | |
| " map_output = gr.HTML(label='地震分佈圖')\n" + | |
| " search_button.click(fn=fetch_and_plot_events, inputs=[mag_slider, days_slider], outputs=map_output)\n\n" + | |
| "demo.launch()\n" | |
| ) | |
| gr.Code( | |
| label="app.py (Level 2 範例)", | |
| value=standalone_advanced_code, | |
| language="python", | |
| interactive=False | |
| ) | |
| # --- Tab 5: 關於 Spaces --- | |
| with gr.TabItem("ℹ️ 關於 Spaces", id=4): | |
| with gr.Row(equal_height=True, variant="panel"): | |
| with gr.Column(): | |
| gr.Markdown( | |
| """ | |
| ### 什麼是 Hugging Face Spaces? | |
| Hugging Face Spaces 是一個由 Hugging Face 提供的免費平台,讓開發者和創作者可以輕鬆地建立、分享和展示他們的機器學習 Demo 或網頁應用程式。 | |
| 它就像是 AI 應用的「YouTube」!您可以使用 Gradio、Streamlit 等 Python 套件快速打造互動介面,或者部署靜態網頁 (HTML/CSS/JS),甚至運行 Docker 容器。 | |
| """ | |
| ) | |
| with gr.Column(): | |
| gr.Markdown( | |
| """ | |
| ### 🚀 如何部署 Gradio 應用程式? | |
| **這本身就是一個 Gradio 應用程式!** 部署它非常簡單: | |
| 1. 在 Hugging Face 建立一個 new Space。 | |
| 2. C選擇 SDK:選擇 **"Gradio"** (這也是預設選項)。 | |
| 3. 建立 Space 後,點選 "Files and versions" 分頁。 | |
| 4. 建立一個名為 `app.py` 的檔案。 | |
| 5. **將這份 Python 程式碼**貼到 `app.py` 中並提交。 | |
| 6. (進階) 如果你的 App 需要 `obspy` 這樣的額外函式庫,請再建立一個 `requirements.txt` 檔案並列出它們。 | |
| 7. **完成!** 您的Gradio應用程式將自動建置並上線。 | |
| """ | |
| ) | |
| # 頁腳 | |
| gr.Markdown( | |
| """ | |
| <hr> | |
| <p id="footer">此頁面由 Gemini 協助生成,展示 "1022 中央地科演講" 中的 Hugging Face 專案。</p> | |
| """ | |
| ) | |
| # 7. 綁定按鈕點擊事件 | |
| recommend_btn.click( | |
| fn=recommend_project, | |
| inputs=None, | |
| outputs=recommend_out | |
| ) | |
| # 8. 啟動應用程式 | |
| if __name__ == "__main__": | |
| demo.launch() |