改进: 托盘图标状态管理和分类配置持久化- 添加活动状态图标(icon_active.png),提供更明显的视觉反馈- 优化图标切换逻辑,确保状态变化时图标正确更新- 增强图标刷新机制,解决某些环境下图标不更新的问题- 添加更全面的错误处理,提高系统托盘功能稳定性- 完善分类配置的持久化和管理机制- 更新README.md,添加v0.1.7版本更新内容
This commit is contained in:
parent
9539cb6cb8
commit
9cf3cde62f
97
project/llmclipboard/CHANGELOG-v0.1.7.md
Normal file
97
project/llmclipboard/CHANGELOG-v0.1.7.md
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# LLMClipboard 托盘图标状态管理改进总结
|
||||||
|
|
||||||
|
## 1. 问题背景
|
||||||
|
|
||||||
|
在之前的版本中,LLMClipboard 应用程序的系统托盘图标在服务状态变化(启动/停止监听)时没有提供足够明显的视觉反馈。具体问题包括:
|
||||||
|
|
||||||
|
- 启动监听后托盘图标不变化,用户无法直观判断应用程序状态
|
||||||
|
- 缺少活动状态的专用图标文件
|
||||||
|
- 图标切换逻辑不完善,在某些环境下不能正确更新
|
||||||
|
|
||||||
|
## 2. 改进内容
|
||||||
|
|
||||||
|
### 2.1 活动状态图标创建
|
||||||
|
|
||||||
|
创建了专用的活动状态图标 `icon_active.png`,通过以下特点与非活动状态图标区分:
|
||||||
|
- 增加亮度 (30%)
|
||||||
|
- 增加对比度 (20%)
|
||||||
|
- 增加颜色饱和度 (50%)
|
||||||
|
|
||||||
|
这使得活动状态下的图标更加明亮、鲜艳,提供更明显的视觉反馈。
|
||||||
|
|
||||||
|
### 2.2 图标切换逻辑优化
|
||||||
|
|
||||||
|
优化了 `toggle_monitoring` 方法中的图标切换逻辑:
|
||||||
|
- 在启动监听时切换到活动状态图标
|
||||||
|
- 在停止监听时切换到非活动状态图标
|
||||||
|
- 添加了图标可见性检查,确保托盘图标始终可见
|
||||||
|
- 更新了图标提示文本,提供状态信息
|
||||||
|
|
||||||
|
### 2.3 图标刷新机制增强
|
||||||
|
|
||||||
|
添加了强制刷新图标的机制,解决某些环境下图标不更新的问题:
|
||||||
|
- 使用 `hide()` 和 `show()` 方法强制刷新图标
|
||||||
|
- 调用 `QApplication.processEvents()` 确保 UI 更新
|
||||||
|
- 在启动监听、停止监听和异常处理时都添加了图标刷新逻辑
|
||||||
|
|
||||||
|
### 2.4 错误处理增强
|
||||||
|
|
||||||
|
完善了图标操作的错误处理:
|
||||||
|
- 添加了 try-except 块捕获可能的异常
|
||||||
|
- 添加了详细的错误日志记录
|
||||||
|
- 实现了错误恢复机制,确保即使出现异常也不会影响应用程序整体功能
|
||||||
|
|
||||||
|
## 3. 技术实现
|
||||||
|
|
||||||
|
### 3.1 图标创建工具
|
||||||
|
|
||||||
|
创建了 `create_active_icon.py` 脚本,用于生成活动状态图标:
|
||||||
|
- 使用 PIL 库处理图像
|
||||||
|
- 通过 ImageEnhance 模块调整亮度、对比度和饱和度
|
||||||
|
- 自动检测资源目录和原始图标位置
|
||||||
|
|
||||||
|
### 3.2 GUI 类修改
|
||||||
|
|
||||||
|
修改了 `gui.py` 中的相关方法:
|
||||||
|
- `toggle_monitoring`: 添加了图标切换和刷新逻辑
|
||||||
|
- 异常处理部分: 添加了图标恢复和刷新逻辑
|
||||||
|
|
||||||
|
### 3.3 测试验证
|
||||||
|
|
||||||
|
通过以下场景验证了改进效果:
|
||||||
|
- 启动监听时图标切换到活动状态
|
||||||
|
- 停止监听时图标切换到非活动状态
|
||||||
|
- 异常情况下图标恢复到非活动状态
|
||||||
|
- 多次切换状态时图标正确更新
|
||||||
|
|
||||||
|
## 4. 效果对比
|
||||||
|
|
||||||
|
| 功能 | 改进前 | 改进后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 视觉反馈 | 无明显区分 | 活动状态图标更明亮、鲜艳 |
|
||||||
|
| 状态指示 | 仅通过菜单文本 | 图标颜色和提示文本 |
|
||||||
|
| 更新可靠性 | 部分环境下不更新 | 强制刷新确保更新 |
|
||||||
|
| 错误处理 | 基础处理 | 全面的异常捕获和恢复 |
|
||||||
|
|
||||||
|
## 5. 后续优化方向
|
||||||
|
|
||||||
|
- 考虑添加动态图标,如活动状态下的动画效果
|
||||||
|
- 优化图标资源加载机制,支持高DPI显示
|
||||||
|
- 添加更多状态指示(如错误状态、处理中状态等)
|
||||||
|
- 实现自定义图标主题支持
|
||||||
|
|
||||||
|
## 6. Commit 消息
|
||||||
|
|
||||||
|
```
|
||||||
|
改进: 托盘图标状态管理和分类配置持久化
|
||||||
|
|
||||||
|
- 添加活动状态图标(icon_active.png),提供更明显的视觉反馈
|
||||||
|
- 优化图标切换逻辑,确保状态变化时图标正确更新
|
||||||
|
- 增强图标刷新机制,解决某些环境下图标不更新的问题
|
||||||
|
- 添加更全面的错误处理,提高系统托盘功能稳定性
|
||||||
|
- 完善分类配置的持久化和管理机制
|
||||||
|
- 更新README.md,添加v0.1.7版本更新内容
|
||||||
|
|
||||||
|
此次更新使系统托盘功能更加稳定可靠,用户可以通过图标颜色
|
||||||
|
直观判断应用程序状态,提升了整体用户体验。
|
||||||
|
```
|
||||||
@ -326,7 +326,18 @@ cd project\llmclipboard ; pyinstaller --clean llmclipboard.spec
|
|||||||
- 改进通知系统样式和错误处理
|
- 改进通知系统样式和错误处理
|
||||||
- 优化UI响应性能
|
- 优化UI响应性能
|
||||||
- 添加uv环境打包支持
|
- 添加uv环境打包支持
|
||||||
-
|
|
||||||
|
## v0.1.7 (2025-03-02)
|
||||||
|
- 改进托盘图标状态管理
|
||||||
|
- 添加活动状态图标,提供更明显的视觉反馈
|
||||||
|
- 优化图标切换逻辑,确保状态变化时图标正确更新
|
||||||
|
- 增强图标刷新机制,解决某些环境下图标不更新的问题
|
||||||
|
- 添加更全面的错误处理,提高系统托盘功能稳定性
|
||||||
|
- 完善分类配置的持久化和管理机制
|
||||||
|
- 支持开发和打包环境下的配置文件路径
|
||||||
|
- 确保配置文件正确保存和加载
|
||||||
|
- 添加更详细的日志记录
|
||||||
|
|
||||||
## 🤝 贡献指南
|
## 🤝 贡献指南
|
||||||
|
|
||||||
1. Fork 项目
|
1. Fork 项目
|
||||||
|
|||||||
@ -18,6 +18,18 @@ if exist dist\LLMClipboard.exe (
|
|||||||
if not exist dist\resources mkdir dist\resources
|
if not exist dist\resources mkdir dist\resources
|
||||||
xcopy /s /y resources dist\resources\
|
xcopy /s /y resources dist\resources\
|
||||||
|
|
||||||
|
:: Create a default config.ini in the dist folder
|
||||||
|
echo Creating default config.ini in dist folder...
|
||||||
|
echo [Settings] > dist\config.ini
|
||||||
|
echo double_click_threshold = 0.3 >> dist\config.ini
|
||||||
|
echo save_location = C:\Users\Documents >> dist\config.ini
|
||||||
|
echo. >> dist\config.ini
|
||||||
|
echo [AI] >> dist\config.ini
|
||||||
|
echo model_type = none >> dist\config.ini
|
||||||
|
echo api_key = >> dist\config.ini
|
||||||
|
echo base_url = https://api.openai.com/v1 >> dist\config.ini
|
||||||
|
echo model = gpt-3.5-turbo >> dist\config.ini
|
||||||
|
|
||||||
:: Create a README.txt in the dist folder
|
:: Create a README.txt in the dist folder
|
||||||
echo Creating README.txt in dist folder...
|
echo Creating README.txt in dist folder...
|
||||||
echo LLMClipboard v0.1.6 > dist\README.txt
|
echo LLMClipboard v0.1.6 > dist\README.txt
|
||||||
@ -29,7 +41,9 @@ if exist dist\LLMClipboard.exe (
|
|||||||
echo 3. Double-click the system tray icon to open the main window >> dist\README.txt
|
echo 3. Double-click the system tray icon to open the main window >> dist\README.txt
|
||||||
echo 4. Right-click the system tray icon to access the menu >> dist\README.txt
|
echo 4. Right-click the system tray icon to access the menu >> dist\README.txt
|
||||||
echo. >> dist\README.txt
|
echo. >> dist\README.txt
|
||||||
echo If you encounter any issues, please check error_log.txt and config_error.txt files >> dist\README.txt
|
echo Configuration: >> dist\README.txt
|
||||||
|
echo - Settings are stored in config.ini in the same folder as the executable >> dist\README.txt
|
||||||
|
echo - If you encounter any issues, please check error_log.txt and config_error.txt files >> dist\README.txt
|
||||||
) else (
|
) else (
|
||||||
echo Build failed, please check error messages.
|
echo Build failed, please check error messages.
|
||||||
)
|
)
|
||||||
|
|||||||
30
project/llmclipboard/categories.json
Normal file
30
project/llmclipboard/categories.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"测试": [
|
||||||
|
"测试1",
|
||||||
|
"测试2",
|
||||||
|
"测试3"
|
||||||
|
],
|
||||||
|
"工作": [
|
||||||
|
"会议",
|
||||||
|
"项目",
|
||||||
|
"计划",
|
||||||
|
"报告",
|
||||||
|
"任务",
|
||||||
|
"进度"
|
||||||
|
],
|
||||||
|
"学习": [
|
||||||
|
"教程",
|
||||||
|
"课程",
|
||||||
|
"学习",
|
||||||
|
"笔记",
|
||||||
|
"知识",
|
||||||
|
"总结"
|
||||||
|
],
|
||||||
|
"生活": [
|
||||||
|
"购物",
|
||||||
|
"健康",
|
||||||
|
"旅行",
|
||||||
|
"美食",
|
||||||
|
"娱乐"
|
||||||
|
]
|
||||||
|
}
|
||||||
58
project/llmclipboard/create_active_icon.py
Normal file
58
project/llmclipboard/create_active_icon.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
创建活动状态图标
|
||||||
|
"""
|
||||||
|
from PIL import Image, ImageEnhance
|
||||||
|
import os
|
||||||
|
|
||||||
|
def create_active_icon(input_path, output_path):
|
||||||
|
"""
|
||||||
|
基于现有图标创建一个活动状态的图标
|
||||||
|
活动状态图标将更亮、更鲜艳
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 打开原始图标
|
||||||
|
img = Image.open(input_path)
|
||||||
|
|
||||||
|
# 增加亮度
|
||||||
|
enhancer = ImageEnhance.Brightness(img)
|
||||||
|
brightened_img = enhancer.enhance(1.3) # 增加30%亮度
|
||||||
|
|
||||||
|
# 增加对比度
|
||||||
|
enhancer = ImageEnhance.Contrast(brightened_img)
|
||||||
|
contrasted_img = enhancer.enhance(1.2) # 增加20%对比度
|
||||||
|
|
||||||
|
# 增加颜色饱和度
|
||||||
|
enhancer = ImageEnhance.Color(contrasted_img)
|
||||||
|
colored_img = enhancer.enhance(1.5) # 增加50%饱和度
|
||||||
|
|
||||||
|
# 保存活动状态图标
|
||||||
|
colored_img.save(output_path)
|
||||||
|
print(f"活动状态图标已创建: {output_path}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建活动状态图标失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 确定图标路径
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
resources_dir = os.path.join(script_dir, "resources")
|
||||||
|
|
||||||
|
# 确保资源目录存在
|
||||||
|
if not os.path.exists(resources_dir):
|
||||||
|
os.makedirs(resources_dir)
|
||||||
|
print(f"创建资源目录: {resources_dir}")
|
||||||
|
|
||||||
|
# 图标路径
|
||||||
|
icon_path = os.path.join(resources_dir, "icon.png")
|
||||||
|
active_icon_path = os.path.join(resources_dir, "icon_active.png")
|
||||||
|
|
||||||
|
# 检查原始图标是否存在
|
||||||
|
if not os.path.exists(icon_path):
|
||||||
|
print(f"错误: 原始图标不存在: {icon_path}")
|
||||||
|
else:
|
||||||
|
# 创建活动状态图标
|
||||||
|
if create_active_icon(icon_path, active_icon_path):
|
||||||
|
print("成功创建活动状态图标")
|
||||||
|
else:
|
||||||
|
print("创建活动状态图标失败")
|
||||||
@ -35,39 +35,185 @@ class StreamToLogger:
|
|||||||
|
|
||||||
class TextCaptureService:
|
class TextCaptureService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# 设置日志记录
|
||||||
|
self.setup_logging()
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
self.load_config()
|
self.load_config()
|
||||||
self.running = True
|
self.running = True
|
||||||
self.last_right_click_time = 0
|
self.last_right_click_time = 0
|
||||||
self.setup_logging()
|
|
||||||
self._mouse_listener = None
|
self._mouse_listener = None
|
||||||
self._keyboard_listener = None
|
self._keyboard_listener = None
|
||||||
|
|
||||||
# 加载AI配置
|
# 加载AI配置
|
||||||
self.ai_config = self.load_ai_config()
|
self.ai_config = self.load_ai_config()
|
||||||
self.doc_processor = DocumentProcessor(ai_config=self.ai_config)
|
|
||||||
|
# 确定配置文件路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# 初始化文档处理器,传入配置路径
|
||||||
|
self.doc_processor = DocumentProcessor(
|
||||||
|
ai_config=self.ai_config,
|
||||||
|
config_path=os.path.join(config_dir, 'categories.json')
|
||||||
|
)
|
||||||
|
self.logger.info(f"文档处理器已初始化,分类配置路径: {os.path.join(config_dir, 'categories.json')}")
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
self.config = configparser.ConfigParser()
|
try:
|
||||||
config_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.ini')
|
# 使用 ConfigParser 的 BasicInterpolation 而不是 ExtendedInterpolation
|
||||||
if os.path.exists(config_file):
|
# 这样可以避免 % 符号的问题
|
||||||
self.config.read(config_file)
|
self.config = configparser.ConfigParser(interpolation=configparser.BasicInterpolation())
|
||||||
self.double_click_threshold = self.config.getfloat('Settings', 'double_click_threshold', fallback=0.3)
|
|
||||||
self.save_location = self.config.get('Settings', 'save_location',
|
# 确定配置文件路径
|
||||||
fallback=os.path.expanduser('~/Documents'))
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
self.config_file = os.path.join(config_dir, 'config.ini')
|
||||||
|
self.logger.info(f"配置文件路径: {self.config_file}")
|
||||||
|
|
||||||
|
if os.path.exists(self.config_file):
|
||||||
|
self.config.read(self.config_file, encoding='utf-8')
|
||||||
|
self.logger.info(f"成功读取配置文件")
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"配置文件不存在,将使用默认配置")
|
||||||
|
# 使用默认配置
|
||||||
|
if not self.config.has_section('Settings'):
|
||||||
|
self.config.add_section('Settings')
|
||||||
|
self.config.set('Settings', 'double_click_threshold', '0.3')
|
||||||
|
self.config.set('Settings', 'save_location', os.path.expanduser('~/Documents'))
|
||||||
|
|
||||||
|
# 保存默认配置
|
||||||
|
try:
|
||||||
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
|
self.config.write(f)
|
||||||
|
self.logger.info(f"已创建默认配置文件: {self.config_file}")
|
||||||
|
except Exception as save_error:
|
||||||
|
self.logger.error(f"创建默认配置文件失败: {save_error}")
|
||||||
|
|
||||||
|
# 获取设置值,处理可能的插值语法错误
|
||||||
|
try:
|
||||||
|
self.double_click_threshold = self.config.getfloat('Settings', 'double_click_threshold', fallback=0.3)
|
||||||
|
except (configparser.InterpolationError, ValueError) as e:
|
||||||
|
self.logger.error(f"读取double_click_threshold设置时出错: {e}")
|
||||||
|
self.double_click_threshold = 0.3
|
||||||
|
|
||||||
|
try:
|
||||||
|
save_location = self.config.get('Settings', 'save_location', fallback=os.path.expanduser('~/Documents'))
|
||||||
|
# 检查路径是否存在,如果不存在则使用默认路径
|
||||||
|
if not os.path.exists(save_location):
|
||||||
|
self.logger.warning(f"保存路径不存在: {save_location},将使用默认路径")
|
||||||
|
save_location = os.path.expanduser('~/Documents')
|
||||||
|
self.save_location = save_location
|
||||||
|
except (configparser.InterpolationError, ValueError) as e:
|
||||||
|
self.logger.error(f"读取save_location设置时出错: {e}")
|
||||||
|
self.save_location = os.path.expanduser('~/Documents')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"加载配置时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
# 使用默认值
|
||||||
|
self.double_click_threshold = 0.3
|
||||||
|
self.save_location = os.path.expanduser('~/Documents')
|
||||||
|
|
||||||
def load_ai_config(self):
|
def load_ai_config(self):
|
||||||
"""加载AI配置"""
|
try:
|
||||||
ai_config = {}
|
if not self.config.has_section('AI'):
|
||||||
if self.config.has_section('AI'):
|
self.config.add_section('AI')
|
||||||
for key, value in self.config.items('AI'):
|
self.config.set('AI', 'model_type', 'none')
|
||||||
ai_config[key] = value
|
self.config.set('AI', 'api_key', '')
|
||||||
|
self.config.set('AI', 'base_url', 'https://api.openai.com/v1')
|
||||||
|
self.config.set('AI', 'model', 'gpt-3.5-turbo')
|
||||||
|
|
||||||
|
# 保存默认AI配置
|
||||||
|
try:
|
||||||
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
|
self.config.write(f)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"保存默认AI配置失败: {e}")
|
||||||
|
|
||||||
|
# 读取AI配置,处理可能的插值语法错误
|
||||||
|
try:
|
||||||
|
self.ai_model_type = self.config.get('AI', 'model_type', fallback='none')
|
||||||
|
except configparser.InterpolationError as e:
|
||||||
|
self.logger.error(f"读取model_type设置时出错: {e}")
|
||||||
|
self.ai_model_type = 'none'
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.ai_api_key = self.config.get('AI', 'api_key', fallback='')
|
||||||
|
except configparser.InterpolationError as e:
|
||||||
|
self.logger.error(f"读取api_key设置时出错: {e}")
|
||||||
|
self.ai_api_key = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.ai_base_url = self.config.get('AI', 'base_url', fallback='https://api.openai.com/v1')
|
||||||
|
except configparser.InterpolationError as e:
|
||||||
|
self.logger.error(f"读取base_url设置时出错: {e}")
|
||||||
|
self.ai_base_url = 'https://api.openai.com/v1'
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.ai_model = self.config.get('AI', 'model', fallback='gpt-3.5-turbo')
|
||||||
|
except configparser.InterpolationError as e:
|
||||||
|
self.logger.error(f"读取model设置时出错: {e}")
|
||||||
|
self.ai_model = 'gpt-3.5-turbo'
|
||||||
|
|
||||||
|
self.logger.info(f"AI配置已加载: 模型类型={self.ai_model_type}, 模型={self.ai_model}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"加载AI配置时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
# 使用默认值
|
||||||
|
self.ai_model_type = 'none'
|
||||||
|
self.ai_api_key = ''
|
||||||
|
self.ai_base_url = 'https://api.openai.com/v1'
|
||||||
|
self.ai_model = 'gpt-3.5-turbo'
|
||||||
|
|
||||||
|
ai_config = {
|
||||||
|
'model_type': self.ai_model_type,
|
||||||
|
'api_key': self.ai_api_key,
|
||||||
|
'base_url': self.ai_base_url,
|
||||||
|
'model': self.ai_model
|
||||||
|
}
|
||||||
return ai_config
|
return ai_config
|
||||||
|
|
||||||
def update_ai_config(self, new_config):
|
def update_ai_config(self, new_config):
|
||||||
"""更新AI配置"""
|
"""更新AI配置"""
|
||||||
self.ai_config = new_config
|
try:
|
||||||
if hasattr(self, 'doc_processor'):
|
if not self.config.has_section('AI'):
|
||||||
self.doc_processor.update_ai_config(new_config)
|
self.config.add_section('AI')
|
||||||
|
|
||||||
|
# 更新配置
|
||||||
|
for key, value in new_config.items():
|
||||||
|
self.config.set('AI', key, str(value))
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
|
self.config.write(f)
|
||||||
|
|
||||||
|
# 更新实例变量
|
||||||
|
self.ai_model_type = new_config.get('model_type', 'none')
|
||||||
|
self.ai_api_key = new_config.get('api_key', '')
|
||||||
|
self.ai_base_url = new_config.get('base_url', 'https://api.openai.com/v1')
|
||||||
|
self.ai_model = new_config.get('model', 'gpt-3.5-turbo')
|
||||||
|
|
||||||
|
self.logger.info(f"AI配置已更新: 模型类型={self.ai_model_type}, 模型={self.ai_model}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"更新AI配置时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
def setup_logging(self):
|
def setup_logging(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -33,7 +33,20 @@ logger.addHandler(console_handler)
|
|||||||
|
|
||||||
class DocumentProcessor:
|
class DocumentProcessor:
|
||||||
def __init__(self, config_path=None, ai_config=None):
|
def __init__(self, config_path=None, ai_config=None):
|
||||||
self.config_path = config_path or os.path.join(os.path.dirname(__file__), 'categories.json')
|
# 确定配置文件路径
|
||||||
|
if config_path:
|
||||||
|
self.config_path = config_path
|
||||||
|
else:
|
||||||
|
# 如果没有提供配置路径,使用默认路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
self.config_path = os.path.join(config_dir, 'categories.json')
|
||||||
|
|
||||||
|
logger.info(f"使用分类配置文件路径: {self.config_path}")
|
||||||
self.load_categories()
|
self.load_categories()
|
||||||
|
|
||||||
# 初始化AI处理器
|
# 初始化AI处理器
|
||||||
@ -45,10 +58,29 @@ class DocumentProcessor:
|
|||||||
|
|
||||||
def load_categories(self):
|
def load_categories(self):
|
||||||
"""加载预定义的分类规则"""
|
"""加载预定义的分类规则"""
|
||||||
if os.path.exists(self.config_path):
|
try:
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
logger.info(f"尝试从 {self.config_path} 加载分类规则")
|
||||||
self.categories = json.load(f)
|
if os.path.exists(self.config_path):
|
||||||
else:
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.categories = json.load(f)
|
||||||
|
logger.info(f"成功加载分类规则,包含 {len(self.categories)} 个分类")
|
||||||
|
for category, keywords in self.categories.items():
|
||||||
|
logger.debug(f"分类 '{category}' 包含 {len(keywords)} 个关键词")
|
||||||
|
else:
|
||||||
|
logger.warning(f"分类规则文件 {self.config_path} 不存在,使用默认分类")
|
||||||
|
self.categories = {
|
||||||
|
"技术": ["编程", "开发", "代码", "框架", "算法", "数据库", "API"],
|
||||||
|
"学习": ["教程", "课程", "学习", "笔记", "知识", "总结"],
|
||||||
|
"工作": ["会议", "项目", "计划", "报告", "任务", "进度"],
|
||||||
|
"想法": ["想法", "创意", "思考", "灵感", "观点", "建议"],
|
||||||
|
"资源": ["工具", "资源", "链接", "参考", "文档", "书籍"]
|
||||||
|
}
|
||||||
|
self._save_categories()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载分类规则时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
# 使用默认分类
|
||||||
self.categories = {
|
self.categories = {
|
||||||
"技术": ["编程", "开发", "代码", "框架", "算法", "数据库", "API"],
|
"技术": ["编程", "开发", "代码", "框架", "算法", "数据库", "API"],
|
||||||
"学习": ["教程", "课程", "学习", "笔记", "知识", "总结"],
|
"学习": ["教程", "课程", "学习", "笔记", "知识", "总结"],
|
||||||
@ -56,50 +88,31 @@ class DocumentProcessor:
|
|||||||
"想法": ["想法", "创意", "思考", "灵感", "观点", "建议"],
|
"想法": ["想法", "创意", "思考", "灵感", "观点", "建议"],
|
||||||
"资源": ["工具", "资源", "链接", "参考", "文档", "书籍"]
|
"资源": ["工具", "资源", "链接", "参考", "文档", "书籍"]
|
||||||
}
|
}
|
||||||
self._save_categories()
|
|
||||||
|
|
||||||
def _save_categories(self):
|
def _save_categories(self):
|
||||||
"""保存分类规则"""
|
"""保存分类规则"""
|
||||||
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
|
try:
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
logger.info(f"保存分类规则到 {self.config_path}")
|
||||||
json.dump(self.categories, f, ensure_ascii=False, indent=2)
|
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
|
||||||
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.categories, f, ensure_ascii=False, indent=2)
|
||||||
|
logger.info(f"分类规则已保存,包含 {len(self.categories)} 个分类")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存分类规则时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def update_categories(self, new_categories):
|
def update_categories(self, new_categories):
|
||||||
"""更新分类规则"""
|
"""更新分类规则"""
|
||||||
self.categories = new_categories
|
try:
|
||||||
self._save_categories()
|
logger.info(f"更新分类规则,新分类包含 {len(new_categories)} 个分类")
|
||||||
logger.info("分类规则已更新")
|
self.categories = new_categories
|
||||||
|
self._save_categories()
|
||||||
def clean_content(self, content):
|
logger.info("分类规则已更新")
|
||||||
"""清理内容,移除多余的换行符和空白"""
|
except Exception as e:
|
||||||
if not content or content.isspace():
|
logger.error(f"更新分类规则时出错: {e}")
|
||||||
logger.debug("收到空内容或纯空白内容")
|
import traceback
|
||||||
return ""
|
traceback.print_exc()
|
||||||
|
|
||||||
logger.debug("原始内容长度: %d", len(content))
|
|
||||||
logger.debug("原始内容前100个字符: %s", content[:100])
|
|
||||||
logger.debug("原始内容包含的换行符数量: %d", content.count('\n'))
|
|
||||||
|
|
||||||
# 移除文件名
|
|
||||||
content = re.sub(r'captured_text_\d{8}_\d{6}', '', content)
|
|
||||||
logger.debug("已移除文件名")
|
|
||||||
logger.debug("移除文件名后的内容长度: %d", len(content))
|
|
||||||
|
|
||||||
# 清理内容,保持原有的换行格式
|
|
||||||
lines = content.splitlines()
|
|
||||||
cleaned_lines = []
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if line or cleaned_lines: # 如果当前行非空或已经有内容,保留这一行
|
|
||||||
cleaned_lines.append(line)
|
|
||||||
|
|
||||||
# 使用原始换行符重新组合内容
|
|
||||||
content = '\n'.join(cleaned_lines)
|
|
||||||
|
|
||||||
logger.debug("清理后内容长度: %d", len(content))
|
|
||||||
logger.debug("清理后内容包含的换行符数量: %d", content.count('\n'))
|
|
||||||
logger.debug("清理后内容的前100个字符: %s", content[:100])
|
|
||||||
return content
|
|
||||||
|
|
||||||
def update_ai_config(self, ai_config):
|
def update_ai_config(self, ai_config):
|
||||||
"""更新AI配置"""
|
"""更新AI配置"""
|
||||||
@ -301,3 +314,34 @@ category: {category}
|
|||||||
'tags': tags,
|
'tags': tags,
|
||||||
'category': category
|
'category': category
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean_content(self, content):
|
||||||
|
"""清理内容,移除多余的换行符和空白"""
|
||||||
|
if not content or content.isspace():
|
||||||
|
logger.debug("收到空内容或纯空白内容")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
logger.debug("原始内容长度: %d", len(content))
|
||||||
|
logger.debug("原始内容前100个字符: %s", content[:100])
|
||||||
|
logger.debug("原始内容包含的换行符数量: %d", content.count('\n'))
|
||||||
|
|
||||||
|
# 移除文件名
|
||||||
|
content = re.sub(r'captured_text_\d{8}_\d{6}', '', content)
|
||||||
|
logger.debug("已移除文件名")
|
||||||
|
logger.debug("移除文件名后的内容长度: %d", len(content))
|
||||||
|
|
||||||
|
# 清理内容,保持原有的换行格式
|
||||||
|
lines = content.splitlines()
|
||||||
|
cleaned_lines = []
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line or cleaned_lines: # 如果当前行非空或已经有内容,保留这一行
|
||||||
|
cleaned_lines.append(line)
|
||||||
|
|
||||||
|
# 使用原始换行符重新组合内容
|
||||||
|
content = '\n'.join(cleaned_lines)
|
||||||
|
|
||||||
|
logger.debug("清理后内容长度: %d", len(content))
|
||||||
|
logger.debug("清理后内容包含的换行符数量: %d", content.count('\n'))
|
||||||
|
logger.debug("清理后内容的前100个字符: %s", content[:100])
|
||||||
|
return content
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser, BasicInterpolation
|
||||||
import darkdetect
|
import darkdetect
|
||||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||||
QHBoxLayout, QPushButton, QLabel, QFileDialog,
|
QHBoxLayout, QPushButton, QLabel, QFileDialog,
|
||||||
QSystemTrayIcon, QMenu, QSpinBox, QStyle, QLineEdit,
|
QListWidget, QLineEdit, QSpinBox, QGroupBox,
|
||||||
QDialog, QTabWidget, QGroupBox, QComboBox, QCheckBox,
|
QSystemTrayIcon, QMenu, QStyle, QMessageBox, QDialog, QTabWidget, QComboBox, QCheckBox,
|
||||||
QDialogButtonBox, QMessageBox, QListWidget)
|
QDialogButtonBox)
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt6.QtGui import QIcon, QAction
|
from PyQt6.QtGui import QIcon, QAction
|
||||||
|
from llmclipboard.category_manager import CategoryManagerDialog
|
||||||
|
|
||||||
class NotificationDialog(QDialog):
|
class NotificationDialog(QDialog):
|
||||||
"""自定义通知对话框类"""
|
"""自定义通知对话框类"""
|
||||||
@ -180,7 +181,13 @@ class AISettingsDialog(QDialog):
|
|||||||
|
|
||||||
# 按钮
|
# 按钮
|
||||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
|
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
|
||||||
button_box.accepted.connect(self.accept)
|
|
||||||
|
# 添加调试信息
|
||||||
|
def on_accepted():
|
||||||
|
print("AI设置对话框保存按钮被点击")
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
button_box.accepted.connect(on_accepted)
|
||||||
button_box.rejected.connect(self.reject)
|
button_box.rejected.connect(self.reject)
|
||||||
layout.addWidget(button_box)
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
@ -317,8 +324,19 @@ class MainWindow(QMainWindow):
|
|||||||
self.service = llmclipboard.app.TextCaptureService()
|
self.service = llmclipboard.app.TextCaptureService()
|
||||||
|
|
||||||
# 加载配置
|
# 加载配置
|
||||||
self.config = ConfigParser()
|
self.config = ConfigParser(interpolation=BasicInterpolation())
|
||||||
self.config_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.ini')
|
|
||||||
|
# 确定配置文件路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
self.config_file = os.path.join(config_dir, 'config.ini')
|
||||||
|
print(f"GUI配置文件路径: {self.config_file}")
|
||||||
|
|
||||||
self.load_config()
|
self.load_config()
|
||||||
|
|
||||||
# 创建主窗口部件
|
# 创建主窗口部件
|
||||||
@ -450,8 +468,10 @@ class MainWindow(QMainWindow):
|
|||||||
def load_config(self):
|
def load_config(self):
|
||||||
try:
|
try:
|
||||||
if os.path.exists(self.config_file):
|
if os.path.exists(self.config_file):
|
||||||
self.config.read(self.config_file)
|
self.config.read(self.config_file, encoding='utf-8')
|
||||||
|
print(f"成功读取GUI配置文件: {self.config_file}")
|
||||||
else:
|
else:
|
||||||
|
print(f"GUI配置文件不存在,将使用默认配置: {self.config_file}")
|
||||||
self.config['Settings'] = {
|
self.config['Settings'] = {
|
||||||
'save_location': os.path.expanduser('~/Documents'),
|
'save_location': os.path.expanduser('~/Documents'),
|
||||||
'double_click_threshold': '0.3'
|
'double_click_threshold': '0.3'
|
||||||
@ -470,11 +490,20 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 更新UI组件(如果存在)
|
# 更新UI组件(如果存在)
|
||||||
if hasattr(self, 'save_path_edit'):
|
if hasattr(self, 'save_path_edit'):
|
||||||
self.save_path_edit.setText(self.config.get('Settings', 'save_location', fallback=os.path.expanduser('~/Documents')))
|
try:
|
||||||
|
save_location = self.config.get('Settings', 'save_location', fallback=os.path.expanduser('~/Documents'))
|
||||||
|
self.save_path_edit.setText(save_location)
|
||||||
|
except (configparser.InterpolationError, ValueError) as e:
|
||||||
|
print(f"读取save_location设置时出错: {e}")
|
||||||
|
self.save_path_edit.setText(os.path.expanduser('~/Documents'))
|
||||||
|
|
||||||
if hasattr(self, 'threshold_spin'):
|
if hasattr(self, 'threshold_spin'):
|
||||||
threshold = int(float(self.config.get('Settings', 'double_click_threshold', fallback='0.3')) * 1000)
|
try:
|
||||||
self.threshold_spin.setValue(threshold)
|
threshold = int(float(self.config.get('Settings', 'double_click_threshold', fallback='0.3')) * 1000)
|
||||||
|
self.threshold_spin.setValue(threshold)
|
||||||
|
except (configparser.InterpolationError, ValueError) as e:
|
||||||
|
print(f"读取double_click_threshold设置时出错: {e}")
|
||||||
|
self.threshold_spin.setValue(300) # 默认值0.3秒,转换为毫秒
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
@ -489,31 +518,62 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def save_config(self):
|
def save_config(self):
|
||||||
try:
|
try:
|
||||||
# 检查属性是否存在
|
print("开始保存配置...")
|
||||||
if hasattr(self, 'save_path_edit') and hasattr(self, 'threshold_spin'):
|
# 确保配置目录存在
|
||||||
|
config_dir = os.path.dirname(self.config_file)
|
||||||
|
if not os.path.exists(config_dir):
|
||||||
|
os.makedirs(config_dir)
|
||||||
|
|
||||||
|
# 更新配置对象
|
||||||
|
if hasattr(self, 'save_path_edit'):
|
||||||
self.config['Settings']['save_location'] = self.save_path_edit.text()
|
self.config['Settings']['save_location'] = self.save_path_edit.text()
|
||||||
|
print(f"保存路径设置为: {self.save_path_edit.text()}")
|
||||||
|
|
||||||
|
if hasattr(self, 'threshold_spin'):
|
||||||
self.config['Settings']['double_click_threshold'] = str(self.threshold_spin.value() / 1000)
|
self.config['Settings']['double_click_threshold'] = str(self.threshold_spin.value() / 1000)
|
||||||
else:
|
print(f"双击阈值设置为: {self.threshold_spin.value() / 1000}")
|
||||||
# 如果属性不存在,使用默认值
|
|
||||||
if not self.config.has_section('Settings'):
|
# 写入配置文件
|
||||||
self.config['Settings'] = {}
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
self.config['Settings'].setdefault('save_location', os.path.expanduser('~/Documents'))
|
|
||||||
self.config['Settings'].setdefault('double_click_threshold', '0.3')
|
|
||||||
print("警告: GUI组件未初始化,使用默认配置值")
|
|
||||||
|
|
||||||
with open(self.config_file, 'w') as f:
|
|
||||||
self.config.write(f)
|
self.config.write(f)
|
||||||
|
|
||||||
# 重新加载服务配置
|
print(f"配置已保存到: {self.config_file}")
|
||||||
if hasattr(self.service, 'load_config'):
|
# 显示保存成功的提示
|
||||||
self.service.load_config()
|
print("准备显示保存成功消息...")
|
||||||
|
|
||||||
if hasattr(self, 'status_label'):
|
# 直接调用show_tray_message方法
|
||||||
self.show_message("设置已保存")
|
self.show_tray_message(
|
||||||
|
"LLMClipboard",
|
||||||
|
"配置已保存成功",
|
||||||
|
"info",
|
||||||
|
2000
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新状态栏
|
||||||
|
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
|
self.status_label.setText("配置已保存成功")
|
||||||
|
|
||||||
|
# 5秒后清除消息
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(5000, lambda: self.status_label.setText("就绪"))
|
||||||
|
|
||||||
|
print("已显示保存成功消息")
|
||||||
|
|
||||||
|
# 更新服务的配置
|
||||||
|
if hasattr(self, 'service'):
|
||||||
|
try:
|
||||||
|
print("准备更新服务配置...")
|
||||||
|
self.service.load_config()
|
||||||
|
print("服务配置已更新")
|
||||||
|
except Exception as service_error:
|
||||||
|
print(f"更新服务配置时出错: {service_error}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
print(f"保存配置时出错: {e}")
|
print(f"保存配置时出错: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
# 显示保存失败的提示
|
||||||
|
self.show_message(f"保存配置失败: {e}", error=True)
|
||||||
# 在打包环境中,确保错误信息被记录
|
# 在打包环境中,确保错误信息被记录
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
error_log_path = os.path.join(os.path.dirname(sys.executable), 'config_error.txt')
|
error_log_path = os.path.join(os.path.dirname(sys.executable), 'config_error.txt')
|
||||||
@ -528,39 +588,103 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def show_ai_settings(self):
|
def show_ai_settings(self):
|
||||||
"""显示AI设置对话框"""
|
"""显示AI设置对话框"""
|
||||||
dialog = AISettingsDialog(self)
|
try:
|
||||||
|
dialog = AISettingsDialog(self)
|
||||||
# 设置当前配置
|
|
||||||
ai_config = {}
|
|
||||||
if self.config.has_section('AI'):
|
|
||||||
for key, value in self.config.items('AI'):
|
|
||||||
ai_config[key] = value
|
|
||||||
|
|
||||||
dialog.set_config(ai_config)
|
|
||||||
|
|
||||||
if dialog.exec():
|
|
||||||
# 获取新配置
|
|
||||||
new_config = dialog.get_config()
|
|
||||||
|
|
||||||
# 保存到配置文件
|
# 设置当前配置
|
||||||
if not self.config.has_section('AI'):
|
ai_config = {}
|
||||||
self.config.add_section('AI')
|
if self.config.has_section('AI'):
|
||||||
|
try:
|
||||||
for key, value in new_config.items():
|
for key in ['model_type', 'api_key', 'base_url', 'model']:
|
||||||
self.config.set('AI', key, str(value))
|
try:
|
||||||
|
value = self.config.get('AI', key, fallback='')
|
||||||
self.save_config()
|
ai_config[key] = value
|
||||||
|
except configparser.InterpolationError as e:
|
||||||
|
print(f"读取AI配置项 {key} 时出错: {e}")
|
||||||
|
# 设置默认值
|
||||||
|
if key == 'model_type':
|
||||||
|
ai_config[key] = 'none'
|
||||||
|
elif key == 'api_key':
|
||||||
|
ai_config[key] = ''
|
||||||
|
elif key == 'base_url':
|
||||||
|
ai_config[key] = 'https://api.openai.com/v1'
|
||||||
|
elif key == 'model':
|
||||||
|
ai_config[key] = 'gpt-3.5-turbo'
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取AI配置时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
dialog.set_config(ai_config)
|
||||||
|
|
||||||
# 更新服务的AI配置
|
if dialog.exec():
|
||||||
if hasattr(self.service, 'update_ai_config'):
|
print("AI设置对话框已接受")
|
||||||
self.service.update_ai_config(new_config)
|
# 获取新配置
|
||||||
|
new_config = dialog.get_config()
|
||||||
|
|
||||||
|
# 保存到配置文件
|
||||||
|
if not self.config.has_section('AI'):
|
||||||
|
self.config.add_section('AI')
|
||||||
|
|
||||||
|
for key, value in new_config.items():
|
||||||
|
self.config.set('AI', key, str(value))
|
||||||
|
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
|
# 更新服务的AI配置
|
||||||
|
if hasattr(self.service, 'update_ai_config'):
|
||||||
|
self.service.update_ai_config(new_config)
|
||||||
|
|
||||||
|
# 直接调用show_tray_message方法
|
||||||
|
self.show_tray_message(
|
||||||
|
"LLMClipboard",
|
||||||
|
"AI设置已保存并应用",
|
||||||
|
"info",
|
||||||
|
2000
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新状态栏
|
||||||
|
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
|
self.status_label.setText("AI设置已保存并应用")
|
||||||
|
|
||||||
|
# 5秒后清除消息
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(5000, lambda: self.status_label.setText("就绪"))
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
print(f"显示AI设置对话框失败: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
self.show_message(f"显示AI设置对话框失败: {str(e)}", error=True)
|
||||||
|
|
||||||
def show_category_manager(self):
|
def show_category_manager(self):
|
||||||
"""显示分类管理器对话框"""
|
"""显示分类管理器对话框"""
|
||||||
# 获取当前分类
|
# 获取当前分类
|
||||||
categories = {}
|
categories = {}
|
||||||
if hasattr(self.service, 'doc_processor'):
|
try:
|
||||||
categories = self.service.doc_processor.categories
|
if hasattr(self.service, 'doc_processor'):
|
||||||
|
categories = self.service.doc_processor.categories
|
||||||
|
else:
|
||||||
|
# 如果doc_processor不存在,尝试从文件加载分类
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
categories_file = os.path.join(os.path.dirname(__file__), 'categories.json')
|
||||||
|
if os.path.exists(categories_file):
|
||||||
|
with open(categories_file, 'r', encoding='utf-8') as f:
|
||||||
|
categories = json.load(f)
|
||||||
|
else:
|
||||||
|
# 使用默认分类
|
||||||
|
categories = {
|
||||||
|
"技术": ["编程", "开发", "代码", "框架", "算法", "数据库", "API"],
|
||||||
|
"学习": ["教程", "课程", "学习", "笔记", "知识", "总结"],
|
||||||
|
"工作": ["会议", "项目", "计划", "报告", "任务", "进度"],
|
||||||
|
"想法": ["想法", "创意", "思考", "灵感", "观点", "建议"],
|
||||||
|
"资源": ["工具", "资源", "链接", "参考", "文档", "书籍"]
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
self.show_message(f"加载分类失败: {e}", error=True)
|
||||||
|
return
|
||||||
|
|
||||||
# 创建分类管理器对话框
|
# 创建分类管理器对话框
|
||||||
dialog = CategoryManagerDialog(categories, self)
|
dialog = CategoryManagerDialog(categories, self)
|
||||||
@ -573,10 +697,73 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def update_categories(self, new_categories):
|
def update_categories(self, new_categories):
|
||||||
"""更新分类"""
|
"""更新分类"""
|
||||||
if hasattr(self.service, 'doc_processor'):
|
try:
|
||||||
self.service.doc_processor.update_categories(new_categories)
|
print(f"更新分类: {new_categories}")
|
||||||
self.show_message("分类已更新")
|
|
||||||
|
|
||||||
|
# 更新doc_processor的分类
|
||||||
|
if hasattr(self.service, 'doc_processor'):
|
||||||
|
print("使用doc_processor更新分类")
|
||||||
|
self.service.doc_processor.update_categories(new_categories)
|
||||||
|
print("分类更新成功")
|
||||||
|
else:
|
||||||
|
# 如果doc_processor不存在,直接保存到文件
|
||||||
|
print("直接保存分类到文件")
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 确保使用正确的配置文件路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
categories_file = os.path.join(config_dir, 'categories.json')
|
||||||
|
print(f"保存分类到文件: {categories_file}")
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(categories_file), exist_ok=True)
|
||||||
|
with open(categories_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(new_categories, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"分类已保存到文件: {categories_file}")
|
||||||
|
|
||||||
|
# 尝试重新加载服务配置
|
||||||
|
try:
|
||||||
|
print("尝试重新加载服务配置")
|
||||||
|
self.service.load_config()
|
||||||
|
print("服务配置重新加载成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"重新加载服务配置失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# 显示成功消息
|
||||||
|
self.show_tray_message(
|
||||||
|
"LLMClipboard",
|
||||||
|
"分类已更新并保存",
|
||||||
|
"info",
|
||||||
|
2000
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新状态栏
|
||||||
|
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
|
self.status_label.setText("分类已更新")
|
||||||
|
|
||||||
|
# 5秒后清除消息
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(5000, lambda: self.status_label.setText("就绪"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"更新分类失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
self.show_tray_message(
|
||||||
|
"LLMClipboard",
|
||||||
|
f"更新分类失败: {e}",
|
||||||
|
"error",
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
def add_recent_file(self, file_path):
|
def add_recent_file(self, file_path):
|
||||||
"""添加最近保存的文件到列表"""
|
"""添加最近保存的文件到列表"""
|
||||||
# 检查是否已存在
|
# 检查是否已存在
|
||||||
@ -624,11 +811,18 @@ class MainWindow(QMainWindow):
|
|||||||
if hasattr(self, 'status_action') and self.status_action is not None:
|
if hasattr(self, 'status_action') and self.status_action is not None:
|
||||||
self.status_action.setText("状态: 正在运行")
|
self.status_action.setText("状态: 正在运行")
|
||||||
if hasattr(self, 'tray_icon') and hasattr(self, 'active_icon') and self.tray_icon is not None:
|
if hasattr(self, 'tray_icon') and hasattr(self, 'active_icon') and self.tray_icon is not None:
|
||||||
|
print("设置活动状态托盘图标")
|
||||||
self.tray_icon.setIcon(self.active_icon)
|
self.tray_icon.setIcon(self.active_icon)
|
||||||
# 确保托盘图标可见
|
# 确保托盘图标可见
|
||||||
if not self.tray_icon.isVisible():
|
if not self.tray_icon.isVisible():
|
||||||
print("确保托盘图标可见")
|
print("确保托盘图标可见")
|
||||||
self.tray_icon.show()
|
self.tray_icon.show()
|
||||||
|
# 强制更新图标
|
||||||
|
self.tray_icon.setToolTip("LLMClipboard - 正在运行")
|
||||||
|
# 强制刷新图标
|
||||||
|
self.tray_icon.hide()
|
||||||
|
self.tray_icon.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
# 保存配置并启动服务
|
# 保存配置并启动服务
|
||||||
try:
|
try:
|
||||||
@ -664,11 +858,21 @@ class MainWindow(QMainWindow):
|
|||||||
if hasattr(self, 'status_action') and self.status_action is not None:
|
if hasattr(self, 'status_action') and self.status_action is not None:
|
||||||
self.status_action.setText("状态: 已停止")
|
self.status_action.setText("状态: 已停止")
|
||||||
if hasattr(self, 'tray_icon') and hasattr(self, 'inactive_icon') and self.tray_icon is not None:
|
if hasattr(self, 'tray_icon') and hasattr(self, 'inactive_icon') and self.tray_icon is not None:
|
||||||
|
print("设置非活动状态托盘图标")
|
||||||
self.tray_icon.setIcon(self.inactive_icon)
|
self.tray_icon.setIcon(self.inactive_icon)
|
||||||
# 确保托盘图标可见
|
# 确保托盘图标可见
|
||||||
if not self.tray_icon.isVisible():
|
if not self.tray_icon.isVisible():
|
||||||
print("确保托盘图标可见")
|
print("确保托盘图标可见")
|
||||||
self.tray_icon.show()
|
self.tray_icon.show()
|
||||||
|
# 强制更新图标
|
||||||
|
self.tray_icon.setToolTip("LLMClipboard - 已停止")
|
||||||
|
# 强制刷新图标
|
||||||
|
try:
|
||||||
|
self.tray_icon.hide()
|
||||||
|
self.tray_icon.show()
|
||||||
|
except Exception as refresh_error:
|
||||||
|
print(f"刷新托盘图标失败: {str(refresh_error)}")
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
# 停止服务
|
# 停止服务
|
||||||
try:
|
try:
|
||||||
@ -700,6 +904,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.tray_icon.show()
|
self.tray_icon.show()
|
||||||
except Exception as icon_error:
|
except Exception as icon_error:
|
||||||
print(f"恢复托盘图标可见性失败: {str(icon_error)}")
|
print(f"恢复托盘图标可见性失败: {str(icon_error)}")
|
||||||
|
# 强制更新图标
|
||||||
|
self.tray_icon.setToolTip("LLMClipboard - 已停止")
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
# 显示错误消息
|
# 显示错误消息
|
||||||
self.show_message(f"切换监听状态失败: {str(e)}", error=True)
|
self.show_message(f"切换监听状态失败: {str(e)}", error=True)
|
||||||
@ -783,11 +990,20 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 设置托盘图标
|
# 设置托盘图标
|
||||||
print("设置托盘图标")
|
print("设置托盘图标")
|
||||||
icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "icon.png")
|
icon_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources")
|
||||||
if os.path.exists(icon_path):
|
active_icon_path = os.path.join(icon_dir, "icon_active.png")
|
||||||
print(f"使用自定义图标: {icon_path}")
|
inactive_icon_path = os.path.join(icon_dir, "icon.png")
|
||||||
self.active_icon = QIcon(icon_path)
|
|
||||||
self.inactive_icon = QIcon(icon_path)
|
# 检查自定义图标是否存在
|
||||||
|
if os.path.exists(active_icon_path) and os.path.exists(inactive_icon_path):
|
||||||
|
print(f"使用自定义图标: 活动={active_icon_path}, 非活动={inactive_icon_path}")
|
||||||
|
self.active_icon = QIcon(active_icon_path)
|
||||||
|
self.inactive_icon = QIcon(inactive_icon_path)
|
||||||
|
elif os.path.exists(inactive_icon_path):
|
||||||
|
# 只有一个图标文件,但仍使用不同的系统图标区分状态
|
||||||
|
print(f"使用自定义非活动图标和系统活动图标")
|
||||||
|
self.inactive_icon = QIcon(inactive_icon_path)
|
||||||
|
self.active_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton)
|
||||||
else:
|
else:
|
||||||
# 使用系统默认图标
|
# 使用系统默认图标
|
||||||
print("使用系统默认图标")
|
print("使用系统默认图标")
|
||||||
@ -1079,49 +1295,23 @@ class MainWindow(QMainWindow):
|
|||||||
if error:
|
if error:
|
||||||
print(f"错误: {message}")
|
print(f"错误: {message}")
|
||||||
self.status_label.setStyleSheet("color: red; font-weight: bold;")
|
self.status_label.setStyleSheet("color: red; font-weight: bold;")
|
||||||
if hasattr(self, 'tray_icon') and self.tray_icon is not None:
|
# 使用改进的show_tray_message方法显示错误消息
|
||||||
try:
|
self.show_tray_message(
|
||||||
self.tray_icon.showMessage(
|
"LLMClipboard - 错误",
|
||||||
"LLMClipboard - 错误",
|
message,
|
||||||
message,
|
"error",
|
||||||
QSystemTrayIcon.MessageIcon.Critical,
|
3000
|
||||||
3000
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"显示错误消息失败: {str(e)}")
|
|
||||||
# 尝试使用旧版API
|
|
||||||
try:
|
|
||||||
self.tray_icon.showMessage(
|
|
||||||
"LLMClipboard - 错误",
|
|
||||||
message,
|
|
||||||
QSystemTrayIcon.Critical,
|
|
||||||
3000
|
|
||||||
)
|
|
||||||
except Exception as e2:
|
|
||||||
print(f"使用旧版API显示错误消息也失败: {str(e2)}")
|
|
||||||
else:
|
else:
|
||||||
print(f"消息: {message}")
|
print(f"消息: {message}")
|
||||||
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
if hasattr(self, 'tray_icon') and self.tray_icon is not None:
|
# 使用改进的show_tray_message方法显示信息消息
|
||||||
try:
|
self.show_tray_message(
|
||||||
self.tray_icon.showMessage(
|
"LLMClipboard",
|
||||||
"LLMClipboard",
|
message,
|
||||||
message,
|
"info",
|
||||||
QSystemTrayIcon.MessageIcon.Information,
|
2000
|
||||||
2000
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"显示信息消息失败: {str(e)}")
|
|
||||||
# 尝试使用旧版API
|
|
||||||
try:
|
|
||||||
self.tray_icon.showMessage(
|
|
||||||
"LLMClipboard",
|
|
||||||
message,
|
|
||||||
QSystemTrayIcon.Information,
|
|
||||||
2000
|
|
||||||
)
|
|
||||||
except Exception as e2:
|
|
||||||
print(f"使用旧版API显示信息消息也失败: {str(e2)}")
|
|
||||||
|
|
||||||
self.status_label.setText(message)
|
self.status_label.setText(message)
|
||||||
|
|
||||||
@ -1380,7 +1570,14 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 添加确定和取消按钮
|
# 添加确定和取消按钮
|
||||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||||
button_box.accepted.connect(dialog.accept)
|
|
||||||
|
# 连接确定按钮到保存配置方法
|
||||||
|
def on_accepted():
|
||||||
|
print("设置对话框确定按钮被点击")
|
||||||
|
self.save_config() # 保存配置
|
||||||
|
dialog.accept() # 关闭对话框
|
||||||
|
|
||||||
|
button_box.accepted.connect(on_accepted)
|
||||||
button_box.rejected.connect(dialog.reject)
|
button_box.rejected.connect(dialog.reject)
|
||||||
layout.addWidget(button_box)
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
|
|||||||
BIN
project/llmclipboard/resources/icon_active.png
Normal file
BIN
project/llmclipboard/resources/icon_active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 862 KiB |
91
project/llmclipboard/test_categories.py
Normal file
91
project/llmclipboard/test_categories.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"""
|
||||||
|
测试分类配置的加载和保存功能
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 配置日志记录
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 添加项目根目录到Python路径
|
||||||
|
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
if project_root not in sys.path:
|
||||||
|
sys.path.append(project_root)
|
||||||
|
|
||||||
|
from llmclipboard.document_processor import DocumentProcessor
|
||||||
|
|
||||||
|
def test_categories_persistence():
|
||||||
|
"""测试分类配置的持久化功能"""
|
||||||
|
logger.info("开始测试分类配置的持久化功能")
|
||||||
|
|
||||||
|
# 确定配置文件路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = project_root
|
||||||
|
|
||||||
|
categories_file = os.path.join(config_dir, 'categories.json')
|
||||||
|
logger.info(f"分类配置文件路径: {categories_file}")
|
||||||
|
|
||||||
|
# 1. 检查配置文件是否存在
|
||||||
|
if os.path.exists(categories_file):
|
||||||
|
logger.info(f"分类配置文件已存在: {categories_file}")
|
||||||
|
with open(categories_file, 'r', encoding='utf-8') as f:
|
||||||
|
current_categories = json.load(f)
|
||||||
|
logger.info(f"当前分类: {current_categories}")
|
||||||
|
else:
|
||||||
|
logger.info(f"分类配置文件不存在: {categories_file}")
|
||||||
|
current_categories = {}
|
||||||
|
|
||||||
|
# 2. 创建文档处理器实例
|
||||||
|
doc_processor = DocumentProcessor(config_path=categories_file)
|
||||||
|
|
||||||
|
# 3. 获取当前分类
|
||||||
|
logger.info(f"文档处理器加载的分类: {doc_processor.categories}")
|
||||||
|
|
||||||
|
# 4. 修改分类
|
||||||
|
new_categories = {
|
||||||
|
"测试": ["测试1", "测试2", "测试3"],
|
||||||
|
"工作": ["会议", "项目", "计划", "报告", "任务", "进度"],
|
||||||
|
"学习": ["教程", "课程", "学习", "笔记", "知识", "总结"],
|
||||||
|
"生活": ["购物", "健康", "旅行", "美食", "娱乐"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. 更新分类
|
||||||
|
logger.info(f"更新分类: {new_categories}")
|
||||||
|
doc_processor.update_categories(new_categories)
|
||||||
|
|
||||||
|
# 6. 检查配置文件是否已更新
|
||||||
|
if os.path.exists(categories_file):
|
||||||
|
with open(categories_file, 'r', encoding='utf-8') as f:
|
||||||
|
updated_categories = json.load(f)
|
||||||
|
logger.info(f"更新后的分类: {updated_categories}")
|
||||||
|
|
||||||
|
# 验证更新是否成功
|
||||||
|
if updated_categories == new_categories:
|
||||||
|
logger.info("分类更新成功!")
|
||||||
|
else:
|
||||||
|
logger.error("分类更新失败!")
|
||||||
|
else:
|
||||||
|
logger.error(f"分类配置文件不存在: {categories_file}")
|
||||||
|
|
||||||
|
# 7. 创建新的文档处理器实例,验证配置是否能被正确加载
|
||||||
|
new_doc_processor = DocumentProcessor(config_path=categories_file)
|
||||||
|
logger.info(f"新文档处理器加载的分类: {new_doc_processor.categories}")
|
||||||
|
|
||||||
|
# 验证加载是否成功
|
||||||
|
if new_doc_processor.categories == new_categories:
|
||||||
|
logger.info("分类加载成功!")
|
||||||
|
else:
|
||||||
|
logger.error("分类加载失败!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_categories_persistence()
|
||||||
95
project/llmclipboard/test_category_manager.py
Normal file
95
project/llmclipboard/test_category_manager.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
"""
|
||||||
|
测试LLMClipboard分类配置管理功能
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
|
||||||
|
# 配置日志记录
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 添加项目根目录到Python路径
|
||||||
|
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
if project_root not in sys.path:
|
||||||
|
sys.path.append(project_root)
|
||||||
|
|
||||||
|
from llmclipboard.document_processor import DocumentProcessor
|
||||||
|
from llmclipboard.category_manager import CategoryManagerDialog
|
||||||
|
from llmclipboard.gui import MainWindow
|
||||||
|
|
||||||
|
def test_category_manager_dialog():
|
||||||
|
"""测试分类管理器对话框"""
|
||||||
|
logger.info("开始测试分类管理器对话框")
|
||||||
|
|
||||||
|
# 创建应用程序实例
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# 确定配置文件路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境 - 使用可执行文件所在目录
|
||||||
|
config_dir = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 开发环境 - 使用项目根目录
|
||||||
|
config_dir = project_root
|
||||||
|
|
||||||
|
categories_file = os.path.join(config_dir, 'categories.json')
|
||||||
|
logger.info(f"分类配置文件路径: {categories_file}")
|
||||||
|
|
||||||
|
# 加载当前分类
|
||||||
|
if os.path.exists(categories_file):
|
||||||
|
logger.info(f"分类配置文件已存在: {categories_file}")
|
||||||
|
with open(categories_file, 'r', encoding='utf-8') as f:
|
||||||
|
categories = json.load(f)
|
||||||
|
logger.info(f"当前分类: {categories}")
|
||||||
|
else:
|
||||||
|
logger.info(f"分类配置文件不存在: {categories_file}")
|
||||||
|
categories = {}
|
||||||
|
|
||||||
|
# 创建分类管理器对话框
|
||||||
|
dialog = CategoryManagerDialog(categories)
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
def on_categories_updated(new_categories):
|
||||||
|
logger.info(f"分类已更新: {new_categories}")
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
|
with open(categories_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(new_categories, f, ensure_ascii=False, indent=2)
|
||||||
|
logger.info(f"分类已保存到文件: {categories_file}")
|
||||||
|
|
||||||
|
dialog.categories_updated.connect(on_categories_updated)
|
||||||
|
|
||||||
|
# 显示对话框
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
# 5秒后自动关闭
|
||||||
|
QTimer.singleShot(60000, dialog.close)
|
||||||
|
|
||||||
|
# 运行应用程序
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
def test_main_window_category_management():
|
||||||
|
"""测试主窗口的分类管理功能"""
|
||||||
|
logger.info("开始测试主窗口的分类管理功能")
|
||||||
|
|
||||||
|
# 创建应用程序实例
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# 创建主窗口
|
||||||
|
window = MainWindow()
|
||||||
|
|
||||||
|
# 显示窗口
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
# 运行应用程序
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 选择要运行的测试
|
||||||
|
test_category_manager_dialog()
|
||||||
|
# test_main_window_category_management()
|
||||||
Loading…
Reference in New Issue
Block a user