From 661932403f12b30497736f0142900315871d729c Mon Sep 17 00:00:00 2001 From: Oct-autumn Date: Fri, 14 Mar 2025 19:59:54 +0800 Subject: [PATCH 01/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A7=81?= =?UTF-8?q?=E8=81=8A=E6=97=B6=E5=BC=95=E7=94=A8=E6=B6=88=E6=81=AF=E5=92=8C?= =?UTF-8?q?=E6=92=A4=E5=9B=9E=E6=B6=88=E6=81=AF=E6=97=B6=E5=8F=91=E7=94=9F?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/bot.py | 7 ++++++- src/plugins/chat/cq_code.py | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index b0ed3e596..00c03f038 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -87,7 +87,12 @@ class ChatBot: platform="qq", ) - group_info = GroupInfo(group_id=event.group_id, group_name=None, platform="qq") + if isinstance(event, GroupRecallNoticeEvent): + group_info = GroupInfo( + group_id=event.group_id, group_name=None, platform="qq" + ) + else: + group_info = None chat = await chat_manager.get_or_create_stream( platform=user_info.platform, user_info=user_info, group_info=group_info diff --git a/src/plugins/chat/cq_code.py b/src/plugins/chat/cq_code.py index 2edc011b2..8967698dd 100644 --- a/src/plugins/chat/cq_code.py +++ b/src/plugins/chat/cq_code.py @@ -249,6 +249,13 @@ class CQCode: if self.reply_message is None: return None + if hasattr(self.reply_message, "group_id"): + group_info = GroupInfo( + platform="qq", group_id=self.reply_message.group_id, group_name="" + ) + else: + group_info = None + if self.reply_message.sender.user_id: message_obj = MessageRecvCQ( user_info=UserInfo( @@ -256,7 +263,7 @@ class CQCode: ), message_id=self.reply_message.message_id, raw_message=str(self.reply_message.message), - group_info=GroupInfo(group_id=self.reply_message.group_id), + group_info=group_info, ) await message_obj.initialize() From 49ea970215289951aca5b896f1c2de27fa4e5692 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Sat, 15 Mar 2025 01:02:37 +0800 Subject: [PATCH 02/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E4=BF=9D=E5=AD=98=E5=8F=AF=E8=83=BD=E4=BC=9A?= =?UTF-8?q?=E6=B8=85=E7=A9=BAlist=E5=86=85=E5=AE=B9=E7=9A=84bug,=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=86=E7=95=8C=E9=9D=A2=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webui.py | 128 ++++++++++++++++++++++--------------------------------- 1 file changed, 52 insertions(+), 76 deletions(-) diff --git a/webui.py b/webui.py index a9041749b..e22f250ea 100644 --- a/webui.py +++ b/webui.py @@ -157,34 +157,6 @@ def format_list_to_str(lst): res = res[:-1] return "[" + res + "]" -def format_list_to_str_alias(lst): - """ - 将Python列表转换为形如["src2.plugins.chat"]的字符串格式 - format_list_to_str(['src2.plugins.chat']) - '["src2.plugins.chat"]' - format_list_to_str([1, "two", 3.0]) - '[1, "two", 3.0]' - """ - resarr = [] - if len(lst) != 0: - resarr = lst.split(", ") - - return resarr - -def format_list_to_int(lst): - resarr = [] - if len(lst) != 0: - resarr = lst.split(", ") - # print(resarr) - # print(type(resarr)) - ans = [] - if len(resarr) != 0: - for lsts in resarr: - temp = int(lsts) - ans.append(temp) - # print(ans) - # print(type(ans)) - return ans #env保存函数 def save_trigger(server_address, server_port, final_result_list,t_mongodb_host,t_mongodb_port,t_mongodb_database_name,t_chatanywhere_base_url,t_chatanywhere_key,t_siliconflow_base_url,t_siliconflow_key,t_deepseek_base_url,t_deepseek_key): @@ -228,7 +200,7 @@ def save_config_to_file(t_config_data): def save_bot_config(t_qqbot_qq, t_nickname,t_nickname_final_result): config_data["bot"]["qq"] = int(t_qqbot_qq) config_data["bot"]["nickname"] = t_nickname - config_data["bot"]["alias_names"] = format_list_to_str_alias(t_nickname_final_result) + config_data["bot"]["alias_names"] = t_nickname_final_result save_config_to_file(config_data) logger.info("Bot配置已保存") return "Bot配置已保存" @@ -298,8 +270,8 @@ def save_message_and_emoji_config(t_min_text_length, config_data["message"]["response_willing_amplifier"] = t_response_willing_amplifier config_data["message"]["response_interested_rate_amplifier"] = t_response_interested_rate_amplifier config_data["message"]["down_frequency_rate"] = t_down_frequency_rate - config_data["message"]["ban_words"] = format_list_to_str_alias(t_ban_words_final_result) - config_data["message"]["ban_msgs_regex"] = format_list_to_str_alias(t_ban_msgs_regex_final_result) + config_data["message"]["ban_words"] =t_ban_words_final_result + config_data["message"]["ban_msgs_regex"] = t_ban_msgs_regex_final_result config_data["emoji"]["check_interval"] = t_check_interval config_data["emoji"]["register_interval"] = t_register_interval config_data["emoji"]["auto_save"] = t_auto_save @@ -358,7 +330,7 @@ def save_memory_mood_config(t_build_memory_interval, t_memory_compress_rate, t_f config_data["memory"]["forget_memory_interval"] = t_forget_memory_interval config_data["memory"]["memory_forget_time"] = t_memory_forget_time config_data["memory"]["memory_forget_percentage"] = t_memory_forget_percentage - config_data["memory"]["memory_ban_words"] = format_list_to_str_alias(t_memory_ban_words_final_result) + config_data["memory"]["memory_ban_words"] = t_memory_ban_words_final_result config_data["mood"]["update_interval"] = t_mood_update_interval config_data["mood"]["decay_rate"] = t_mood_decay_rate config_data["mood"]["intensity_factor"] = t_mood_intensity_factor @@ -383,9 +355,9 @@ def save_other_config(t_keywords_reaction_enabled,t_enable_advance_output, t_ena def save_group_config(t_talk_allowed_final_result, t_talk_frequency_down_final_result, t_ban_user_id_final_result,): - config_data["groups"]["talk_allowed"] = format_list_to_int(t_talk_allowed_final_result) - config_data["groups"]["talk_frequency_down"] = format_list_to_int(t_talk_frequency_down_final_result) - config_data["groups"]["ban_user_id"] = format_list_to_int(t_ban_user_id_final_result) + config_data["groups"]["talk_allowed"] = t_talk_allowed_final_result + config_data["groups"]["talk_frequency_down"] = t_talk_frequency_down_final_result + config_data["groups"]["ban_user_id"] = t_ban_user_id_final_result save_config_to_file(config_data) logger.info("群聊设置已保存到 bot_config.toml 文件中") return "群聊设置已保存" @@ -393,11 +365,11 @@ def save_group_config(t_talk_allowed_final_result, with gr.Blocks(title="MaimBot配置文件编辑") as app: gr.Markdown( value=""" - 欢迎使用由墨梓柒MotricSeven编写的MaimBot配置文件编辑器\n + ### 欢迎使用由墨梓柒MotricSeven编写的MaimBot配置文件编辑器\n """ ) gr.Markdown( - value="配置文件版本:" + config_data["inner"]["version"] + value="### 配置文件版本:" + config_data["inner"]["version"] ) with gr.Tabs(): with gr.TabItem("0-环境设置"): @@ -539,7 +511,7 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: interactive=True ) with gr.Row(): - save_env_btn = gr.Button("保存环境配置") + save_env_btn = gr.Button("保存环境配置",variant="primary") with gr.Row(): save_env_btn.click( save_trigger, @@ -608,7 +580,7 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: elem_classes="save_bot_btn" ).click( save_bot_config, - inputs=[qqbot_qq, nickname,nickname_final_result], + inputs=[qqbot_qq, nickname,nickname_list_state], outputs=[gr.Textbox( label="保存Bot结果" )] @@ -658,18 +630,19 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: interactive=True ) with gr.Row(): - gr.Button( + personal_save_btn = gr.Button( "保存人格配置", variant="primary", elem_id="save_personality_btn", elem_classes="save_personality_btn" - ).click( - save_personality_config, - inputs=[personality_1, personality_2, personality_3, prompt_schedule], - outputs=[gr.Textbox( - label="保存人格结果" - )] ) + with gr.Row(): + personal_save_message = gr.Textbox(label="保存人格结果") + personal_save_btn.click( + save_personality_config, + inputs=[personality_1, personality_2, personality_3, prompt_schedule], + outputs=[personal_save_message] + ) with gr.TabItem("3-消息&表情包设置"): with gr.Row(): with gr.Column(scale=3): @@ -783,33 +756,36 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: with gr.Row(): check_prompt = gr.Textbox(value=config_data['emoji']['check_prompt'], label="表情包过滤要求") with gr.Row(): - gr.Button( + emoji_save_btn = gr.Button( "保存消息&表情包设置", variant="primary", elem_id="save_personality_btn", elem_classes="save_personality_btn" - ).click( - save_message_and_emoji_config, - inputs=[ - min_text_length, - max_context_size, - emoji_chance, - thinking_timeout, - response_willing_amplifier, - response_interested_rate_amplifier, - down_frequency_rate, - ban_words_final_result, - ban_msgs_regex_final_result, - check_interval, - register_interval, - auto_save, - enable_check, - check_prompt - ], - outputs=[gr.Textbox( - label="消息&表情包设置保存结果" - )] ) + with gr.Row(): + emoji_save_message = gr.Textbox( + label="消息&表情包设置保存结果" + ) + emoji_save_btn.click( + save_message_and_emoji_config, + inputs=[ + min_text_length, + max_context_size, + emoji_chance, + thinking_timeout, + response_willing_amplifier, + response_interested_rate_amplifier, + down_frequency_rate, + ban_words_list_state, + ban_msgs_regex_list_state, + check_interval, + register_interval, + auto_save, + enable_check, + check_prompt + ], + outputs=[emoji_save_message] + ) with gr.TabItem("4-回复&模型设置"): with gr.Row(): with gr.Column(scale=3): @@ -892,7 +868,7 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: with gr.Row(): vlm_model_provider = gr.Dropdown(choices=["SILICONFLOW","DEEP_SEEK", "CHAT_ANY_WHERE"], value=config_data['model']['vlm']['provider'], label="识图模型提供商") with gr.Row(): - save_model_btn = gr.Button("保存回复&模型设置") + save_model_btn = gr.Button("保存回复&模型设置",variant="primary", elem_id="save_model_btn") with gr.Row(): save_btn_message = gr.Textbox() save_model_btn.click( @@ -961,13 +937,13 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: with gr.Row(): mood_intensity_factor = gr.Number(value=config_data['mood']['mood_intensity_factor'], label="心情强度因子") with gr.Row(): - save_memory_mood_btn = gr.Button("保存 [Memory] 配置") + save_memory_mood_btn = gr.Button("保存记忆&心情设置",variant="primary") with gr.Row(): save_memory_mood_message = gr.Textbox() with gr.Row(): save_memory_mood_btn.click( save_memory_mood_config, - inputs=[build_memory_interval, memory_compress_rate, forget_memory_interval, memory_forget_time, memory_forget_percentage, memory_ban_words_final_result, mood_update_interval, mood_decay_rate, mood_intensity_factor], + inputs=[build_memory_interval, memory_compress_rate, forget_memory_interval, memory_forget_time, memory_forget_percentage, memory_ban_words_list_state, mood_update_interval, mood_decay_rate, mood_intensity_factor], outputs=[save_memory_mood_message] ) with gr.TabItem("6-群组设置"): @@ -1093,16 +1069,16 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: outputs=[ban_user_id_list_state, ban_user_id_list_display, ban_user_id_item_to_delete, ban_user_id_final_result] ) with gr.Row(): - save_group_btn = gr.Button("保存群组设置") + save_group_btn = gr.Button("保存群组设置",variant="primary") with gr.Row(): save_group_btn_message = gr.Textbox() with gr.Row(): save_group_btn.click( save_group_config, inputs=[ - talk_allowed_final_result, - talk_frequency_down_final_result, - ban_user_id_final_result, + talk_allowed_list_state, + talk_frequency_down_list_state, + ban_user_id_list_state, ], outputs=[save_group_btn_message] ) @@ -1138,7 +1114,7 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app: with gr.Row(): word_replace_rate = gr.Slider(minimum=0, maximum=1, step=0.001, value=config_data['chinese_typo']['word_replace_rate'], label="整词替换概率") with gr.Row(): - save_other_config_btn = gr.Button("保存其他配置") + save_other_config_btn = gr.Button("保存其他配置",variant="primary") with gr.Row(): save_other_config_message = gr.Textbox() with gr.Row(): From f3fef69968fe87acc4d34c668d652cc2adf2a9c2 Mon Sep 17 00:00:00 2001 From: AL76 <735756072@qq.com> Date: Sat, 15 Mar 2025 02:45:41 +0800 Subject: [PATCH 03/29] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Elogger=E5=B7=A5?= =?UTF-8?q?=E5=8E=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/logger.py | 127 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/common/logger.py diff --git a/src/common/logger.py b/src/common/logger.py new file mode 100644 index 000000000..6093920f0 --- /dev/null +++ b/src/common/logger.py @@ -0,0 +1,127 @@ +from loguru import logger +from typing import Dict, Optional, Union, List, Any +import sys +from types import ModuleType +from pathlib import Path + + +# 类型别名 +LoguruLogger = logger.__class__ + +# 全局注册表:记录模块与处理器ID的映射 +_handler_registry: Dict[str, List[int]] = {} + +# 获取日志存储根地址 +current_file_path = Path(__file__).resolve() +PROJECT_ROOT = current_file_path.parent.parent.parent +LOG_ROOT = str(PROJECT_ROOT / "logs") + +# 默认全局配置 +DEFAULT_CONFIG = { + + # 日志级别配置 + "level": "INFO", # 全局基础日志级别(若未指定console/file_level则生效) + "console_level": "INFO", # 控制台默认级别(可覆盖) + "file_level": "DEBUG", # 文件默认级别(可覆盖) + + # 格式配置 + "console_format": ( + "{time:YYYY-MM-DD HH:mm:ss} | " + "{level: <8} | " + "{extra[module]: <20} | " + "{message}" + ), + "file_format": ( + "{time:YYYY-MM-DD HH:mm:ss} | " + "{level: <8} | " + "{extra[module]: <20} | " + "{message}" + ), + "log_dir": LOG_ROOT, # 默认日志目录,需保留 + "rotation": "100 MB", # 设定轮转 + "retention": "7 days", # 设定时长 + "compression": "zip", # 设定压缩 +} + + +class LogConfig: + """日志配置类""" + + def __init__(self, **kwargs): + self.config = DEFAULT_CONFIG.copy() + self.config.update(kwargs) + + def to_dict(self) -> dict: + return self.config.copy() + + def update(self, **kwargs): + self.config.update(kwargs) + + +def get_module_logger( + module: Union[str, ModuleType], + *, + console_level: Optional[str] = None, + file_level: Optional[str] = None, + extra_handlers: Optional[List[dict]] = None, + config: Optional[LogConfig] = None +) -> LoguruLogger: + module_name = module if isinstance(module, str) else module.__name__ + current_config = config.config if config else DEFAULT_CONFIG + + # 若模块已注册,先移除旧处理器(避免重复添加) + if module_name in _handler_registry: + for handler_id in _handler_registry[module_name]: + logger.remove(handler_id) + del _handler_registry[module_name] + + handler_ids = [] + + # 控制台处理器 + console_id = logger.add( + sink=sys.stderr, + level=console_level or current_config["console_level"], + format=current_config["console_format"], + filter=lambda record: record["extra"].get("module") == module_name, + enqueue=current_config.get("enqueue", True), + backtrace=current_config.get("backtrace", False), + diagnose=current_config.get("diagnose", False), + ) + handler_ids.append(console_id) + + # 文件处理器 + log_dir = Path(current_config["log_dir"]) + log_dir.mkdir(parents=True, exist_ok=True) + log_file = log_dir / f"{module_name}_{{time:YYYY-MM-DD}}.log" + + file_id = logger.add( + sink=str(log_file), + level=file_level or current_config["file_level"], + format=current_config["file_format"], + rotation=current_config["rotation"], + retention=current_config["retention"], + compression=current_config["compression"], + encoding=current_config.get("encoding", "utf-8"), + filter=lambda record: record["extra"].get("module") == module_name, + enqueue=current_config.get("enqueue", True), + ) + handler_ids.append(file_id) + + # 额外处理器 + if extra_handlers: + for handler in extra_handlers: + handler_id = logger.add(**handler) + handler_ids.append(handler_id) + + # 更新注册表 + _handler_registry[module_name] = handler_ids + + return logger.bind(module=module_name) + + +def remove_module_logger(module_name: str) -> None: + """清理指定模块的日志处理器""" + if module_name in _handler_registry: + for handler_id in _handler_registry[module_name]: + logger.remove(handler_id) + del _handler_registry[module_name] \ No newline at end of file From 77df50e66658e5c608972a2481cf78d069345ae3 Mon Sep 17 00:00:00 2001 From: AL76 <735756072@qq.com> Date: Sat, 15 Mar 2025 02:46:38 +0800 Subject: [PATCH 04/29] =?UTF-8?q?reformat:=20=E6=A0=BC=E5=BC=8F=E5=8C=96me?= =?UTF-8?q?mory.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/memory_system/memory.py | 135 ++++++++++++++-------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/src/plugins/memory_system/memory.py b/src/plugins/memory_system/memory.py index 0952e0024..47066ead5 100644 --- a/src/plugins/memory_system/memory.py +++ b/src/plugins/memory_system/memory.py @@ -27,6 +27,7 @@ logger = log_module.setup_logger(LogClassification.MEMORY) logger.info("初始化记忆系统") + class Memory_graph: def __init__(self): self.G = nx.Graph() # 使用 networkx 的图结构 @@ -35,9 +36,9 @@ class Memory_graph: # 避免自连接 if concept1 == concept2: return - + current_time = datetime.datetime.now().timestamp() - + # 如果边已存在,增加 strength if self.G.has_edge(concept1, concept2): self.G[concept1][concept2]['strength'] = self.G[concept1][concept2].get('strength', 1) + 1 @@ -45,14 +46,14 @@ class Memory_graph: self.G[concept1][concept2]['last_modified'] = current_time else: # 如果是新边,初始化 strength 为 1 - self.G.add_edge(concept1, concept2, - strength=1, - created_time=current_time, # 添加创建时间 - last_modified=current_time) # 添加最后修改时间 + self.G.add_edge(concept1, concept2, + strength=1, + created_time=current_time, # 添加创建时间 + last_modified=current_time) # 添加最后修改时间 def add_dot(self, concept, memory): current_time = datetime.datetime.now().timestamp() - + if concept in self.G: if 'memory_items' in self.G.nodes[concept]: if not isinstance(self.G.nodes[concept]['memory_items'], list): @@ -68,10 +69,10 @@ class Memory_graph: self.G.nodes[concept]['last_modified'] = current_time else: # 如果是新节点,创建新的记忆列表 - self.G.add_node(concept, - memory_items=[memory], - created_time=current_time, # 添加创建时间 - last_modified=current_time) # 添加最后修改时间 + self.G.add_node(concept, + memory_items=[memory], + created_time=current_time, # 添加创建时间 + last_modified=current_time) # 添加最后修改时间 def get_dot(self, concept): # 检查节点是否存在于图中 @@ -210,12 +211,13 @@ class Hippocampus: # 成功抽取短期消息样本 # 数据写回:增加记忆次数 for message in messages: - db.messages.update_one({"_id": message["_id"]}, {"$set": {"memorized_times": message["memorized_times"] + 1}}) + db.messages.update_one({"_id": message["_id"]}, + {"$set": {"memorized_times": message["memorized_times"] + 1}}) return messages try_count += 1 # 三次尝试均失败 return None - + def get_memory_sample(self, chat_size=20, time_frequency: dict = {'near': 2, 'mid': 4, 'far': 3}): """获取记忆样本 @@ -225,7 +227,7 @@ class Hippocampus: # 硬编码:每条消息最大记忆次数 # 如有需求可写入global_config max_memorized_time_per_msg = 3 - + current_timestamp = datetime.datetime.now().timestamp() chat_samples = [] @@ -324,20 +326,20 @@ class Hippocampus: # 为每个话题查找相似的已存在主题 existing_topics = list(self.memory_graph.G.nodes()) similar_topics = [] - + for existing_topic in existing_topics: topic_words = set(jieba.cut(topic)) existing_words = set(jieba.cut(existing_topic)) - + all_words = topic_words | existing_words v1 = [1 if word in topic_words else 0 for word in all_words] v2 = [1 if word in existing_words else 0 for word in all_words] - + similarity = cosine_similarity(v1, v2) - + if similarity >= 0.6: similar_topics.append((existing_topic, similarity)) - + similar_topics.sort(key=lambda x: x[1], reverse=True) similar_topics = similar_topics[:5] similar_topics_dict[topic] = similar_topics @@ -358,7 +360,7 @@ class Hippocampus: async def operation_build_memory(self, chat_size=20): time_frequency = {'near': 1, 'mid': 4, 'far': 4} memory_samples = self.get_memory_sample(chat_size, time_frequency) - + for i, messages in enumerate(memory_samples, 1): all_topics = [] # 加载进度可视化 @@ -371,14 +373,14 @@ class Hippocampus: compress_rate = global_config.memory_compress_rate compressed_memory, similar_topics_dict = await self.memory_compress(messages, compress_rate) logger.info(f"压缩后记忆数量: {len(compressed_memory)},似曾相识的话题: {len(similar_topics_dict)}") - + current_time = datetime.datetime.now().timestamp() - + for topic, memory in compressed_memory: logger.info(f"添加节点: {topic}") self.memory_graph.add_dot(topic, memory) all_topics.append(topic) - + # 连接相似的已存在主题 if topic in similar_topics_dict: similar_topics = similar_topics_dict[topic] @@ -386,11 +388,11 @@ class Hippocampus: if topic != similar_topic: strength = int(similarity * 10) logger.info(f"连接相似节点: {topic} 和 {similar_topic} (强度: {strength})") - self.memory_graph.G.add_edge(topic, similar_topic, - strength=strength, - created_time=current_time, - last_modified=current_time) - + self.memory_graph.G.add_edge(topic, similar_topic, + strength=strength, + created_time=current_time, + last_modified=current_time) + # 连接同批次的相关话题 for i in range(len(all_topics)): for j in range(i + 1, len(all_topics)): @@ -416,7 +418,7 @@ class Hippocampus: # 计算内存中节点的特征值 memory_hash = self.calculate_node_hash(concept, memory_items) - + # 获取时间信息 created_time = data.get('created_time', datetime.datetime.now().timestamp()) last_modified = data.get('last_modified', datetime.datetime.now().timestamp()) @@ -466,7 +468,7 @@ class Hippocampus: edge_hash = self.calculate_edge_hash(source, target) edge_key = (source, target) strength = data.get('strength', 1) - + # 获取边的时间信息 created_time = data.get('created_time', datetime.datetime.now().timestamp()) last_modified = data.get('last_modified', datetime.datetime.now().timestamp()) @@ -499,7 +501,7 @@ class Hippocampus: """从数据库同步数据到内存中的图结构""" current_time = datetime.datetime.now().timestamp() need_update = False - + # 清空当前图 self.memory_graph.G.clear() @@ -510,7 +512,7 @@ class Hippocampus: memory_items = node.get('memory_items', []) if not isinstance(memory_items, list): memory_items = [memory_items] if memory_items else [] - + # 检查时间字段是否存在 if 'created_time' not in node or 'last_modified' not in node: need_update = True @@ -520,22 +522,22 @@ class Hippocampus: update_data['created_time'] = current_time if 'last_modified' not in node: update_data['last_modified'] = current_time - + db.graph_data.nodes.update_one( {'concept': concept}, {'$set': update_data} ) logger.info(f"[时间更新] 节点 {concept} 添加缺失的时间字段") - + # 获取时间信息(如果不存在则使用当前时间) created_time = node.get('created_time', current_time) last_modified = node.get('last_modified', current_time) - + # 添加节点到图中 - self.memory_graph.G.add_node(concept, - memory_items=memory_items, - created_time=created_time, - last_modified=last_modified) + self.memory_graph.G.add_node(concept, + memory_items=memory_items, + created_time=created_time, + last_modified=last_modified) # 从数据库加载所有边 edges = list(db.graph_data.edges.find()) @@ -543,7 +545,7 @@ class Hippocampus: source = edge['source'] target = edge['target'] strength = edge.get('strength', 1) - + # 检查时间字段是否存在 if 'created_time' not in edge or 'last_modified' not in edge: need_update = True @@ -553,24 +555,24 @@ class Hippocampus: update_data['created_time'] = current_time if 'last_modified' not in edge: update_data['last_modified'] = current_time - + db.graph_data.edges.update_one( {'source': source, 'target': target}, {'$set': update_data} ) logger.info(f"[时间更新] 边 {source} - {target} 添加缺失的时间字段") - + # 获取时间信息(如果不存在则使用当前时间) created_time = edge.get('created_time', current_time) last_modified = edge.get('last_modified', current_time) - + # 只有当源节点和目标节点都存在时才添加边 if source in self.memory_graph.G and target in self.memory_graph.G: - self.memory_graph.G.add_edge(source, target, - strength=strength, - created_time=created_time, - last_modified=last_modified) - + self.memory_graph.G.add_edge(source, target, + strength=strength, + created_time=created_time, + last_modified=last_modified) + if need_update: logger.success("[数据库] 已为缺失的时间字段进行补充") @@ -578,44 +580,44 @@ class Hippocampus: """随机选择图中一定比例的节点和边进行检查,根据时间条件决定是否遗忘""" # 检查数据库是否为空 # logger.remove() - + logger.info(f"[遗忘] 开始检查数据库... 当前Logger信息:") # logger.info(f"- Logger名称: {logger.name}") logger.info(f"- Logger等级: {logger.level}") # logger.info(f"- Logger处理器: {[handler.__class__.__name__ for handler in logger.handlers]}") - + # logger2 = setup_logger(LogModule.MEMORY) # logger2.info(f"[遗忘] 开始检查数据库... 当前Logger信息:") # logger.info(f"[遗忘] 开始检查数据库... 当前Logger信息:") - + all_nodes = list(self.memory_graph.G.nodes()) all_edges = list(self.memory_graph.G.edges()) - + if not all_nodes and not all_edges: logger.info("[遗忘] 记忆图为空,无需进行遗忘操作") return - + check_nodes_count = max(1, int(len(all_nodes) * percentage)) check_edges_count = max(1, int(len(all_edges) * percentage)) - + nodes_to_check = random.sample(all_nodes, check_nodes_count) edges_to_check = random.sample(all_edges, check_edges_count) - + edge_changes = {'weakened': 0, 'removed': 0} node_changes = {'reduced': 0, 'removed': 0} - + current_time = datetime.datetime.now().timestamp() - + # 检查并遗忘连接 logger.info("[遗忘] 开始检查连接...") for source, target in edges_to_check: edge_data = self.memory_graph.G[source][target] last_modified = edge_data.get('last_modified') - - if current_time - last_modified > 3600*global_config.memory_forget_time: + + if current_time - last_modified > 3600 * global_config.memory_forget_time: current_strength = edge_data.get('strength', 1) new_strength = current_strength - 1 - + if new_strength <= 0: self.memory_graph.G.remove_edge(source, target) edge_changes['removed'] += 1 @@ -625,23 +627,23 @@ class Hippocampus: edge_data['last_modified'] = current_time edge_changes['weakened'] += 1 logger.info(f"[遗忘] 连接减弱: {source} -> {target} (强度: {current_strength} -> {new_strength})") - + # 检查并遗忘话题 logger.info("[遗忘] 开始检查节点...") for node in nodes_to_check: node_data = self.memory_graph.G.nodes[node] last_modified = node_data.get('last_modified', current_time) - - if current_time - last_modified > 3600*24: + + if current_time - last_modified > 3600 * 24: memory_items = node_data.get('memory_items', []) if not isinstance(memory_items, list): memory_items = [memory_items] if memory_items else [] - + if memory_items: current_count = len(memory_items) removed_item = random.choice(memory_items) memory_items.remove(removed_item) - + if memory_items: self.memory_graph.G.nodes[node]['memory_items'] = memory_items self.memory_graph.G.nodes[node]['last_modified'] = current_time @@ -651,7 +653,7 @@ class Hippocampus: self.memory_graph.G.remove_node(node) node_changes['removed'] += 1 logger.info(f"[遗忘] 节点移除: {node}") - + if any(count > 0 for count in edge_changes.values()) or any(count > 0 for count in node_changes.values()): self.sync_memory_to_db() logger.info("[遗忘] 统计信息:") @@ -943,6 +945,7 @@ def segment_text(text): seg_text = list(jieba.cut(text)) return seg_text + driver = get_driver() config = driver.config From 9b72b5a996e486b23cc8f2c042fdf1d255fc569d Mon Sep 17 00:00:00 2001 From: AL76 <735756072@qq.com> Date: Sat, 15 Mar 2025 04:19:20 +0800 Subject: [PATCH 05/29] =?UTF-8?q?refactor:=20=E4=B8=BB=E7=A8=8B=E5=BA=8Fbo?= =?UTF-8?q?t.py=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/bot.py b/bot.py index 7a97f485e..50d04fd08 100644 --- a/bot.py +++ b/bot.py @@ -10,10 +10,11 @@ import uvicorn from dotenv import load_dotenv from nonebot.adapters.onebot.v11 import Adapter import platform -from src.plugins.utils.logger_config import LogModule, LogClassification +from src.common.logger import get_module_logger -# 配置日志格式 +# 配置主程序日志格式 +logger = get_module_logger("主程序") # 获取没有加载env时的环境变量 env_mask = {key: os.getenv(key) for key in os.environ} @@ -76,11 +77,11 @@ def init_env(): def load_env(): # 使用闭包实现对加载器的横向扩展,避免大量重复判断 def prod(): - logger.success("加载生产环境变量配置") + logger.success("成功加载生产环境变量配置") load_dotenv(".env.prod", override=True) # override=True 允许覆盖已存在的环境变量 def dev(): - logger.success("加载开发环境变量配置") + logger.success("成功加载开发环境变量配置") load_dotenv(".env.dev", override=True) # override=True 允许覆盖已存在的环境变量 fn_map = {"prod": prod, "dev": dev} @@ -100,11 +101,6 @@ def load_env(): RuntimeError(f"ENVIRONMENT 配置错误,请检查 .env 文件中的 ENVIRONMENT 变量及对应 .env.{env} 是否存在") -def load_logger(): - global logger # 使得bot.py中其他函数也能调用 - log_module = LogModule() - logger = log_module.setup_logger(LogClassification.BASE) - def scan_provider(env_config: dict): provider = {} @@ -206,8 +202,6 @@ def raw_main(): if __name__ == "__main__": try: - # 配置日志,使得主程序直接退出时候也能访问logger - load_logger() raw_main() app = nonebot.get_asgi() From ddb8ea6610f1592c6cc78656a5b07be2acbbb357 Mon Sep 17 00:00:00 2001 From: AL76 <735756072@qq.com> Date: Sat, 15 Mar 2025 04:22:31 +0800 Subject: [PATCH 06/29] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=B7=A5=E5=8E=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/logger.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/common/logger.py b/src/common/logger.py index 6093920f0..4808fc77e 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -1,9 +1,10 @@ from loguru import logger -from typing import Dict, Optional, Union, List, Any +from typing import Dict, Optional, Union, List import sys from types import ModuleType from pathlib import Path +# logger.remove() # 类型别名 LoguruLogger = logger.__class__ @@ -13,22 +14,20 @@ _handler_registry: Dict[str, List[int]] = {} # 获取日志存储根地址 current_file_path = Path(__file__).resolve() -PROJECT_ROOT = current_file_path.parent.parent.parent -LOG_ROOT = str(PROJECT_ROOT / "logs") +LOG_ROOT = "logs" # 默认全局配置 DEFAULT_CONFIG = { # 日志级别配置 - "level": "INFO", # 全局基础日志级别(若未指定console/file_level则生效) - "console_level": "INFO", # 控制台默认级别(可覆盖) + "console_level": "DEBUG", # 控制台默认级别(可覆盖) "file_level": "DEBUG", # 文件默认级别(可覆盖) # 格式配置 "console_format": ( "{time:YYYY-MM-DD HH:mm:ss} | " "{level: <8} | " - "{extra[module]: <20} | " + "{extra[module]: <4} | " "{message}" ), "file_format": ( @@ -124,4 +123,4 @@ def remove_module_logger(module_name: str) -> None: if module_name in _handler_registry: for handler_id in _handler_registry[module_name]: logger.remove(handler_id) - del _handler_registry[module_name] \ No newline at end of file + del _handler_registry[module_name] From 2ab99f1ba999189f022ad441ca9ef2780a919768 Mon Sep 17 00:00:00 2001 From: ChangingSelf Date: Fri, 14 Mar 2025 23:53:59 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=E6=96=B0=E5=A2=9EGUI=EF=BC=9A=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=9F=A5=E7=9C=8B=E5=99=A8=EF=BC=88=E4=B8=8D=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=E4=BB=BB=E4=BD=95=E7=8E=B0=E6=9C=89=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gui/logger_gui.py | 348 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/gui/logger_gui.py diff --git a/src/gui/logger_gui.py b/src/gui/logger_gui.py new file mode 100644 index 000000000..05bda5d01 --- /dev/null +++ b/src/gui/logger_gui.py @@ -0,0 +1,348 @@ +import customtkinter as ctk +import subprocess +import threading +import queue +import re +import os +import signal +from collections import defaultdict + +# 设置应用的外观模式和默认颜色主题 +ctk.set_appearance_mode("dark") +ctk.set_default_color_theme("blue") + + +class LogViewerApp(ctk.CTk): + """日志查看器应用的主类,继承自customtkinter的CTk类""" + + def __init__(self): + """初始化日志查看器应用的界面和状态""" + super().__init__() + self.title("日志查看器") + self.geometry("1200x800") + + # 初始化进程、日志队列、日志数据等变量 + self.process = None + self.log_queue = queue.Queue() + self.log_data = [] + self.available_levels = set() + self.available_modules = set() + self.sorted_modules = [] + self.module_checkboxes = {} # 存储模块复选框的字典 + + # 日志颜色配置 + self.color_config = { + "time": "#888888", + "DEBUG": "#2196F3", + "INFO": "#4CAF50", + "WARNING": "#FF9800", + "ERROR": "#F44336", + "module": "#D4D0AB", + "default": "#FFFFFF", + } + + # 列可见性配置 + self.column_visibility = {"show_time": True, "show_level": True, "show_module": True} + + # 选中的日志等级和模块 + self.selected_levels = set() + self.selected_modules = set() + + # 创建界面组件并启动日志队列处理 + self.create_widgets() + self.after(100, self.process_log_queue) + + def create_widgets(self): + """创建应用界面的各个组件""" + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(1, weight=1) + + # 控制面板 + control_frame = ctk.CTkFrame(self) + control_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5) + + self.start_btn = ctk.CTkButton(control_frame, text="启动", command=self.start_process) + self.start_btn.pack(side="left", padx=5) + + self.stop_btn = ctk.CTkButton(control_frame, text="停止", command=self.stop_process, state="disabled") + self.stop_btn.pack(side="left", padx=5) + + self.clear_btn = ctk.CTkButton(control_frame, text="清屏", command=self.clear_logs) + self.clear_btn.pack(side="left", padx=5) + + column_filter_frame = ctk.CTkFrame(control_frame) + column_filter_frame.pack(side="left", padx=20) + + self.time_check = ctk.CTkCheckBox(column_filter_frame, text="显示时间", command=self.refresh_logs) + self.time_check.pack(side="left", padx=5) + self.time_check.select() + + self.level_check = ctk.CTkCheckBox(column_filter_frame, text="显示等级", command=self.refresh_logs) + self.level_check.pack(side="left", padx=5) + self.level_check.select() + + self.module_check = ctk.CTkCheckBox(column_filter_frame, text="显示模块", command=self.refresh_logs) + self.module_check.pack(side="left", padx=5) + self.module_check.select() + + # 筛选面板 + filter_frame = ctk.CTkFrame(self) + filter_frame.grid(row=0, column=1, rowspan=2, sticky="ns", padx=5) + + ctk.CTkLabel(filter_frame, text="日志等级筛选").pack(pady=5) + self.level_scroll = ctk.CTkScrollableFrame(filter_frame, width=150, height=200) + self.level_scroll.pack(fill="both", expand=True, padx=5) + + ctk.CTkLabel(filter_frame, text="模块筛选").pack(pady=5) + self.module_filter_entry = ctk.CTkEntry(filter_frame, placeholder_text="输入模块过滤词") + self.module_filter_entry.pack(pady=5) + self.module_filter_entry.bind("", self.update_module_filter) + + self.module_scroll = ctk.CTkScrollableFrame(filter_frame, width=300, height=200) + self.module_scroll.pack(fill="both", expand=True, padx=5) + + self.log_text = ctk.CTkTextbox(self, wrap="word") + self.log_text.grid(row=1, column=0, sticky="nsew", padx=10, pady=5) + + self.init_text_tags() + + def update_module_filter(self, event): + """根据模块过滤词更新模块复选框的显示""" + filter_text = self.module_filter_entry.get().strip().lower() + for module, checkbox in self.module_checkboxes.items(): + if filter_text in module.lower(): + checkbox.pack(anchor="w", padx=5, pady=2) + else: + checkbox.pack_forget() + + def update_filters(self, level, module): + """更新日志等级和模块的筛选器""" + if level not in self.available_levels: + self.available_levels.add(level) + self.add_checkbox(self.level_scroll, level, "level") + + module_key = self.get_module_key(module) + if module_key not in self.available_modules: + self.available_modules.add(module_key) + self.sorted_modules = sorted(self.available_modules, key=lambda x: x.lower()) + self.rebuild_module_checkboxes() + + def rebuild_module_checkboxes(self): + """重新构建模块复选框""" + # 清空现有复选框 + for widget in self.module_scroll.winfo_children(): + widget.destroy() + self.module_checkboxes.clear() + + # 重建排序后的复选框 + for module in self.sorted_modules: + self.add_checkbox(self.module_scroll, module, "module") + + def add_checkbox(self, parent, text, type_): + """在指定父组件中添加复选框""" + + def update_filter(): + if type_ == "level": + if cb.get(): + self.selected_levels.add(text) + else: + self.selected_levels.discard(text) + elif type_ == "module": + if cb.get(): + self.selected_modules.add(text) + else: + self.selected_modules.discard(text) + self.refresh_logs() + + cb = ctk.CTkCheckBox(parent, text=text, command=update_filter) + cb.select() # 默认选中 + + # 记录初始选中状态 + if type_ == "level": + self.selected_levels.add(text) + elif type_ == "module": + self.selected_modules.add(text) + self.module_checkboxes[text] = cb # 存储模块复选框引用 + + cb.pack(anchor="w", padx=5, pady=2) + return cb + + def check_filter(self, entry): + """检查日志条目是否符合当前筛选条件""" + level_ok = not self.selected_levels or entry["level"] in self.selected_levels + module_key = self.get_module_key(entry["module"]) + module_ok = not self.selected_modules or module_key in self.selected_modules + return level_ok and module_ok + + def init_text_tags(self): + """初始化日志文本的颜色标签""" + for tag, color in self.color_config.items(): + self.log_text.tag_config(tag, foreground=color) + self.log_text.tag_config("default", foreground=self.color_config["default"]) + + def start_process(self): + """启动日志进程并开始读取输出""" + self.process = subprocess.Popen( + ["nb", "run"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + encoding="utf-8", + errors="ignore", + ) + self.start_btn.configure(state="disabled") + self.stop_btn.configure(state="normal") + threading.Thread(target=self.read_output, daemon=True).start() + + def stop_process(self): + """停止日志进程并清理相关资源""" + if self.process: + try: + # 终止整个进程组(Windows需要特殊处理) + if hasattr(self.process, "pid"): + if os.name == "nt": + subprocess.run(["taskkill", "/F", "/T", "/PID", str(self.process.pid)], check=True) + else: + os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) + except Exception as e: + print(f"Error terminating process: {e}") + finally: + self.process = None + # 清理队列和重置界面状态 + self.log_queue.queue.clear() + self.start_btn.configure(state="normal") + self.stop_btn.configure(state="disabled") + # 强制刷新日志显示 + self.refresh_logs() + + def read_output(self): + """读取日志进程的输出并放入队列""" + while self.process and self.process.poll() is None: + line = self.process.stdout.readline() + if line: + self.log_queue.put(line) + + def process_log_queue(self): + """处理日志队列中的日志条目""" + while not self.log_queue.empty(): + line = self.log_queue.get() + self.process_log_line(line) + self.after(100, self.process_log_queue) + + def process_log_line(self, line): + """解析单行日志并更新日志数据和筛选器""" + match = re.match( + r"""^ + (?:(?P