From 3034c3671727c2d94b0723c8a5d87b086913e58e Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 27 Feb 2025 20:21:21 +0800 Subject: [PATCH] =?UTF-8?q?v0.1.5=E6=8E=A8=E7=90=86=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E7=9A=84ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 想用gradio写个ui,无果 放弃 写了一个单独的推理界面可视化ui,有bug之后修 --- _thingking.bat | 3 + reasoning_log.json | 1 + requirements.txt | Bin 896 -> 1106 bytes src/plugins/chat/.stream.py | 201 ----------------- src/plugins/chat/__init__.py | 1 - src/plugins/chat/bot.py | 4 +- src/plugins/chat/bot_config_toml | 33 +++ src/plugins/chat/config.py | 10 +- src/plugins/chat/cq_code.py | 6 +- src/plugins/chat/info_gui.py | 76 ------- src/plugins/chat/llm_generator.py | 173 ++++++--------- src/plugins/chat/reasoning_gui.py | 343 ++++++++++++++++++++++++++++++ 12 files changed, 450 insertions(+), 401 deletions(-) create mode 100644 _thingking.bat create mode 100644 reasoning_log.json delete mode 100644 src/plugins/chat/.stream.py create mode 100644 src/plugins/chat/bot_config_toml delete mode 100644 src/plugins/chat/info_gui.py create mode 100644 src/plugins/chat/reasoning_gui.py diff --git a/_thingking.bat b/_thingking.bat new file mode 100644 index 000000000..b4558ba18 --- /dev/null +++ b/_thingking.bat @@ -0,0 +1,3 @@ +call conda activate niuniu +cd . + diff --git a/reasoning_log.json b/reasoning_log.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/reasoning_log.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b74e55a31d6e2202e45a958268d1633effc6a20d..684627886548ee785742eb557605ab9648f88b7a 100644 GIT binary patch delta 218 zcmZo*zr-;?NidorjUkbtn4ttn7BFNo#BNmF$S7XQP{fb}gqaLU48{z047LnL40;R( zn@=)&GA5TWrAFJeviWOc*DxVa@{pTTurQ diff --git a/src/plugins/chat/.stream.py b/src/plugins/chat/.stream.py deleted file mode 100644 index 44fed5b70..000000000 --- a/src/plugins/chat/.stream.py +++ /dev/null @@ -1,201 +0,0 @@ -from typing import Dict, List, Optional -from dataclasses import dataclass -import time -import threading -import asyncio -from .message import Message -from .storage import MessageStorage -from .topic_identifier import TopicIdentifier -from ...common.database import Database -import random - -@dataclass -class Topic: - id: str - name: str - messages: List[Message] - created_time: float - last_active_time: float - message_count: int - is_active: bool = True - -class MessageStream: - def __init__(self): - self.storage = MessageStorage() - self.active_topics: Dict[int, List[Topic]] = {} # group_id -> topics - self.topic_identifier = TopicIdentifier() - self.db = Database.get_instance() - self.topic_lock = threading.Lock() - - async def start(self): - """异步初始化""" - asyncio.create_task(self._monitor_topics()) - - async def _monitor_topics(self): - """定时监控主题状态""" - while True: - await asyncio.sleep(30) - self._print_active_topics() - self._check_inactive_topics() - self._remove_small_topic() - - def _print_active_topics(self): - """打印当前活跃主题""" - print("\n" + "="*50) - print("\033[1;36m【当前活跃主题】\033[0m") # 青色 - for group_id, topics in self.active_topics.items(): - active_topics = [t for t in topics if t.is_active] - if active_topics: - print(f"\n\033[1;33m群组 {group_id}:\033[0m") # 黄色 - for topic in active_topics: - print(f"\033[1;32m- {topic.name}\033[0m (消息数: {topic.message_count})") # 绿色 - - def _check_inactive_topics(self): - """检查并处理不活跃主题""" - current_time = time.time() - INACTIVE_TIME = 600 # 60秒内没有新增内容 - # MAX_MESSAGES_WITHOUT_TOPIC = 5 # 最新5条消息都不是这个主题就归档 - - with self.topic_lock: - for group_id, topics in self.active_topics.items(): - - for topic in topics: - if not topic.is_active: - continue - - # 检查是否超过不活跃时间 - time_inactive = current_time - topic.last_active_time - if time_inactive > INACTIVE_TIME: - # print(f"\033[1;33m[主题超时]\033[0m {topic.name} 已有 {int(time_inactive)} 秒未更新") - self._archive_topic(group_id, topic) - topic.is_active = False - continue - - - def _archive_topic(self, group_id: int, topic: Topic): - """将主题存档到数据库""" - # 查找是否有同名主题 - existing_topic = self.db.db.archived_topics.find_one({ - "name": topic.name - }) - - if existing_topic: - # 合并消息列表并去重 - existing_messages = existing_topic.get("messages", []) - new_messages = [ - { - "user_id": msg.user_id, - "plain_text": msg.plain_text, - "time": msg.time - } for msg in topic.messages - ] - - # 使用集合去重 - seen_texts = set() - unique_messages = [] - - # 先处理现有消息 - for msg in existing_messages: - if msg["plain_text"] not in seen_texts: - seen_texts.add(msg["plain_text"]) - unique_messages.append(msg) - - # 再处理新消息 - for msg in new_messages: - if msg["plain_text"] not in seen_texts: - seen_texts.add(msg["plain_text"]) - unique_messages.append(msg) - - # 更新主题信息 - self.db.db.archived_topics.update_one( - {"_id": existing_topic["_id"]}, - { - "$set": { - "messages": unique_messages, - "message_count": len(unique_messages), - "last_active_time": max(existing_topic["last_active_time"], topic.last_active_time), - "last_merged_time": time.time() - } - } - ) - print(f"\033[1;33m[主题合并]\033[0m 主题 {topic.name} 已合并,总消息数: {len(unique_messages)}") - - else: - # 存储新主题 - self.db.db.archived_topics.insert_one({ - "topic_id": topic.id, - "name": topic.name, - "messages": [ - { - "user_id": msg.user_id, - "plain_text": msg.plain_text, - "time": msg.time - } for msg in topic.messages - ], - "created_time": topic.created_time, - "last_active_time": topic.last_active_time, - "message_count": topic.message_count - }) - print(f"\033[1;32m[主题存档]\033[0m {topic.name} (群组: {group_id})") - - async def process_message(self, message: Message,topic:List[str]): - """处理新消息,返回识别出的主题列表""" - # 存储消息(包含主题) - await self.storage.store_message(message, topic) - self._update_topics(message.group_id, topic, message) - - def _update_topics(self, group_id: int, topic_names: List[str], message: Message) -> None: - """更新群组主题""" - current_time = time.time() - - # 确保群组存在 - if group_id not in self.active_topics: - self.active_topics[group_id] = [] - - # 查找现有主题 - for topic_name in topic_names: - for topic in self.active_topics[group_id]: - if topic.name == topic_name: - topic.messages.append(message) - topic.last_active_time = current_time - topic.message_count += 1 - print(f"\033[1;35m[更新主题]\033[0m {topic_name}") # 绿色 - break - else: - # 创建新主题 - new_topic = Topic( - id=f"{group_id}_{int(current_time)}", - name=topic_name, - messages=[message], - created_time=current_time, - last_active_time=current_time, - message_count=1 - ) - self.active_topics[group_id].append(new_topic) - - self._check_inactive_topics() - - def _remove_small_topic(self): - """随机移除一个12小时内没有新增内容的小主题""" - try: - current_time = time.time() - inactive_time = 12 * 3600 # 24小时 - - # 获取所有符合条件的主题 - topics = list(self.db.db.archived_topics.find({ - "message_count": {"$lt": 3}, # 消息数小于2 - "last_active_time": {"$lt": current_time - inactive_time} - })) - - if not topics: - return - - # 随机选择一个主题删除 - topic_to_remove = random.choice(topics) - inactive_hours = (current_time - topic_to_remove.get("last_active_time", 0)) / 3600 - - self.db.db.archived_topics.delete_one({"_id": topic_to_remove["_id"]}) - print(f"\033[1;31m[主题清理]\033[0m 已移除小主题: {topic_to_remove['name']} " - f"不活跃时间: {int(inactive_hours)}小时)") - except Exception as e: - print(f"\033[1;31m[错误]\033[0m 移除小主题失败: {str(e)}") diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index de1fa4e19..bafcaa238 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -11,7 +11,6 @@ from ..schedule.schedule_generator import bot_schedule from .willing_manager import willing_manager - # 获取驱动器 driver = get_driver() diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 48e967d7d..09fc3aa08 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -6,7 +6,6 @@ from .llm_generator import LLMResponseGenerator from .message_stream import MessageStream, MessageStreamContainer from .topic_identifier import topic_identifier from random import random -from nonebot.log import logger from .group_info_manager import GroupInfoManager # 导入群信息管理器 from .emoji_manager import emoji_manager # 导入表情包管理器 import time @@ -15,7 +14,6 @@ from .cq_code import CQCode # 导入CQCode模块 from .message_send_control import message_sender # 导入消息发送控制器 from .message import Message_Thinking # 导入 Message_Thinking 类 from .relationship_manager import relationship_manager -from .prompt_builder import prompt_builder from .willing_manager import willing_manager # 导入意愿管理器 @@ -132,7 +130,7 @@ class ChatBot: ) current_willing = willing_manager.get_willing(event.group_id) - + print(f"\033[1;32m[{current_time}][{message.group_name}]{message.user_nickname}:\033[0m {message.processed_plain_text}\033[1;36m[回复意愿:{current_willing:.2f}][概率:{reply_probability:.1f}]\033[0m") response = "" if random() < reply_probability: diff --git a/src/plugins/chat/bot_config_toml b/src/plugins/chat/bot_config_toml new file mode 100644 index 000000000..1cef503af --- /dev/null +++ b/src/plugins/chat/bot_config_toml @@ -0,0 +1,33 @@ +[database] +host = "127.0.0.1" +port = 27017 +name = "MegBot" + +[bot] +qq = #填入你的机器人QQ + +[message] +min_text_length = 2 +max_context_size = 15 +emoji_chance = 0.2 + +[emoji] +check_interval = 120 +register_interval = 10 + +[response] +model_r1_probability = 0.2 #使用R1回复的概率(没启用) + + +[groups] +read_allowed = [ + #可以读取消息的群 +] + +talk_allowed = [ + #可以回复消息的群 +] + +talk_frequency_down = [ + #降低回复频率的群 +] diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 06ac85b5a..44e14827d 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -10,11 +10,11 @@ import tomli # 添加这行导入 # logger.remove() # # 只禁用 INFO 级别的日志输出到控制台 -# logging.getLogger('nonebot').handlers.clear() -# console_handler = logging.StreamHandler() -# console_handler.setLevel(logging.WARNING) # 只输出 WARNING 及以上级别 -# logging.getLogger('nonebot').addHandler(console_handler) -# logging.getLogger('nonebot').setLevel(logging.WARNING) +logging.getLogger('nonebot').handlers.clear() +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.WARNING) # 只输出 WARNING 及以上级别 +logging.getLogger('nonebot').addHandler(console_handler) +logging.getLogger('nonebot').setLevel(logging.WARNING) @dataclass class BotConfig: diff --git a/src/plugins/chat/cq_code.py b/src/plugins/chat/cq_code.py index b92e5d420..41e0a31a5 100644 --- a/src/plugins/chat/cq_code.py +++ b/src/plugins/chat/cq_code.py @@ -98,12 +98,10 @@ class CQCode: # 添加更多请求头 headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8', + 'User-Agent': 'QQ/8.9.68.11565 CFNetwork/1220.1 Darwin/20.3.0', + 'Accept': 'image/*;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', - 'Referer': 'https://multimedia.nt.qq.com.cn', - 'Origin': 'https://multimedia.nt.qq.com.cn', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' } diff --git a/src/plugins/chat/info_gui.py b/src/plugins/chat/info_gui.py deleted file mode 100644 index 582efc9e5..000000000 --- a/src/plugins/chat/info_gui.py +++ /dev/null @@ -1,76 +0,0 @@ -import gradio as gr -import time -import threading -from typing import Dict, List -from .message import Message - -class MessageWindow: - def __init__(self): - self.interface = None - self._running = False - self.messages_history = [] - - def _create_window(self): - """创建Gradio界面""" - with gr.Blocks(title="实时消息监控") as self.interface: - with gr.Row(): - with gr.Column(): - self.message_box = gr.Dataframe( - headers=["时间", "群号", "发送者", "消息内容"], - datatype=["str", "str", "str", "str"], - row_count=20, - col_count=(4, "fixed"), - interactive=False, - wrap=True - ) - - # 每1秒自动刷新 - self.interface.load(self._update_display, None, [self.message_box], every=1) - - # 启动界面 - self.interface.queue() - self._running = True - self.interface.launch(share=False, server_port=7860) - - def _update_display(self): - """更新消息显示""" - display_data = [] - for msg in self.messages_history[-1000:]: # 只显示最近1000条消息 - time_str = time.strftime("%H:%M:%S", time.localtime(msg["time"])) - display_data.append([ - time_str, - str(msg["group_id"]), - f"{msg['user_nickname']}({msg['user_id']})", - msg["plain_text"] - ]) - return display_data - - def update_messages(self, group_id: int, messages: List[Message]): - """接收新消息更新""" - for msg in messages: - self.messages_history.append({ - "time": msg.time, - "group_id": group_id, - "user_id": msg.user_id, - "user_nickname": msg.user_nickname, - "plain_text": msg.plain_text - }) - - # 保持最多存储1000条消息 - if len(self.messages_history) > 1000: - self.messages_history = self.messages_history[-1000:] - - def start(self): - """启动窗口""" - # 在新线程中启动窗口 - threading.Thread(target=self._create_window, daemon=True).start() - - def stop(self): - """停止窗口""" - self._running = False - if self.interface: - self.interface.close() - -# 创建全局实例 -message_window = MessageWindow() - diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index 5ab9d1e46..dece848b2 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -8,96 +8,21 @@ from .config import BotConfig from ...common.database import Database import random import time -import subprocess import os -import sys -import threading -import queue import numpy as np from dotenv import load_dotenv from .relationship_manager import relationship_manager from ..schedule.schedule_generator import bot_schedule from .prompt_builder import prompt_builder from .config import llm_config -from .willing_manager import willing_manager from .utils import get_embedding, split_into_sentences, process_text_with_typos -import aiohttp + # 获取当前文件的绝对路径 current_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.abspath(os.path.join(current_dir, '..', '..', '..')) load_dotenv(os.path.join(root_dir, '.env')) -class ReasoningWindow: - def __init__(self): - self.process = None - self.message_queue = queue.Queue() - self.is_running = False - self.content_file = "reasoning_content.txt" - - def start(self): - if self.process is None: - # 创建用于显示的批处理文件 - with open("reasoning_window.bat", "w", encoding="utf-8") as f: - f.write('@echo off\n') - f.write('chcp 65001\n') # 设置UTF-8编码 - f.write('title Magellan Reasoning Process\n') - f.write('echo Waiting for reasoning content...\n') - f.write(':loop\n') - f.write('if exist "reasoning_update.txt" (\n') - f.write(' type "reasoning_update.txt" >> "reasoning_content.txt"\n') - f.write(' del "reasoning_update.txt"\n') - f.write(' cls\n') - f.write(' type "reasoning_content.txt"\n') - f.write(')\n') - f.write('timeout /t 1 /nobreak >nul\n') - f.write('goto loop\n') - - # 清空内容文件 - with open(self.content_file, "w", encoding="utf-8") as f: - f.write("") - - # 启动新窗口 - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - self.process = subprocess.Popen(['cmd', '/c', 'start', 'reasoning_window.bat'], - shell=True, - startupinfo=startupinfo) - self.is_running = True - - # 启动处理线程 - threading.Thread(target=self._process_messages, daemon=True).start() - - def _process_messages(self): - while self.is_running: - try: - # 获取新消息 - text = self.message_queue.get(timeout=1) - # 写入更新文件 - with open("reasoning_update.txt", "w", encoding="utf-8") as f: - f.write(text) - except queue.Empty: - continue - except Exception as e: - print(f"处理推理内容时出错: {e}") - - def update_content(self, text: str): - if self.is_running: - self.message_queue.put(text) - - def stop(self): - self.is_running = False - if self.process: - self.process.terminate() - self.process = None - # 清理文件 - for file in ["reasoning_window.bat", "reasoning_content.txt", "reasoning_update.txt"]: - if os.path.exists(file): - os.remove(file) - -# 创建全局单例 -reasoning_window = ReasoningWindow() - class LLMResponseGenerator: def __init__(self, config: BotConfig): self.config = config @@ -107,7 +32,7 @@ class LLMResponseGenerator: ) self.db = Database.get_instance() - reasoning_window.start() + # 当前使用的模型类型 self.current_model_type = 'r1' # 默认使用 R1 @@ -115,7 +40,7 @@ class LLMResponseGenerator: """根据当前模型类型选择对应的生成函数""" # 使用随机数选择模型 rand = random.random() - if rand < 0.6: # 60%概率使用 R1 + if rand < 0.8: # 60%概率使用 R1 self.current_model_type = "r1" elif rand < 0.5: # 20%概率使用 V3 self.current_model_type = "v3" @@ -170,11 +95,26 @@ class LLMResponseGenerator: response = await loop.run_in_executor(None, create_completion) if response.choices[0].message.content: content = response.choices[0].message.content - reasoning_content = response.choices[0].message.reasoning_content + # 获取推理内容 + reasoning_content = "模型思考过程:\n" + prompt + if hasattr(response.choices[0].message, "reasoning"): + reasoning_content = response.choices[0].message.reasoning or reasoning_content + elif hasattr(response.choices[0].message, "reasoning_content"): + reasoning_content = response.choices[0].message.reasoning_content or reasoning_content + + # 保存推理结果到数据库 + self.db.db.reasoning_logs.insert_one({ + 'time': time.time(), + 'group_id': message.group_id, + 'user': sender_name, + 'message': message.processed_plain_text, + 'model': "DeepSeek-R1", + 'reasoning': reasoning_content, + 'response': content, + 'prompt': prompt + }) else: return None - # 更新推理窗口 - self._update_reasoning_window(message, prompt, reasoning_content, content, sender_name) return content @@ -207,8 +147,18 @@ class LLMResponseGenerator: if response.choices[0].message.content: content = response.choices[0].message.content - # V3 模型没有 reasoning_content - self._update_reasoning_window(message, prompt, "V3模型无推理过程", content, sender_name) + # 保存推理结果到数据库 + self.db.db.reasoning_logs.insert_one({ + 'time': time.time(), + 'group_id': message.group_id, + 'user': sender_name, + 'message': message.processed_plain_text, + 'model': "DeepSeek-V3", + 'reasoning': "V3模型无推理过程", + 'response': content, + 'prompt': prompt + }) + return content else: print(f"[ERROR] V3 回复发送生成失败: {response}") @@ -246,11 +196,27 @@ class LLMResponseGenerator: response = await loop.run_in_executor(None, create_completion) if response.choices[0].message.content: content = response.choices[0].message.content - reasoning_content = response.choices[0].message.reasoning_content + # 获取推理内容 + reasoning_content = "模型思考过程:\n" + prompt + if hasattr(response.choices[0].message, "reasoning"): + reasoning_content = response.choices[0].message.reasoning or reasoning_content + elif hasattr(response.choices[0].message, "reasoning_content"): + reasoning_content = response.choices[0].message.reasoning_content or reasoning_content + + # 保存推理结果到数据库 + self.db.db.reasoning_logs.insert_one({ + 'time': time.time(), + 'group_id': message.group_id, + 'user': sender_name, + 'message': message.processed_plain_text, + 'model': "DeepSeek-R1-Distill", + 'reasoning': reasoning_content, + 'response': content, + 'prompt': prompt + }) else: return None - # 更新推理窗口 - self._update_reasoning_window(message, prompt, reasoning_content, content, sender_name) + return content @@ -272,31 +238,6 @@ class LLMResponseGenerator: return group_chat - def _update_reasoning_window(self, message, prompt, reasoning_content, content, sender_name): - """更新推理窗口内容""" - current_time = time.strftime("%Y-%m-%d %H:%M:%S") - - # 获取当前使用的模型名称 - model_name = { - 'r1': 'DeepSeek-R1', - 'v3': 'DeepSeek-V3', - 'r1_distill': 'DeepSeek-R1-Distill-Qwen-32B' - }.get(self.current_model_type, '未知模型') - - display_text = ( - f"Time: {current_time}\n" - f"Group: {message.group_name}\n" - f"User: {sender_name}\n" - f"Model: {model_name}\n" - f"\033[1;32mMessage:\033[0m {message.processed_plain_text}\n\n" - f"\033[1;32mPrompt:\033[0m \n{prompt}\n" - f"\n-------------------------------------------------------" - f"\n\033[1;32mReasoning Process:\033[0m\n{reasoning_content}\n" - f"\n\033[1;32mResponse Content:\033[0m\n{content}\n" - f"\n{'='*50}\n" - ) - reasoning_window.update_content(display_text) - async def _get_emotion_tags(self, content: str) -> List[str]: """提取情感标签""" try: @@ -335,6 +276,11 @@ class LLMResponseGenerator: if not content: return None, [] + # 检查回复是否过长(超过200个字符) + if len(content) > 200: + print(f"回复过长 ({len(content)} 字符),返回默认回复") + return "麦麦不知道哦", ["angry"] + emotion_tags = await self._get_emotion_tags(content) # 添加错别字和处理标点符号 @@ -359,6 +305,11 @@ class LLMResponseGenerator: if current_message: messages.append(current_message.strip()) + # 检查分割后的消息数量是否过多(超过3条) + if len(messages) > 3: + print(f"分割后消息数量过多 ({len(messages)} 条),返回默认回复") + return "麦麦不知道哦", ["angry"] + return messages, emotion_tags return processed_response, emotion_tags diff --git a/src/plugins/chat/reasoning_gui.py b/src/plugins/chat/reasoning_gui.py new file mode 100644 index 000000000..79e274d6d --- /dev/null +++ b/src/plugins/chat/reasoning_gui.py @@ -0,0 +1,343 @@ +import customtkinter as ctk +from typing import Dict, List +import json +from datetime import datetime +import time +import threading +import queue +import sys +import os + +from pymongo import MongoClient +from typing import Optional + +class Database: + _instance: Optional["Database"] = None + + def __init__(self, host: str, port: int, db_name: str): + self.client = MongoClient(host, port) + self.db = self.client[db_name] + + @classmethod + def initialize(cls, host: str, port: int, db_name: str) -> "Database": + if cls._instance is None: + cls._instance = cls(host, port, db_name) + return cls._instance + + @classmethod + def get_instance(cls) -> "Database": + if cls._instance is None: + raise RuntimeError("Database not initialized") + return cls._instance + + + +class ReasoningGUI: + def __init__(self): + # 记录启动时间戳,转换为Unix时间戳 + self.start_timestamp = datetime.now().timestamp() + print(f"程序启动时间戳: {self.start_timestamp}") + + # 设置主题 + ctk.set_appearance_mode("dark") + ctk.set_default_color_theme("blue") + + # 创建主窗口 + self.root = ctk.CTk() + self.root.title('麦麦推理') + self.root.geometry('800x600') + self.root.protocol("WM_DELETE_WINDOW", self._on_closing) + + # 初始化数据库连接 + try: + self.db = Database.get_instance().db + print("数据库连接成功") + except RuntimeError: + print("数据库未初始化,正在尝试初始化...") + try: + Database.initialize("localhost", 27017, "maimai_bot") + self.db = Database.get_instance().db + print("数据库初始化成功") + except Exception as e: + print(f"数据库初始化失败: {e}") + sys.exit(1) + + # 存储群组数据 + self.group_data: Dict[str, List[dict]] = {} + + # 创建更新队列 + self.update_queue = queue.Queue() + + # 创建主框架 + self.frame = ctk.CTkFrame(self.root) + self.frame.pack(pady=20, padx=20, fill="both", expand=True) + + # 添加标题 + self.title = ctk.CTkLabel(self.frame, text="AI推理监控系统", font=("Arial", 24)) + self.title.pack(pady=10, padx=10) + + # 创建左右分栏 + self.paned = ctk.CTkFrame(self.frame) + self.paned.pack(fill="both", expand=True, padx=10, pady=10) + + # 左侧群组列表 + self.left_frame = ctk.CTkFrame(self.paned, width=200) + self.left_frame.pack(side="left", fill="y", padx=5, pady=5) + + self.group_label = ctk.CTkLabel(self.left_frame, text="群组列表", font=("Arial", 16)) + self.group_label.pack(pady=5) + + self.group_listbox = ctk.CTkTextbox(self.left_frame, width=180, height=400) + self.group_listbox.pack(pady=5, padx=5) + + # 右侧内容显示 + self.right_frame = ctk.CTkFrame(self.paned) + self.right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5) + + self.content_label = ctk.CTkLabel(self.right_frame, text="推理内容", font=("Arial", 16)) + self.content_label.pack(pady=5) + + # 创建富文本显示框 + self.content_text = ctk.CTkTextbox(self.right_frame, width=500, height=400) + self.content_text.pack(pady=5, padx=5, fill="both", expand=True) + + # 配置文本标签 - 只使用颜色 + self.content_text.tag_config("timestamp", foreground="#888888") # 时间戳使用灰色 + self.content_text.tag_config("user", foreground="#4CAF50") # 用户名使用绿色 + self.content_text.tag_config("message", foreground="#2196F3") # 消息使用蓝色 + self.content_text.tag_config("model", foreground="#9C27B0") # 模型名称使用紫色 + self.content_text.tag_config("reasoning", foreground="#FF9800") # 推理过程使用橙色 + self.content_text.tag_config("response", foreground="#E91E63") # 回复使用粉色 + self.content_text.tag_config("separator", foreground="#666666") # 分隔符使用深灰色 + + # 底部控制栏 + self.control_frame = ctk.CTkFrame(self.frame) + self.control_frame.pack(fill="x", padx=10, pady=5) + + self.clear_button = ctk.CTkButton( + self.control_frame, + text="清除显示", + command=self.clear_display, + width=120 + ) + self.clear_button.pack(side="left", padx=5) + + # 添加群组点击事件 + self.group_listbox.bind('', self._on_group_click) + + # 启动自动更新线程 + self.update_thread = threading.Thread(target=self._auto_update, daemon=True) + self.update_thread.start() + + # 启动GUI更新检查 + self.root.after(100, self._process_queue) + + def _on_closing(self): + """处理窗口关闭事件""" + self.root.quit() + sys.exit(0) + + def _process_queue(self): + """处理更新队列中的任务""" + try: + while True: + task = self.update_queue.get_nowait() + if task['type'] == 'update_group_list': + self._update_group_list_gui() + elif task['type'] == 'update_display': + self._update_display_gui(task['group_id']) + except queue.Empty: + pass + finally: + # 继续检查队列 + self.root.after(100, self._process_queue) + + def _update_group_list_gui(self): + """在主线程中更新群组列表""" + self.group_listbox.delete("1.0", "end") + for group_id in self.group_data.keys(): + self.group_listbox.insert("end", f"群号: {group_id}\n") + + def _update_display_gui(self, group_id: str): + """在主线程中更新显示内容""" + if group_id in self.group_data: + self.content_text.delete("1.0", "end") + for item in self.group_data[group_id]: + # 时间戳 + time_str = item['time'].strftime("%Y-%m-%d %H:%M:%S") + self.content_text.insert("end", f"[{time_str}]\n", "timestamp") + + # 用户信息 + self.content_text.insert("end", "用户: ", "timestamp") + self.content_text.insert("end", f"{item.get('user', '未知')}\n", "user") + + # 消息内容 + self.content_text.insert("end", "消息: ", "timestamp") + self.content_text.insert("end", f"{item.get('message', '')}\n", "message") + + # 模型信息 + self.content_text.insert("end", "模型: ", "timestamp") + self.content_text.insert("end", f"{item.get('model', '')}\n", "model") + + # 推理过程 + self.content_text.insert("end", "推理过程:\n", "timestamp") + reasoning_text = item.get('reasoning', '') + # 处理推理过程中的Markdown格式 + lines = reasoning_text.split('\n') + for line in lines: + if line.strip(): + # 添加缩进 + self.content_text.insert("end", " " + line + "\n", "reasoning") + + # 回复内容 + self.content_text.insert("end", "回复: ", "timestamp") + self.content_text.insert("end", f"{item.get('response', '')}\n", "response") + + # 分隔符 + self.content_text.insert("end", f"\n{'='*50}\n\n", "separator") + + # 滚动到顶部 + self.content_text.see("1.0") + + def _auto_update(self): + """自动更新函数""" + while True: + try: + # 从数据库获取最新数据,只获取启动时间之后的记录 + query = {"time": {"$gt": self.start_timestamp}} + print(f"查询条件: {query}") + + # 先获取一条记录检查时间格式 + sample = self.db.reasoning_logs.find_one() + if sample: + print(f"样本记录时间格式: {type(sample['time'])} 值: {sample['time']}") + + cursor = self.db.reasoning_logs.find(query).sort("time", -1) + new_data = {} + total_count = 0 + + for item in cursor: + # 调试输出 + if total_count == 0: + print(f"记录时间: {item['time']}, 类型: {type(item['time'])}") + + total_count += 1 + group_id = str(item.get('group_id', 'unknown')) + if group_id not in new_data: + new_data[group_id] = [] + + # 转换时间戳为datetime对象 + if isinstance(item['time'], (int, float)): + time_obj = datetime.fromtimestamp(item['time']) + elif isinstance(item['time'], datetime): + time_obj = item['time'] + else: + print(f"未知的时间格式: {type(item['time'])}") + time_obj = datetime.now() # 使用当前时间作为后备 + + new_data[group_id].append({ + 'time': time_obj, + 'user': item.get('user_nickname', item.get('user_id', '未知')), + 'message': item.get('message', ''), + 'model': item.get('model', '未知'), + 'reasoning': item.get('reasoning', ''), + 'response': item.get('response', ''), + }) + + print(f"从数据库加载了 {total_count} 条记录,分布在 {len(new_data)} 个群组中") + + # 更新数据 + if new_data != self.group_data: + self.group_data = new_data + print("数据已更新,正在刷新显示...") + # 将更新任务添加到队列 + self.update_queue.put({'type': 'update_group_list'}) + if self.group_data: + latest_group = next(iter(self.group_data)) + self.update_queue.put({ + 'type': 'update_display', + 'group_id': latest_group + }) + except Exception as e: + print(f"自动更新出错: {e}") + + # 每5秒更新一次 + time.sleep(5) + + def _on_group_click(self, event): + """处理群组点击事件""" + try: + # 获取点击位置的文本行 + index = self.group_listbox.index(f"@{event.x},{event.y}") + line = self.group_listbox.get(f"{index} linestart", f"{index} lineend") + if line.startswith("群号: "): + group_id = line.replace("群号: ", "").strip() + self.update_display(group_id) + except Exception as e: + print(f"处理群组点击事件出错: {e}") + + def update_display(self, group_id: str): + """更新显示指定群组的内容""" + if group_id in self.group_data: + self.content_text.delete("1.0", "end") + for item in self.group_data[group_id]: + # 时间戳 + time_str = item['time'].strftime("%Y-%m-%d %H:%M:%S") + self.content_text.insert("end", f"[{time_str}]\n", "timestamp") + + # 用户信息 + self.content_text.insert("end", "用户: ", "timestamp") + self.content_text.insert("end", f"** {item.get('user', '未知')} **\n", "user") + + # 消息内容 + self.content_text.insert("end", "消息: ", "timestamp") + self.content_text.insert("end", f"{item.get('message', '')}\n", "message") + + # 模型信息 + self.content_text.insert("end", "模型: ", "timestamp") + self.content_text.insert("end", f"{item.get('model', '')}\n", "model") + + # 推理过程 + self.content_text.insert("end", "推理过程:\n", "timestamp") + reasoning_text = item.get('reasoning', '') + # 处理推理过程中的Markdown格式 + lines = reasoning_text.split('\n') + for line in lines: + if line.strip(): + # 添加缩进 + self.content_text.insert("end", " " + line + "\n", "reasoning") + + # 回复内容 + self.content_text.insert("end", "回复: ", "timestamp") + self.content_text.insert("end", f"{item.get('response', '')}\n", "response") + + # 分隔符 + self.content_text.insert("end", f"\n{'='*50}\n\n", "separator") + + # 滚动到顶部 + self.content_text.see("1.0") + + def clear_display(self): + """清除显示内容""" + self.content_text.delete("1.0", "end") + + def run(self): + """运行GUI""" + self.root.mainloop() + + +def main(): + """主函数""" + Database.initialize( + "127.0.0.1", + 27017, + "MegBot" + ) + + app = ReasoningGUI() + app.run() + + + +if __name__ == "__main__": + main()