i18n: wire startup and config messages
This commit is contained in:
122
bot.py
122
bot.py
@@ -1,21 +1,26 @@
|
|||||||
# raise RuntimeError("System Not Ready")
|
# raise RuntimeError("System Not Ready")
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from rich.traceback import install
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import platform
|
import platform
|
||||||
import traceback
|
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from dotenv import load_dotenv
|
import sys
|
||||||
from pathlib import Path
|
import time
|
||||||
from rich.traceback import install
|
import traceback
|
||||||
from src.common.logger import initialize_logging, get_logger, shutdown_logging
|
|
||||||
|
from src.common.i18n import set_locale, t, tn
|
||||||
|
from src.common.logger import get_logger, initialize_logging, shutdown_logging
|
||||||
|
|
||||||
# 设置工作目录为脚本所在目录
|
# 设置工作目录为脚本所在目录
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.chdir(script_dir)
|
os.chdir(script_dir)
|
||||||
|
set_locale(os.getenv("MAIBOT_LOCALE", "zh-CN"))
|
||||||
|
|
||||||
env_path = Path(__file__).parent / ".env"
|
env_path = Path(__file__).parent / ".env"
|
||||||
template_env_path = Path(__file__).parent / "template" / "template.env"
|
template_env_path = Path(__file__).parent / "template" / "template.env"
|
||||||
@@ -26,15 +31,17 @@ else:
|
|||||||
try:
|
try:
|
||||||
if template_env_path.exists():
|
if template_env_path.exists():
|
||||||
shutil.copyfile(template_env_path, env_path)
|
shutil.copyfile(template_env_path, env_path)
|
||||||
print("未找到.env,已从 template/template.env 自动创建")
|
print(t("startup.env_created"))
|
||||||
load_dotenv(str(env_path), override=True)
|
load_dotenv(str(env_path), override=True)
|
||||||
else:
|
else:
|
||||||
print("未找到.env文件,也未找到模板 template/template.env")
|
print(t("startup.env_template_missing"))
|
||||||
raise FileNotFoundError(".env 文件不存在,请创建并配置所需的环境变量")
|
raise FileNotFoundError(t("startup.env_file_missing"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"自动创建 .env 失败: {e}")
|
print(t("startup.env_auto_create_failed", error=e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
set_locale(os.getenv("MAIBOT_LOCALE", "zh-CN"))
|
||||||
|
|
||||||
# 检查是否是 Worker 进程,只在 Worker 进程中输出详细的初始化信息
|
# 检查是否是 Worker 进程,只在 Worker 进程中输出详细的初始化信息
|
||||||
# Runner 进程只需要基本的日志功能,不需要详细的初始化日志
|
# Runner 进程只需要基本的日志功能,不需要详细的初始化日志
|
||||||
is_worker = os.environ.get("MAIBOT_WORKER_PROCESS") == "1"
|
is_worker = os.environ.get("MAIBOT_WORKER_PROCESS") == "1"
|
||||||
@@ -46,7 +53,7 @@ logger = get_logger("main")
|
|||||||
RESTART_EXIT_CODE = 42
|
RESTART_EXIT_CODE = 42
|
||||||
print("-----------------------------------------")
|
print("-----------------------------------------")
|
||||||
print("\n\n\n\n\n")
|
print("\n\n\n\n\n")
|
||||||
print("警告:Dev进入不稳定开发状态,任何插件与WebUI均可能无法正常工作!")
|
print(t("startup.dev_branch_warning"))
|
||||||
print("\n\n\n\n\n")
|
print("\n\n\n\n\n")
|
||||||
print("-----------------------------------------")
|
print("-----------------------------------------")
|
||||||
|
|
||||||
@@ -64,8 +71,8 @@ def run_runner_process():
|
|||||||
env["MAIBOT_WORKER_PROCESS"] = "1"
|
env["MAIBOT_WORKER_PROCESS"] = "1"
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
logger.info(f"正在启动 {script_file}...")
|
logger.info(t("startup.launching_script", script_file=script_file))
|
||||||
logger.info("正在编译着色器:1/114514")
|
logger.info(t("startup.compiling_shaders"))
|
||||||
|
|
||||||
# 启动子进程 (Worker)
|
# 启动子进程 (Worker)
|
||||||
# 使用 sys.executable 确保使用相同的 Python 解释器
|
# 使用 sys.executable 确保使用相同的 Python 解释器
|
||||||
@@ -78,11 +85,11 @@ def run_runner_process():
|
|||||||
return_code = process.wait()
|
return_code = process.wait()
|
||||||
|
|
||||||
if return_code == RESTART_EXIT_CODE:
|
if return_code == RESTART_EXIT_CODE:
|
||||||
logger.info("检测到重启请求 (退出码 42),正在重启...")
|
logger.info(t("startup.restart_requested", exit_code=RESTART_EXIT_CODE))
|
||||||
time.sleep(1) # 稍作等待
|
time.sleep(1) # 稍作等待
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
logger.info(f"程序已退出 (退出码 {return_code})")
|
logger.info(t("startup.program_exited", return_code=return_code))
|
||||||
sys.exit(return_code)
|
sys.exit(return_code)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@@ -94,7 +101,7 @@ def run_runner_process():
|
|||||||
process.terminate()
|
process.terminate()
|
||||||
process.wait(timeout=5)
|
process.wait(timeout=5)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
logger.warning("子进程未响应,强制关闭...")
|
logger.warning(t("startup.child_process_force_kill"))
|
||||||
process.kill()
|
process.kill()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
@@ -128,7 +135,7 @@ from src.manager.async_task_manager import async_task_manager # noqa
|
|||||||
# 设置工作目录为脚本所在目录
|
# 设置工作目录为脚本所在目录
|
||||||
# script_dir = os.path.dirname(os.path.abspath(__file__))
|
# script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
# os.chdir(script_dir)
|
# os.chdir(script_dir)
|
||||||
logger.info(f"已设置工作目录为: {script_dir}")
|
logger.info(t("startup.worker_dir_set", script_dir=script_dir))
|
||||||
|
|
||||||
|
|
||||||
confirm_logger = get_logger("confirm")
|
confirm_logger = get_logger("confirm")
|
||||||
@@ -150,16 +157,16 @@ def print_opensource_notice():
|
|||||||
notice_lines = [
|
notice_lines = [
|
||||||
"",
|
"",
|
||||||
f"{Fore.CYAN}{'═' * 70}{Style.RESET_ALL}",
|
f"{Fore.CYAN}{'═' * 70}{Style.RESET_ALL}",
|
||||||
f"{Fore.GREEN} ★ MaiBot - 开源 AI 聊天机器人 ★{Style.RESET_ALL}",
|
f"{Fore.GREEN}{t('startup.opensource_title')}{Style.RESET_ALL}",
|
||||||
f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}",
|
f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}",
|
||||||
f"{Fore.YELLOW} 本项目是完全免费的开源软件,基于 GPL-3.0 协议发布{Style.RESET_ALL}",
|
f"{Fore.YELLOW}{t('startup.opensource_free_notice')}{Style.RESET_ALL}",
|
||||||
f"{Fore.WHITE} 如果有人向你「出售本软件」,你被骗了!{Style.RESET_ALL}",
|
f"{Fore.WHITE}{t('startup.opensource_scamming_notice')}{Style.RESET_ALL}",
|
||||||
"",
|
"",
|
||||||
f"{Fore.WHITE} 官方仓库: {Fore.BLUE}https://github.com/MaiM-with-u/MaiBot {Style.RESET_ALL}",
|
f"{Fore.WHITE}{t('startup.opensource_repo')}{Fore.BLUE}{t('startup.opensource_repo_value')} {Style.RESET_ALL}",
|
||||||
f"{Fore.WHITE} 官方文档: {Fore.BLUE}https://docs.mai-mai.org {Style.RESET_ALL}",
|
f"{Fore.WHITE}{t('startup.opensource_docs')}{Fore.BLUE}{t('startup.opensource_docs_value')} {Style.RESET_ALL}",
|
||||||
f"{Fore.WHITE} 官方群聊: {Fore.BLUE}1006149251{Style.RESET_ALL}",
|
f"{Fore.WHITE}{t('startup.opensource_group')}{Fore.BLUE}{t('startup.opensource_group_value')}{Style.RESET_ALL}",
|
||||||
f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}",
|
f"{Fore.CYAN}{'─' * 70}{Style.RESET_ALL}",
|
||||||
f"{Fore.RED} ⚠ 将本软件作为「商品」倒卖、隐瞒开源性质均违反协议!{Style.RESET_ALL}",
|
f"{Fore.RED} ⚠ {t('startup.opensource_resale_warning').strip()}{Style.RESET_ALL}",
|
||||||
f"{Fore.CYAN}{'═' * 70}{Style.RESET_ALL}",
|
f"{Fore.CYAN}{'═' * 70}{Style.RESET_ALL}",
|
||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
@@ -173,7 +180,7 @@ def easter_egg():
|
|||||||
from colorama import init, Fore
|
from colorama import init, Fore
|
||||||
|
|
||||||
init()
|
init()
|
||||||
text = "多年以后,面对AI行刑队,张三将会回想起他2023年在会议上讨论人工智能的那个下午"
|
text = t("startup.easter_egg")
|
||||||
rainbow_colors = [Fore.RED, Fore.YELLOW, Fore.GREEN, Fore.CYAN, Fore.BLUE, Fore.MAGENTA]
|
rainbow_colors = [Fore.RED, Fore.YELLOW, Fore.GREEN, Fore.CYAN, Fore.BLUE, Fore.MAGENTA]
|
||||||
rainbow_text = ""
|
rainbow_text = ""
|
||||||
for i, char in enumerate(text):
|
for i, char in enumerate(text):
|
||||||
@@ -183,7 +190,7 @@ def easter_egg():
|
|||||||
|
|
||||||
async def graceful_shutdown(): # sourcery skip: use-named-expression
|
async def graceful_shutdown(): # sourcery skip: use-named-expression
|
||||||
try:
|
try:
|
||||||
logger.info("正在优雅关闭麦麦...")
|
logger.info(t("startup.shutdown_started"))
|
||||||
|
|
||||||
# 关闭 WebUI 服务器
|
# 关闭 WebUI 服务器
|
||||||
# try:
|
# try:
|
||||||
@@ -203,16 +210,17 @@ async def graceful_shutdown(): # sourcery skip: use-named-expression
|
|||||||
|
|
||||||
# 停止新版本插件运行时
|
# 停止新版本插件运行时
|
||||||
from src.plugin_runtime.integration import get_plugin_runtime_manager
|
from src.plugin_runtime.integration import get_plugin_runtime_manager
|
||||||
|
|
||||||
await get_plugin_runtime_manager().stop()
|
await get_plugin_runtime_manager().stop()
|
||||||
|
|
||||||
# 停止所有异步任务
|
# 停止所有异步任务
|
||||||
await async_task_manager.stop_and_wait_all_tasks()
|
await async_task_manager.stop_and_wait_all_tasks()
|
||||||
|
|
||||||
# 获取所有剩余任务,排除当前任务
|
# 获取所有剩余任务,排除当前任务
|
||||||
remaining_tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
remaining_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
|
||||||
|
|
||||||
if remaining_tasks:
|
if remaining_tasks:
|
||||||
logger.info(f"正在取消 {len(remaining_tasks)} 个剩余任务...")
|
logger.info(tn("startup.remaining_tasks_cancelling", len(remaining_tasks)))
|
||||||
|
|
||||||
# 取消所有剩余任务
|
# 取消所有剩余任务
|
||||||
for task in remaining_tasks:
|
for task in remaining_tasks:
|
||||||
@@ -222,23 +230,23 @@ async def graceful_shutdown(): # sourcery skip: use-named-expression
|
|||||||
# 等待所有任务完成,设置超时
|
# 等待所有任务完成,设置超时
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(asyncio.gather(*remaining_tasks, return_exceptions=True), timeout=15.0)
|
await asyncio.wait_for(asyncio.gather(*remaining_tasks, return_exceptions=True), timeout=15.0)
|
||||||
logger.info("所有剩余任务已成功取消")
|
logger.info(t("startup.remaining_tasks_cancelled"))
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.warning("等待任务取消超时,强制继续关闭")
|
logger.warning(t("startup.remaining_tasks_cancel_timeout"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"等待任务取消时发生异常: {e}")
|
logger.error(t("startup.remaining_tasks_cancel_error", error=e))
|
||||||
|
|
||||||
logger.info("麦麦优雅关闭完成")
|
logger.info(t("startup.shutdown_completed"))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"麦麦关闭失败: {e}", exc_info=True)
|
logger.error(t("startup.shutdown_failed", error=e), exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
def _calculate_file_hash(file_path: Path, file_type: str) -> str:
|
def _calculate_file_hash(file_path: Path, file_type: str) -> str:
|
||||||
"""计算文件的MD5哈希值"""
|
"""计算文件的MD5哈希值"""
|
||||||
if not file_path.exists():
|
if not file_path.exists():
|
||||||
logger.error(f"{file_type} 文件不存在")
|
logger.error(t("startup.file_not_found", file_type=file_type))
|
||||||
raise FileNotFoundError(f"{file_type} 文件不存在")
|
raise FileNotFoundError(t("startup.file_not_found", file_type=file_type))
|
||||||
|
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@@ -267,26 +275,42 @@ def _check_agreement_status(file_hash: str, confirm_file: Path, env_var: str) ->
|
|||||||
|
|
||||||
def _prompt_user_confirmation(eula_hash: str, privacy_hash: str) -> None:
|
def _prompt_user_confirmation(eula_hash: str, privacy_hash: str) -> None:
|
||||||
"""提示用户确认协议"""
|
"""提示用户确认协议"""
|
||||||
confirm_logger.critical("EULA或隐私条款内容已更新,请在阅读后重新确认,继续运行视为同意更新后的以上两款协议")
|
confirm_logger.critical(t("startup.agreement_reconfirm"))
|
||||||
confirm_logger.critical(
|
confirm_logger.critical(
|
||||||
f'输入"同意"或"confirmed"或设置环境变量"EULA_AGREE={eula_hash}"和"PRIVACY_AGREE={privacy_hash}"继续运行'
|
t(
|
||||||
|
"startup.agreement_confirm_prompt",
|
||||||
|
eula_hash=eula_hash,
|
||||||
|
privacy_hash=privacy_hash,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
user_input = input().strip().lower()
|
user_input = input().strip().lower()
|
||||||
if user_input in ["同意", "confirmed"]:
|
if user_input in ["同意", "confirmed"]:
|
||||||
return
|
return
|
||||||
confirm_logger.critical('请输入"同意"或"confirmed"以继续运行')
|
confirm_logger.critical(t("startup.agreement_confirm_retry"))
|
||||||
|
|
||||||
|
|
||||||
def _save_confirmations(eula_updated: bool, privacy_updated: bool, eula_hash: str, privacy_hash: str) -> None:
|
def _save_confirmations(eula_updated: bool, privacy_updated: bool, eula_hash: str, privacy_hash: str) -> None:
|
||||||
"""保存用户确认结果"""
|
"""保存用户确认结果"""
|
||||||
if eula_updated:
|
if eula_updated:
|
||||||
logger.info(f"更新EULA确认文件{eula_hash}")
|
logger.info(
|
||||||
|
t(
|
||||||
|
"startup.agreement_updated",
|
||||||
|
agreement_name=t("startup.eula_name"),
|
||||||
|
file_hash=eula_hash,
|
||||||
|
)
|
||||||
|
)
|
||||||
Path("eula.confirmed").write_text(eula_hash, encoding="utf-8")
|
Path("eula.confirmed").write_text(eula_hash, encoding="utf-8")
|
||||||
|
|
||||||
if privacy_updated:
|
if privacy_updated:
|
||||||
logger.info(f"更新隐私条款确认文件{privacy_hash}")
|
logger.info(
|
||||||
|
t(
|
||||||
|
"startup.agreement_updated",
|
||||||
|
agreement_name=t("startup.privacy_name"),
|
||||||
|
file_hash=privacy_hash,
|
||||||
|
)
|
||||||
|
)
|
||||||
Path("privacy.confirmed").write_text(privacy_hash, encoding="utf-8")
|
Path("privacy.confirmed").write_text(privacy_hash, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
@@ -321,7 +345,7 @@ def raw_main():
|
|||||||
print_opensource_notice()
|
print_opensource_notice()
|
||||||
|
|
||||||
check_eula()
|
check_eula()
|
||||||
logger.info("检查EULA和隐私条款完成")
|
logger.info(t("startup.eula_privacy_checked"))
|
||||||
|
|
||||||
easter_egg()
|
easter_egg()
|
||||||
|
|
||||||
@@ -353,7 +377,7 @@ if __name__ == "__main__":
|
|||||||
loop.run_until_complete(main_tasks)
|
loop.run_until_complete(main_tasks)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.warning("收到中断信号,正在优雅关闭...")
|
logger.warning(t("startup.interrupt_received"))
|
||||||
|
|
||||||
# 取消主任务
|
# 取消主任务
|
||||||
if "main_tasks" in locals() and main_tasks and not main_tasks.done():
|
if "main_tasks" in locals() and main_tasks and not main_tasks.done():
|
||||||
@@ -368,7 +392,7 @@ if __name__ == "__main__":
|
|||||||
try:
|
try:
|
||||||
loop.run_until_complete(graceful_shutdown())
|
loop.run_until_complete(graceful_shutdown())
|
||||||
except Exception as ge:
|
except Exception as ge:
|
||||||
logger.error(f"优雅关闭时发生错误: {ge}")
|
logger.error(t("startup.graceful_shutdown_error", error=ge))
|
||||||
# 新增:检测外部请求关闭
|
# 新增:检测外部请求关闭
|
||||||
|
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
@@ -378,24 +402,24 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
exit_code = 1 if e.code else 0
|
exit_code = 1 if e.code else 0
|
||||||
if exit_code == RESTART_EXIT_CODE:
|
if exit_code == RESTART_EXIT_CODE:
|
||||||
logger.info("收到重启信号,准备退出并请求重启...")
|
logger.info(t("startup.restart_signal_received"))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"主程序发生异常: {str(e)} {str(traceback.format_exc())}")
|
logger.error(t("startup.main_error", error=f"{str(e)} {str(traceback.format_exc())}"))
|
||||||
exit_code = 1 # 标记发生错误
|
exit_code = 1 # 标记发生错误
|
||||||
finally:
|
finally:
|
||||||
# 确保 loop 在任何情况下都尝试关闭(如果存在且未关闭)
|
# 确保 loop 在任何情况下都尝试关闭(如果存在且未关闭)
|
||||||
if "loop" in locals() and loop and not loop.is_closed():
|
if "loop" in locals() and loop and not loop.is_closed():
|
||||||
loop.close()
|
loop.close()
|
||||||
print("[主程序] 事件循环已关闭")
|
print(t("startup.event_loop_closed"))
|
||||||
|
|
||||||
# 关闭日志系统,释放文件句柄
|
# 关闭日志系统,释放文件句柄
|
||||||
try:
|
try:
|
||||||
shutdown_logging()
|
shutdown_logging()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"关闭日志系统时出错: {e}")
|
print(t("startup.logging_shutdown_error", error=e))
|
||||||
|
|
||||||
print("[主程序] 准备退出...")
|
print(t("startup.prepare_exit"))
|
||||||
|
|
||||||
# 使用 os._exit() 强制退出,避免被阻塞
|
# 使用 os._exit() 强制退出,避免被阻塞
|
||||||
# 由于已经在 graceful_shutdown() 中完成了所有清理工作,这是安全的
|
# 由于已经在 graceful_shutdown() 中完成了所有清理工作,这是安全的
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ requires-python = ">=3.10"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp-cors>=0.8.1",
|
"aiohttp-cors>=0.8.1",
|
||||||
"aiohttp>=3.12.14",
|
"aiohttp>=3.12.14",
|
||||||
|
"Babel>=2.17.0",
|
||||||
"colorama>=0.4.6",
|
"colorama>=0.4.6",
|
||||||
"faiss-cpu>=1.11.0",
|
"faiss-cpu>=1.11.0",
|
||||||
"fastapi>=0.116.0",
|
"fastapi>=0.116.0",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
aiohttp-cors>=0.8.1
|
aiohttp-cors>=0.8.1
|
||||||
aiohttp>=3.12.14
|
aiohttp>=3.12.14
|
||||||
|
Babel>=2.17.0
|
||||||
colorama>=0.4.6
|
colorama>=0.4.6
|
||||||
faiss-cpu>=1.11.0
|
faiss-cpu>=1.11.0
|
||||||
fastapi>=0.116.0
|
fastapi>=0.116.0
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Mapping, Sequence, TypeVar
|
from typing import Any, Callable, Mapping, Sequence, TypeVar
|
||||||
from datetime import datetime
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import tomlkit
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .legacy_migration import try_migrate_legacy_bot_config_dict
|
import tomlkit
|
||||||
|
|
||||||
|
from .config_base import AttributeData, ConfigBase, Field
|
||||||
|
from .config_utils import compare_versions, output_config_changes, recursive_parse_item_to_table
|
||||||
|
from .file_watcher import FileChange, FileWatcher
|
||||||
|
from .legacy_migration import try_migrate_legacy_bot_config_dict
|
||||||
|
from .model_configs import APIProvider, ModelInfo, ModelTaskConfig
|
||||||
from .official_configs import (
|
from .official_configs import (
|
||||||
BotConfig,
|
BotConfig,
|
||||||
PersonalityConfig,
|
|
||||||
ExpressionConfig,
|
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
EmojiConfig,
|
|
||||||
KeywordReactionConfig,
|
|
||||||
ChineseTypoConfig,
|
ChineseTypoConfig,
|
||||||
|
DatabaseConfig,
|
||||||
|
DebugConfig,
|
||||||
|
EmojiConfig,
|
||||||
|
ExperimentalConfig,
|
||||||
|
ExpressionConfig,
|
||||||
|
KeywordReactionConfig,
|
||||||
|
LPMMKnowledgeConfig,
|
||||||
|
MaiSakaConfig,
|
||||||
|
MaimMessageConfig,
|
||||||
|
MemoryConfig,
|
||||||
|
MessageReceiveConfig,
|
||||||
|
PersonalityConfig,
|
||||||
|
RelationshipConfig,
|
||||||
ResponsePostProcessConfig,
|
ResponsePostProcessConfig,
|
||||||
ResponseSplitterConfig,
|
ResponseSplitterConfig,
|
||||||
TelemetryConfig,
|
TelemetryConfig,
|
||||||
ExperimentalConfig,
|
|
||||||
MessageReceiveConfig,
|
|
||||||
MaimMessageConfig,
|
|
||||||
LPMMKnowledgeConfig,
|
|
||||||
RelationshipConfig,
|
|
||||||
ToolConfig,
|
ToolConfig,
|
||||||
VoiceConfig,
|
VoiceConfig,
|
||||||
MemoryConfig,
|
|
||||||
DebugConfig,
|
|
||||||
WebUIConfig,
|
WebUIConfig,
|
||||||
DatabaseConfig,
|
|
||||||
MaiSakaConfig,
|
|
||||||
)
|
)
|
||||||
from .model_configs import ModelInfo, ModelTaskConfig, APIProvider
|
from src.common.i18n import t
|
||||||
from .config_base import ConfigBase, Field, AttributeData
|
|
||||||
from .config_utils import recursive_parse_item_to_table, output_config_changes, compare_versions
|
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.config.file_watcher import FileChange, FileWatcher
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
如果你想要修改配置文件,请递增version的值
|
如果你想要修改配置文件,请递增version的值
|
||||||
@@ -146,27 +146,33 @@ class ModelConfig(ConfigBase):
|
|||||||
|
|
||||||
def model_post_init(self, context: Any = None):
|
def model_post_init(self, context: Any = None):
|
||||||
if not self.models:
|
if not self.models:
|
||||||
raise ValueError("模型列表不能为空,请在配置中设置有效的模型列表。")
|
raise ValueError(t("config.models_empty"))
|
||||||
if not self.api_providers:
|
if not self.api_providers:
|
||||||
raise ValueError("API提供商列表不能为空,请在配置中设置有效的API提供商列表。")
|
raise ValueError(t("config.api_providers_empty"))
|
||||||
|
|
||||||
# 检查API提供商名称是否重复
|
# 检查API提供商名称是否重复
|
||||||
provider_names = [provider.name for provider in self.api_providers]
|
provider_names = [provider.name for provider in self.api_providers]
|
||||||
if len(provider_names) != len(set(provider_names)):
|
if len(provider_names) != len(set(provider_names)):
|
||||||
raise ValueError("API提供商名称存在重复,请检查配置文件。")
|
raise ValueError(t("config.api_provider_name_duplicate"))
|
||||||
|
|
||||||
# 检查模型名称是否重复
|
# 检查模型名称是否重复
|
||||||
model_names = [model.name for model in self.models]
|
model_names = [model.name for model in self.models]
|
||||||
if len(model_names) != len(set(model_names)):
|
if len(model_names) != len(set(model_names)):
|
||||||
raise ValueError("模型名称存在重复,请检查配置文件。")
|
raise ValueError(t("config.model_name_duplicate"))
|
||||||
|
|
||||||
api_providers_dict = {provider.name: provider for provider in self.api_providers}
|
api_providers_dict = {provider.name: provider for provider in self.api_providers}
|
||||||
|
|
||||||
for model in self.models:
|
for model in self.models:
|
||||||
if not model.model_identifier:
|
if not model.model_identifier:
|
||||||
raise ValueError(f"模型 '{model.name}' 的 model_identifier 不能为空")
|
raise ValueError(t("config.model_identifier_empty", model_name=model.name))
|
||||||
if not model.api_provider or model.api_provider not in api_providers_dict:
|
if not model.api_provider or model.api_provider not in api_providers_dict:
|
||||||
raise ValueError(f"模型 '{model.name}' 的 api_provider '{model.api_provider}' 不存在")
|
raise ValueError(
|
||||||
|
t(
|
||||||
|
"config.model_api_provider_missing",
|
||||||
|
api_provider=model.api_provider,
|
||||||
|
model_name=model.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
return super().model_post_init(context)
|
return super().model_post_init(context)
|
||||||
|
|
||||||
|
|
||||||
@@ -188,11 +194,11 @@ class ConfigManager:
|
|||||||
self._last_hot_reload_monotonic: float = 0.0
|
self._last_hot_reload_monotonic: float = 0.0
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
logger.info(f"MaiCore当前版本: {MMC_VERSION}")
|
logger.info(t("config.current_version", version=MMC_VERSION))
|
||||||
logger.info("正在品鉴配置文件...")
|
logger.info(t("config.loading"))
|
||||||
self.global_config = self.load_global_config()
|
self.global_config = self.load_global_config()
|
||||||
self.model_config = self.load_model_config()
|
self.model_config = self.load_model_config()
|
||||||
logger.info("非常的新鲜,非常的美味!")
|
logger.info(t("config.loaded"))
|
||||||
|
|
||||||
def load_global_config(self) -> Config:
|
def load_global_config(self) -> Config:
|
||||||
config, updated = load_config_from_file(Config, self.bot_config_path, CONFIG_VERSION)
|
config, updated = load_config_from_file(Config, self.bot_config_path, CONFIG_VERSION)
|
||||||
@@ -208,12 +214,12 @@ class ConfigManager:
|
|||||||
|
|
||||||
def get_global_config(self) -> Config:
|
def get_global_config(self) -> Config:
|
||||||
if self.global_config is None:
|
if self.global_config is None:
|
||||||
raise RuntimeError("global_config 未初始化")
|
raise RuntimeError(t("config.global_not_initialized"))
|
||||||
return self.global_config
|
return self.global_config
|
||||||
|
|
||||||
def get_model_config(self) -> ModelConfig:
|
def get_model_config(self) -> ModelConfig:
|
||||||
if self.model_config is None:
|
if self.model_config is None:
|
||||||
raise RuntimeError("model_config 未初始化")
|
raise RuntimeError(t("config.model_not_initialized"))
|
||||||
return self.model_config
|
return self.model_config
|
||||||
|
|
||||||
def register_reload_callback(self, callback: Callable[[], object]) -> None:
|
def register_reload_callback(self, callback: Callable[[], object]) -> None:
|
||||||
@@ -240,18 +246,18 @@ class ConfigManager:
|
|||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"配置重载失败: {exc}")
|
logger.error(t("config.reload_failed", error=exc))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if global_updated or model_updated:
|
if global_updated or model_updated:
|
||||||
logger.warning("检测到配置版本更新,热重载仅更新内存数据")
|
logger.warning(t("config.version_update_detected"))
|
||||||
|
|
||||||
self.global_config = global_config_new
|
self.global_config = global_config_new
|
||||||
self.model_config = model_config_new
|
self.model_config = model_config_new
|
||||||
global global_config, model_config
|
global global_config, model_config
|
||||||
global_config = global_config_new
|
global_config = global_config_new
|
||||||
model_config = model_config_new
|
model_config = model_config_new
|
||||||
logger.info("配置热重载完成")
|
logger.info(t("config.hot_reload_completed"))
|
||||||
|
|
||||||
for callback in list(self._reload_callbacks):
|
for callback in list(self._reload_callbacks):
|
||||||
try:
|
try:
|
||||||
@@ -259,7 +265,7 @@ class ConfigManager:
|
|||||||
if asyncio.iscoroutine(result):
|
if asyncio.iscoroutine(result):
|
||||||
await result
|
await result
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"配置重载回调执行失败: {exc}")
|
logger.warning(t("config.reload_callback_failed", error=exc))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def start_file_watcher(self) -> None:
|
async def start_file_watcher(self) -> None:
|
||||||
@@ -277,7 +283,7 @@ class ConfigManager:
|
|||||||
paths=[self.bot_config_path, self.model_config_path],
|
paths=[self.bot_config_path, self.model_config_path],
|
||||||
)
|
)
|
||||||
await self._file_watcher.start()
|
await self._file_watcher.start()
|
||||||
logger.info("配置文件监视器已启动")
|
logger.info(t("config.file_watcher_started"))
|
||||||
|
|
||||||
async def stop_file_watcher(self) -> None:
|
async def stop_file_watcher(self) -> None:
|
||||||
if self._file_watcher is None:
|
if self._file_watcher is None:
|
||||||
@@ -287,14 +293,16 @@ class ConfigManager:
|
|||||||
self._file_watcher_subscription_id = None
|
self._file_watcher_subscription_id = None
|
||||||
watcher_stats = self._file_watcher.stats
|
watcher_stats = self._file_watcher.stats
|
||||||
logger.info(
|
logger.info(
|
||||||
"配置文件监视器停止统计: "
|
t(
|
||||||
f"batches={watcher_stats.batches_seen}, "
|
"config.file_watcher_stop_stats",
|
||||||
f"changes={watcher_stats.changes_seen}, "
|
batches=watcher_stats.batches_seen,
|
||||||
f"ok={watcher_stats.callbacks_succeeded}, "
|
changes=watcher_stats.changes_seen,
|
||||||
f"failed={watcher_stats.callbacks_failed}, "
|
cooldown_skip=watcher_stats.callbacks_skipped_cooldown,
|
||||||
f"timeout={watcher_stats.callbacks_timed_out}, "
|
failed=watcher_stats.callbacks_failed,
|
||||||
f"cooldown_skip={watcher_stats.callbacks_skipped_cooldown}, "
|
ok=watcher_stats.callbacks_succeeded,
|
||||||
f"restart={watcher_stats.restart_count}"
|
restart=watcher_stats.restart_count,
|
||||||
|
timeout=watcher_stats.callbacks_timed_out,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await self._file_watcher.stop()
|
await self._file_watcher.stop()
|
||||||
self._file_watcher = None
|
self._file_watcher = None
|
||||||
@@ -304,14 +312,14 @@ class ConfigManager:
|
|||||||
return
|
return
|
||||||
now_monotonic = asyncio.get_running_loop().time()
|
now_monotonic = asyncio.get_running_loop().time()
|
||||||
if now_monotonic - self._last_hot_reload_monotonic < self._hot_reload_min_interval_s:
|
if now_monotonic - self._last_hot_reload_monotonic < self._hot_reload_min_interval_s:
|
||||||
logger.debug("文件变更触发过于频繁,已跳过本次重载")
|
logger.debug(t("config.reload_skipped_too_frequent"))
|
||||||
return
|
return
|
||||||
self._last_hot_reload_monotonic = now_monotonic
|
self._last_hot_reload_monotonic = now_monotonic
|
||||||
logger.info("检测到配置文件变更,触发热重载")
|
logger.info(t("config.file_change_detected"))
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self.reload_config(), timeout=self._hot_reload_timeout_s)
|
await asyncio.wait_for(self.reload_config(), timeout=self._hot_reload_timeout_s)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.error(f"配置热重载超时(>{self._hot_reload_timeout_s}s)")
|
logger.error(t("config.reload_timeout", timeout_seconds=self._hot_reload_timeout_s))
|
||||||
|
|
||||||
|
|
||||||
def generate_new_config_file(config_class: type[T], config_path: Path, inner_config_version: str) -> None:
|
def generate_new_config_file(config_class: type[T], config_path: Path, inner_config_version: str) -> None:
|
||||||
@@ -333,10 +341,10 @@ def load_config_from_file(
|
|||||||
config_data = tomlkit.load(f)
|
config_data = tomlkit.load(f)
|
||||||
inner_table = config_data.get("inner")
|
inner_table = config_data.get("inner")
|
||||||
if not isinstance(inner_table, Mapping):
|
if not isinstance(inner_table, Mapping):
|
||||||
raise TypeError("配置文件缺少 inner 版本信息")
|
raise TypeError(t("config.missing_inner_version"))
|
||||||
inner_version = inner_table.get("version")
|
inner_version = inner_table.get("version")
|
||||||
if not isinstance(inner_version, str):
|
if not isinstance(inner_version, str):
|
||||||
raise TypeError("配置文件 inner.version 类型错误")
|
raise TypeError(t("config.invalid_inner_version"))
|
||||||
old_ver: str = inner_version
|
old_ver: str = inner_version
|
||||||
config_data.remove("inner") # 移除 inner 部分,避免干扰后续处理
|
config_data.remove("inner") # 移除 inner 部分,避免干扰后续处理
|
||||||
config_data = config_data.unwrap() # 转换为普通字典,方便后续处理
|
config_data = config_data.unwrap() # 转换为普通字典,方便后续处理
|
||||||
@@ -352,9 +360,7 @@ def load_config_from_file(
|
|||||||
# 基于未被部分构造污染的 original_data 做迁移尝试
|
# 基于未被部分构造污染的 original_data 做迁移尝试
|
||||||
mig = try_migrate_legacy_bot_config_dict(original_data)
|
mig = try_migrate_legacy_bot_config_dict(original_data)
|
||||||
if mig.migrated:
|
if mig.migrated:
|
||||||
logger.warning(
|
logger.warning(t("config.legacy_migrated", reason=mig.reason))
|
||||||
f"检测到旧版配置结构,已尝试自动修复: {mig.reason}。建议稍后检查并保存生成的新配置文件。"
|
|
||||||
)
|
|
||||||
migrated_data = mig.data
|
migrated_data = mig.data
|
||||||
target_config = config_class.from_dict(attribute_data, migrated_data)
|
target_config = config_class.from_dict(attribute_data, migrated_data)
|
||||||
else:
|
else:
|
||||||
@@ -367,7 +373,7 @@ def load_config_from_file(
|
|||||||
updated = True
|
updated = True
|
||||||
return target_config, updated
|
return target_config, updated
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(f"配置文件{config_path.name}解析失败")
|
logger.critical(t("config.parse_failed", file_name=config_path.name))
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
@@ -402,11 +408,11 @@ def write_config_to_file(
|
|||||||
aot = tomlkit.aot()
|
aot = tomlkit.aot()
|
||||||
for item in config_field:
|
for item in config_field:
|
||||||
if not isinstance(item, ConfigBase):
|
if not isinstance(item, ConfigBase):
|
||||||
raise TypeError("配置写入只支持ConfigBase子类")
|
raise TypeError(t("config.write_unsupported_type"))
|
||||||
aot.append(recursive_parse_item_to_table(item, override_repr=override_repr))
|
aot.append(recursive_parse_item_to_table(item, override_repr=override_repr))
|
||||||
full_config_data.add(config_item_name, aot)
|
full_config_data.add(config_item_name, aot)
|
||||||
else:
|
else:
|
||||||
raise TypeError("配置写入只支持ConfigBase子类")
|
raise TypeError(t("config.write_unsupported_type"))
|
||||||
|
|
||||||
# 备份旧文件
|
# 备份旧文件
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Set, Tuple, Union, get_args, get_origin
|
||||||
|
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
from typing import Any, get_args, get_origin, TYPE_CHECKING, Literal, List, Set, Tuple, Dict, Union
|
|
||||||
import types
|
|
||||||
from tomlkit import items
|
from tomlkit import items
|
||||||
|
|
||||||
import tomlkit
|
import tomlkit
|
||||||
|
import types
|
||||||
|
|
||||||
from .config_base import ConfigBase
|
from .config_base import ConfigBase
|
||||||
|
from src.common.i18n import t
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .config_base import AttributeData
|
from .config_base import AttributeData
|
||||||
@@ -132,15 +135,20 @@ def convert_field(config_item_name: str, config_item_info: FieldInfo, value: Any
|
|||||||
|
|
||||||
def output_config_changes(attr_data: "AttributeData", logger, old_ver: str, new_ver: str, file_name: str):
|
def output_config_changes(attr_data: "AttributeData", logger, old_ver: str, new_ver: str, file_name: str):
|
||||||
"""输出配置变更信息"""
|
"""输出配置变更信息"""
|
||||||
logger.info("-------- 配置文件变更信息 --------")
|
logger.info(t("config.change_summary_header"))
|
||||||
logger.info(f"新增配置数量: {len(attr_data.missing_attributes)}")
|
logger.info(t("config.added_count", count=len(attr_data.missing_attributes)))
|
||||||
for attr in attr_data.missing_attributes:
|
for attr in attr_data.missing_attributes:
|
||||||
logger.info(f"配置文件中新增配置项: {attr}")
|
logger.info(t("config.added_item", attribute=attr))
|
||||||
logger.info(f"移除配置数量: {len(attr_data.redundant_attributes)}")
|
logger.info(t("config.removed_count", count=len(attr_data.redundant_attributes)))
|
||||||
for attr in attr_data.redundant_attributes:
|
for attr in attr_data.redundant_attributes:
|
||||||
logger.warning(f"移除配置项: {attr}")
|
logger.warning(t("config.removed_item", attribute=attr))
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{file_name}配置文件已经更新. Old: {old_ver} -> New: {new_ver} 建议检查新配置文件中的内容, 以免丢失重要信息"
|
t(
|
||||||
|
"config.file_updated",
|
||||||
|
file_name=file_name,
|
||||||
|
new_version=new_ver,
|
||||||
|
old_version=old_ver,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .config_base import ConfigBase, Field
|
from .config_base import ConfigBase, Field
|
||||||
|
from src.common.i18n import t
|
||||||
|
|
||||||
|
|
||||||
class APIProvider(ConfigBase):
|
class APIProvider(ConfigBase):
|
||||||
@@ -77,11 +79,11 @@ class APIProvider(ConfigBase):
|
|||||||
def model_post_init(self, context: Any = None):
|
def model_post_init(self, context: Any = None):
|
||||||
"""确保api_key在repr中不被显示"""
|
"""确保api_key在repr中不被显示"""
|
||||||
if not self.api_key:
|
if not self.api_key:
|
||||||
raise ValueError("API密钥不能为空, 请在配置中设置有效的API密钥。")
|
raise ValueError(t("config.api_key_empty"))
|
||||||
if not self.base_url and self.client_type != "gemini": # TODO: 允许gemini使用base_url
|
if not self.base_url and self.client_type != "gemini": # TODO: 允许gemini使用base_url
|
||||||
raise ValueError("API基础URL不能为空, 请在配置中设置有效的基础URL。")
|
raise ValueError(t("config.api_base_url_empty"))
|
||||||
if not self.name:
|
if not self.name:
|
||||||
raise ValueError("API提供商名称不能为空, 请在配置中设置有效的名称。")
|
raise ValueError(t("config.api_provider_name_empty"))
|
||||||
return super().model_post_init(context)
|
return super().model_post_init(context)
|
||||||
|
|
||||||
|
|
||||||
@@ -178,11 +180,11 @@ class ModelInfo(ConfigBase):
|
|||||||
|
|
||||||
def model_post_init(self, context: Any = None):
|
def model_post_init(self, context: Any = None):
|
||||||
if not self.model_identifier:
|
if not self.model_identifier:
|
||||||
raise ValueError("模型标识符不能为空, 请在配置中设置有效的模型标识符。")
|
raise ValueError(t("config.model_identifier_empty_generic"))
|
||||||
if not self.name:
|
if not self.name:
|
||||||
raise ValueError("模型名称不能为空, 请在配置中设置有效的模型名称。")
|
raise ValueError(t("config.model_name_empty"))
|
||||||
if not self.api_provider:
|
if not self.api_provider:
|
||||||
raise ValueError("API提供商不能为空, 请在配置中设置有效的API提供商。")
|
raise ValueError(t("config.model_api_provider_empty"))
|
||||||
return super().model_post_init(context)
|
return super().model_post_init(context)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
63
src/main.py
63
src/main.py
@@ -1,31 +1,30 @@
|
|||||||
|
from maim_message import MessageServer
|
||||||
|
from rich.traceback import install
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from maim_message import MessageServer
|
|
||||||
|
|
||||||
from src.common.remote import TelemetryHeartBeatTask
|
from src.bw_learner.expression_auto_check_task import ExpressionAutoCheckTask
|
||||||
from src.manager.async_task_manager import async_task_manager
|
|
||||||
from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask
|
|
||||||
|
|
||||||
# from src.chat.utils.token_statistics import TokenStatisticsTask
|
|
||||||
from src.chat.emoji_system.emoji_manager import emoji_manager
|
from src.chat.emoji_system.emoji_manager import emoji_manager
|
||||||
from src.chat.message_receive.chat_manager import chat_manager
|
|
||||||
from src.config.config import config_manager, global_config
|
|
||||||
from src.chat.message_receive.bot import chat_bot
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
from src.common.message_server.server import get_global_server, Server
|
|
||||||
from src.chat.knowledge import lpmm_start_up
|
from src.chat.knowledge import lpmm_start_up
|
||||||
from rich.traceback import install
|
from src.chat.message_receive.bot import chat_bot
|
||||||
|
from src.chat.message_receive.chat_manager import chat_manager
|
||||||
|
from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask
|
||||||
|
from src.common.i18n import t
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
from src.common.message_server import get_global_api
|
||||||
|
from src.common.message_server.server import Server, get_global_server
|
||||||
|
from src.common.remote import TelemetryHeartBeatTask
|
||||||
|
from src.config.config import config_manager, global_config
|
||||||
|
from src.manager.async_task_manager import async_task_manager
|
||||||
|
from src.plugin_runtime.integration import get_plugin_runtime_manager
|
||||||
|
from src.prompt.prompt_manager import prompt_manager
|
||||||
|
|
||||||
# from src.api.main import start_api_server
|
# from src.api.main import start_api_server
|
||||||
|
|
||||||
# 导入插件运行时
|
# 导入插件运行时
|
||||||
from src.plugin_runtime.integration import get_plugin_runtime_manager
|
|
||||||
|
|
||||||
# 导入消息API和traceback模块
|
# 导入消息API和traceback模块
|
||||||
from src.common.message_server import get_global_api
|
# from src.chat.utils.token_statistics import TokenStatisticsTask
|
||||||
from src.bw_learner.expression_auto_check_task import ExpressionAutoCheckTask
|
|
||||||
|
|
||||||
from src.prompt.prompt_manager import prompt_manager
|
|
||||||
|
|
||||||
install(extra_lines=3)
|
install(extra_lines=3)
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ class MainSystem:
|
|||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
|
|
||||||
if not global_config.webui.enabled:
|
if not global_config.webui.enabled:
|
||||||
logger.info("WebUI 已禁用")
|
logger.info(t("startup.webui_disabled"))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -56,26 +55,16 @@ class MainSystem:
|
|||||||
self.webui_server = get_webui_server()
|
self.webui_server = get_webui_server()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 初始化 WebUI 服务器失败: {e}")
|
logger.error(t("startup.webui_server_init_failed", error=e))
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""初始化系统组件"""
|
"""初始化系统组件"""
|
||||||
logger.info(f"正在唤醒{global_config.bot.nickname}......")
|
logger.info(t("startup.waking_up", nickname=global_config.bot.nickname))
|
||||||
|
|
||||||
# 其他初始化任务
|
# 其他初始化任务
|
||||||
await asyncio.gather(self._init_components())
|
await asyncio.gather(self._init_components())
|
||||||
|
|
||||||
logger.info(f"""
|
logger.info(t("startup.initialization_completed_banner", nickname=global_config.bot.nickname))
|
||||||
--------------------------------
|
|
||||||
全部系统初始化完成,{global_config.bot.nickname}已成功唤醒
|
|
||||||
--------------------------------
|
|
||||||
如果想要自定义{global_config.bot.nickname}的功能,请查阅:https://docs.mai-mai.org/manual/usage/
|
|
||||||
或者遇到了问题,请访问我们的文档:https://docs.mai-mai.org/
|
|
||||||
--------------------------------
|
|
||||||
如果你想要编写或了解插件相关内容,请访问开发文档https://docs.mai-mai.org/develop/
|
|
||||||
--------------------------------
|
|
||||||
如果你需要查阅模型的消耗以及麦麦的统计数据,请访问根目录的maibot_statistics.html文件
|
|
||||||
""")
|
|
||||||
|
|
||||||
async def _init_components(self):
|
async def _init_components(self):
|
||||||
"""初始化其他组件"""
|
"""初始化其他组件"""
|
||||||
@@ -107,13 +96,13 @@ class MainSystem:
|
|||||||
|
|
||||||
# 初始化表情管理器
|
# 初始化表情管理器
|
||||||
emoji_manager.load_emojis_from_db()
|
emoji_manager.load_emojis_from_db()
|
||||||
logger.info("表情包管理器初始化成功")
|
logger.info(t("startup.emoji_manager_initialized"))
|
||||||
|
|
||||||
# 初始化聊天管理器
|
# 初始化聊天管理器
|
||||||
await chat_manager.initialize()
|
await chat_manager.initialize()
|
||||||
asyncio.create_task(chat_manager.regularly_save_sessions())
|
asyncio.create_task(chat_manager.regularly_save_sessions())
|
||||||
|
|
||||||
logger.info("聊天管理器初始化成功")
|
logger.info(t("startup.chat_manager_initialized"))
|
||||||
|
|
||||||
# await asyncio.sleep(0.5) #防止logger输出飞了
|
# await asyncio.sleep(0.5) #防止logger输出飞了
|
||||||
|
|
||||||
@@ -134,9 +123,9 @@ class MainSystem:
|
|||||||
# logger.info("已触发 ON_START 事件")
|
# logger.info("已触发 ON_START 事件")
|
||||||
try:
|
try:
|
||||||
init_time = int(1000 * (time.time() - init_start_time))
|
init_time = int(1000 * (time.time() - init_start_time))
|
||||||
logger.info(f"初始化完成,神经元放电{init_time}次")
|
logger.info(t("startup.initialization_completed_cycles", init_time=init_time))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"启动大脑和外部世界失败: {e}")
|
logger.error(t("startup.brain_external_world_failed", error=e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def schedule_tasks(self):
|
async def schedule_tasks(self):
|
||||||
@@ -154,7 +143,7 @@ class MainSystem:
|
|||||||
|
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.info("调度任务已取消")
|
logger.info(t("startup.schedule_cancelled"))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# async def forget_memory_task(self):
|
# async def forget_memory_task(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user