#!/usr/bin/env python3 """ GUI 集成示例 演示如何在 GUI 中集成处理流程和结果展示 """ import sys import tkinter as tk from tkinter import ttk, filedialog from pathlib import Path from typing import Optional # 添加项目根目录到路径 project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from src.core.processor import ImageProcessor, ProcessCallback, ProcessResult, create_markdown_result from src.config.settings import get_settings from src.utils.logger import init_logger, get_logger from src.gui.widgets import ResultWidget, MessageHandler, ProgressDialog # 初始化日志 log_dir = project_root / "logs" init_logger(log_dir=log_dir, level="INFO", colored_console=True) logger = get_logger(__name__) class GUIProcessCallback(ProcessCallback): """ GUI 回调类 """ def __init__(self, progress_dialog: ProgressDialog, status_label: ttk.Label): super().__init__() self.progress_dialog = progress_dialog self.status_label = status_label def on_start(self, message: str = "开始处理"): self.status_label.config(text=message) def on_ocr_start(self, message: str = "开始 OCR 识别"): self.status_label.config(text=message) if self.progress_dialog and not self.progress_dialog.is_cancelled(): self.progress_dialog.set_message(message) def on_ocr_complete(self, result): self.status_label.config(text=f"OCR 完成: {len(result.results)} 行") def on_ai_start(self, message: str = "开始 AI 分类"): self.status_label.config(text=message) if self.progress_dialog and not self.progress_dialog.is_cancelled(): self.progress_dialog.set_message(message) def on_ai_complete(self, result): self.status_label.config(text=f"AI 完成: {result.category.value}") def on_save_start(self, message: str = "开始保存"): self.status_label.config(text=message) def on_save_complete(self, record_id: int): self.status_label.config(text=f"保存成功: ID={record_id}") def on_error(self, message: str, exception=None): self.status_label.config(text=f"错误: {message}") def on_complete(self, result: ProcessResult): if result.success: self.status_label.config(text=f"完成! 耗时 {result.process_time:.2f}s") else: self.status_label.config(text="处理失败") class ProcessorDemoGUI: """ 处理器演示 GUI """ def __init__(self): self.root = tk.Tk() self.root.title("CutThenThink - 处理流程整合演示") self.root.geometry("900x700") # 加载配置 self.settings = get_settings() # 消息处理器 self.message_handler = MessageHandler(self.root) # 处理器 self.processor: Optional[ImageProcessor] = None # 创建 UI self._create_ui() def _create_ui(self): """创建 UI""" # 顶部工具栏 toolbar = ttk.Frame(self.root, padding=10) toolbar.pack(side=tk.TOP, fill=tk.X) # 选择图片按钮 self.select_button = ttk.Button( toolbar, text="📁 选择图片", command=self._on_select_image ) self.select_button.pack(side=tk.LEFT, padx=5) # 处理按钮 self.process_button = ttk.Button( toolbar, text="🚀 开始处理", command=self._on_process, state=tk.DISABLED ) self.process_button.pack(side=tk.LEFT, padx=5) # 分隔符 ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=10) # 当前图片标签 ttk.Label(toolbar, text="当前图片:").pack(side=tk.LEFT, padx=5) self.image_label = ttk.Label(toolbar, text="未选择", foreground="gray") self.image_label.pack(side=tk.LEFT, padx=5) # 状态栏 self.status_label = ttk.Label( self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W, padding=(5, 2) ) self.status_label.pack(side=tk.BOTTOM, fill=tk.X) # 主内容区域(使用 PanedWindow 分割) paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL) paned.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10) # 左侧:结果展示 left_frame = ttk.LabelFrame(paned, text="处理结果", padding=5) paned.add(left_frame, weight=2) self.result_widget = ResultWidget(left_frame) self.result_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True) # 右侧:日志 right_frame = ttk.LabelFrame(paned, text="处理日志", padding=5) paned.add(right_frame, weight=1) self.log_text = tk.Text( right_frame, wrap=tk.WORD, font=("Consolas", 9), state=tk.DISABLED ) self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 配置日志标签 self.log_text.tag_config("INFO", foreground="#3498db") self.log_text.tag_config("WARNING", foreground="#f39c12") self.log_text.tag_config("ERROR", foreground="#e74c3c") self.log_text.tag_config("SUCCESS", foreground="#27ae60") def _log(self, level: str, message: str): """添加日志""" self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, f"{message}\n", level) self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) self.root.update() def _on_select_image(self): """选择图片""" filename = filedialog.askopenfilename( title="选择图片", filetypes=[ ("图片文件", "*.png *.jpg *.jpeg *.bmp *.gif"), ("所有文件", "*.*") ] ) if filename: self.current_image_path = filename self.image_label.config(text=Path(filename).name) self.process_button.config(state=tk.NORMAL) self._log("INFO", f"已选择图片: {filename}") def _on_process(self): """处理图片""" if not hasattr(self, 'current_image_path'): return # 创建进度对话框 progress_dialog = ProgressDialog( self.root, title="处理中", message="正在处理图片...", cancelable=True ) # 创建回调 callback = GUIProcessCallback(progress_dialog, self.status_label) # 创建处理器 self.processor = ImageProcessor( ocr_config={ 'mode': self.settings.ocr.mode.value, 'lang': self.settings.ocr.lang, 'use_gpu': self.settings.ocr.use_gpu }, ai_config=self.settings.ai, db_path=str(project_root / "data" / "cutnthink.db"), callback=callback ) # 处理图片(在后台执行) self.root.after(100, lambda: self._do_process(progress_dialog)) def _do_process(self, progress_dialog: ProgressDialog): """执行处理""" try: self._log("INFO", "开始处理...") # 处理 result = self.processor.process_image(self.current_image_path) # 关闭进度对话框 if not progress_dialog.is_cancelled(): progress_dialog.close() # 显示结果 if result.success: self.result_widget.set_result(result) self._log("SUCCESS", f"处理成功! 耗时 {result.process_time:.2f}s") self.message_handler.show_info( "处理完成", f"处理成功!\n耗时: {result.process_time:.2f}秒\n记录ID: {result.record_id}" ) else: self._log("ERROR", f"处理失败: {result.error_message}") self.message_handler.show_error( "处理失败", result.error_message or "未知错误" ) except Exception as e: progress_dialog.close() logger.error(f"处理失败: {e}", exc_info=True) self._log("ERROR", f"处理失败: {e}") self.message_handler.show_error("处理失败", str(e), exception=e) def run(self): """运行 GUI""" self.root.mainloop() def main(): """主函数""" app = ProcessorDemoGUI() app.run() if __name__ == "__main__": main()