import customtkinter as ctk import logging import traceback import os from douyin_imbot.browser.browser_manager import BrowserManager from douyin_imbot.ui.profile_manager_panel import ProfileManagerPanel logger = logging.getLogger(__name__) class MainWindow(ctk.CTk): """抖音 IM 机器人主窗口.""" def __init__(self, settings=None): """ 初始化主窗口. Args: settings: 应用程序配置 """ try: super().__init__() self.settings = settings or {} self.title("抖音自动回复机器人") self.geometry("1100x700") # 设置主题和外观 ctk.set_appearance_mode("System") # 系统主题 ctk.set_default_color_theme("blue") # 蓝色主题 # 创建浏览器管理器 self.browser_manager = BrowserManager(settings) # 初始化变量 self.current_profile = None self.profile_dialog = None # 创建UI self._create_widgets() logger.info("主窗口初始化完成") except Exception as e: logger.error(f"主窗口初始化失败: {str(e)}") logger.error(traceback.format_exc()) raise def _create_widgets(self): """创建UI组件""" try: # 配置主网格 self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) # 创建左侧导航栏 self.sidebar_frame = ctk.CTkFrame(self, width=200, corner_radius=0) self.sidebar_frame.grid(row=0, column=0, sticky="nsew") self.sidebar_frame.grid_rowconfigure(5, weight=1) # 增加行数以适应新的按钮 # 标题 self.app_title = ctk.CTkLabel( self.sidebar_frame, text="抖音 IM 机器人", font=ctk.CTkFont(size=20, weight="bold"), ) self.app_title.grid(row=0, column=0, padx=20, pady=(20, 20)) # 状态指示器 self.status_frame = ctk.CTkFrame(self.sidebar_frame) self.status_frame.grid(row=1, column=0, padx=20, pady=(0, 20), sticky="ew") self.status_label = ctk.CTkLabel(self.status_frame, text="状态:") self.status_label.grid(row=0, column=0, padx=10, pady=5, sticky="w") self.status_value = ctk.CTkLabel( self.status_frame, text="未启动", text_color="gray" ) self.status_value.grid(row=0, column=1, padx=10, pady=5, sticky="e") # 导航按钮 - 修改为只有控制面板和设置按钮 self.dashboard_button = ctk.CTkButton( self.sidebar_frame, text="控制面板", height=40, command=self.show_dashboard, ) self.dashboard_button.grid(row=2, column=0, padx=20, pady=10, sticky="ew") self.settings_button = ctk.CTkButton( self.sidebar_frame, text="设置", height=40, command=self.show_settings ) self.settings_button.grid(row=3, column=0, padx=20, pady=10, sticky="ew") self.about_button = ctk.CTkButton( self.sidebar_frame, text="关于", height=40, command=self.show_about ) self.about_button.grid(row=4, column=0, padx=20, pady=10, sticky="ew") # 主内容区域 - 使用帧切换而不是选项卡 self.main_content = ctk.CTkFrame(self) self.main_content.grid(row=0, column=1, padx=20, pady=20, sticky="nsew") self.main_content.grid_columnconfigure(0, weight=1) self.main_content.grid_rowconfigure(0, weight=1) # 创建各个内容区域的帧,但暂时不显示 self.dashboard_frame = ctk.CTkFrame(self.main_content) self.settings_frame = ctk.CTkFrame(self.main_content) self.about_frame = ctk.CTkFrame(self.main_content) # 初始化各个内容区域 self.setup_dashboard() self.setup_settings_panel() self.setup_about_panel() # 默认显示控制面板 self.show_dashboard() # 确保配置文件列表在UI加载后刷新 self.after(500, self.refresh_profile_list) logger.info("UI 组件创建完成") except Exception as e: logger.error(f"创建UI组件失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"UI初始化错误: {str(e)}") def setup_dashboard(self): """设置控制面板.""" try: # 配置控制面板帧 self.dashboard_frame.grid_columnconfigure(0, weight=1) self.dashboard_frame.grid_rowconfigure( 2, weight=1 ) # 减少行数,因为移除了控制按钮区域 # 集成浏览器配置管理面板 self.profile_manager_frame = ctk.CTkFrame(self.dashboard_frame) self.profile_manager_frame.grid( row=1, column=0, padx=10, pady=10, sticky="ew" ) # 创建配置管理器 self.profile_manager = ProfileManagerPanel( self.profile_manager_frame, self.browser_manager, on_profile_change=self.on_profile_change, ) self.profile_manager.pack(fill="both", expand=True) # 移除控制按钮区域 # 日志区域 log_frame = ctk.CTkFrame(self.dashboard_frame) log_frame.grid(row=2, column=0, padx=10, pady=10, sticky="nsew") log_frame.grid_columnconfigure(0, weight=1) log_frame.grid_rowconfigure(1, weight=1) log_label = ctk.CTkLabel( log_frame, text="运行日志:", anchor="w", font=ctk.CTkFont(size=16) ) log_label.grid(row=0, column=0, padx=10, pady=(10, 5), sticky="w") self.log_textbox = ctk.CTkTextbox(log_frame, height=200) self.log_textbox.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nsew") self.log_textbox.insert("0.0", "准备就绪...\n") self.log_textbox.configure(state="disabled") # 清除日志按钮 self.clear_log_button = ctk.CTkButton( log_frame, text="清除日志", command=self.clear_log, width=100 ) self.clear_log_button.grid( row=2, column=0, padx=10, pady=(0, 10), sticky="e" ) logger.info("控制面板设置完成") except Exception as e: logger.error(f"设置控制面板失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"控制面板初始化错误: {str(e)}") def setup_settings_panel(self): """设置设置面板.""" try: self.settings_frame.grid_columnconfigure(0, weight=1) # 标题 title = ctk.CTkLabel( self.settings_frame, text="设置", font=ctk.CTkFont(size=24, weight="bold"), ) title.grid(row=0, column=0, padx=20, pady=20) # 设置选项 options_frame = ctk.CTkFrame(self.settings_frame) options_frame.grid(row=1, column=0, padx=20, pady=10, sticky="ew") options_frame.grid_columnconfigure(1, weight=1) # API 密钥设置 api_label = ctk.CTkLabel(options_frame, text="API 密钥:", width=150) api_label.grid(row=0, column=0, padx=10, pady=15, sticky="w") self.entry_api_key = ctk.CTkEntry(options_frame, width=400) self.entry_api_key.grid(row=0, column=1, padx=10, pady=15, sticky="ew") self.entry_api_key.insert(0, self.settings.get("api_key", "")) # 消息间隔设置 interval_label = ctk.CTkLabel( options_frame, text="消息间隔 (秒):", width=150 ) interval_label.grid(row=1, column=0, padx=10, pady=15, sticky="w") self.entry_message_interval = ctk.CTkEntry(options_frame, width=400) self.entry_message_interval.grid( row=1, column=1, padx=10, pady=15, sticky="ew" ) self.entry_message_interval.insert( 0, self.settings.get("message_interval", "5") ) # 高级设置区域 advanced_frame = ctk.CTkFrame(self.settings_frame) advanced_frame.grid(row=2, column=0, padx=20, pady=(20, 10), sticky="ew") advanced_label = ctk.CTkLabel( advanced_frame, text="高级设置", font=ctk.CTkFont(size=18, weight="bold"), ) advanced_label.pack(pady=10, padx=10, anchor="w") # 保存按钮 save_button = ctk.CTkButton( self.settings_frame, text="保存设置", command=self.save_settings, width=200, height=40, font=ctk.CTkFont(size=16), ) save_button.grid(row=3, column=0, pady=30) logger.info("设置面板设置完成") except Exception as e: logger.error(f"设置设置面板失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"设置面板初始化错误: {str(e)}") def setup_about_panel(self): """设置关于面板.""" try: self.about_frame.grid_columnconfigure(0, weight=1) # 标题 title = ctk.CTkLabel( self.about_frame, text="关于", font=ctk.CTkFont(size=24, weight="bold") ) title.grid(row=0, column=0, padx=20, pady=20) # 信息卡片 info_frame = ctk.CTkFrame(self.about_frame) info_frame.grid(row=1, column=0, padx=40, pady=20, sticky="ew") version_label = ctk.CTkLabel( info_frame, text="抖音 IM 机器人 v1.0.0", font=ctk.CTkFont(size=18) ) version_label.pack(pady=(20, 10)) description = ctk.CTkLabel( info_frame, text="抖音 IM 机器人是一个自动回复工具,可以帮助你自动处理抖音私信。", wraplength=500, font=ctk.CTkFont(size=14), ) description.pack(pady=10, padx=30) # 添加更多关于信息 features_label = ctk.CTkLabel( info_frame, text="主要功能:", font=ctk.CTkFont(size=16, weight="bold"), anchor="w", ) features_label.pack(pady=(20, 10), padx=30, anchor="w") features_text = """• 自动回复抖音私信 • 支持多种浏览器配置 • 自定义回复内容 • 日志记录与监控""" features = ctk.CTkLabel( info_frame, text=features_text, font=ctk.CTkFont(size=14), justify="left", anchor="w", ) features.pack(pady=5, padx=30, anchor="w") copyright_label = ctk.CTkLabel( info_frame, text="© 2023 版权所有", font=ctk.CTkFont(size=12) ) copyright_label.pack(pady=20) logger.info("关于面板设置完成") except Exception as e: logger.error(f"设置关于面板失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"关于面板初始化错误: {str(e)}") def show_dashboard(self): """显示控制面板.""" try: self._hide_all_frames() self.dashboard_frame.grid(row=0, column=0, sticky="nsew") self.highlight_button(self.dashboard_button) logger.debug("显示控制面板") except Exception as e: logger.error(f"显示控制面板失败: {str(e)}") logger.error(traceback.format_exc()) def show_settings(self): """显示设置界面.""" try: self._hide_all_frames() self.settings_frame.grid(row=0, column=0, sticky="nsew") self.highlight_button(self.settings_button) logger.debug("显示设置面板") except Exception as e: logger.error(f"显示设置面板失败: {str(e)}") logger.error(traceback.format_exc()) def show_about(self): """显示关于界面.""" try: self._hide_all_frames() self.about_frame.grid(row=0, column=0, sticky="nsew") self.highlight_button(self.about_button) logger.debug("显示关于面板") except Exception as e: logger.error(f"显示关于面板失败: {str(e)}") logger.error(traceback.format_exc()) def _hide_all_frames(self): """隐藏所有内容帧.""" try: for frame in [ self.dashboard_frame, self.settings_frame, self.about_frame, ]: frame.grid_forget() except Exception as e: logger.error(f"隐藏内容帧失败: {str(e)}") logger.error(traceback.format_exc()) def highlight_button(self, active_button): """高亮当前活动按钮.""" try: for button in [ self.dashboard_button, self.settings_button, self.about_button, ]: if button == active_button: button.configure( fg_color=["#3a7ebf", "#1f538d"] ) # 使用主题较深的颜色 else: button.configure(fg_color=["#3B8ED0", "#1E6FBA"]) # 恢复默认颜色 except Exception as e: logger.error(f"高亮按钮失败: {str(e)}") logger.error(traceback.format_exc()) def update_log(self, message): """更新日志文本框.""" try: if hasattr(self, "log_textbox") and self.log_textbox: self.log_textbox.configure(state="normal") self.log_textbox.insert("end", f"{message}\n") self.log_textbox.see("end") self.log_textbox.configure(state="disabled") else: logger.warning(f"日志无法更新,日志组件未初始化: {message}") except Exception as e: logger.error(f"更新日志失败: {str(e)}") logger.error(traceback.format_exc()) def clear_log(self): """清除日志内容.""" try: self.log_textbox.configure(state="normal") self.log_textbox.delete("0.0", "end") self.log_textbox.insert("0.0", "日志已清除...\n") self.log_textbox.configure(state="disabled") logger.info("日志已清除") except Exception as e: logger.error(f"清除日志失败: {str(e)}") logger.error(traceback.format_exc()) def save_settings(self): """保存设置.""" try: # 收集所有设置项 for key in ["api_key", "message_interval"]: entry = getattr(self, f"entry_{key}", None) if entry: self.settings[key] = entry.get() # 保存设置到文件 from douyin_imbot.config.settings import save_settings if save_settings(self.settings): logger.info("设置已保存") self.update_log("设置已保存") else: logger.error("保存设置失败") self.update_log("保存设置失败") except Exception as e: logger.error(f"保存设置失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"保存设置失败: {str(e)}") def on_profile_selected(self, profile_name): """当用户选择不同的配置文件时触发""" try: if not profile_name: logger.warning("配置文件名为空") self.update_log("选择了无效的配置文件") return if profile_name == "无可用配置": logger.warning("选择了无效的配置文件") self.update_log("选择了无效的配置文件") return self.current_profile = profile_name logger.info(f"用户选择配置文件: {profile_name}") self.update_log(f"已选择配置文件: {profile_name}") # 更新配置文件详情显示 try: profile_details = ( self.browser_manager.get_profile_details(profile_name) or {} ) details_text = ( f"用户目录: {profile_details.get('user_data_dir', '无')}\n" ) details_text += f"上次使用: {profile_details.get('last_used', '从未')}" except Exception as detail_error: logger.error(f"获取配置详情失败: {str(detail_error)}") logger.error(traceback.format_exc()) details_text = "获取配置详情失败" # 检查组件是否已创建 if hasattr(self, "profile_details_label") and self.profile_details_label: self.profile_details_label.configure(text=details_text) else: logger.warning("配置详情标签未创建") except Exception as e: logger.error(f"选择配置文件时出错: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"选择配置文件时出错: {str(e)}") def refresh_profile_list(self): """刷新配置文件列表""" try: # 使用配置管理器刷新配置 if hasattr(self, "profile_manager") and self.profile_manager: self.profile_manager.refresh_profiles() self.update_log("配置文件列表已刷新") return True return False except Exception as e: logger.error(f"刷新配置文件列表失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"刷新配置文件列表失败: {str(e)}") return False def on_profile_change(self): """ 当浏览器配置文件发生变化时调用 可以在这里更新UI或执行其他操作 """ try: if hasattr(self, "refresh_profile_list"): # 使用after方法避免在对话框销毁期间刷新UI self.after(100, self.refresh_profile_list) logger.info("配置文件已更改,列表已刷新") except Exception as e: logger.error(f"响应配置文件变化失败: {str(e)}") logger.error(traceback.format_exc()) self.update_log(f"更新配置列表失败: {str(e)}")