Spaces:
Running
Running
File size: 7,300 Bytes
536a9af 7a6cc2f 536a9af b206b16 536a9af 7a6cc2f 536a9af 7a6cc2f 536a9af b206b16 7a6cc2f b206b16 7a6cc2f b206b16 7a6cc2f 536a9af 7a6cc2f 536a9af 7a6cc2f 536a9af |
1 2 3 4 5 6 7 8 9 10 11 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# core/callbacks.py
import io
import contextlib
import traceback
from datetime import datetime
import pytz
import difflib
import re
from services import cwa_service, news_service, pws_service, usgs_service
from core.visits import get_current_visit_count
from core.notifications import send_line_notification_in_background
from config.data import KNOWLEDGE_BASE
# (execute_user_code 函式維持不變,此處省略以節省篇幅)
def execute_user_code(code_string, source_lab):
"""Executes user-provided code in a restricted environment and sends a notification."""
string_io = io.StringIO()
status = "✅ 成功"
error_info = ""
fig = None
try:
with contextlib.redirect_stdout(string_io):
local_scope = {}
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)
exec(code_string, local_scope)
console_output = string_io.getvalue()
fig = local_scope.get('fig')
if fig is None:
status = "⚠️ 警告"
error_info = "程式碼執行完畢,但未找到 'fig' 物件。"
return None, f"{error_info}\nPrint 輸出:\n{console_output}"
success_message = f"✅ 程式碼執行成功!\n\n--- Console Output ---\n{console_output}"
return fig, success_message
except Exception:
status = "❌ 失敗"
error_info = traceback.format_exc()
final_message = f"❌ 程式碼執行失敗!\n\n--- Error Traceback ---\n{error_info}"
return None, final_message
finally:
tz = pytz.timezone('Asia/Taipei')
current_time = datetime.now(tz).strftime('%H:%M:%S')
visit_count = get_current_visit_count()
notification_text = (
f"🔬 程式碼實驗室互動!\n\n"
f"時間: {current_time}\n"
f"實驗室: {source_lab}\n"
f"執行狀態: {status}\n"
f"總載入數: {visit_count}"
)
if status == "❌ 失敗":
error_type = error_info.strip().split('\n')[-1]
notification_text += f"\n錯誤類型: {error_type}"
send_line_notification_in_background(notification_text)
LIVE_TOOLS = [
{"keywords": ["新聞", "今日新聞", "news"], "function": news_service.fetch_today_news, "name": "今日新聞"},
{"keywords": ["cwa地震", "顯著地震", "有感地震"], "function": cwa_service.fetch_significant_earthquakes, "name": "CWA 顯著有感地震"},
{"keywords": ["地震預警", "cwa alarm", "eew", "現在有地震預警嗎?"], "function": cwa_service.fetch_cwa_alarm_list, "name": "CWA 地震預警"},
{"keywords": ["全球地震", "usgs", "最近全球有哪些大地震"], "function": usgs_service.fetch_global_last24h_text, "name": "全球顯著地震"},
{"keywords": ["pws發布", "pws info"], "function": pws_service.fetch_latest_pws_info, "name": "PWS 發布情形"},
{"keywords": ["pws地震", "pws alert", "pws", "最新的 pws 地震警報"], "function": pws_service.fetch_cwa_pws_earthquake_info, "name": "PWS 地震警報"},
]
def find_best_match_from_kb(user_input, knowledge_base, threshold=0.6):
"""(原有的函式) 從靜態知識庫中尋找最佳匹配的答案。"""
best_score = 0
best_answer = "這個問題很有趣,不過我的知識庫目前還沒有收錄相關的答案。您可以試著問我關於**課程評分、Anaconda安裝、Colab與Codespaces的差別**等問題!"
best_match_keyword = None
for category, entries in knowledge_base.items():
for entry in entries:
for keyword in entry['keywords']:
score = difflib.SequenceMatcher(None, user_input.lower(), keyword.lower()).ratio()
if score > best_score:
best_score = score
best_match_keyword = keyword
best_answer = entry['answer']
if best_score >= threshold:
return best_answer
elif best_match_keyword:
return f"這個問題我不是很確定,您是指 **「{best_match_keyword}」** 嗎?\n\n我目前找到的相關資料如下:\n\n{best_answer}"
else:
return best_answer
# --- ✨ 以下是修改後的核心函式 ---
def ai_chatbot_with_kb(message, history):
"""
處理聊天機器人互動的主函式。
優先處理快捷指令,其次是進階查詢,再其次是關鍵字工具,最後查詢靜態知識庫。
"""
# (通知邏輯維持不變)
tz = pytz.timezone('Asia/Taipei')
current_time = datetime.now(tz).strftime('%H:%M:%S')
visit_count = get_current_visit_count()
notification_text = (
f"🤖 AI 助教被提問!\n\n"
f"時間: {current_time}\n"
f"使用者問題:\n「{message}」\n\n"
f"總載入數: {visit_count}"
)
send_line_notification_in_background(notification_text)
user_message = message.strip()
# 步驟 1: 檢查是否為快捷指令
if user_message.startswith('#'):
# ✨ 新增 elif 區塊來處理 #7
if user_message == '#7':
print("💡 觸發進階查詢說明 (#7)")
return "好的,請依照以下格式提供查詢的日期範圍與規模:\n`查詢 YYYY-MM-DD 到 YYYY-MM-DD 規模 X.X 以上地震`\n\n例如:`查詢 2024-04-01 到 2024-04-07 規模 6.0 以上地震`"
try:
tool_index = int(user_message[1:]) - 1
if 0 <= tool_index < len(LIVE_TOOLS):
tool = LIVE_TOOLS[tool_index]
print(f"🚀 觸發快捷指令:{tool['name']}")
return tool["function"]()
else:
return f"⚠️ 無效的快捷指令!請輸入 #1 到 #{len(LIVE_TOOLS)} 之間的數字,或 #7 查看進階查詢說明。"
except (ValueError, IndexError):
return "⚠️ 快捷指令格式錯誤!請輸入像 `#1` 這樣的格式。"
# 步驟 2: 檢查是否為進階地震查詢格式
pattern = r"查詢\s*(\d{4}-\d{2}-\d{2})\s*到\s*(\d{4}-\d{2}-\d{2})\s*規模\s*(\d+(?:\.\d+)?)\s*以上地震"
match = re.search(pattern, user_message)
if match:
start_date, end_date, magnitude = match.groups()
print(f"🔍 觸發進階地震查詢:{start_date} 到 {end_date}, M≥{magnitude}")
try:
return usgs_service.fetch_usgs_earthquakes_by_date(start_date, end_date, float(magnitude))
except Exception as e:
return f"❌ 執行進階查詢時發生錯誤:{e}"
# 步驟 3: 如果都不是,則檢查是否觸發即時工具 (關鍵字比對)
user_message_lower = user_message.lower()
for tool in LIVE_TOOLS:
for keyword in tool["keywords"]:
if keyword in user_message_lower:
try:
print(f"🔍 觸發即時工具:{tool['name']}")
return tool["function"]()
except Exception as e:
return f"❌ 執行「{tool['name']}」工具時發生錯誤:{e}"
# 步驟 4: 如果都沒有觸發,則查詢靜態知識庫
return find_best_match_from_kb(user_message_lower, KNOWLEDGE_BASE)
|