From 35ec390dfdde3c6cc41b1dab9016d23624c47c29 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 22 Jul 2025 22:38:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E5=85=A8=E5=B1=80=E5=90=AF=E7=94=A8=E5=92=8C=E7=A6=81?= =?UTF-8?q?=E7=94=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 3 + src/chat/chat_loop/heartFC_chat.py | 81 +++++++++++--------- src/chat/message_receive/chat_stream.py | 11 ++- src/plugin_system/core/component_registry.py | 77 ++++++++++++++++++- 4 files changed, 129 insertions(+), 43 deletions(-) diff --git a/changes.md b/changes.md index 86b2f9b28..9ca193ac7 100644 --- a/changes.md +++ b/changes.md @@ -46,6 +46,9 @@ 11. 修正了`command`所编译的`Pattern`注册时的错误输出。 12. `events_manager`有了task相关逻辑了。 13. 现在有了插件卸载和重载功能了,也就是热插拔。 +14. 实现了组件的全局启用和禁用功能。 + - 通过`enable_component`和`disable_component`方法来启用或禁用组件。 + - 不过这个操作不会保存到配置文件~ ### TODO 把这个看起来就很别扭的config获取方式改一下 diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 5a82e8390..efe7413cf 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -21,7 +21,7 @@ from src.plugin_system.base.component_types import ActionInfo, ChatMode from src.plugin_system.apis import generator_api, send_api, message_api from src.chat.willing.willing_manager import get_willing_manager from src.chat.mai_thinking.mai_think import mai_thinking_manager -from maim_message.message_base import GroupInfo,UserInfo +from maim_message.message_base import GroupInfo ENABLE_THINKING = False @@ -257,31 +257,29 @@ class HeartFChatting: ) person_name = await person_info_manager.get_value(person_id, "person_name") return f"{person_name}:{message_data.get('processed_plain_text')}" - + async def send_typing(self): - group_info = GroupInfo(platform = "amaidesu_default",group_id = 114514,group_name = "内心") - - chat = await get_chat_manager().get_or_create_stream( - platform = "amaidesu_default", - user_info = None, - group_info = group_info + group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心") + + chat = await get_chat_manager().get_or_create_stream( + platform="amaidesu_default", + user_info=None, # type: ignore + group_info=group_info, ) - - + await send_api.custom_to_stream( message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False ) - + async def stop_typing(self): - group_info = GroupInfo(platform = "amaidesu_default",group_id = 114514,group_name = "内心") - - chat = await get_chat_manager().get_or_create_stream( - platform = "amaidesu_default", - user_info = None, - group_info = group_info + group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心") + + chat = await get_chat_manager().get_or_create_stream( + platform="amaidesu_default", + user_info=None, # type: ignore + group_info=group_info, ) - - + await send_api.custom_to_stream( message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False ) @@ -295,7 +293,7 @@ class HeartFChatting: cycle_timers, thinking_id = self.start_cycle() logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - + await self.send_typing() async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): @@ -364,15 +362,12 @@ class HeartFChatting: logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") # 发送回复 (不再需要传入 chat) - reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data) - + reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, message_data) + await self.stop_typing() - - - + if ENABLE_THINKING: await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) - return True @@ -504,10 +499,9 @@ class HeartFChatting: """ interested_rate = (message_data.get("interest_value") or 0.0) * self.willing_amplifier - + self.willing_manager.setup(message_data, self.chat_stream) - - + reply_probability = await self.willing_manager.get_reply_probability(message_data.get("message_id", "")) talk_frequency = -1.00 @@ -517,7 +511,7 @@ class HeartFChatting: if additional_config and "maimcore_reply_probability_gain" in additional_config: reply_probability += additional_config["maimcore_reply_probability_gain"] reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 - + talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id) reply_probability = talk_frequency * reply_probability @@ -527,9 +521,9 @@ class HeartFChatting: # 打印消息信息 mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" - + # logger.info(f"[{mes_name}] 当前聊天频率: {talk_frequency:.2f},兴趣值: {interested_rate:.2f},回复概率: {reply_probability * 100:.1f}%") - + if reply_probability > 0.05: logger.info( f"[{mes_name}]" @@ -545,7 +539,6 @@ class HeartFChatting: # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) self.willing_manager.delete(message_data.get("message_id", "")) return False - async def _generate_response( self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str @@ -570,7 +563,7 @@ class HeartFChatting: logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - async def _send_response(self, reply_set, reply_to, thinking_start_time,message_data): + async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data): current_time = time.time() new_message_count = message_api.count_new_messages( chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time @@ -592,13 +585,27 @@ class HeartFChatting: if not first_replied: if need_reply: await send_api.text_to_stream( - text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, reply_to_platform_id=reply_to_platform_id, typing=False + text=data, + stream_id=self.chat_stream.stream_id, + reply_to=reply_to, + reply_to_platform_id=reply_to_platform_id, + typing=False, ) else: - await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to_platform_id=reply_to_platform_id, typing=False) + await send_api.text_to_stream( + text=data, + stream_id=self.chat_stream.stream_id, + reply_to_platform_id=reply_to_platform_id, + typing=False, + ) first_replied = True else: - await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to_platform_id=reply_to_platform_id, typing=True) + await send_api.text_to_stream( + text=data, + stream_id=self.chat_stream.stream_id, + reply_to_platform_id=reply_to_platform_id, + typing=True, + ) reply_text += data return reply_text diff --git a/src/chat/message_receive/chat_stream.py b/src/chat/message_receive/chat_stream.py index e4a61900e..2ee2be05a 100644 --- a/src/chat/message_receive/chat_stream.py +++ b/src/chat/message_receive/chat_stream.py @@ -163,20 +163,25 @@ class ChatManager: """注册消息到聊天流""" stream_id = self._generate_stream_id( message.message_info.platform, # type: ignore - message.message_info.user_info, # type: ignore + message.message_info.user_info, message.message_info.group_info, ) self.last_messages[stream_id] = message # logger.debug(f"注册消息到聊天流: {stream_id}") @staticmethod - def _generate_stream_id(platform: str, user_info: UserInfo, group_info: Optional[GroupInfo] = None) -> str: + def _generate_stream_id( + platform: str, user_info: Optional[UserInfo], group_info: Optional[GroupInfo] = None + ) -> str: """生成聊天流唯一ID""" + if not user_info and not group_info: + raise ValueError("用户信息或群组信息必须提供") + if group_info: # 组合关键信息 components = [platform, str(group_info.group_id)] else: - components = [platform, str(user_info.user_id), "private"] + components = [platform, str(user_info.user_id), "private"] # type: ignore # 使用MD5生成唯一ID key = "_".join(components) diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index a804c5be7..e9509dd9b 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -110,11 +110,17 @@ class ComponentRegistry: # 根据组件类型进行特定注册(使用原始名称) match component_type: case ComponentType.ACTION: - ret = self._register_action_component(component_info, component_class) # type: ignore + assert isinstance(component_info, ActionInfo) + assert issubclass(component_class, BaseAction) + ret = self._register_action_component(component_info, component_class) case ComponentType.COMMAND: - ret = self._register_command_component(component_info, component_class) # type: ignore + assert isinstance(component_info, CommandInfo) + assert issubclass(component_class, BaseCommand) + ret = self._register_command_component(component_info, component_class) case ComponentType.EVENT_HANDLER: - ret = self._register_event_handler_component(component_info, component_class) # type: ignore + assert isinstance(component_info, EventHandlerInfo) + assert issubclass(component_class, BaseEventHandler) + ret = self._register_event_handler_component(component_info, component_class) case _: logger.warning(f"未知组件类型: {component_type}") @@ -218,6 +224,71 @@ class ComponentRegistry: self._components_classes.pop(component_name, None) logger.info(f"组件 {component_name} 已移除") + # === 组件全局启用/禁用方法 === + + def enable_component(self, component_name: str, component_type: ComponentType) -> bool: + """全局的启用某个组件 + Parameters: + component_name: 组件名称 + component_type: 组件类型 + Returns: + bool: 启用成功返回True,失败返回False + """ + target_component_class = self.get_component_class(component_name, component_type) + target_component_info = self.get_component_info(component_name, component_type) + if not target_component_class or not target_component_info: + logger.warning(f"组件 {component_name} 未注册,无法启用") + return False + target_component_info.enabled = True + match component_type: + case ComponentType.ACTION: + assert isinstance(target_component_info, ActionInfo) + self._default_actions[component_name] = target_component_info + case ComponentType.COMMAND: + assert isinstance(target_component_info, CommandInfo) + pattern = target_component_info.command_pattern + self._command_patterns[re.compile(pattern)] = component_name + case ComponentType.EVENT_HANDLER: + assert isinstance(target_component_info, EventHandlerInfo) + assert issubclass(target_component_class, BaseEventHandler) + self._enabled_event_handlers[component_name] = target_component_class + from .events_manager import events_manager # 延迟导入防止循环导入问题 + + events_manager.register_event_subscriber(target_component_info, target_component_class) + self._components[component_name].enabled = True + self._components_by_type[component_type][component_name].enabled = True + logger.info(f"组件 {component_name} 已启用") + return True + + async def disable_component(self, component_name: str, component_type: ComponentType) -> bool: + """全局的禁用某个组件 + Parameters: + component_name: 组件名称 + component_type: 组件类型 + Returns: + bool: 禁用成功返回True,失败返回False + """ + target_component_class = self.get_component_class(component_name, component_type) + target_component_info = self.get_component_info(component_name, component_type) + if not target_component_class or not target_component_info: + logger.warning(f"组件 {component_name} 未注册,无法禁用") + return False + target_component_info.enabled = False + match component_type: + case ComponentType.ACTION: + self._default_actions.pop(component_name, None) + case ComponentType.COMMAND: + self._command_patterns = {k: v for k, v in self._command_patterns.items() if v != component_name} + case ComponentType.EVENT_HANDLER: + self._enabled_event_handlers.pop(component_name, None) + from .events_manager import events_manager # 延迟导入防止循环导入问题 + + await events_manager.unregister_event_subscriber(component_name) + self._components[component_name].enabled = False + self._components_by_type[component_type][component_name].enabled = False + logger.info(f"组件 {component_name} 已禁用") + return True + # === 组件查询方法 === def get_component_info( self, component_name: str, component_type: Optional[ComponentType] = None