feat: 实现CutThenThink P0阶段核心功能
项目初始化 - 创建完整项目结构(src/, data/, docs/, examples/, tests/) - 配置requirements.txt依赖 - 创建.gitignore P0基础框架 - 数据库模型:Record模型,6种分类类型 - 配置管理:YAML配置,支持AI/OCR/云存储/UI配置 - OCR模块:PaddleOCR本地识别,支持云端扩展 - AI模块:支持OpenAI/Claude/通义/Ollama,6种分类 - 存储模块:完整CRUD,搜索,统计,导入导出 - 主窗口框架:侧边栏导航,米白配色方案 - 图片处理:截图/剪贴板/文件选择/图片预览 - 处理流程整合:OCR→AI→存储串联,Markdown展示,剪贴板复制 - 分类浏览:卡片网格展示,分类筛选,搜索,详情查看 技术栈 - PyQt6 + SQLAlchemy + PaddleOCR + OpenAI/Claude SDK - 共47个Python文件,4000+行代码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
267
examples/gui_integration_example.py
Normal file
267
examples/gui_integration_example.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user