diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index a9654449e..95bb5b071 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -19,7 +19,7 @@ from src.chat.focus_chat.hfc_utils import get_recent_message_stats from src.person_info.person_info import get_person_info_manager from src.plugin_system.apis import generator_api,send_api,message_api from src.chat.willing.willing_manager import get_willing_manager -from .priority_manager import PriorityManager +from ...mais4u.mais4u_chat.priority_manager import PriorityManager from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat @@ -448,18 +448,10 @@ class HeartFChatting: return False, "", "" - async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...") self.running = False # <-- 在开始关闭时设置标志位 - - # 记录最终的消息统计 - if self._message_count > 0: - logger.info(f"{self.log_prefix} 本次focus会话共发送了 {self._message_count} 条消息") - if self._fatigue_triggered: - logger.info(f"{self.log_prefix} 因疲惫而退出focus模式") - # 取消循环任务 if self._loop_task and not self._loop_task.done(): logger.info(f"{self.log_prefix} 正在取消HeartFChatting循环任务") @@ -477,10 +469,7 @@ class HeartFChatting: # 清理状态 self.running = False self._loop_task = None - - # 重置消息计数器,为下次启动做准备 - self.reset_message_count() - + logger.info(f"{self.log_prefix} HeartFChatting关闭完成") diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 631b0aaec..6247e6d1d 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -45,9 +45,6 @@ class SubHeartflow: """异步初始化方法,创建兴趣流并确定聊天类型""" await self.heart_fc_instance.start() - - - async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" if self.heart_fc_instance.running: diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 8863c60fa..7cc428573 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -103,15 +103,13 @@ class ActionPlanner: logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到") # 如果没有可用动作或只有no_reply动作,直接返回no_reply - if not current_available_actions or ( - len(current_available_actions) == 1 and "no_reply" in current_available_actions - ): - action = "no_reply" - reasoning = "没有可用的动作" if not current_available_actions else "只有no_reply动作可用,跳过规划" + if not current_available_actions: + if mode == "focus": + action = "no_reply" + else: + action = "no_action" + reasoning = "没有可用的动作" logger.info(f"{self.log_prefix}{reasoning}") - logger.debug( - f"{self.log_prefix}[focus]沉默后恢复到默认动作集, 当前可用: {list(self.action_manager.get_using_actions().keys())}" - ) return { "action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning}, } diff --git a/src/chat/utils/utils_image.py b/src/chat/utils/utils_image.py index 17cfb2323..57185d474 100644 --- a/src/chat/utils/utils_image.py +++ b/src/chat/utils/utils_image.py @@ -116,10 +116,10 @@ class ImageManager: if image_base64_processed is None: logger.warning("GIF转换失败,无法获取描述") return "[表情包(GIF处理失败)]" - prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,使用1-2个词描述一下表情包表达的情感和内容,简短一些,输出一段平文本,不超过15个字" + prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,使用1-2个词描述一下表情包表达的情感和内容,简短一些,输出一段平文本,只输出1-2个词就好,不要输出其他内容" description, _ = await self._llm.generate_response_for_image(prompt, image_base64_processed, "jpg") else: - prompt = "图片是一个表情包,请用使用1-2个词描述一下表情包所表达的情感和内容,简短一些,输出一段平文本,不超过15个字" + prompt = "图片是一个表情包,请用使用1-2个词描述一下表情包所表达的情感和内容,简短一些,输出一段平文本,只输出1-2个词就好,不要输出其他内容" description, _ = await self._llm.generate_response_for_image(prompt, image_base64, image_format) if description is None: diff --git a/src/mais4u/mais4u_chat/normal_chat.py b/src/mais4u/mais4u_chat/normal_chat.py new file mode 100644 index 000000000..741c2fc79 --- /dev/null +++ b/src/mais4u/mais4u_chat/normal_chat.py @@ -0,0 +1,211 @@ +import asyncio +import time +from typing import Optional +from src.config.config import global_config +from src.common.logger import get_logger +from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager +from src.chat.planner_actions.action_manager import ActionManager +from src.person_info.relationship_builder_manager import relationship_builder_manager +from .priority_manager import PriorityManager +import traceback +from src.chat.planner_actions.planner import ActionPlanner +from src.chat.planner_actions.action_modifier import ActionModifier +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive + +from src.chat.utils.utils import get_chat_type_and_target_info + +logger = get_logger("normal_chat") + +LOOP_INTERVAL = 0.3 + +class NormalChat: + """ + 普通聊天处理类,负责处理非核心对话的聊天逻辑。 + 每个聊天(私聊或群聊)都会有一个独立的NormalChat实例。 + """ + + def __init__( + self, + chat_stream: ChatStream, + on_switch_to_focus_callback=None, + get_cooldown_progress_callback=None, + ): + """ + 初始化NormalChat实例。 + + Args: + chat_stream (ChatStream): 聊天流对象,包含与特定聊天相关的所有信息。 + """ + self.chat_stream = chat_stream + self.stream_id = chat_stream.stream_id + self.last_read_time = time.time()-1 + + self.stream_name = get_chat_manager().get_stream_name(self.stream_id) or self.stream_id + + self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) + + self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.stream_id) + + self.start_time = time.time() + + # self.mood_manager = mood_manager + self.start_time = time.time() + + self.running = False + + self._initialized = False # Track initialization status + + # Planner相关初始化 + self.action_manager = ActionManager() + self.planner = ActionPlanner(self.stream_id, self.action_manager, mode="normal") + self.action_modifier = ActionModifier(self.action_manager, self.stream_id) + self.enable_planner = global_config.normal_chat.enable_planner # 从配置中读取是否启用planner + + # 记录最近的回复内容,每项包含: {time, user_message, response, is_mentioned, is_reference_reply} + self.recent_replies = [] + self.max_replies_history = 20 # 最多保存最近20条回复记录 + + # 添加回调函数,用于在满足条件时通知切换到focus_chat模式 + self.on_switch_to_focus_callback = on_switch_to_focus_callback + + # 添加回调函数,用于获取冷却进度 + self.get_cooldown_progress_callback = get_cooldown_progress_callback + + self._disabled = False # 增加停用标志 + + self.timeout_count = 0 + + self.action_type: Optional[str] = None # 当前动作类型 + self.is_parallel_action: bool = False # 是否是可并行动作 + + # 任务管理 + self._chat_task: Optional[asyncio.Task] = None + self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer + self._disabled = False # 停用标志 + + # 新增:回复模式和优先级管理器 + self.reply_mode = self.chat_stream.context.get_priority_mode() + if self.reply_mode == "priority": + self.priority_manager = PriorityManager( + normal_queue_max_size=5, + ) + else: + self.priority_manager = None + + + + # async def _interest_mode_loopbody(self): + # try: + # await asyncio.sleep(LOOP_INTERVAL) + + # if self._disabled: + # return False + + # now = time.time() + # new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + # chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + # ) + + # if new_messages_data: + # self.last_read_time = now + + # for msg_data in new_messages_data: + # try: + # self.adjust_reply_frequency() + # await self.normal_response( + # message_data=msg_data, + # is_mentioned=msg_data.get("is_mentioned", False), + # interested_rate=msg_data.get("interest_rate", 0.0) * self.willing_amplifier, + # ) + # return True + # except Exception as e: + # logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") + + + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") + # return False + # except Exception: + # logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) + # await asyncio.sleep(10) + + async def _priority_mode_loopbody(self): + try: + await asyncio.sleep(LOOP_INTERVAL) + + if self._disabled: + return False + + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + ) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + if self.priority_manager: + self.priority_manager.add_message(msg_data, msg_data.get("interest_rate", 0.0)) + return True + except Exception as e: + logger.error(f"[{self.stream_name}] 添加消息到优先级队列时出错: {e} {traceback.format_exc()}") + + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 优先级消息生产者任务被取消") + return False + except Exception: + logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) + await asyncio.sleep(10) + + # async def _interest_message_polling_loop(self): + # """ + # [Interest Mode] 通过轮询数据库获取新消息并直接处理。 + # """ + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务开始") + # try: + # while not self._disabled: + # success = await self._interest_mode_loopbody() + + # if not success: + # break + + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") + + + + + async def _priority_chat_loop(self): + """ + 使用优先级队列的消息处理循环。 + """ + while not self._disabled: + try: + if self.priority_manager and not self.priority_manager.is_empty(): + # 获取最高优先级的消息,现在是字典 + message_data = self.priority_manager.get_highest_priority_message() + + if message_data: + logger.info( + f"[{self.stream_name}] 从队列中取出消息进行处理: User {message_data.get('user_id')}, Time: {time.strftime('%H:%M:%S', time.localtime(message_data.get('time')))}" + ) + + do_reply = await self.reply_one_message(message_data) + response_set = do_reply if do_reply else [] + factor = 0.5 + cnt = sum([len(r) for r in response_set]) + await asyncio.sleep(max(1, factor * cnt - 3)) # 等待tts + + # 等待一段时间再检查队列 + await asyncio.sleep(1) + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 优先级聊天循环被取消。") + break + except Exception: + logger.error(f"[{self.stream_name}] 优先级聊天循环出现错误: {traceback.format_exc()}", exc_info=True) + # 出现错误时,等待更长时间避免频繁报错 + await asyncio.sleep(10)