cwadayi commited on
Commit
536a9af
·
verified ·
1 Parent(s): 19ad81e

Update core/callbacks.py

Browse files
Files changed (1) hide show
  1. 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
- from services import cwa_service, news_service, pws_service, usgs_service
10
- from core.visits import get_current_visit_count
11
- from core.notifications import send_line_notification_in_background
12
- from config.data import KNOWLEDGE_BASE
13
-
14
- # (execute_user_code 函式維持不變,此處省略以節省篇幅)
15
- def execute_user_code(code_string, source_lab):
16
- """Executes user-provided code in a restricted environment and sends a notification."""
17
- string_io = io.StringIO()
18
- status = "✅ 成功"
19
- error_info = ""
20
- fig = None
21
- try:
22
- with contextlib.redirect_stdout(string_io):
23
- local_scope = {}
24
- 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)
25
- exec(code_string, local_scope)
26
- console_output = string_io.getvalue()
27
- fig = local_scope.get('fig')
28
- if fig is None:
29
- status = "⚠️ 警告"
30
- error_info = "程式碼執行完畢,但未找到 'fig' 物件。"
31
- return None, f"{error_info}\nPrint 輸出:\n{console_output}"
32
- success_message = f"✅ 程式碼執行成功!\n\n--- Console Output ---\n{console_output}"
33
- return fig, success_message
34
- except Exception:
35
- status = "❌ 失敗"
36
- error_info = traceback.format_exc()
37
- final_message = f"❌ 程式碼執行失敗!\n\n--- Error Traceback ---\n{error_info}"
38
- return None, final_message
39
- finally:
40
- tz = pytz.timezone('Asia/Taipei')
41
- current_time = datetime.now(tz).strftime('%H:%M:%S')
42
- visit_count = get_current_visit_count()
43
- notification_text = (
44
- f"🔬 程式碼實驗室互動!\n\n"
45
- f"時間: {current_time}\n"
46
- f"實驗室: {source_lab}\n"
47
- f"執行狀態: {status}\n"
48
- f"總載入數: {visit_count}"
49
- )
50
- if status == "❌ 失敗":
51
- error_type = error_info.strip().split('\n')[-1]
52
- notification_text += f"\n錯誤類型: {error_type}"
53
- send_line_notification_in_background(notification_text)
54
-
55
- LIVE_TOOLS = [
56
- {"keywords": ["新聞", "今日新聞", "news"], "function": news_service.fetch_today_news, "name": "今日新聞"},
57
- {"keywords": ["cwa地震", "顯著地震", "有感地震"], "function": cwa_service.fetch_significant_earthquakes, "name": "CWA 顯著有感地震"},
58
- # --- 在這裡新增了 "eew" "現在有地震預警嗎?" ---
59
- {"keywords": ["地震預警", "cwa alarm", "eew", "現在有地震預警嗎?"], "function": cwa_service.fetch_cwa_alarm_list, "name": "CWA 地震預警"},
60
- {"keywords": ["全球地震", "usgs", "最近全球有哪些大地震"], "function": usgs_service.fetch_global_last24h_text, "name": "全球顯著地震"},
61
- {"keywords": ["pws發布", "pws info"], "function": pws_service.fetch_latest_pws_info, "name": "PWS 發布情形"},
62
- {"keywords": ["pws地震", "pws alert", "pws", "最新的 pws 地震警報"], "function": pws_service.fetch_cwa_pws_earthquake_info, "name": "PWS 地震警報"},
63
- ]
64
-
65
- def find_best_match_from_kb(user_input, knowledge_base, threshold=0.6):
66
- """(原有的函式) 從靜態知識庫中尋找最佳匹配的答案。"""
67
- best_score = 0
68
- best_answer = "這個問題很有趣,不過我的知識庫目前還沒有收錄相關的答案。您可以試著問我關於**課程評分、Anaconda安裝、Colab與Codespaces的差別**等問題!"
69
- best_match_keyword = None
70
- for category, entries in knowledge_base.items():
71
- for entry in entries:
72
- for keyword in entry['keywords']:
73
- score = difflib.SequenceMatcher(None, user_input.lower(), keyword.lower()).ratio()
74
- if score > best_score:
75
- best_score = score
76
- best_match_keyword = keyword
77
- best_answer = entry['answer']
78
- if best_score >= threshold:
79
- return best_answer
80
- elif best_match_keyword:
81
- return f"這個問題我不是很確定,您是指 **「{best_match_keyword}」** 嗎?\n\n我目前找到的相關資料如下:\n\n{best_answer}"
82
- else:
83
- return best_answer
84
-
85
- def ai_chatbot_with_kb(message, history):
86
- """
87
- 處理聊天機器人互動的主函式。
88
- 優先檢查是否觸發即時工具,若無,則查詢靜態知識庫。
89
- """
90
- # (通知邏輯維持不變)
91
- tz = pytz.timezone('Asia/Taipei')
92
- current_time = datetime.now(tz).strftime('%H:%M:%S')
93
- visit_count = get_current_visit_count()
94
- notification_text = (
95
- f"🤖 AI 助教被提問!\n\n"
96
- f"時間: {current_time}\n"
97
- f"使用者問題:\n「{message}」\n\n"
98
- f"總載入數: {visit_count}"
99
- )
100
- send_line_notification_in_background(notification_text)
101
-
102
- user_message = message.lower().strip()
103
-
104
- # 步驟 1: 檢查是否觸發任何即時工具
105
- for tool in LIVE_TOOLS:
106
- for keyword in tool["keywords"]:
107
- if keyword in user_message:
108
- try:
109
- # 如果觸發,執行對應的函式並回傳結果
110
- print(f"🔍 觸發即時工具:{tool['name']}")
111
- return tool["function"]()
112
- except Exception as e:
113
- return f" 執行「{tool['name']}」工具時發生錯誤:{e}"
114
-
115
- # 步驟 2: 如果沒有觸發工具,則查詢靜態知識庫 (使用舊有的模糊比對)
116
- return find_best_match_from_kb(user_message, KNOWLEDGE_BASE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)