cwadayi commited on
Commit
d6395fe
·
verified ·
1 Parent(s): e7f9177

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +27 -136
app.py CHANGED
@@ -11,7 +11,7 @@ import ast # [新增] 用於安全地解析字典
11
  # [修復] 將字典定義為一個變數,以避免解析錯誤
12
  # -------------------------------------------------------------------
13
  TUTORIAL_BASIC_INITIAL_DICT = {
14
- "板塊构造 (Plate Tectonics)": "地球的岩石圈被分成許多稱為「板塊」的巨大板塊,這些板塊在軟流圈上緩慢移動,彼此碰撞或分離,形成了火山、地震和山脈。",
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
- # 注意:這裡的沙盒相對寬鬆, Level 2 範例更嚴謹
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, "callable": callable,
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
- parsed_code = ast.parse(code_string)
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
- # [修復] Dropdown 的 choices 會由 "套用變更" 按鈕的邏輯動態更新
 
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__": {}, "gradio": gr}, local_scope)
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="地震分佈圖", value="<p style='text-align:center; color:grey;'>點擊 '搜尋' 按鈕來載入地圖</p>")
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=update_advanced_code,
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=search_button_click,
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=level_1_standalone_code,
 
 
 
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=level_2_standalone_code,
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` 檔案並列出它們 (就像這個 App 一樣)。
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
  )