diff --git a/src/chat/focus_chat/expressors/default_expressor.py b/src/chat/focus_chat/expressors/default_expressor.py index befe045e8..ed8409108 100644 --- a/src/chat/focus_chat/expressors/default_expressor.py +++ b/src/chat/focus_chat/expressors/default_expressor.py @@ -408,6 +408,10 @@ class DefaultExpressor: # 为每个消息片段生成唯一ID type = msg_text[0] data = msg_text[1] + + if global_config.experimental.debug_show_chat_mode and type == "text": + data += "ᶠ" + part_message_id = f"{thinking_id}_{i}" message_segment = Seg(type=type, data=data) diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index eb5affb33..0d4ac281a 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -13,10 +13,11 @@ from src.chat.heart_flow.mai_state_manager import MaiStateInfo from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo from .utils_chat import get_chat_type_and_target_info from src.config.config import global_config - +from rich.traceback import install logger = get_logger("sub_heartflow") +install(extra_lines=3) class SubHeartflow: def __init__( @@ -49,7 +50,7 @@ class SubHeartflow: self.chat_target_info: Optional[dict] = None # --- End Initialization --- - # 兴趣检测器 + # 兴趣消息集合 self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {} # 活动状态管理 diff --git a/src/chat/heart_flow/subheartflow_manager.py b/src/chat/heart_flow/subheartflow_manager.py index 6e8e7b868..e3e68f55d 100644 --- a/src/chat/heart_flow/subheartflow_manager.py +++ b/src/chat/heart_flow/subheartflow_manager.py @@ -91,9 +91,6 @@ class SubHeartflowManager: if subflow.should_stop: logger.warning(f"尝试获取已停止的子心流 {subheartflow_id},正在重新激活") subflow.should_stop = False # 重置停止标志 - - subflow.last_active_time = time.time() # 更新活跃时间 - # logger.debug(f"获取到已存在的子心流: {subheartflow_id}") return subflow try: diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 0cf0908f4..419482699 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -30,10 +30,8 @@ class NormalChat: def __init__(self, chat_stream: ChatStream, interest_dict: dict = None, on_switch_to_focus_callback=None): """初始化 NormalChat 实例。只进行同步操作。""" - # Basic info from chat_stream (sync) self.chat_stream = chat_stream self.stream_id = chat_stream.stream_id - # Get initial stream name, might be updated in initialize self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id # Interest dict @@ -46,7 +44,6 @@ class NormalChat: self.gpt = NormalChatGenerator() self.mood_manager = mood_manager self.start_time = time.time() - self.last_speak_time = 0 self._chat_task: Optional[asyncio.Task] = None self._initialized = False # Track initialization status @@ -57,20 +54,14 @@ class NormalChat: # 添加回调函数,用于在满足条件时通知切换到focus_chat模式 self.on_switch_to_focus_callback = on_switch_to_focus_callback - # 最近回复检查相关 - self._last_check_time = time.time() - self._check_interval = 10 # 每10秒检查一次是否需要切换到focus模式 async def initialize(self): """异步初始化,获取聊天类型和目标信息。""" if self._initialized: return - # --- Use utility function to determine chat type and fetch info --- self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.stream_id) - # Update stream_name again after potential async call in util func self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id - # --- End using utility function --- self._initialized = True logger.info(f"[{self.stream_name}] NormalChat 实例 initialize 完成 (异步部分)。") @@ -123,6 +114,8 @@ class NormalChat: mark_head = False first_bot_msg = None for msg in response_set: + if global_config.experimental.debug_show_chat_mode: + msg += "ⁿ" message_segment = Seg(type="text", data=msg) bot_message = MessageSending( message_id=thinking_id, @@ -147,8 +140,6 @@ class NormalChat: await message_manager.add_message(message_set) - self.last_speak_time = time.time() - return first_bot_msg # 改为实例方法 @@ -208,12 +199,6 @@ class NormalChat: logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出") break - # 定期检查是否需要切换到focus模式 - # current_time = time.time() - # if current_time - self._last_check_time > self._check_interval: - # await self._check_switch_to_focus() - # self._last_check_time = current_time - items_to_process = list(self.interest_dict.items()) if not items_to_process: continue @@ -378,118 +363,10 @@ class NormalChat: elif not do_reply: # 不回复处理 await willing_manager.not_reply_handle(message.message_info.message_id) - # else: # do_reply is True but response_set is None (handled above) - # logger.info(f"[{self.stream_name}] 决定回复但模型未生成内容。触发: {message.processed_plain_text[:20]}...") # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) willing_manager.delete(message.message_info.message_id) - # --- 新增:处理初始高兴趣消息的私有方法 --- - async def _process_initial_interest_messages(self): - """处理启动时存在于 interest_dict 中的高兴趣消息。""" - if not self.interest_dict: - return # 如果 interest_dict 为 None 或空,直接返回 - - items_to_process = list(self.interest_dict.items()) - if not items_to_process: - return # 没有初始消息,直接返回 - - logger.info(f"[{self.stream_name}] 发现 {len(items_to_process)} 条初始兴趣消息,开始处理高兴趣部分...") - interest_values = [item[1][1] for item in items_to_process] # 提取兴趣值列表 - - messages_to_reply = [] # 需要立即回复的消息 - - if len(interest_values) == 1: - # 如果只有一个消息,直接处理 - messages_to_reply.append(items_to_process[0]) - logger.info(f"[{self.stream_name}] 只有一条初始消息,直接处理。") - elif len(interest_values) > 1: - # 计算均值和标准差 - try: - mean_interest = statistics.mean(interest_values) - stdev_interest = statistics.stdev(interest_values) - threshold = mean_interest + stdev_interest - logger.info( - f"[{self.stream_name}] 初始兴趣值 均值: {mean_interest:.2f}, 标准差: {stdev_interest:.2f}, 阈值: {threshold:.2f}" - ) - - # 找出高于阈值的消息 - for item in items_to_process: - msg_id, (message, interest_value, is_mentioned) = item - if interest_value > threshold: - messages_to_reply.append(item) - logger.info(f"[{self.stream_name}] 找到 {len(messages_to_reply)} 条高于阈值的初始消息进行处理。") - except statistics.StatisticsError as e: - logger.error(f"[{self.stream_name}] 计算初始兴趣统计值时出错: {e},跳过初始处理。") - - # 处理需要回复的消息 - processed_count = 0 - # --- 修改:迭代前创建要处理的ID列表副本,防止迭代时修改 --- - messages_to_process_initially = list(messages_to_reply) # 创建副本 - # --- 新增:限制最多处理两条消息 --- - messages_to_process_initially = messages_to_process_initially[:2] - # --- 新增结束 --- - for item in messages_to_process_initially: # 使用副本迭代 - msg_id, (message, interest_value, is_mentioned) = item - # --- 修改:在处理前尝试 pop,防止竞争 --- - popped_item = self.interest_dict.pop(msg_id, None) - if popped_item is None: - logger.warning(f"[{self.stream_name}] 初始兴趣消息 {msg_id} 在处理前已被移除,跳过。") - continue # 如果消息已被其他任务处理(pop),则跳过 - # --- 修改结束 --- - - try: - logger.info(f"[{self.stream_name}] 处理初始高兴趣消息 {msg_id} (兴趣值: {interest_value:.2f})") - await self.normal_response( - message=message, is_mentioned=is_mentioned, interested_rate=interest_value, rewind_response=True - ) - processed_count += 1 - except Exception as e: - logger.error(f"[{self.stream_name}] 处理初始兴趣消息 {msg_id} 时出错: {e}\\n{traceback.format_exc()}") - - # --- 新增:处理完后清空整个字典 --- - logger.info( - f"[{self.stream_name}] 处理了 {processed_count} 条初始高兴趣消息。现在清空所有剩余的初始兴趣消息..." - ) - self.interest_dict.clear() - # --- 新增结束 --- - - logger.info( - f"[{self.stream_name}] 初始高兴趣消息处理完毕,共处理 {processed_count} 条。剩余 {len(self.interest_dict)} 条待轮询。" - ) - - # --- 新增结束 --- - - # 保持 staticmethod, 因为不依赖实例状态, 但需要 chat 对象来获取日志上下文 - @staticmethod - def _check_ban_words(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息中是否包含过滤词""" - stream_name = chat_manager.get_stream_name(chat.stream_id) or chat.stream_id - for word in global_config.chat.ban_words: - if word in text: - logger.info( - f"[{stream_name}][{chat.group_info.group_name if chat.group_info else '私聊'}]" - f"{userinfo.user_nickname}:{text}" - ) - logger.info(f"[{stream_name}][过滤词识别] 消息中含有 '{word}',filtered") - return True - return False - - # 保持 staticmethod, 因为不依赖实例状态, 但需要 chat 对象来获取日志上下文 - @staticmethod - def _check_ban_regex(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息是否匹配过滤正则表达式""" - stream_name = chat_manager.get_stream_name(chat.stream_id) or chat.stream_id - for pattern in global_config.chat.ban_msgs_regex: - if pattern.search(text): - logger.info( - f"[{stream_name}][{chat.group_info.group_name if chat.group_info else '私聊'}]" - f"{userinfo.user_nickname}:{text}" - ) - logger.info(f"[{stream_name}][正则表达式过滤] 消息匹配到 '{pattern.pattern}',filtered") - return True - return False - # 改为实例方法, 移除 chat 参数 async def start_chat(self): @@ -498,10 +375,6 @@ class NormalChat: await self.initialize() # Ensure initialized before starting tasks if self._chat_task is None or self._chat_task.done(): - logger.info(f"[{self.stream_name}] 开始回顾消息...") - # Process initial messages first - await self._process_initial_interest_messages() - # Then start polling task logger.info(f"[{self.stream_name}] 开始处理兴趣消息...") polling_task = asyncio.create_task(self._reply_interested_message()) polling_task.add_done_callback(lambda t: self._handle_task_completion(t)) diff --git a/src/chat/normal_chat/normal_chat_generator.py b/src/chat/normal_chat/normal_chat_generator.py index b55d41e7c..e0a88b4ed 100644 --- a/src/chat/normal_chat/normal_chat_generator.py +++ b/src/chat/normal_chat/normal_chat_generator.py @@ -8,6 +8,7 @@ from src.chat.utils.utils import process_llm_response from src.chat.utils.timer_calculator import Timer from src.common.logger_manager import get_logger from src.chat.utils.info_catcher import info_catcher_manager +from src.person_info.person_info import person_info_manager logger = get_logger("llm") @@ -63,22 +64,25 @@ class NormalChatGenerator: async def _generate_response_with_model(self, message: MessageThinking, model: LLMRequest, thinking_id: str): info_catcher = info_catcher_manager.get_info_catcher(thinking_id) + + person_id = person_info_manager.get_person_id(message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id) + + person_name = await person_info_manager.get_value(person_id, "person_name") + if message.chat_stream.user_info.user_cardname and message.chat_stream.user_info.user_nickname: sender_name = ( - f"[({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}]" - f"{message.chat_stream.user_info.user_cardname}" + f"[{message.chat_stream.user_info.user_nickname}]" + f"[群昵称:{message.chat_stream.user_info.user_cardname}](你叫ta{person_name})" ) elif message.chat_stream.user_info.user_nickname: - sender_name = f"({message.chat_stream.user_info.user_id}){message.chat_stream.user_info.user_nickname}" + sender_name = f"[{message.chat_stream.user_info.user_nickname}](你叫ta{person_name})" else: sender_name = f"用户({message.chat_stream.user_info.user_id})" + + # 构建prompt with Timer() as t_build_prompt: prompt = await prompt_builder.build_prompt( - build_mode="normal", - reason="", - current_mind_info="", - structured_info="", message_txt=message.processed_plain_text, sender_name=sender_name, chat_stream=message.chat_stream, diff --git a/src/chat/normal_chat/normal_prompt.py b/src/chat/normal_chat/normal_prompt.py index 88a1fadc1..365d42ded 100644 --- a/src/chat/normal_chat/normal_prompt.py +++ b/src/chat/normal_chat/normal_prompt.py @@ -17,14 +17,6 @@ logger = get_logger("prompt") def init_prompt(): - # Prompt( - # """ - # 你有以下信息可供参考: - # {structured_info} - # 以上的消息是你获取到的消息,或许可以帮助你更好地回复。 - # """, - # "info_from_tools", - # ) Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1") Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") @@ -84,15 +76,9 @@ class PromptBuilder: async def build_prompt( self, - build_mode, chat_stream, - reason=None, - current_mind_info=None, - structured_info=None, message_txt=None, sender_name="某人", - in_mind_reply=None, - target_message=None, ) -> Optional[str]: return await self._build_prompt_normal(chat_stream, message_txt or "", sender_name) @@ -188,8 +174,6 @@ class PromptBuilder: prompt_ger += "你喜欢用反问句" if random.random() < 0.02: prompt_ger += "你喜欢用文言文" - if random.random() < 0.04: - prompt_ger += "你喜欢用流行梗" moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 4c66b7428..4db9dc1b3 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -6,7 +6,9 @@ import re from src.common.message_repository import find_messages, count_messages from src.person_info.person_info import person_info_manager from src.chat.utils.utils import translate_timestamp_to_human_readable +from rich.traceback import install +install(extra_lines=3) def get_raw_msg_by_timestamp( timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" @@ -225,7 +227,7 @@ async def _build_readable_messages_internal( if not reply_person_name: reply_person_name = aaa # 在内容前加上回复信息 - content = re.sub(reply_pattern, f"回复 {reply_person_name}", content, count=1) + content = re.sub(reply_pattern, lambda m, name=reply_person_name: f"回复 {name}", content, count=1) # 检查是否有 @ 字段 @<{member_info.get('nickname')}:{member_info.get('user_id')}> at_pattern = r"@<([^:<>]+):([^:<>]+)>" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index afd6676b1..7bb393f6d 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -341,6 +341,9 @@ class TelemetryConfig(ConfigBase): class ExperimentalConfig(ConfigBase): """实验功能配置类""" + debug_show_chat_mode: bool = True + """是否在回复后显示当前聊天模式""" + enable_friend_chat: bool = False """是否启用好友聊天""" diff --git a/src/plugins/test_plugin/actions/__init__.py b/src/plugins/test_plugin/actions/__init__.py index 7d96ea8a4..6797e51bf 100644 --- a/src/plugins/test_plugin/actions/__init__.py +++ b/src/plugins/test_plugin/actions/__init__.py @@ -2,6 +2,5 @@ # 导入所有动作模块以确保装饰器被执行 from . import test_action # noqa - # from . import online_action # noqa from . import mute_action # noqa diff --git a/src/plugins/test_plugin/actions/mute_action.py b/src/plugins/test_plugin/actions/mute_action.py index c693c59af..6a5331baa 100644 --- a/src/plugins/test_plugin/actions/mute_action.py +++ b/src/plugins/test_plugin/actions/mute_action.py @@ -13,7 +13,7 @@ class MuteAction(PluginAction): action_description = "如果某人违反了公序良俗,或者别人戳你太多,或者某人刷屏,一定要禁言某人,如果你很生气,可以禁言某人,可以自选禁言时长,视严重程度而定。" action_parameters = { "target": "禁言对象,输入你要禁言的对象的名字,必填", - "duration": "禁言时长,输入你要禁言的时长,单位为秒,必填,必须为数字", + "duration": "禁言时长,输入你要禁言的时长(秒),单位为秒,必填,必须为数字", "reason": "禁言理由,可选", } action_require = [ @@ -21,7 +21,6 @@ class MuteAction(PluginAction): "当有人刷屏时使用", "当有人要求禁言自己时使用", "当有人戳你两次以上时,防止刷屏,禁言他,必须牢记", - "当千石可乐或可乐酱要求你禁言时使用", "当你想回避某个话题时使用", ] default = True # 默认动作,是否手动添加到使用集 @@ -47,22 +46,27 @@ class MuteAction(PluginAction): if not user_id: error_msg = f"未找到用户 {target} 的ID" + await self.send_message_by_expressor(f"压根没 {target} 这个人") logger.error(f"{self.log_prefix} {error_msg}") return False, error_msg # 发送表达情绪的消息 - await self.send_message_by_expressor(f"我要禁言{target},时长{duration}秒,理由:{reason}") + await self.send_message_by_expressor(f"禁言{target} {duration}秒,因为{reason}") try: # 确保duration是字符串类型 - duration_str = str(duration) + if int(duration) < 60: + duration = 60 + if int(duration) > 3600*24*30: + duration = 3600*24*30 + duration_str = str(int(duration)) # 发送群聊禁言命令,按照新格式 await self.send_message( type="command", data={"name": "GROUP_BAN", "args": {"qq_id": str(user_id), "duration": duration_str}} ) - logger.info(f"{self.log_prefix} 成功禁言用户 {target}({user_id}),时长 {duration} 秒") + logger.info(f"{self.log_prefix} 成功发送禁言命令,用户 {target}({user_id}),时长 {duration} 秒") return True, f"成功禁言 {target},时长 {duration} 秒" except Exception as e: diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 1b7a3feb5..b68fde809 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -180,6 +180,7 @@ key_file = "" # SSL密钥文件路径,仅在use_wss=true时有效 enable = true [experimental] #实验性功能 +debug_show_chat_mode = true # 是否在回复后显示当前聊天模式 enable_friend_chat = false # 是否启用好友聊天 pfc_chatting = false # 是否启用PFC聊天,该功能仅作用于私聊,与回复模式独立,在0.7.0暂时无效