From 8a5450eeaef23f73b3ae79155a1fe80879b042b3 Mon Sep 17 00:00:00 2001 From: xiaji Date: Fri, 16 Jan 2026 16:37:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0PyInstaller=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=8A=E6=B8=85=E7=90=86=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加translate.ico图标文件 更新.gitignore忽略.spec文件 修改main.py导入路径为main_window_final 优化translator.py的错误处理 添加pyinstaller-one技能目录及文档 更新main.spec配置排除不必要的依赖 添加verify_features.py功能验证脚本 --- .gitignore | 7 +- .../documents/实现基于PySide6的翻译GUI工具.md | 117 ++++++++++++------ .trae/skills/pyinstaller-one/SKILL.md | 17 +++ .trae/skills/pyinstaller-one/clean.py | 17 +++ main.py | 2 +- main.spec | 10 +- translate.ico | Bin 0 -> 11288 bytes translator.py | 4 +- verify_features.py | 64 ++++++++++ 9 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 .trae/skills/pyinstaller-one/SKILL.md create mode 100644 .trae/skills/pyinstaller-one/clean.py create mode 100644 translate.ico create mode 100644 verify_features.py diff --git a/.gitignore b/.gitignore index cf48f15..c9c682a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,13 @@ env/ *.venv/ *.env/ + # Python编译文件 *.pyc +*.spec __pycache__/ +build/ +dist/ # 日志文件 logs/ @@ -18,6 +22,7 @@ logs/ *.swp *.swo *~ +*.spec # 操作系统文件 .DS_Store @@ -30,4 +35,4 @@ models/*.gguf test* # 自己 -.gitignore \ No newline at end of file +.gitignore diff --git a/.trae/documents/实现基于PySide6的翻译GUI工具.md b/.trae/documents/实现基于PySide6的翻译GUI工具.md index 0ded97c..0fc3375 100644 --- a/.trae/documents/实现基于PySide6的翻译GUI工具.md +++ b/.trae/documents/实现基于PySide6的翻译GUI工具.md @@ -1,6 +1,7 @@ # 实现基于PySide6的翻译GUI工具 ## 1. 项目结构设计 + ``` translate/ ├── main.py # 主程序入口 @@ -19,44 +20,72 @@ translate/ ## 2. 核心功能实现 ### 2.1 依赖库安装 -- PySide6:GUI框架 -- llama-cpp-python:运行GGUF模型 -- python-docx:Word文件处理 -- loguru:日志管理 -- psutil:系统资源监控 -- GPUtil:GPU资源监控(如果需要) + +* PySide6:GUI框架 + +* llama-cpp-python:运行GGUF模型 + +* python-docx:Word文件处理 + +* loguru:日志管理 + +* psutil:系统资源监控 + +* GPUtil:GPU资源监控(如果需要) ### 2.2 系统资源监控 -- 实现CPU占用率监控 -- 实现内存占用率监控 -- 实现显卡内存占用率监控 -- 定期更新状态栏显示 + +* 实现CPU占用率监控 + +* 实现内存占用率监控 + +* 实现显卡内存占用率监控 + +* 定期更新状态栏显示 ### 2.3 模型管理 -- 实现模型加载和初始化 -- 支持模型文件选择和切换 -- 显示模型状态(就绪/加载中) + +* 实现模型加载和初始化 + +* 支持模型文件选择和切换 + +* 显示模型状态(就绪/加载中) ### 2.4 翻译核心 -- 使用llama-cpp-python调用模型进行翻译 -- 支持添加文本背景和场景介绍 -- 支持术语定义功能 + +* 使用llama-cpp-python调用模型进行翻译 + +* 支持添加文本背景和场景介绍 + +* 支持术语定义功能 ### 2.5 GUI界面设计 -- 实现与参考图一致的界面布局 -- 顶部模型信息和更换按钮 -- 可折叠的高级辅助面板 - - 文本背景/场景介绍输入框 - - 术语定义列表和管理 -- 原文输入区域,支持Word导入 -- 翻译按钮 -- 译文结果区域,支持Word导出和复制 -- 底部状态栏,显示系统资源占用情况 + +* 实现与参考图一致的界面布局 + +* 顶部模型信息和更换按钮 + +* 可折叠的高级辅助面板 + + * 文本背景/场景介绍输入框 + + * 术语定义列表和管理 + +* 原文输入区域,支持Word导入 + +* 翻译按钮 + +* 译文结果区域,支持Word导出和复制 + +* 底部状态栏,显示系统资源占用情况 ### 2.6 Word文件处理 -- 实现docx文件的导入,提取文本内容 -- 实现译文的docx文件导出 -- 保持文档格式(尽可能) + +* 实现docx文件的导入,提取文本内容 + +* 实现译文的docx文件导出 + +* 保持文档格式(尽可能) ## 3. 代码实现步骤 @@ -72,17 +101,27 @@ translate/ ## 4. 注意事项 -- 确保模型文件路径正确配置 -- 处理模型加载和翻译过程中的异常 -- 优化GUI响应速度,避免翻译过程中界面卡顿 -- 实现良好的错误提示和日志记录 -- 支持Windows系统的文件路径格式 -- 系统监控模块要低开销运行,避免影响翻译性能 +* 确保模型文件路径正确配置 + +* 处理模型加载和翻译过程中的异常 + +* 优化GUI响应速度,避免翻译过程中界面卡顿 + +* 实现良好的错误提示和日志记录 + +* 支持Windows系统的文件路径格式 + +* 系统监控模块要低开销运行,避免影响翻译性能 ## 5. 未来扩展考虑 -- 支持更多模型格式 -- 实现批量翻译功能 -- 支持更多文档格式 -- 添加翻译历史记录 -- 实现翻译质量评估 \ No newline at end of file +* 支持更多模型格式 + +* 实现批量翻译功能 + +* 支持更多文档格式 + +* 添加翻译历史记录 + +* 实现翻译质量评估 + diff --git a/.trae/skills/pyinstaller-one/SKILL.md b/.trae/skills/pyinstaller-one/SKILL.md new file mode 100644 index 0000000..e0d8e79 --- /dev/null +++ b/.trae/skills/pyinstaller-one/SKILL.md @@ -0,0 +1,17 @@ +--- +name: pyinstaller-one +description: 基于python代码使用pyinstaller打包的时候,用这个统一打包的要求格式。 +--- + +## 元数据 +name: pyinstaller个性化打包 +description: 打包的时候,要求生成为一个exe文件,使用ico等等 + +## 概述 +此 Skill 用于给有GUI界面的python代码,打包的时候,生成一个统一的要求:生成一个exe文件,去掉控制台窗口,使用本目录下的ico文件作为程序的图标。打包时,如果之前有打包过的文件(dist/build 文件夹),自动覆盖旧文件,不用手动确认,一键打包到底。 + +## 打包命令示例 +pyinstaller --onefile --noconsole --icon=图标文件名.ico --distpath=. --hidden-import=PySide6.Qt6Compat python程序名.py + +## 清除多余文件 +在windows环境下,执行clean.py \ No newline at end of file diff --git a/.trae/skills/pyinstaller-one/clean.py b/.trae/skills/pyinstaller-one/clean.py new file mode 100644 index 0000000..268139a --- /dev/null +++ b/.trae/skills/pyinstaller-one/clean.py @@ -0,0 +1,17 @@ +import os +import shutil +import glob +import subprocess + +# 2. 清理打包残留文件 等价:rd /s /q build + del /f *.spec +print("开始清理打包残留文件...") +# 删除build文件夹 +if os.path.exists("build") and os.path.isdir("build"): + shutil.rmtree("build") +# 删除所有.spec文件 +spec_files = glob.glob("*.spec") +for spec_file in spec_files: + if os.path.exists(spec_file): + os.remove(spec_file) + +print("✅ 打包完成 + 残留文件清理完毕!exe文件已生成在当前目录") \ No newline at end of file diff --git a/main.py b/main.py index 9e6d4a5..71433f6 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ import sys from PySide6.QtWidgets import QApplication -from ui.main_window import MainWindow +from ui.main_window_final import MainWindow from utils.logger import logger def main(): diff --git a/main.spec b/main.spec index c81aa78..cca3d7a 100644 --- a/main.spec +++ b/main.spec @@ -1,20 +1,16 @@ # -*- mode: python ; coding: utf-8 -*- -from PyInstaller.utils.hooks import collect_data_files - -datas = [] -datas += collect_data_files('PySide6') a = Analysis( ['main.py'], pathex=[], binaries=[], - datas=datas, - hiddenimports=['PySide6.QtSvg'], + datas=[], + hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], - excludes=[], + excludes=['matplotlib', 'numpy', 'IPython', 'PyQt5', 'PyQt6'], noarchive=False, optimize=0, ) diff --git a/translate.ico b/translate.ico new file mode 100644 index 0000000000000000000000000000000000000000..2d9a7e6c4731cd1014c671c50ab4a22208303b32 GIT binary patch literal 11288 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