diff --git a/bot.py b/bot.py index 7e2359690..010416294 100644 --- a/bot.py +++ b/bot.py @@ -17,6 +17,11 @@ from rich.traceback import install 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") confirm_logger = get_logger("confirm") diff --git a/launcher.py b/launcher.py deleted file mode 100644 index cc7a1c49e..000000000 --- a/launcher.py +++ /dev/null @@ -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/ - 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) diff --git a/src/do_tool/tool_can_use/rename_person_tool.py b/src/do_tool/tool_can_use/rename_person_tool.py index d9f23cf4d..3b95bc43a 100644 --- a/src/do_tool/tool_can_use/rename_person_tool.py +++ b/src/do_tool/tool_can_use/rename_person_tool.py @@ -8,14 +8,16 @@ logger = get_logger("rename_person_tool") class RenamePersonTool(BaseTool): name = "rename_person" - description = "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。" + description = ( + "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。你想给人改名,叫别人别的称呼,需要调用这个工具。" + ) parameters = { "type": "object", "properties": { "person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"}, "message_content": { "type": "string", - "description": "可选的。当前的聊天内容或特定要求,用于提供取名建议的上下文。", + "description": "当前的聊天内容或特定要求,用于提供取名建议的上下文,尽可能详细。", }, }, "required": ["person_name"], diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index 1cf584964..f25594b7f 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -672,12 +672,12 @@ class SubHeartflowManager: """处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)""" # 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁 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 的状态转换请求 --- # - 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。 通常在连续多次 "no_reply" 后被调用。 @@ -729,6 +729,8 @@ class SubHeartflowManager: f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})" ) try: + # 从HFC到CHAT时,清空兴趣字典 + subflow.clear_interest_dict() await subflow.change_chat_state(target_state) final_state = subflow.chat_state.chat_status if final_state == target_state: diff --git a/src/plugins/person_info/person_info.py b/src/plugins/person_info/person_info.py index d4e69d7e2..fe4069024 100644 --- a/src/plugins/person_info/person_info.py +++ b/src/plugins/person_info/person_info.py @@ -215,9 +215,11 @@ class PersonInfoManager: if old_name: 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: qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}。\n" qv_name_prompt += "请用json给出你的想法,并给出理由,示例如下:" diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 5f215009f..52280783d 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -69,8 +69,8 @@ nonebot-qq="http://127.0.0.1:18002/api/message" allow_focus_mode = true # 是否允许专注聊天状态 # 是否启用heart_flowC(HFC)模式 # 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间),进行主动的观察和回复,并给出回复,比较消耗token -base_normal_chat_num = 3 # 最多允许多少个群进行普通聊天 -base_focused_chat_num = 2 # 最多允许多少个群进行专注聊天 +base_normal_chat_num = 8 # 最多允许多少个群进行普通聊天 +base_focused_chat_num = 5 # 最多允许多少个群进行专注聊天 observation_context_size = 15 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖 message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟