Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,7 +11,7 @@ import ast # [新增] 用於安全地解析字典
|
|
| 11 |
# [修復] 將字典定義為一個變數,以避免解析錯誤
|
| 12 |
# -------------------------------------------------------------------
|
| 13 |
TUTORIAL_BASIC_INITIAL_DICT = {
|
| 14 |
-
"
|
| 15 |
"地震波 (Seismic Wave)": "地震時從震源向四面八方傳播的能量波。主要分為 P 波(壓縮波,速度快)和 S 波(剪力波,速度慢),它們是我們研究地球內部的主要工具。",
|
| 16 |
"海嘯 (Tsunami)": "由海底地震、火山爆發或山崩引起的巨大海浪。它在深海中傳播速度極快(可達時速 800 公里),但波高很低;當它靠近淺海岸時,速度減慢,波高則急遽增加。"
|
| 17 |
}
|
|
@@ -25,7 +25,6 @@ TUTORIAL_CODE_BASIC = textwrap.dedent(f"""
|
|
| 25 |
# 2. 修改 "海嘯" 的解釋。
|
| 26 |
# 3. 按下方的 "套用變更" 按鈕。
|
| 27 |
# 4. 回到 "Live Demo" 分頁看看你的修改!
|
| 28 |
-
import gradio as gr
|
| 29 |
|
| 30 |
# 1. 知識庫 (你的地科字典)
|
| 31 |
geo_dict = {TUTORIAL_BASIC_INITIAL_DICT!r}
|
|
@@ -34,54 +33,17 @@ geo_dict = {TUTORIAL_BASIC_INITIAL_DICT!r}
|
|
| 34 |
def get_definition(term):
|
| 35 |
# geo_dict 會從這個程式碼的 "全域" 範圍內被找到
|
| 36 |
return geo_dict.get(term, "查無此名詞")
|
| 37 |
-
|
| 38 |
-
# --- 以下是 Gradio 啟動介面的程式碼 ---
|
| 39 |
-
# (當你獨立部署此 App 時需要)
|
| 40 |
-
|
| 41 |
-
# 取得詞條列表
|
| 42 |
-
term_list = list(geo_dict.keys())
|
| 43 |
-
|
| 44 |
-
# 建立介面
|
| 45 |
-
with gr.Blocks() as demo:
|
| 46 |
-
gr.Markdown("# 🚀 Level 1: 地科字典")
|
| 47 |
-
|
| 48 |
-
term_input = gr.Dropdown(
|
| 49 |
-
choices=term_list,
|
| 50 |
-
label="請選擇一個地科名詞",
|
| 51 |
-
value=term_list[0] if term_list else None
|
| 52 |
-
)
|
| 53 |
-
definition_output = gr.Textbox(label="名詞解釋", lines=4, interactive=False)
|
| 54 |
-
|
| 55 |
-
term_input.change(
|
| 56 |
-
fn=get_definition,
|
| 57 |
-
inputs=term_input,
|
| 58 |
-
outputs=definition_output
|
| 59 |
-
)
|
| 60 |
-
|
| 61 |
-
# 網頁載入時,自動顯示第一個
|
| 62 |
-
if term_list:
|
| 63 |
-
demo.load(
|
| 64 |
-
fn=get_definition,
|
| 65 |
-
inputs=lambda: term_list[0],
|
| 66 |
-
outputs=definition_output
|
| 67 |
-
)
|
| 68 |
-
|
| 69 |
-
# demo.launch() # 在這個整合 App 中由主程式控制
|
| 70 |
""")
|
| 71 |
|
| 72 |
# -------------------------------------------------------------------
|
| 73 |
# 範例 1 (基礎) 的動態執行 Wrapper
|
| 74 |
# -------------------------------------------------------------------
|
| 75 |
def run_basic_code(term, code_string):
|
| 76 |
-
"""
|
| 77 |
-
在受限環境中執行學生編輯的 Level 1 程式碼
|
| 78 |
-
"""
|
| 79 |
local_scope = {}
|
| 80 |
try:
|
| 81 |
# 在一個受限的環境中執行程式碼
|
| 82 |
# 這會定義 geo_dict 和 get_definition
|
| 83 |
-
|
| 84 |
-
exec(code_string, {"__builtins__": {}, "gradio": gr}, local_scope)
|
| 85 |
|
| 86 |
get_definition_func = local_scope.get("get_definition")
|
| 87 |
|
|
@@ -105,17 +67,10 @@ TUTORIAL_CODE_ADVANCED = textwrap.dedent("""
|
|
| 105 |
# 3. 按下方的 "套用變更" 按鈕。
|
| 106 |
# 4. 回到 "Live Demo" 分頁,搜尋地震看看你的修改!
|
| 107 |
|
| 108 |
-
# --- 匯入函式庫 (主程式已安全載入) ---
|
| 109 |
-
# import gradio as gr
|
| 110 |
-
# import folium
|
| 111 |
-
# from obspy.clients.fdsn import Client
|
| 112 |
-
# from obspy import UTCDateTime
|
| 113 |
-
|
| 114 |
# 1. 定義核心函式:搜尋並繪圖
|
| 115 |
def fetch_and_plot_events(min_mag, days_ago):
|
| 116 |
try:
|
| 117 |
# Folium, Client, UTCDateTime 已經被安全地匯入
|
| 118 |
-
# 你可以直接使用它們
|
| 119 |
client = Client("IRIS")
|
| 120 |
endtime = UTCDateTime.now()
|
| 121 |
starttime = endtime - (days_ago * 24 * 3600)
|
|
@@ -162,58 +117,33 @@ def fetch_and_plot_events(min_mag, days_ago):
|
|
| 162 |
fill_opacity=0.6
|
| 163 |
).add_to(m)
|
| 164 |
|
| 165 |
-
# 回傳 Folium 地圖的 HTML 內容
|
| 166 |
return m._repr_html_()
|
| 167 |
|
| 168 |
except Exception as e:
|
| 169 |
return f"<p>發生錯誤:{e}</p>"
|
| 170 |
-
|
| 171 |
-
# --- 以下是 Gradio 啟動介面的程式碼 ---
|
| 172 |
-
# (當你獨立部署此 App 時需要)
|
| 173 |
-
# with gr.Blocks() as demo:
|
| 174 |
-
# gr.Markdown("# 🚀 Level 2: 進階地震地圖")
|
| 175 |
-
# with gr.Row():
|
| 176 |
-
# mag_slider = gr.Slider(minimum=4.0, maximum=8.0, value=5.0, step=0.1, label="最小地震規模 (M)")
|
| 177 |
-
# days_slider = gr.Slider(minimum=1, maximum=30, value=7, step=1, label="搜尋天數 (過去幾天)")
|
| 178 |
-
# search_button = gr.Button("搜尋並繪製地圖 🌍", variant="primary")
|
| 179 |
-
# map_output = gr.HTML(label="地震分佈圖")
|
| 180 |
-
#
|
| 181 |
-
# search_button.click(
|
| 182 |
-
# fn=fetch_and_plot_events,
|
| 183 |
-
# inputs=[mag_slider, days_slider],
|
| 184 |
-
# outputs=map_output
|
| 185 |
-
# )
|
| 186 |
-
#
|
| 187 |
-
# demo.launch() # 在這個整合 App 中由主程式控制
|
| 188 |
""")
|
| 189 |
|
| 190 |
# -------------------------------------------------------------------
|
| 191 |
# 範例 2 (進階) 的安全沙盒與動態執行 Wrapper
|
| 192 |
# -------------------------------------------------------------------
|
| 193 |
def run_advanced_code(min_mag, days_ago, code_string):
|
| 194 |
-
""
|
| 195 |
-
在嚴格的受限環境中執行學生編輯的 Level 2 程式碼
|
| 196 |
-
"""
|
| 197 |
-
# 建立一個安全的 "全域" 環境,只允許必要的模組和內建函式
|
| 198 |
safe_globals = {
|
| 199 |
"__builtins__": {
|
| 200 |
"print": print, "Exception": Exception, "len": len, "str": str,
|
| 201 |
"float": float, "int": int, "list": list, "dict": dict, "range": range,
|
| 202 |
-
"True": True, "False": False, "None": None
|
| 203 |
-
"isinstance": isinstance, "ValueError": ValueError
|
| 204 |
},
|
| 205 |
"folium": folium,
|
| 206 |
"Client": Client,
|
| 207 |
-
"UTCDateTime": UTCDateTime
|
| 208 |
-
"gr": gr # 允許 Gradio (雖然在這個範例中沒用到,但未來可能有用)
|
| 209 |
}
|
| 210 |
|
| 211 |
local_scope = {}
|
| 212 |
|
| 213 |
try:
|
| 214 |
# 在安全沙盒中執行學生程式碼
|
| 215 |
-
|
| 216 |
-
exec(compile(parsed_code, "<string>", "exec"), safe_globals, local_scope)
|
| 217 |
|
| 218 |
fetch_func = local_scope.get("fetch_and_plot_events")
|
| 219 |
|
|
@@ -261,7 +191,6 @@ projects_data = {
|
|
| 261 |
}
|
| 262 |
|
| 263 |
def recommend_project():
|
| 264 |
-
"""從所有專案中隨機挑選一個"""
|
| 265 |
all_projects = [item for sublist in projects_data.values() for item in sublist]
|
| 266 |
recommendation = random.choice(all_projects)
|
| 267 |
return f"""
|
|
@@ -269,9 +198,6 @@ def recommend_project():
|
|
| 269 |
[**點此立即前往 🚀**]({recommendation['url']})
|
| 270 |
"""
|
| 271 |
|
| 272 |
-
# -------------------------------------------------------------------
|
| 273 |
-
# 獨立部署範例所需的 requirements.txt 內容
|
| 274 |
-
# -------------------------------------------------------------------
|
| 275 |
TUTORIAL_REQUIREMENTS = textwrap.dedent("""
|
| 276 |
gradio
|
| 277 |
obspy
|
|
@@ -434,10 +360,11 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 434 |
with gr.TabItem("🚀 Live Demo"):
|
| 435 |
gr.Markdown("在這裡測試你的程式碼!修改「Edit Code」分頁中的內容,按下「套用變更」,然後在這裡查看結果。")
|
| 436 |
|
| 437 |
-
# [修復]
|
|
|
|
| 438 |
dropdown = gr.Dropdown(
|
| 439 |
label="請選擇一個地科名詞",
|
| 440 |
-
choices=list(TUTORIAL_BASIC_INITIAL_DICT.keys()) #
|
| 441 |
)
|
| 442 |
output_textbox = gr.Textbox(label="名詞解釋", lines=5, interactive=False)
|
| 443 |
|
|
@@ -457,18 +384,13 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 457 |
|
| 458 |
# 1. "套用變更" 按鈕的邏輯
|
| 459 |
def update_basic_code(new_code):
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
1. 嘗試執行新程式碼
|
| 463 |
-
2. 如果成功,解析出新的 geo_dict
|
| 464 |
-
3. 動態更新 Live Demo 分頁中的 Dropdown 選項
|
| 465 |
-
4. 儲存新的程式碼到 state
|
| 466 |
-
"""
|
| 467 |
local_scope = {}
|
| 468 |
try:
|
| 469 |
# [修復] 使用 ast.parse 來檢查語法,然後用 exec 執行
|
| 470 |
parsed_code = ast.parse(new_code)
|
| 471 |
-
exec(compile(parsed_code, "<string>", "exec"), {"__builtins__": {}
|
| 472 |
|
| 473 |
new_dict = local_scope.get("geo_dict")
|
| 474 |
if not isinstance(new_dict, dict):
|
|
@@ -476,7 +398,6 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 476 |
|
| 477 |
# 成功!更新 Dropdown 選項並儲存程式碼
|
| 478 |
new_choices = list(new_dict.keys())
|
| 479 |
-
# [關鍵] 回傳一個字典來更新多個元件
|
| 480 |
return {
|
| 481 |
basic_code_state: new_code, # 儲存新程式碼到 state
|
| 482 |
dropdown: gr.Dropdown(choices=new_choices, label="請選擇一個地科名詞"), # 更新 Dropdown
|
|
@@ -485,8 +406,8 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 485 |
except Exception as e:
|
| 486 |
# 失敗!
|
| 487 |
return {
|
| 488 |
-
basic_code_state: basic_code_state, # 保持舊程式碼
|
| 489 |
-
dropdown: dropdown, # 保持舊 Dropdown
|
| 490 |
apply_msg_basic: gr.Markdown(f"❌ 程式碼錯誤,未套用:\n```\n{e}\n```")
|
| 491 |
}
|
| 492 |
|
|
@@ -517,7 +438,7 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 517 |
mag_slider = gr.Slider(minimum=4.0, maximum=8.0, value=5.0, step=0.1, label="最小地震規模 (M)")
|
| 518 |
days_slider = gr.Slider(minimum=1, maximum=30, value=7, step=1, label="搜尋天數 (過去幾天)")
|
| 519 |
search_button = gr.Button("搜尋並繪製地圖 🌍", variant="primary")
|
| 520 |
-
map_output = gr.HTML(label="地震分佈圖"
|
| 521 |
|
| 522 |
with gr.TabItem("✏️ Edit Code"):
|
| 523 |
gr.Markdown("在這裡修改 Python 程式碼,然後按下「套用變更」儲存。")
|
|
@@ -535,34 +456,19 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 535 |
|
| 536 |
# 1. "套用變更" 按鈕的邏輯
|
| 537 |
def update_advanced_code(new_code):
|
| 538 |
-
""
|
| 539 |
-
|
| 540 |
-
1. 僅儲存新程式碼到 state
|
| 541 |
-
2. 提示使用者去按 "搜尋"
|
| 542 |
-
(我們不在這裡執行,因為執行需要時間,而且需要參數)
|
| 543 |
-
"""
|
| 544 |
return new_code, "✅ 變更已儲存!請回到 'Live Demo' 分頁並點擊 '搜尋' 來執行新程式碼。"
|
| 545 |
|
| 546 |
apply_btn_advanced.click(
|
| 547 |
-
fn=
|
| 548 |
inputs=[advanced_code_editor],
|
| 549 |
outputs=[advanced_code_state, apply_msg_advanced]
|
| 550 |
)
|
| 551 |
|
| 552 |
# 2. "Live Demo" Search 按鈕的邏輯
|
| 553 |
-
def search_button_click(min_mag, days_ago, code_string):
|
| 554 |
-
"""
|
| 555 |
-
點擊搜尋時,顯示「載入中」訊息,
|
| 556 |
-
然後呼叫 run_advanced_code 執行沙盒程式碼
|
| 557 |
-
"""
|
| 558 |
-
yield "<p style='text-align:center; color:grey;'>🌍 正在從 IRIS 擷取資料並繪製地圖,請稍候...</p>"
|
| 559 |
-
|
| 560 |
-
# 執行學生的程式碼
|
| 561 |
-
html_result = run_advanced_code(min_mag, days_ago, code_string)
|
| 562 |
-
yield html_result
|
| 563 |
-
|
| 564 |
search_button.click(
|
| 565 |
-
fn=
|
| 566 |
inputs=[mag_slider, days_slider, advanced_code_state], # [動態] 傳入儲存的程式碼
|
| 567 |
outputs=map_output
|
| 568 |
)
|
|
@@ -594,17 +500,12 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 594 |
</ol>
|
| 595 |
"""
|
| 596 |
)
|
| 597 |
-
# 產生獨立的 Level 1 程式碼 (從範本中擷取)
|
| 598 |
-
level_1_standalone_code = (
|
| 599 |
-
"import gradio as gr\n\n" +
|
| 600 |
-
TUTORIAL_CODE_BASIC.split("# --- 以下是 Gradio 啟動介面的程式碼 ---")[0] +
|
| 601 |
-
"\n" +
|
| 602 |
-
TUTORIAL_CODE_BASIC.split("# (當你獨立部署此 App 時需要)")[1].split("# demo.launch()")[0] +
|
| 603 |
-
"\ndemo.launch()"
|
| 604 |
-
)
|
| 605 |
gr.Code(
|
| 606 |
-
label="app.py (Level 1
|
| 607 |
-
value=
|
|
|
|
|
|
|
|
|
|
| 608 |
language="python",
|
| 609 |
interactive=False
|
| 610 |
)
|
|
@@ -621,22 +522,12 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 621 |
gr.Code(
|
| 622 |
label="requirements.txt (必備)",
|
| 623 |
value=TUTORIAL_REQUIREMENTS,
|
| 624 |
-
language="text",
|
| 625 |
interactive=False
|
| 626 |
)
|
| 627 |
gr.Markdown("<h4>2. <code>app.py</code></h4><p>建立 `app.py` 檔案並貼上下面的內容。</p>")
|
| 628 |
-
# 產生獨立的 Level 2 程式碼 (從範本中擷取)
|
| 629 |
-
level_2_standalone_code = (
|
| 630 |
-
"import gradio as gr\nimport folium\nfrom obspy.clients.fdsn import Client\nfrom obspy import UTCDateTime\n\n" +
|
| 631 |
-
TUTORIAL_CODE_ADVANCED.split("# --- 匯入函式庫 (主程式已安全載入) ---")[0] + # (如果TUTORIAL_CODE_ADVANCED有開頭的話)
|
| 632 |
-
TUTORIAL_CODE_ADVANCED.split("# --- 以下是 Gradio 啟動介面的程式碼 ---")[0] +
|
| 633 |
-
"\n" +
|
| 634 |
-
TUTORIAL_CODE_ADVANCED.split("# (當你獨立部署此 App 時需要)")[1].split("# demo.launch()")[0] +
|
| 635 |
-
"\ndemo.launch()"
|
| 636 |
-
)
|
| 637 |
gr.Code(
|
| 638 |
-
label="app.py (Level 2
|
| 639 |
-
value=
|
| 640 |
language="python",
|
| 641 |
interactive=False
|
| 642 |
)
|
|
@@ -663,7 +554,7 @@ with gr.Blocks(css=css, title="地球物理 x Hugging Face 專案展示") as dem
|
|
| 663 |
3. 建立 Space 後,點選 "Files and versions" 分頁。
|
| 664 |
4. 建立一個名為 `app.py` 的檔案。
|
| 665 |
5. **將這份 Python 程式碼**貼到 `app.py` 中並提交。
|
| 666 |
-
6. (進階) 如果你的 App 需要 `obspy` 這樣的額外函式庫,請再建立一個 `requirements.txt`
|
| 667 |
7. **完成!** 您的Gradio應用程式將自動建置並上線。
|
| 668 |
"""
|
| 669 |
)
|
|
|
|
| 11 |
# [修復] 將字典定義為一個變數,以避免解析錯誤
|
| 12 |
# -------------------------------------------------------------------
|
| 13 |
TUTORIAL_BASIC_INITIAL_DICT = {
|
| 14 |
+
"板塊構造 (Plate Tectonics)": "地球的岩石圈被分成許多稱為「板塊」的巨大板塊,這些板塊在軟流圈上緩慢移動,彼此碰撞或分離,形成了火山、地震和山脈。",
|
| 15 |
"地震波 (Seismic Wave)": "地震時從震源向四面八方傳播的能量波。主要分為 P 波(壓縮波,速度快)和 S 波(剪力波,速度慢),它們是我們研究地球內部的主要工具。",
|
| 16 |
"海嘯 (Tsunami)": "由海底地震、火山爆發或山崩引起的巨大海浪。它在深海中傳播速度極快(可達時速 800 公里),但波高很低;當它靠近淺海岸時,速度減慢,波高則急遽增加。"
|
| 17 |
}
|
|
|
|
| 25 |
# 2. 修改 "海嘯" 的解釋。
|
| 26 |
# 3. 按下方的 "套用變更" 按鈕。
|
| 27 |
# 4. 回到 "Live Demo" 分頁看看你的修改!
|
|
|
|
| 28 |
|
| 29 |
# 1. 知識庫 (你的地科字典)
|
| 30 |
geo_dict = {TUTORIAL_BASIC_INITIAL_DICT!r}
|
|
|
|
| 33 |
def get_definition(term):
|
| 34 |
# geo_dict 會從這個程式碼的 "全域" 範圍內被找到
|
| 35 |
return geo_dict.get(term, "查無此名詞")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
""")
|
| 37 |
|
| 38 |
# -------------------------------------------------------------------
|
| 39 |
# 範例 1 (基礎) 的動態執行 Wrapper
|
| 40 |
# -------------------------------------------------------------------
|
| 41 |
def run_basic_code(term, code_string):
|
|
|
|
|
|
|
|
|
|
| 42 |
local_scope = {}
|
| 43 |
try:
|
| 44 |
# 在一個受限的環境中執行程式碼
|
| 45 |
# 這會定義 geo_dict 和 get_definition
|
| 46 |
+
exec(code_string, {"__builtins__": {}}, local_scope)
|
|
|
|
| 47 |
|
| 48 |
get_definition_func = local_scope.get("get_definition")
|
| 49 |
|
|
|
|
| 67 |
# 3. 按下方的 "套用變更" 按鈕。
|
| 68 |
# 4. 回到 "Live Demo" 分頁,搜尋地震看看你的修改!
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
# 1. 定義核心函式:搜尋並繪圖
|
| 71 |
def fetch_and_plot_events(min_mag, days_ago):
|
| 72 |
try:
|
| 73 |
# Folium, Client, UTCDateTime 已經被安全地匯入
|
|
|
|
| 74 |
client = Client("IRIS")
|
| 75 |
endtime = UTCDateTime.now()
|
| 76 |
starttime = endtime - (days_ago * 24 * 3600)
|
|
|
|
| 117 |
fill_opacity=0.6
|
| 118 |
).add_to(m)
|
| 119 |
|
|
|
|
| 120 |
return m._repr_html_()
|
| 121 |
|
| 122 |
except Exception as e:
|
| 123 |
return f"<p>發生錯誤:{e}</p>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
""")
|
| 125 |
|
| 126 |
# -------------------------------------------------------------------
|
| 127 |
# 範例 2 (進階) 的安全沙盒與動態執行 Wrapper
|
| 128 |
# -------------------------------------------------------------------
|
| 129 |
def run_advanced_code(min_mag, days_ago, code_string):
|
| 130 |
+
# 建立一個安全的 "全域" 環境,只允許必要的模組
|
|
|
|
|
|
|
|
|
|
| 131 |
safe_globals = {
|
| 132 |
"__builtins__": {
|
| 133 |
"print": print, "Exception": Exception, "len": len, "str": str,
|
| 134 |
"float": float, "int": int, "list": list, "dict": dict, "range": range,
|
| 135 |
+
"True": True, "False": False, "None": None
|
|
|
|
| 136 |
},
|
| 137 |
"folium": folium,
|
| 138 |
"Client": Client,
|
| 139 |
+
"UTCDateTime": UTCDateTime
|
|
|
|
| 140 |
}
|
| 141 |
|
| 142 |
local_scope = {}
|
| 143 |
|
| 144 |
try:
|
| 145 |
# 在安全沙盒中執行學生程式碼
|
| 146 |
+
exec(code_string, safe_globals, local_scope)
|
|
|
|
| 147 |
|
| 148 |
fetch_func = local_scope.get("fetch_and_plot_events")
|
| 149 |
|
|
|
|
| 191 |
}
|
| 192 |
|
| 193 |
def recommend_project():
|
|
|
|
| 194 |
all_projects = [item for sublist in projects_data.values() for item in sublist]
|
| 195 |
recommendation = random.choice(all_projects)
|
| 196 |
return f"""
|
|
|
|
| 198 |
[**點此立即前往 🚀**]({recommendation['url']})
|
| 199 |
"""
|
| 200 |
|
|
|
|
|
|
|
|
|
|
| 201 |
TUTORIAL_REQUIREMENTS = textwrap.dedent("""
|
| 202 |
gradio
|
| 203 |
obspy
|
|
|
|
| 360 |
with gr.TabItem("🚀 Live Demo"):
|
| 361 |
gr.Markdown("在這裡測試你的程式碼!修改「Edit Code」分頁中的內容,按下「套用變更」,然後在這裡查看結果。")
|
| 362 |
|
| 363 |
+
# [***** 修復 *****]
|
| 364 |
+
# 更改了 choices 的來源,不再使用 eval(splitlines())
|
| 365 |
dropdown = gr.Dropdown(
|
| 366 |
label="請選擇一個地科名詞",
|
| 367 |
+
choices=list(TUTORIAL_BASIC_INITIAL_DICT.keys()) # <-- 錯誤已修正
|
| 368 |
)
|
| 369 |
output_textbox = gr.Textbox(label="名詞解釋", lines=5, interactive=False)
|
| 370 |
|
|
|
|
| 384 |
|
| 385 |
# 1. "套用變更" 按鈕的邏輯
|
| 386 |
def update_basic_code(new_code):
|
| 387 |
+
# 執行程式碼以檢查是否有語法錯誤
|
| 388 |
+
# 並動態更新 Dropdown 的選項
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
local_scope = {}
|
| 390 |
try:
|
| 391 |
# [修復] 使用 ast.parse 來檢查語法,然後用 exec 執行
|
| 392 |
parsed_code = ast.parse(new_code)
|
| 393 |
+
exec(compile(parsed_code, "<string>", "exec"), {"__builtins__": {}}, local_scope)
|
| 394 |
|
| 395 |
new_dict = local_scope.get("geo_dict")
|
| 396 |
if not isinstance(new_dict, dict):
|
|
|
|
| 398 |
|
| 399 |
# 成功!更新 Dropdown 選項並儲存程式碼
|
| 400 |
new_choices = list(new_dict.keys())
|
|
|
|
| 401 |
return {
|
| 402 |
basic_code_state: new_code, # 儲存新程式碼到 state
|
| 403 |
dropdown: gr.Dropdown(choices=new_choices, label="請選擇一個地科名詞"), # 更新 Dropdown
|
|
|
|
| 406 |
except Exception as e:
|
| 407 |
# 失敗!
|
| 408 |
return {
|
| 409 |
+
basic_code_state: basic_code_state, # 保持舊程式碼
|
| 410 |
+
dropdown: dropdown, # 保持舊 Dropdown
|
| 411 |
apply_msg_basic: gr.Markdown(f"❌ 程式碼錯誤,未套用:\n```\n{e}\n```")
|
| 412 |
}
|
| 413 |
|
|
|
|
| 438 |
mag_slider = gr.Slider(minimum=4.0, maximum=8.0, value=5.0, step=0.1, label="最小地震規模 (M)")
|
| 439 |
days_slider = gr.Slider(minimum=1, maximum=30, value=7, step=1, label="搜尋天數 (過去幾天)")
|
| 440 |
search_button = gr.Button("搜尋並繪製地圖 🌍", variant="primary")
|
| 441 |
+
map_output = gr.HTML(label="地震分佈圖")
|
| 442 |
|
| 443 |
with gr.TabItem("✏️ Edit Code"):
|
| 444 |
gr.Markdown("在這裡修改 Python 程式碼,然後按下「套用變更」儲存。")
|
|
|
|
| 456 |
|
| 457 |
# 1. "套用變更" 按鈕的邏輯
|
| 458 |
def update_advanced_code(new_code):
|
| 459 |
+
# 僅儲存,我們在點擊 "Search" 時才做完整測試
|
| 460 |
+
apply_msg_advanced.value = "✅ 變更已儲存!請回到 'Live Demo' 分頁並點擊 '搜尋' 來執行新程式碼。"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
return new_code, "✅ 變更已儲存!請回到 'Live Demo' 分頁並點擊 '搜尋' 來執行新程式碼。"
|
| 462 |
|
| 463 |
apply_btn_advanced.click(
|
| 464 |
+
fn=lambda x: (x, "✅ 變更已儲存!請回到 'Live Demo' 分頁並點擊 '搜尋' 來執行新程式碼。"), # 簡單的儲存
|
| 465 |
inputs=[advanced_code_editor],
|
| 466 |
outputs=[advanced_code_state, apply_msg_advanced]
|
| 467 |
)
|
| 468 |
|
| 469 |
# 2. "Live Demo" Search 按鈕的邏輯
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
search_button.click(
|
| 471 |
+
fn=run_advanced_code,
|
| 472 |
inputs=[mag_slider, days_slider, advanced_code_state], # [動態] 傳入儲存的程式碼
|
| 473 |
outputs=map_output
|
| 474 |
)
|
|
|
|
| 500 |
</ol>
|
| 501 |
"""
|
| 502 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
gr.Code(
|
| 504 |
+
label="app.py (Level 1 範例)",
|
| 505 |
+
value=TUTORIAL_CODE_BASIC.replace(
|
| 506 |
+
"def get_definition(term):",
|
| 507 |
+
"import gradio as gr\n\ndef get_definition(term):\n global geo_dict"
|
| 508 |
+
) + "\n\ndemo.launch()", # 補上 import 和 launch
|
| 509 |
language="python",
|
| 510 |
interactive=False
|
| 511 |
)
|
|
|
|
| 522 |
gr.Code(
|
| 523 |
label="requirements.txt (必備)",
|
| 524 |
value=TUTORIAL_REQUIREMENTS,
|
|
|
|
| 525 |
interactive=False
|
| 526 |
)
|
| 527 |
gr.Markdown("<h4>2. <code>app.py</code></h4><p>建立 `app.py` 檔案並貼上下面的內容。</p>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
gr.Code(
|
| 529 |
+
label="app.py (Level 2 範例)",
|
| 530 |
+
value="# --- 匯入函式庫 ---\nimport gradio as gr\nimport folium\nfrom obspy.clients.fdsn import Client\nfrom obspy import UTCDateTime\n\n" + TUTORIAL_CODE_ADVANCED + "\n\ndemo.launch()", # 補上 import 和 launch
|
| 531 |
language="python",
|
| 532 |
interactive=False
|
| 533 |
)
|
|
|
|
| 554 |
3. 建立 Space 後,點選 "Files and versions" 分頁。
|
| 555 |
4. 建立一個名為 `app.py` 的檔案。
|
| 556 |
5. **將這份 Python 程式碼**貼到 `app.py` 中並提交。
|
| 557 |
+
6. (進階) 如果你的 App 需要 `obspy` 這樣的額外函式庫,請再建立一個 `requirements.txt` 檔案並列出它們。
|
| 558 |
7. **完成!** 您的Gradio應用程式將自動建置並上線。
|
| 559 |
"""
|
| 560 |
)
|