File size: 5,806 Bytes
57b8366
 
 
 
 
 
3aab70e
57b8366
3aab70e
57b8366
 
 
 
8eb449d
57b8366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3aab70e
 
 
4e2f039
 
a1bc869
3aab70e
7fa4021
3aab70e
57b8366
3aab70e
 
8eb449d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57b8366
8eb449d
3aab70e
 
8eb449d
3aab70e
57b8366
 
 
 
 
 
 
 
 
 
 
3aab70e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# core/callbacks.py
import io
import contextlib
import traceback
from datetime import datetime
import pytz
import difflib

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 顯著有感地震"},
    # --- ✨ 在這裡新增了 "eew" 和 "現在有地震預警嗎?" ---
    {"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.lower().strip()

    # 步驟 1: 檢查是否觸發任何即時工具
    for tool in LIVE_TOOLS:
        for keyword in tool["keywords"]:
            if keyword in user_message:
                try:
                    # 如果觸發,執行對應的函式並回傳結果
                    print(f"🔍 觸發即時工具:{tool['name']}")
                    return tool["function"]()
                except Exception as e:
                    return f"❌ 執行「{tool['name']}」工具時發生錯誤:{e}"

    # 步驟 2: 如果沒有觸發工具,則查詢靜態知識庫 (使用舊有的模糊比對)
    return find_best_match_from_kb(user_message, KNOWLEDGE_BASE)