diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index d0e1f9c9..09a3a94d 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -39,6 +39,11 @@ class HeartFCMessageReceiver: message_data: 原始消息字符串 """ try: + # 通知消息不处理 + if message.is_notify: + logger.debug("通知消息,跳过处理") + return + # 1. 消息解析与初始化 userinfo = message.message_info.user_info chat = message.chat_stream diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py index 8d85d166..b70f30ff 100644 --- a/src/chat/message_receive/storage.py +++ b/src/chat/message_receive/storage.py @@ -33,6 +33,11 @@ class MessageStorage: async def store_message(message: Union[MessageSending, MessageRecv], chat_stream: ChatStream) -> None: """存储消息到数据库""" try: + # 通知消息不存储 + if isinstance(message, MessageRecv) and message.is_notify: + logger.debug("通知消息,跳过存储") + return + pattern = r".*?|.*?|.*?" # print(message) diff --git a/src/common/toml_utils.py b/src/common/toml_utils.py new file mode 100644 index 00000000..0c34f6ab --- /dev/null +++ b/src/common/toml_utils.py @@ -0,0 +1,67 @@ +""" +TOML 工具函数 + +提供 TOML 文件的格式化保存功能,确保数组等元素以美观的多行格式输出。 +""" + +from typing import Any +import tomlkit +from tomlkit.items import AoT, Table, Array + + +def _format_toml_value(obj: Any, threshold: int, depth: int = 0) -> Any: + """递归格式化 TOML 值,将数组转换为多行格式""" + # 处理 AoT (Array of Tables) - 保持原样,递归处理内部 + if isinstance(obj, AoT): + for item in obj: + _format_toml_value(item, threshold, depth) + return obj + + # 处理字典类型 (dict 或 Table) + if isinstance(obj, (dict, Table)): + for k, v in obj.items(): + obj[k] = _format_toml_value(v, threshold, depth) + return obj + + # 处理列表类型 (list 或 Array) + if isinstance(obj, (list, Array)): + # 如果是纯 list (非 tomlkit Array) 且包含字典/表,视为 AoT 的列表形式 + # 保持结构递归处理,避免转换为 Inline Table Array (因为 Inline Table 必须单行,复杂对象不友好) + if isinstance(obj, list) and not isinstance(obj, Array) and obj and isinstance(obj[0], (dict, Table)): + for i, item in enumerate(obj): + obj[i] = _format_toml_value(item, threshold, depth) + return obj + + # 决定是否多行:仅在顶层且长度超过阈值时 + should_multiline = (depth == 0 and len(obj) > threshold) + + # 如果已经是 tomlkit Array,原地修改以保留注释 + if isinstance(obj, Array): + obj.multiline(should_multiline) + for i, item in enumerate(obj): + obj[i] = _format_toml_value(item, threshold, depth + 1) + return obj + + # 普通 list:转换为 tomlkit 数组 + arr = tomlkit.array() + arr.multiline(should_multiline) + + for item in obj: + arr.append(_format_toml_value(item, threshold, depth + 1)) + return arr + + # 其他基本类型直接返回 + return obj + + +def save_toml_with_format(data: Any, file_path: str, multiline_threshold: int = 1) -> None: + """格式化 TOML 数据并保存到文件""" + formatted = _format_toml_value(data, multiline_threshold) if multiline_threshold >= 0 else data + with open(file_path, "w", encoding="utf-8") as f: + tomlkit.dump(formatted, f) + + +def format_toml_string(data: Any, multiline_threshold: int = 1) -> str: + """格式化 TOML 数据并返回字符串""" + formatted = _format_toml_value(data, multiline_threshold) if multiline_threshold >= 0 else data + return tomlkit.dumps(formatted) \ No newline at end of file diff --git a/src/config/config.py b/src/config/config.py index 1eca07fb..3f5db816 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -11,6 +11,7 @@ from rich.traceback import install from typing import List, Optional from src.common.logger import get_logger +from src.common.toml_utils import format_toml_string from src.config.config_base import ConfigBase from src.config.official_configs import ( BotConfig, @@ -252,7 +253,7 @@ def _update_config_generic(config_name: str, template_name: str): # 如果配置有更新,立即保存到文件 if config_updated: with open(old_config_path, "w", encoding="utf-8") as f: - f.write(tomlkit.dumps(old_config)) + f.write(format_toml_string(old_config)) logger.info(f"已保存更新后的{config_name}配置文件") else: logger.info(f"未检测到{config_name}模板默认值变动") @@ -313,9 +314,9 @@ def _update_config_generic(config_name: str, template_name: str): logger.info(f"开始合并{config_name}新旧配置...") _update_dict(new_config, old_config) - # 保存更新后的配置(保留注释和格式) + # 保存更新后的配置(保留注释和格式,数组多行格式化) with open(new_config_path, "w", encoding="utf-8") as f: - f.write(tomlkit.dumps(new_config)) + f.write(format_toml_string(new_config)) logger.info(f"{config_name}配置文件更新完成,建议检查新配置文件中的内容,以免丢失重要信息") diff --git a/src/webui/config_routes.py b/src/webui/config_routes.py index f26bcb09..5e54c8f7 100644 --- a/src/webui/config_routes.py +++ b/src/webui/config_routes.py @@ -8,6 +8,7 @@ from fastapi import APIRouter, HTTPException, Body from typing import Any, Annotated from src.common.logger import get_logger +from src.common.toml_utils import save_toml_with_format from src.config.config import Config, APIAdapterConfig, CONFIG_DIR, PROJECT_ROOT from src.config.official_configs import ( BotConfig, @@ -237,10 +238,9 @@ async def update_bot_config(config_data: ConfigBody): except Exception as e: raise HTTPException(status_code=400, detail=f"配置数据验证失败: {str(e)}") from e - # 保存配置文件 + # 保存配置文件(格式化数组为多行) config_path = os.path.join(CONFIG_DIR, "bot_config.toml") - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(config_data, f) + save_toml_with_format(config_data, config_path) logger.info("麦麦主程序配置已更新") return {"success": True, "message": "配置已保存"} @@ -261,10 +261,9 @@ async def update_model_config(config_data: ConfigBody): except Exception as e: raise HTTPException(status_code=400, detail=f"配置数据验证失败: {str(e)}") from e - # 保存配置文件 + # 保存配置文件(格式化数组为多行) config_path = os.path.join(CONFIG_DIR, "model_config.toml") - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(config_data, f) + save_toml_with_format(config_data, config_path) logger.info("模型配置已更新") return {"success": True, "message": "配置已保存"} @@ -312,9 +311,8 @@ async def update_bot_config_section(section_name: str, section_data: SectionBody except Exception as e: raise HTTPException(status_code=400, detail=f"配置数据验证失败: {str(e)}") from e - # 保存配置(tomlkit.dump 会保留注释) - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(config_data, f) + # 保存配置(格式化数组为多行,保留注释) + save_toml_with_format(config_data, config_path) logger.info(f"配置节 '{section_name}' 已更新(保留注释)") return {"success": True, "message": f"配置节 '{section_name}' 已保存"} @@ -411,9 +409,8 @@ async def update_model_config_section(section_name: str, section_data: SectionBo except Exception as e: raise HTTPException(status_code=400, detail=f"配置数据验证失败: {str(e)}") from e - # 保存配置(tomlkit.dump 会保留注释) - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(config_data, f) + # 保存配置(格式化数组为多行,保留注释) + save_toml_with_format(config_data, config_path) logger.info(f"配置节 '{section_name}' 已更新(保留注释)") return {"success": True, "message": f"配置节 '{section_name}' 已保存"} diff --git a/src/webui/plugin_routes.py b/src/webui/plugin_routes.py index 1236b02e..bf4784df 100644 --- a/src/webui/plugin_routes.py +++ b/src/webui/plugin_routes.py @@ -4,6 +4,7 @@ from typing import Optional, List, Dict, Any from pathlib import Path import json from src.common.logger import get_logger +from src.common.toml_utils import save_toml_with_format from src.config.config import MMC_VERSION from .git_mirror_service import get_git_mirror_service, set_update_progress_callback from .token_manager import get_token_manager @@ -1416,8 +1417,7 @@ async def update_plugin_config( # 更新值 for key, value in request.config.items(): existing_doc[key] = value - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(existing_doc, f) + save_toml_with_format(existing_doc, str(config_path)) logger.info(f"已更新插件配置: {plugin_id}") @@ -1544,9 +1544,8 @@ async def toggle_plugin(plugin_id: str, authorization: Optional[str] = Header(No new_enabled = not current_enabled config["plugin"]["enabled"] = new_enabled - # 写入配置(保留注释) - with open(config_path, "w", encoding="utf-8") as f: - tomlkit.dump(config, f) + # 写入配置(保留注释,格式化数组) + save_toml_with_format(config, str(config_path)) status = "启用" if new_enabled else "禁用" logger.info(f"已{status}插件: {plugin_id}")