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)