Merge branch 'dev' of https://github.com/MaiM-with-u/MaiBot into dev
This commit is contained in:
5
bot.py
5
bot.py
@@ -17,6 +17,11 @@ from rich.traceback import install
|
|||||||
|
|
||||||
install(extra_lines=3)
|
install(extra_lines=3)
|
||||||
|
|
||||||
|
# 设置工作目录为脚本所在目录
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
os.chdir(script_dir)
|
||||||
|
print(f"已设置工作目录为: {script_dir}")
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("main")
|
logger = get_logger("main")
|
||||||
confirm_logger = get_logger("confirm")
|
confirm_logger = get_logger("confirm")
|
||||||
|
|||||||
302
launcher.py
302
launcher.py
@@ -1,302 +0,0 @@
|
|||||||
import flet as ft
|
|
||||||
import os
|
|
||||||
import atexit
|
|
||||||
import psutil # Keep for initial PID checks maybe, though state should handle it
|
|
||||||
# import asyncio # <--- 如果不再需要其他异步任务,可以考虑移除
|
|
||||||
|
|
||||||
# --- Import refactored modules --- #
|
|
||||||
from src.MaiGoi.state import AppState
|
|
||||||
from src.MaiGoi.process_manager import (
|
|
||||||
start_bot_and_show_console,
|
|
||||||
output_processor_loop, # Needed for restarting on navigate
|
|
||||||
cleanup_on_exit,
|
|
||||||
handle_disconnect,
|
|
||||||
)
|
|
||||||
from src.MaiGoi.ui_views import (
|
|
||||||
create_main_view,
|
|
||||||
create_console_view,
|
|
||||||
create_adapters_view,
|
|
||||||
create_process_output_view,
|
|
||||||
)
|
|
||||||
from src.MaiGoi.config_manager import load_config
|
|
||||||
|
|
||||||
# --- Import the new settings view --- #
|
|
||||||
from src.MaiGoi.ui_settings_view import create_settings_view
|
|
||||||
|
|
||||||
# --- Global AppState instance --- #
|
|
||||||
# This holds all the state previously scattered as globals
|
|
||||||
app_state = AppState()
|
|
||||||
|
|
||||||
# --- File Picker Result Handler Placeholder ---
|
|
||||||
# We need a placeholder function or logic to handle the result here if needed
|
|
||||||
# For now, the result will be handled within the adapters view itself.
|
|
||||||
# def handle_file_picker_result(e: ft.FilePickerResultEvent):
|
|
||||||
# print("File picker result in launcher (should be handled in view):", e.files)
|
|
||||||
|
|
||||||
# --- atexit Cleanup Registration --- #
|
|
||||||
# Register the cleanup function from the process manager module
|
|
||||||
# It needs access to the app_state
|
|
||||||
atexit.register(cleanup_on_exit, app_state)
|
|
||||||
print("[Main Script] atexit cleanup handler from process_manager registered.", flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Routing Logic --- #
|
|
||||||
def route_change(route: ft.RouteChangeEvent):
|
|
||||||
"""Handles Flet route changes, creating and appending views."""
|
|
||||||
page = route.page
|
|
||||||
target_route = route.route
|
|
||||||
|
|
||||||
# --- 移除异步显示弹窗的辅助函数 ---
|
|
||||||
# async def show_python_path_dialog():
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# Clear existing views before adding new ones
|
|
||||||
page.views.clear()
|
|
||||||
|
|
||||||
# Always add the main view
|
|
||||||
main_view = create_main_view(page, app_state)
|
|
||||||
page.views.append(main_view)
|
|
||||||
|
|
||||||
# --- Handle Specific Routes --- #
|
|
||||||
if target_route == "/console":
|
|
||||||
# 清理:移除之前添加的 is_python_dialog_opening 标志(如果愿意)
|
|
||||||
# app_state.is_python_dialog_opening = False # 可选清理
|
|
||||||
|
|
||||||
console_view = create_console_view(page, app_state)
|
|
||||||
page.views.append(console_view)
|
|
||||||
|
|
||||||
# --- 仅设置标志 ---
|
|
||||||
print(f"[Route Change /console] Checking python_path: '{app_state.python_path}'")
|
|
||||||
if not app_state.python_path:
|
|
||||||
print("[Route Change /console] python_path is empty, setting flag.")
|
|
||||||
app_state.needs_python_path_dialog = True
|
|
||||||
# *** 不再在这里打开弹窗 ***
|
|
||||||
|
|
||||||
# Check process status and potentially restart processor loop if needed
|
|
||||||
is_running = app_state.bot_pid is not None and psutil.pid_exists(app_state.bot_pid)
|
|
||||||
print(
|
|
||||||
f"[Route Change /console] Checking status: PID={app_state.bot_pid}, is_running={is_running}, stop_event={app_state.stop_event.is_set()}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_running:
|
|
||||||
print("[Route Change /console] Process is running.", flush=True)
|
|
||||||
# If the processor loop was stopped (e.g., by navigating away or stop button),
|
|
||||||
# but the process is still running, restart the loop.
|
|
||||||
if app_state.stop_event.is_set():
|
|
||||||
print("[Route Change /console] Stop event was set, clearing and restarting processor loop.", flush=True)
|
|
||||||
app_state.stop_event.clear()
|
|
||||||
# Make sure output_list_view is available before starting loop
|
|
||||||
if not app_state.output_list_view:
|
|
||||||
print("[Route Change /console] Warning: output_list_view is None when restarting loop. Creating.")
|
|
||||||
app_state.output_list_view = ft.ListView(
|
|
||||||
expand=True, spacing=2, auto_scroll=app_state.is_auto_scroll_enabled, padding=5
|
|
||||||
)
|
|
||||||
console_view.controls[1].controls[0].content = app_state.output_list_view # Update content in view
|
|
||||||
|
|
||||||
page.run_task(output_processor_loop, page, app_state)
|
|
||||||
else:
|
|
||||||
print("[Route Change /console] Process is not running.", flush=True)
|
|
||||||
# Ensure console view shows the 'not running' state if needed
|
|
||||||
if app_state.output_list_view:
|
|
||||||
# Check if already has the message? Might add duplicates.
|
|
||||||
# Simple approach: just add it if the list is empty or last msg isn't it.
|
|
||||||
add_not_running_msg = True
|
|
||||||
if app_state.output_list_view.controls:
|
|
||||||
last_control = app_state.output_list_view.controls[-1]
|
|
||||||
# Check if it's Text and value is not None before checking content
|
|
||||||
if (
|
|
||||||
isinstance(last_control, ft.Text)
|
|
||||||
and last_control.value is not None
|
|
||||||
and "Bot 进程未运行" in last_control.value
|
|
||||||
):
|
|
||||||
add_not_running_msg = False
|
|
||||||
if add_not_running_msg:
|
|
||||||
app_state.output_list_view.controls.append(ft.Text("--- Bot 进程未运行 ---", italic=True))
|
|
||||||
else:
|
|
||||||
# If list view doesn't exist here, create it and add the message
|
|
||||||
print("[Route Change /console] Creating ListView to show 'not running' message.")
|
|
||||||
app_state.output_list_view = ft.ListView(
|
|
||||||
expand=True, spacing=2, auto_scroll=app_state.is_auto_scroll_enabled, padding=5
|
|
||||||
)
|
|
||||||
app_state.output_list_view.controls.append(ft.Text("--- Bot 进程未运行 ---", italic=True))
|
|
||||||
# Update the console view container's content
|
|
||||||
console_view.controls[1].controls[0].content = app_state.output_list_view
|
|
||||||
elif target_route == "/adapters":
|
|
||||||
adapters_view = create_adapters_view(page, app_state)
|
|
||||||
page.views.append(adapters_view)
|
|
||||||
elif target_route == "/settings":
|
|
||||||
# Call the new settings view function
|
|
||||||
settings_view = create_settings_view(page, app_state)
|
|
||||||
page.views.append(settings_view)
|
|
||||||
|
|
||||||
# --- Handle Dynamic Adapter Output Route --- #
|
|
||||||
# Check if the route matches the pattern /adapters/<something>
|
|
||||||
elif target_route.startswith("/adapters/") and len(target_route.split("/")) == 3:
|
|
||||||
parts = target_route.split("/")
|
|
||||||
process_id = parts[2] # Extract the process ID (which is the script path for now)
|
|
||||||
print(f"[Route Change] Detected adapter output route for ID: {process_id}")
|
|
||||||
adapter_output_view = create_process_output_view(page, app_state, process_id)
|
|
||||||
if adapter_output_view:
|
|
||||||
page.views.append(adapter_output_view)
|
|
||||||
else:
|
|
||||||
# If view creation failed (e.g., process state not found), show error and stay on previous view?
|
|
||||||
# Or redirect back to /adapters? Let's go back to adapters list.
|
|
||||||
print(f"[Route Change] Failed to create output view for {process_id}. Redirecting to /adapters.")
|
|
||||||
# Avoid infinite loop if /adapters also fails
|
|
||||||
if len(page.views) > 1: # Ensure we don't pop the main view
|
|
||||||
page.views.pop() # Pop the failed view attempt
|
|
||||||
# Find the adapters view if it exists, otherwise just update
|
|
||||||
adapters_view_index = -1
|
|
||||||
for i, view in enumerate(page.views):
|
|
||||||
if view.route == "/adapters":
|
|
||||||
adapters_view_index = i
|
|
||||||
break
|
|
||||||
if adapters_view_index == -1: # Adapters view wasn't in stack? Add it.
|
|
||||||
adapters_view = create_adapters_view(page, app_state)
|
|
||||||
page.views.append(adapters_view)
|
|
||||||
# Go back to the adapters list route to rebuild the view stack correctly
|
|
||||||
page.go("/adapters")
|
|
||||||
return # Prevent page.update() below
|
|
||||||
|
|
||||||
# Update the page to show the correct view(s)
|
|
||||||
page.update()
|
|
||||||
|
|
||||||
|
|
||||||
def view_pop(e: ft.ViewPopEvent):
|
|
||||||
"""Handles view popping (e.g., back navigation)."""
|
|
||||||
page = e.page
|
|
||||||
# Remove the top view
|
|
||||||
page.views.pop()
|
|
||||||
if page.views:
|
|
||||||
top_view = page.views[-1]
|
|
||||||
# Go to the route of the view now at the top of the stack
|
|
||||||
# This will trigger route_change again to rebuild the view stack correctly
|
|
||||||
page.go(top_view.route)
|
|
||||||
# else: print("Warning: Popped the last view.")
|
|
||||||
|
|
||||||
|
|
||||||
# --- Main Application Setup --- #
|
|
||||||
def main(page: ft.Page):
|
|
||||||
# Load initial config and store in state
|
|
||||||
if os.path.exists("logs/interest/interest_history.log"):
|
|
||||||
os.remove("logs/interest/interest_history.log")
|
|
||||||
loaded_config = load_config()
|
|
||||||
app_state.gui_config = loaded_config
|
|
||||||
app_state.adapter_paths = loaded_config.get("adapters", []).copy()
|
|
||||||
app_state.bot_script_path = loaded_config.get("bot_script_path", "bot.py") # Load bot script path
|
|
||||||
|
|
||||||
# 加载用户自定义的 Python 路径
|
|
||||||
if "python_path" in loaded_config and os.path.exists(loaded_config["python_path"]):
|
|
||||||
app_state.python_path = loaded_config["python_path"]
|
|
||||||
print(f"[Main] 从配置加载 Python 路径: {app_state.python_path}")
|
|
||||||
|
|
||||||
print(f"[Main] Initial adapters loaded: {app_state.adapter_paths}")
|
|
||||||
|
|
||||||
# Set script_dir in AppState early
|
|
||||||
app_state.script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
print(f"[Main] Script directory set in state: {app_state.script_dir}", flush=True)
|
|
||||||
|
|
||||||
# --- Setup File Picker --- #
|
|
||||||
# Create the FilePicker instance
|
|
||||||
# The on_result handler will be set dynamically in the view that uses it
|
|
||||||
app_state.file_picker = ft.FilePicker()
|
|
||||||
# Add the FilePicker to the page's overlay controls
|
|
||||||
page.overlay.append(app_state.file_picker)
|
|
||||||
print("[Main] FilePicker created and added to page overlay.")
|
|
||||||
|
|
||||||
page.title = "MaiBot 启动器"
|
|
||||||
page.window.width = 1400
|
|
||||||
page.window.height = 1000 # Increased height slightly for monitor
|
|
||||||
page.vertical_alignment = ft.MainAxisAlignment.START
|
|
||||||
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
|
|
||||||
|
|
||||||
# --- Apply Theme from Config --- #
|
|
||||||
saved_theme = app_state.gui_config.get("theme", "System").upper()
|
|
||||||
try:
|
|
||||||
page.theme_mode = ft.ThemeMode[saved_theme]
|
|
||||||
print(f"[Main] Applied theme from config: {page.theme_mode}")
|
|
||||||
except KeyError:
|
|
||||||
print(f"[Main] Warning: Invalid theme '{saved_theme}' in config. Falling back to System.")
|
|
||||||
page.theme_mode = ft.ThemeMode.SYSTEM
|
|
||||||
|
|
||||||
# --- 自定义主题颜色 --- #
|
|
||||||
# 创建深色主题,使橙色变得更暗
|
|
||||||
dark_theme = ft.Theme(
|
|
||||||
color_scheme_seed=ft.colors.ORANGE,
|
|
||||||
primary_color=ft.colors.ORANGE_700, # 使用更暗的橙色
|
|
||||||
color_scheme=ft.ColorScheme(
|
|
||||||
primary=ft.colors.ORANGE_700,
|
|
||||||
primary_container=ft.colors.ORANGE_800,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建亮色主题
|
|
||||||
light_theme = ft.Theme(
|
|
||||||
color_scheme_seed=ft.colors.ORANGE,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 设置自定义主题
|
|
||||||
page.theme = light_theme
|
|
||||||
page.dark_theme = dark_theme
|
|
||||||
|
|
||||||
page.padding = 0 # <-- 将页面 padding 设置为 0
|
|
||||||
|
|
||||||
# --- Create the main 'Start Bot' button and store in state --- #
|
|
||||||
# This button needs to exist before the first route_change call
|
|
||||||
app_state.start_bot_button = ft.FilledButton(
|
|
||||||
"启动 MaiBot 主程序 (bot.py)",
|
|
||||||
icon=ft.icons.SMART_TOY_OUTLINED,
|
|
||||||
# The click handler now calls the function from process_manager
|
|
||||||
on_click=lambda _: start_bot_and_show_console(page, app_state),
|
|
||||||
expand=True,
|
|
||||||
tooltip="启动主程序并在新视图中显示控制台输出",
|
|
||||||
)
|
|
||||||
print("[Main] Start Bot Button created and stored in state.", flush=True)
|
|
||||||
|
|
||||||
# --- Routing Setup --- #
|
|
||||||
page.on_route_change = route_change
|
|
||||||
page.on_view_pop = view_pop
|
|
||||||
|
|
||||||
# --- Disconnect Handler --- #
|
|
||||||
# Pass app_state to the disconnect handler
|
|
||||||
page.on_disconnect = lambda e: handle_disconnect(page, app_state, e)
|
|
||||||
print("[Main] Registered page.on_disconnect handler.", flush=True)
|
|
||||||
|
|
||||||
# Prevent immediate close to allow cleanup
|
|
||||||
page.window_prevent_close = True
|
|
||||||
|
|
||||||
# --- Hide Native Title Bar --- #
|
|
||||||
# page.window_title_bar_hidden = True
|
|
||||||
# page.window.frameless = True
|
|
||||||
|
|
||||||
# --- Initial Navigation --- #
|
|
||||||
# Trigger the initial route change to build the first view
|
|
||||||
page.go(page.route if page.route else "/")
|
|
||||||
|
|
||||||
|
|
||||||
# --- Run Flet App --- #
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# No need to initialize globals here anymore, AppState handles it.
|
|
||||||
ft.app(target=main)
|
|
||||||
# This print will appear *after* the Flet window closes,
|
|
||||||
# but *before* the atexit handler runs.
|
|
||||||
print("[Main Script] Flet app exited. atexit handler should run next.", flush=True)
|
|
||||||
|
|
||||||
# --- Removed Code Sections (Previously Globals and Functions) ---
|
|
||||||
# (Keep this comment block or similar for reference if desired)
|
|
||||||
# Removed: bot_process, bot_pid, output_queue, stop_event, interest_monitor_control,
|
|
||||||
# output_list_view, start_bot_button (now in AppState),
|
|
||||||
# is_auto_scroll_enabled (now in AppState)
|
|
||||||
# Removed: ansi_converter
|
|
||||||
# Removed: cleanup_on_exit (moved to process_manager)
|
|
||||||
# Removed: update_page_safe (moved to utils)
|
|
||||||
# Removed: show_snackbar (moved to utils)
|
|
||||||
# Removed: run_script (moved to utils)
|
|
||||||
# Removed: handle_disconnect (moved to process_manager)
|
|
||||||
# Removed: stop_bot_process (moved to process_manager)
|
|
||||||
# Removed: read_process_output (moved to process_manager)
|
|
||||||
# Removed: output_processor_loop (moved to process_manager)
|
|
||||||
# Removed: start_bot_and_show_console (moved to process_manager)
|
|
||||||
# Removed: create_console_view (moved to ui_views)
|
|
||||||
# (Main view creation logic also moved to ui_views within create_main_view)
|
|
||||||
@@ -8,14 +8,16 @@ logger = get_logger("rename_person_tool")
|
|||||||
|
|
||||||
class RenamePersonTool(BaseTool):
|
class RenamePersonTool(BaseTool):
|
||||||
name = "rename_person"
|
name = "rename_person"
|
||||||
description = "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。"
|
description = (
|
||||||
|
"这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。你想给人改名,叫别人别的称呼,需要调用这个工具。"
|
||||||
|
)
|
||||||
parameters = {
|
parameters = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"},
|
"person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"},
|
||||||
"message_content": {
|
"message_content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "可选的。当前的聊天内容或特定要求,用于提供取名建议的上下文。",
|
"description": "当前的聊天内容或特定要求,用于提供取名建议的上下文,尽可能详细。",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["person_name"],
|
"required": ["person_name"],
|
||||||
|
|||||||
@@ -672,12 +672,12 @@ class SubHeartflowManager:
|
|||||||
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
|
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
|
||||||
# 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁
|
# 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁
|
||||||
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
|
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
|
||||||
await self.sbhf_focus_into_absent(subheartflow_id)
|
await self.sbhf_focus_into_absent_or_chat(subheartflow_id)
|
||||||
|
|
||||||
# --- 结束新增 --- #
|
# --- 结束新增 --- #
|
||||||
|
|
||||||
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
|
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
|
||||||
async def sbhf_focus_into_absent(self, subflow_id: Any):
|
async def sbhf_focus_into_absent_or_chat(self, subflow_id: Any):
|
||||||
"""
|
"""
|
||||||
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT 或 CHAT。
|
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT 或 CHAT。
|
||||||
通常在连续多次 "no_reply" 后被调用。
|
通常在连续多次 "no_reply" 后被调用。
|
||||||
@@ -729,6 +729,8 @@ class SubHeartflowManager:
|
|||||||
f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})"
|
f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
# 从HFC到CHAT时,清空兴趣字典
|
||||||
|
subflow.clear_interest_dict()
|
||||||
await subflow.change_chat_state(target_state)
|
await subflow.change_chat_state(target_state)
|
||||||
final_state = subflow.chat_state.chat_status
|
final_state = subflow.chat_state.chat_status
|
||||||
if final_state == target_state:
|
if final_state == target_state:
|
||||||
|
|||||||
@@ -215,9 +215,11 @@ class PersonInfoManager:
|
|||||||
if old_name:
|
if old_name:
|
||||||
qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason},"
|
qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason},"
|
||||||
|
|
||||||
qv_name_prompt += f"\n其他取名的要求是:{request}"
|
qv_name_prompt += f"\n其他取名的要求是:{request},不要太浮夸"
|
||||||
|
|
||||||
qv_name_prompt += "\n请根据以上用户信息,想想你叫他什么比较好,请最好使用用户的qq昵称,可以稍作修改"
|
qv_name_prompt += (
|
||||||
|
"\n请根据以上用户信息,想想你叫他什么比较好,不要太浮夸,请最好使用用户的qq昵称,可以稍作修改"
|
||||||
|
)
|
||||||
if existing_names:
|
if existing_names:
|
||||||
qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}。\n"
|
qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}。\n"
|
||||||
qv_name_prompt += "请用json给出你的想法,并给出理由,示例如下:"
|
qv_name_prompt += "请用json给出你的想法,并给出理由,示例如下:"
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ nonebot-qq="http://127.0.0.1:18002/api/message"
|
|||||||
allow_focus_mode = true # 是否允许专注聊天状态
|
allow_focus_mode = true # 是否允许专注聊天状态
|
||||||
# 是否启用heart_flowC(HFC)模式
|
# 是否启用heart_flowC(HFC)模式
|
||||||
# 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间),进行主动的观察和回复,并给出回复,比较消耗token
|
# 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间),进行主动的观察和回复,并给出回复,比较消耗token
|
||||||
base_normal_chat_num = 3 # 最多允许多少个群进行普通聊天
|
base_normal_chat_num = 8 # 最多允许多少个群进行普通聊天
|
||||||
base_focused_chat_num = 2 # 最多允许多少个群进行专注聊天
|
base_focused_chat_num = 5 # 最多允许多少个群进行专注聊天
|
||||||
|
|
||||||
observation_context_size = 15 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖
|
observation_context_size = 15 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖
|
||||||
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟
|
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟
|
||||||
|
|||||||
Reference in New Issue
Block a user