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)