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:
congsh
2026-02-11 18:21:31 +08:00
commit c4a77f8aa4
79 changed files with 19412 additions and 0 deletions

View 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()