import os from dataclasses import field, dataclass import tomlkit import shutil from datetime import datetime from tomlkit import TOMLDocument from tomlkit.items import Table from src.common.logger import get_logger from rich.traceback import install from src.config.config_base import ConfigBase from src.config.official_configs import ( BotConfig, PersonalityConfig, IdentityConfig, ExpressionConfig, ChatConfig, NormalChatConfig, FocusChatConfig, EmojiConfig, MemoryConfig, MoodConfig, KeywordReactionConfig, ChineseTypoConfig, ResponsePostProcessConfig, ResponseSplitterConfig, TelemetryConfig, ExperimentalConfig, ModelConfig, FocusChatProcessorConfig, MessageReceiveConfig, MaimMessageConfig, LPMMKnowledgeConfig, RelationshipConfig, ) install(extra_lines=3) # 配置主程序日志格式 logger = get_logger("config") CONFIG_DIR = "config" TEMPLATE_DIR = "template" # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ MMC_VERSION = "0.8.0-snapshot.1" def update_config(): # 获取根目录路径 old_config_dir = f"{CONFIG_DIR}/old" # 定义文件路径 template_path = f"{TEMPLATE_DIR}/bot_config_template.toml" old_config_path = f"{CONFIG_DIR}/bot_config.toml" new_config_path = f"{CONFIG_DIR}/bot_config.toml" # 检查配置文件是否存在 if not os.path.exists(old_config_path): logger.info("配置文件不存在,从模板创建新配置") os.makedirs(CONFIG_DIR, exist_ok=True) # 创建文件夹 shutil.copy2(template_path, old_config_path) # 复制模板文件 logger.info(f"已创建新配置文件,请填写后重新运行: {old_config_path}") # 如果是新创建的配置文件,直接返回 quit() # 读取旧配置文件和模板文件 with open(old_config_path, "r", encoding="utf-8") as f: old_config = tomlkit.load(f) with open(template_path, "r", encoding="utf-8") as f: new_config = tomlkit.load(f) # 检查version是否相同 if old_config and "inner" in old_config and "inner" in new_config: old_version = old_config["inner"].get("version") new_version = new_config["inner"].get("version") if old_version and new_version and old_version == new_version: logger.info(f"检测到配置文件版本号相同 (v{old_version}),跳过更新") return else: logger.info(f"检测到版本号不同: 旧版本 v{old_version} -> 新版本 v{new_version}") else: logger.info("已有配置文件未检测到版本号,可能是旧版本。将进行更新") # 创建old目录(如果不存在) os.makedirs(old_config_dir, exist_ok=True) # 生成带时间戳的新文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") old_backup_path = f"{old_config_dir}/bot_config_{timestamp}.toml" # 移动旧配置文件到old目录 shutil.move(old_config_path, old_backup_path) logger.info(f"已备份旧配置文件到: {old_backup_path}") # 复制模板文件到配置目录 shutil.copy2(template_path, new_config_path) logger.info(f"已创建新配置文件: {new_config_path}") def update_dict(target: TOMLDocument | dict, source: TOMLDocument | dict): """ 将source字典的值更新到target字典中(如果target中存在相同的键) """ for key, value in source.items(): # 跳过version字段的更新 if key == "version": continue if key in target: if isinstance(value, dict) and isinstance(target[key], (dict, Table)): update_dict(target[key], value) else: try: # 对数组类型进行特殊处理 if isinstance(value, list): # 如果是空数组,确保它保持为空数组 target[key] = tomlkit.array(str(value)) if value else tomlkit.array() else: # 其他类型使用item方法创建新值 target[key] = tomlkit.item(value) except (TypeError, ValueError): # 如果转换失败,直接赋值 target[key] = value # 将旧配置的值更新到新配置中 logger.info("开始合并新旧配置...") update_dict(new_config, old_config) # 保存更新后的配置(保留注释和格式) with open(new_config_path, "w", encoding="utf-8") as f: f.write(tomlkit.dumps(new_config)) logger.info("配置文件更新完成,建议检查新配置文件中的内容,以免丢失重要信息") quit() @dataclass class Config(ConfigBase): """总配置类""" MMC_VERSION: str = field(default=MMC_VERSION, repr=False, init=False) # 硬编码的版本信息 bot: BotConfig personality: PersonalityConfig identity: IdentityConfig relationship: RelationshipConfig chat: ChatConfig message_receive: MessageReceiveConfig normal_chat: NormalChatConfig focus_chat: FocusChatConfig focus_chat_processor: FocusChatProcessorConfig emoji: EmojiConfig expression: ExpressionConfig memory: MemoryConfig mood: MoodConfig keyword_reaction: KeywordReactionConfig chinese_typo: ChineseTypoConfig response_post_process: ResponsePostProcessConfig response_splitter: ResponseSplitterConfig telemetry: TelemetryConfig experimental: ExperimentalConfig model: ModelConfig maim_message: MaimMessageConfig lpmm_knowledge: LPMMKnowledgeConfig def load_config(config_path: str) -> Config: """ 加载配置文件 :param config_path: 配置文件路径 :return: Config对象 """ # 读取配置文件 with open(config_path, "r", encoding="utf-8") as f: config_data = tomlkit.load(f) # 创建Config对象 try: return Config.from_dict(config_data) except Exception as e: logger.critical("配置文件解析失败") raise e def get_config_dir() -> str: """ 获取配置目录 :return: 配置目录路径 """ return CONFIG_DIR # 获取配置文件路径 logger.info(f"MaiCore当前版本: {MMC_VERSION}") update_config() logger.info("正在品鉴配置文件...") global_config = load_config(config_path=f"{CONFIG_DIR}/bot_config.toml") logger.info("非常的新鲜,非常的美味!")