Spaces:
Running
Running
Update core/callbacks.py
Browse files- core/callbacks.py +132 -116
core/callbacks.py
CHANGED
|
@@ -1,116 +1,132 @@
|
|
| 1 |
-
# core/callbacks.py
|
| 2 |
-
import io
|
| 3 |
-
import contextlib
|
| 4 |
-
import traceback
|
| 5 |
-
from datetime import datetime
|
| 6 |
-
import pytz
|
| 7 |
-
import difflib
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
from
|
| 11 |
-
from core.
|
| 12 |
-
from
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
exec(
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
f"
|
| 46 |
-
f"
|
| 47 |
-
f"
|
| 48 |
-
f"
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
{"keywords": ["
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
{"keywords": ["
|
| 61 |
-
{"keywords": ["
|
| 62 |
-
{"keywords": ["pws
|
| 63 |
-
]
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
f"
|
| 98 |
-
f"
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# core/callbacks.py
|
| 2 |
+
import io
|
| 3 |
+
import contextlib
|
| 4 |
+
import traceback
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import pytz
|
| 7 |
+
import difflib
|
| 8 |
+
import re # ✨ 1. 匯入 re 模組
|
| 9 |
+
|
| 10 |
+
from services import cwa_service, news_service, pws_service, usgs_service
|
| 11 |
+
from core.visits import get_current_visit_count
|
| 12 |
+
from core.notifications import send_line_notification_in_background
|
| 13 |
+
from config.data import KNOWLEDGE_BASE
|
| 14 |
+
|
| 15 |
+
# (execute_user_code 函式維持不變,此處省略以節省篇幅)
|
| 16 |
+
def execute_user_code(code_string, source_lab):
|
| 17 |
+
"""Executes user-provided code in a restricted environment and sends a notification."""
|
| 18 |
+
string_io = io.StringIO()
|
| 19 |
+
status = "✅ 成功"
|
| 20 |
+
error_info = ""
|
| 21 |
+
fig = None
|
| 22 |
+
try:
|
| 23 |
+
with contextlib.redirect_stdout(string_io):
|
| 24 |
+
local_scope = {}
|
| 25 |
+
exec("import matplotlib.pyplot as plt; import numpy as np; import cartopy.crs as ccrs; import cartopy.feature as cfeature; from matplotlib.ticker import FixedFormatter", local_scope)
|
| 26 |
+
exec(code_string, local_scope)
|
| 27 |
+
console_output = string_io.getvalue()
|
| 28 |
+
fig = local_scope.get('fig')
|
| 29 |
+
if fig is None:
|
| 30 |
+
status = "⚠️ 警告"
|
| 31 |
+
error_info = "程式碼執行完畢,但未找到 'fig' 物件。"
|
| 32 |
+
return None, f"{error_info}\nPrint 輸出:\n{console_output}"
|
| 33 |
+
success_message = f"✅ 程式碼執行成功!\n\n--- Console Output ---\n{console_output}"
|
| 34 |
+
return fig, success_message
|
| 35 |
+
except Exception:
|
| 36 |
+
status = "❌ 失敗"
|
| 37 |
+
error_info = traceback.format_exc()
|
| 38 |
+
final_message = f"❌ 程式碼執行失敗!\n\n--- Error Traceback ---\n{error_info}"
|
| 39 |
+
return None, final_message
|
| 40 |
+
finally:
|
| 41 |
+
tz = pytz.timezone('Asia/Taipei')
|
| 42 |
+
current_time = datetime.now(tz).strftime('%H:%M:%S')
|
| 43 |
+
visit_count = get_current_visit_count()
|
| 44 |
+
notification_text = (
|
| 45 |
+
f"🔬 程式碼實驗室互動!\n\n"
|
| 46 |
+
f"時間: {current_time}\n"
|
| 47 |
+
f"實驗室: {source_lab}\n"
|
| 48 |
+
f"執行狀態: {status}\n"
|
| 49 |
+
f"總載入數: {visit_count}"
|
| 50 |
+
)
|
| 51 |
+
if status == "❌ 失敗":
|
| 52 |
+
error_type = error_info.strip().split('\n')[-1]
|
| 53 |
+
notification_text += f"\n錯誤類型: {error_type}"
|
| 54 |
+
send_line_notification_in_background(notification_text)
|
| 55 |
+
|
| 56 |
+
LIVE_TOOLS = [
|
| 57 |
+
{"keywords": ["新聞", "今日新聞", "news"], "function": news_service.fetch_today_news, "name": "今日新聞"},
|
| 58 |
+
{"keywords": ["cwa地震", "顯著地震", "有感地震"], "function": cwa_service.fetch_significant_earthquakes, "name": "CWA 顯著有感地震"},
|
| 59 |
+
# --- ✨ 在這裡新增了 "eew" 和 "現在有地震預警嗎?" ---
|
| 60 |
+
{"keywords": ["地震預警", "cwa alarm", "eew", "現在有地震預警嗎?"], "function": cwa_service.fetch_cwa_alarm_list, "name": "CWA 地震預警"},
|
| 61 |
+
{"keywords": ["全球地震", "usgs", "最近全球有哪些大地震"], "function": usgs_service.fetch_global_last24h_text, "name": "全球顯著地震"},
|
| 62 |
+
{"keywords": ["pws發布", "pws info"], "function": pws_service.fetch_latest_pws_info, "name": "PWS 發布情形"},
|
| 63 |
+
{"keywords": ["pws地震", "pws alert", "pws", "最新的 pws 地震警報"], "function": pws_service.fetch_cwa_pws_earthquake_info, "name": "PWS 地震警報"},
|
| 64 |
+
]
|
| 65 |
+
|
| 66 |
+
def find_best_match_from_kb(user_input, knowledge_base, threshold=0.6):
|
| 67 |
+
"""(原有的函式) 從靜態知識庫中尋找最佳匹配的答案。"""
|
| 68 |
+
best_score = 0
|
| 69 |
+
best_answer = "這個問題很有趣,不過我的知識庫目前還沒有收錄相關的答案。您可以試著問我關於**課程評分、Anaconda安裝、Colab與Codespaces的差別**等問題!"
|
| 70 |
+
best_match_keyword = None
|
| 71 |
+
for category, entries in knowledge_base.items():
|
| 72 |
+
for entry in entries:
|
| 73 |
+
for keyword in entry['keywords']:
|
| 74 |
+
score = difflib.SequenceMatcher(None, user_input.lower(), keyword.lower()).ratio()
|
| 75 |
+
if score > best_score:
|
| 76 |
+
best_score = score
|
| 77 |
+
best_match_keyword = keyword
|
| 78 |
+
best_answer = entry['answer']
|
| 79 |
+
if best_score >= threshold:
|
| 80 |
+
return best_answer
|
| 81 |
+
elif best_match_keyword:
|
| 82 |
+
return f"這個問題我不是很確定,您是指 **「{best_match_keyword}」** 嗎?\n\n我目前找到的相關資料如下:\n\n{best_answer}"
|
| 83 |
+
else:
|
| 84 |
+
return best_answer
|
| 85 |
+
|
| 86 |
+
# --- ✨ 2. 這是修改後的核心函式 ---
|
| 87 |
+
def ai_chatbot_with_kb(message, history):
|
| 88 |
+
"""
|
| 89 |
+
處理聊天機器人互動的主函式。
|
| 90 |
+
優先檢查進階查詢格式,其次檢查即時工具,最後查詢靜態知識庫。
|
| 91 |
+
"""
|
| 92 |
+
# (通知邏輯維持不變)
|
| 93 |
+
tz = pytz.timezone('Asia/Taipei')
|
| 94 |
+
current_time = datetime.now(tz).strftime('%H:%M:%S')
|
| 95 |
+
visit_count = get_current_visit_count()
|
| 96 |
+
notification_text = (
|
| 97 |
+
f"🤖 AI 助教被提問!\n\n"
|
| 98 |
+
f"時間: {current_time}\n"
|
| 99 |
+
f"使用者問題:\n「{message}」\n\n"
|
| 100 |
+
f"總載入數: {visit_count}"
|
| 101 |
+
)
|
| 102 |
+
send_line_notification_in_background(notification_text)
|
| 103 |
+
|
| 104 |
+
user_message = message.strip() # 保留大小寫以符合可能的未來需求,但比對時再轉小寫
|
| 105 |
+
|
| 106 |
+
# 步驟 1: 檢查是否為進階地震查詢格式
|
| 107 |
+
# 使用正規表示式來匹配 "查詢 YYYY-MM-DD 到 YYYY-MM-DD 規模 X.X 以上地震"
|
| 108 |
+
pattern = r"查詢\s*(\d{4}-\d{2}-\d{2})\s*到\s*(\d{4}-\d{2}-\d{2})\s*規模\s*(\d+(?:\.\d+)?)\s*以上地震"
|
| 109 |
+
match = re.search(pattern, user_message)
|
| 110 |
+
|
| 111 |
+
if match:
|
| 112 |
+
start_date, end_date, magnitude = match.groups()
|
| 113 |
+
print(f"🔍 觸發進階地震查詢:{start_date} 到 {end_date}, M≥{magnitude}")
|
| 114 |
+
try:
|
| 115 |
+
# 呼叫 usgs_service 中的新函式
|
| 116 |
+
return usgs_service.fetch_usgs_earthquakes_by_date(start_date, end_date, float(magnitude))
|
| 117 |
+
except Exception as e:
|
| 118 |
+
return f"❌ 執行進階查詢時發生錯誤:{e}"
|
| 119 |
+
|
| 120 |
+
# 步驟 2: 如果不是進階查詢,則檢查是否觸發任何即時工具 (關鍵字比對)
|
| 121 |
+
user_message_lower = user_message.lower()
|
| 122 |
+
for tool in LIVE_TOOLS:
|
| 123 |
+
for keyword in tool["keywords"]:
|
| 124 |
+
if keyword in user_message_lower:
|
| 125 |
+
try:
|
| 126 |
+
print(f"🔍 觸發即時工具:{tool['name']}")
|
| 127 |
+
return tool["function"]()
|
| 128 |
+
except Exception as e:
|
| 129 |
+
return f"❌ 執行「{tool['name']}」工具時發生錯誤:{e}"
|
| 130 |
+
|
| 131 |
+
# 步驟 3: 如果都沒有觸發,則查詢靜態知識庫
|
| 132 |
+
return find_best_match_from_kb(user_message_lower, KNOWLEDGE_BASE)
|