feat: 实现基于PySide6的翻译GUI工具初始版本
添加主程序入口、GUI界面、翻译核心逻辑、Word文件处理、系统监控和日志模块
This commit is contained in:
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# 虚拟环境
|
||||
venv/
|
||||
env/
|
||||
*.venv/
|
||||
*.env/
|
||||
|
||||
# Python编译文件
|
||||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# IDE配置
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 操作系统文件
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 模型文件
|
||||
models/*.gguf
|
||||
88
.trae/documents/实现基于PySide6的翻译GUI工具.md
Normal file
88
.trae/documents/实现基于PySide6的翻译GUI工具.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 实现基于PySide6的翻译GUI工具
|
||||
|
||||
## 1. 项目结构设计
|
||||
```
|
||||
translate/
|
||||
├── main.py # 主程序入口
|
||||
├── translator.py # 翻译核心逻辑
|
||||
├── models/ # 模型文件目录
|
||||
│ └── HY-MT1.5-1.8B_bf16_Q4_K_M-GGUF.gguf # 模型文件
|
||||
├── ui/
|
||||
│ └── main_window.py # GUI界面设计
|
||||
├── utils/
|
||||
│ ├── word_handler.py # Word文件处理
|
||||
│ ├── system_monitor.py # 系统资源监控
|
||||
│ └── logger.py # 日志配置
|
||||
└── requirements.txt # 依赖库列表
|
||||
```
|
||||
|
||||
## 2. 核心功能实现
|
||||
|
||||
### 2.1 依赖库安装
|
||||
- PySide6:GUI框架
|
||||
- llama-cpp-python:运行GGUF模型
|
||||
- python-docx:Word文件处理
|
||||
- loguru:日志管理
|
||||
- psutil:系统资源监控
|
||||
- GPUtil:GPU资源监控(如果需要)
|
||||
|
||||
### 2.2 系统资源监控
|
||||
- 实现CPU占用率监控
|
||||
- 实现内存占用率监控
|
||||
- 实现显卡内存占用率监控
|
||||
- 定期更新状态栏显示
|
||||
|
||||
### 2.3 模型管理
|
||||
- 实现模型加载和初始化
|
||||
- 支持模型文件选择和切换
|
||||
- 显示模型状态(就绪/加载中)
|
||||
|
||||
### 2.4 翻译核心
|
||||
- 使用llama-cpp-python调用模型进行翻译
|
||||
- 支持添加文本背景和场景介绍
|
||||
- 支持术语定义功能
|
||||
|
||||
### 2.5 GUI界面设计
|
||||
- 实现与参考图一致的界面布局
|
||||
- 顶部模型信息和更换按钮
|
||||
- 可折叠的高级辅助面板
|
||||
- 文本背景/场景介绍输入框
|
||||
- 术语定义列表和管理
|
||||
- 原文输入区域,支持Word导入
|
||||
- 翻译按钮
|
||||
- 译文结果区域,支持Word导出和复制
|
||||
- 底部状态栏,显示系统资源占用情况
|
||||
|
||||
### 2.6 Word文件处理
|
||||
- 实现docx文件的导入,提取文本内容
|
||||
- 实现译文的docx文件导出
|
||||
- 保持文档格式(尽可能)
|
||||
|
||||
## 3. 代码实现步骤
|
||||
|
||||
1. 创建项目目录结构
|
||||
2. 编写requirements.txt文件
|
||||
3. 实现日志配置模块
|
||||
4. 实现系统资源监控模块
|
||||
5. 实现Word文件处理模块
|
||||
6. 实现翻译核心逻辑
|
||||
7. 实现GUI界面设计,包括状态栏
|
||||
8. 实现主程序入口,整合所有功能
|
||||
9. 测试各功能模块
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
- 确保模型文件路径正确配置
|
||||
- 处理模型加载和翻译过程中的异常
|
||||
- 优化GUI响应速度,避免翻译过程中界面卡顿
|
||||
- 实现良好的错误提示和日志记录
|
||||
- 支持Windows系统的文件路径格式
|
||||
- 系统监控模块要低开销运行,避免影响翻译性能
|
||||
|
||||
## 5. 未来扩展考虑
|
||||
|
||||
- 支持更多模型格式
|
||||
- 实现批量翻译功能
|
||||
- 支持更多文档格式
|
||||
- 添加翻译历史记录
|
||||
- 实现翻译质量评估
|
||||
27
main.py
Normal file
27
main.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from ui.main_window import MainWindow
|
||||
from utils.logger import logger
|
||||
|
||||
def main():
|
||||
"""主程序入口"""
|
||||
logger.info("启动PrivaTrans翻译工具")
|
||||
|
||||
# 创建应用程序
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 设置应用程序样式
|
||||
app.setStyle("Fusion")
|
||||
|
||||
# 创建主窗口
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
|
||||
# 运行应用程序
|
||||
exit_code = app.exec()
|
||||
|
||||
logger.info(f"PrivaTrans翻译工具退出,退出码: {exit_code}")
|
||||
sys.exit(exit_code)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
PySide6
|
||||
llama-cpp-python
|
||||
python-docx
|
||||
loguru
|
||||
psutil
|
||||
GPUtil
|
||||
105
translator.py
Normal file
105
translator.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from llama_cpp import Llama
|
||||
import os
|
||||
from utils.logger import logger
|
||||
|
||||
class Translator:
|
||||
def __init__(self, model_path=None):
|
||||
self.model = None
|
||||
self.model_path = model_path
|
||||
self.is_ready = False
|
||||
self.model_name = ""
|
||||
|
||||
def load_model(self, model_path=None):
|
||||
"""加载模型"""
|
||||
if model_path:
|
||||
self.model_path = model_path
|
||||
|
||||
if not self.model_path:
|
||||
logger.error("未提供模型路径")
|
||||
return False
|
||||
|
||||
if not os.path.exists(self.model_path):
|
||||
logger.error(f"模型文件不存在: {self.model_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
logger.info(f"开始加载模型: {self.model_path}")
|
||||
self.model = Llama(
|
||||
model_path=self.model_path,
|
||||
n_ctx=2048,
|
||||
n_threads=4,
|
||||
n_gpu_layers=100 # 尽可能使用GPU加速
|
||||
)
|
||||
self.is_ready = True
|
||||
self.model_name = os.path.basename(self.model_path)
|
||||
logger.info(f"模型加载成功: {self.model_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"模型加载失败: {e}")
|
||||
self.is_ready = False
|
||||
return False
|
||||
|
||||
def translate(self, text, context="", terms=None):
|
||||
"""执行翻译"""
|
||||
if not self.is_ready or not self.model:
|
||||
logger.error("模型未就绪,无法执行翻译")
|
||||
return ""
|
||||
|
||||
try:
|
||||
# 构建翻译提示词
|
||||
prompt = self._build_prompt(text, context, terms)
|
||||
|
||||
logger.info(f"开始翻译,输入长度: {len(text)} 字符")
|
||||
|
||||
# 调用模型进行翻译
|
||||
output = self.model(
|
||||
prompt,
|
||||
max_tokens=2048,
|
||||
temperature=0.7,
|
||||
top_p=0.95,
|
||||
stop=["\n原文:", "\n译文:", "\n###"]
|
||||
)
|
||||
|
||||
translated_text = output["choices"][0]["text"].strip()
|
||||
logger.info(f"翻译完成,输出长度: {len(translated_text)} 字符")
|
||||
|
||||
return translated_text
|
||||
except Exception as e:
|
||||
logger.error(f"翻译失败: {e}")
|
||||
return ""
|
||||
|
||||
def _build_prompt(self, text, context="", terms=None):
|
||||
"""构建翻译提示词"""
|
||||
prompt = "你是一个专业的翻译助手,根据以下要求将中文翻译成英文:\n"
|
||||
|
||||
if context:
|
||||
prompt += f"\n文本背景/场景介绍:{context}\n"
|
||||
|
||||
if terms:
|
||||
prompt += "\n术语定义:\n"
|
||||
for term in terms:
|
||||
prompt += f"{term}\n"
|
||||
|
||||
prompt += f"\n原文:{text}\n译文:"
|
||||
|
||||
return prompt
|
||||
|
||||
def unload_model(self):
|
||||
"""卸载模型"""
|
||||
if self.model:
|
||||
try:
|
||||
del self.model
|
||||
self.model = None
|
||||
self.is_ready = False
|
||||
logger.info("模型已卸载")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"模型卸载失败: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_model_info(self):
|
||||
"""获取模型信息"""
|
||||
if self.is_ready:
|
||||
return f"{self.model_name.split('.')[0]}"
|
||||
return "未加载模型"
|
||||
386
ui/main_window.py
Normal file
386
ui/main_window.py
Normal file
@@ -0,0 +1,386 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton,
|
||||
QLineEdit, QLabel, QGroupBox, QScrollArea, QToolButton, QStatusBar,
|
||||
QFileDialog, QMessageBox, QListWidget, QListWidgetItem, QFrame, QSplitter
|
||||
)
|
||||
from PySide6.QtCore import Qt, QTimer, QSize
|
||||
from PySide6.QtGui import QFont, QPalette, QColor
|
||||
from utils.word_handler import WordHandler
|
||||
from utils.system_monitor import SystemMonitor
|
||||
from translator import Translator
|
||||
from utils.logger import logger
|
||||
import os
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("PrivaTrans")
|
||||
self.setMinimumSize(800, 600)
|
||||
|
||||
# 初始化组件
|
||||
self.translator = Translator()
|
||||
self.system_monitor = SystemMonitor()
|
||||
self.word_handler = WordHandler()
|
||||
|
||||
# 默认模型路径
|
||||
self.default_model_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "models", "HY-MT1.5-1.8B_bf16_Q4_K_M.gguf")
|
||||
|
||||
# 术语列表
|
||||
self.terms = []
|
||||
|
||||
# 初始化UI
|
||||
self.init_ui()
|
||||
|
||||
# 初始化状态栏定时器
|
||||
self.init_status_bar_timer()
|
||||
|
||||
# 加载默认模型
|
||||
self.load_default_model()
|
||||
|
||||
def init_ui(self):
|
||||
"""初始化UI界面"""
|
||||
# 主布局
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setSpacing(20)
|
||||
main_layout.setContentsMargins(20, 20, 20, 10)
|
||||
|
||||
# 顶部标题
|
||||
title_label = QLabel("PrivaTrans")
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(24)
|
||||
title_font.setBold(True)
|
||||
title_label.setFont(title_font)
|
||||
title_label.setStyleSheet("color: #1a365d;")
|
||||
|
||||
# 模型信息和更换按钮
|
||||
model_layout = QHBoxLayout()
|
||||
|
||||
self.model_status_label = QLabel()
|
||||
self.model_status_label.setStyleSheet("color: #2d3748;")
|
||||
|
||||
self.model_info_label = QLabel()
|
||||
self.model_info_label.setStyleSheet("color: #2d3748; font-weight: bold;")
|
||||
|
||||
self.change_model_btn = QPushButton("更换")
|
||||
self.change_model_btn.setFixedSize(80, 32)
|
||||
self.change_model_btn.setStyleSheet(
|
||||
"background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px;"
|
||||
)
|
||||
self.change_model_btn.clicked.connect(self.change_model)
|
||||
|
||||
model_layout.addWidget(self.model_status_label)
|
||||
model_layout.addWidget(self.model_info_label)
|
||||
model_layout.addStretch()
|
||||
model_layout.addWidget(self.change_model_btn)
|
||||
|
||||
# 高级辅助面板(可折叠)
|
||||
self.advanced_group = QGroupBox("高级辅助 (背景与术语)")
|
||||
self.advanced_group.setCheckable(True)
|
||||
self.advanced_group.setChecked(False)
|
||||
self.advanced_group.setStyleSheet(
|
||||
"QGroupBox { border: 1px solid #e2e8f0; border-radius: 8px; margin-top: 10px; }"
|
||||
"QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; }"
|
||||
)
|
||||
|
||||
advanced_layout = QVBoxLayout(self.advanced_group)
|
||||
advanced_layout.setSpacing(15)
|
||||
advanced_layout.setContentsMargins(15, 15, 15, 15)
|
||||
|
||||
# 文本背景/场景介绍
|
||||
context_label = QLabel("文本背景 / 场景介绍")
|
||||
context_label.setStyleSheet("color: #4a5568; font-weight: bold;")
|
||||
|
||||
self.context_edit = QTextEdit()
|
||||
self.context_edit.setPlaceholderText("例如:这是一份关于建筑工程的合同...")
|
||||
self.context_edit.setFixedHeight(80)
|
||||
self.context_edit.setStyleSheet(
|
||||
"border: 1px solid #e2e8f0; border-radius: 6px; padding: 8px;"
|
||||
)
|
||||
|
||||
# 术语定义
|
||||
terms_label = QLabel("术语簿 (定义 A=B)")
|
||||
terms_label.setStyleSheet("color: #4a5568; font-weight: bold;")
|
||||
|
||||
self.terms_list = QListWidget()
|
||||
self.terms_list.setStyleSheet(
|
||||
"border: 1px solid #e2e8f0; border-radius: 6px;"
|
||||
)
|
||||
|
||||
terms_input_layout = QHBoxLayout()
|
||||
self.term_input = QLineEdit()
|
||||
self.term_input.setPlaceholderText("你好 = what's up")
|
||||
self.term_input.setStyleSheet(
|
||||
"border: 1px solid #e2e8f0; border-radius: 6px; padding: 8px; flex: 1;"
|
||||
)
|
||||
|
||||
add_term_btn = QPushButton("+")
|
||||
add_term_btn.setFixedSize(32, 32)
|
||||
add_term_btn.setStyleSheet(
|
||||
"background-color: #3182ce; color: white; border: none; border-radius: 6px;"
|
||||
)
|
||||
add_term_btn.clicked.connect(self.add_term)
|
||||
|
||||
terms_input_layout.addWidget(self.term_input)
|
||||
terms_input_layout.addWidget(add_term_btn)
|
||||
|
||||
# 术语列表项右键菜单
|
||||
self.terms_list.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.terms_list.customContextMenuRequested.connect(self.show_term_context_menu)
|
||||
|
||||
advanced_layout.addWidget(context_label)
|
||||
advanced_layout.addWidget(self.context_edit)
|
||||
advanced_layout.addWidget(terms_label)
|
||||
advanced_layout.addWidget(self.terms_list)
|
||||
advanced_layout.addLayout(terms_input_layout)
|
||||
|
||||
# 原文输入区域
|
||||
input_layout = QVBoxLayout()
|
||||
|
||||
input_header_layout = QHBoxLayout()
|
||||
input_label = QLabel("原文内容")
|
||||
input_label.setStyleSheet("color: #4a5568; font-weight: bold;")
|
||||
|
||||
import_word_btn = QPushButton("导入 Word")
|
||||
import_word_btn.setFixedSize(120, 32)
|
||||
import_word_btn.setStyleSheet(
|
||||
"background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px;"
|
||||
)
|
||||
import_word_btn.clicked.connect(self.import_word)
|
||||
|
||||
input_header_layout.addWidget(input_label)
|
||||
input_header_layout.addStretch()
|
||||
input_header_layout.addWidget(import_word_btn)
|
||||
|
||||
self.source_text = QTextEdit()
|
||||
self.source_text.setStyleSheet(
|
||||
"border: 1px solid #e2e8f0; border-radius: 6px; padding: 10px;"
|
||||
)
|
||||
|
||||
input_layout.addLayout(input_header_layout)
|
||||
input_layout.addWidget(self.source_text)
|
||||
|
||||
# 翻译按钮
|
||||
self.translate_btn = QPushButton("开始翻译")
|
||||
self.translate_btn.setFixedHeight(50)
|
||||
self.translate_btn.setStyleSheet(
|
||||
"background-color: #3182ce; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold;"
|
||||
)
|
||||
self.translate_btn.clicked.connect(self.start_translation)
|
||||
|
||||
# 译文结果区域
|
||||
output_layout = QVBoxLayout()
|
||||
|
||||
output_header_layout = QHBoxLayout()
|
||||
output_label = QLabel("译文结果")
|
||||
output_label.setStyleSheet("color: #4a5568; font-weight: bold;")
|
||||
|
||||
output_buttons_layout = QHBoxLayout()
|
||||
export_word_btn = QPushButton("导出 Word")
|
||||
export_word_btn.setFixedSize(120, 32)
|
||||
export_word_btn.setStyleSheet(
|
||||
"background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px; margin-right: 10px;"
|
||||
)
|
||||
export_word_btn.clicked.connect(self.export_word)
|
||||
|
||||
copy_btn = QPushButton("复制内容")
|
||||
copy_btn.setFixedSize(120, 32)
|
||||
copy_btn.setStyleSheet(
|
||||
"background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px;"
|
||||
)
|
||||
copy_btn.clicked.connect(self.copy_result)
|
||||
|
||||
output_buttons_layout.addWidget(export_word_btn)
|
||||
output_buttons_layout.addWidget(copy_btn)
|
||||
|
||||
output_header_layout.addWidget(output_label)
|
||||
output_header_layout.addStretch()
|
||||
output_header_layout.addLayout(output_buttons_layout)
|
||||
|
||||
self.result_text = QTextEdit()
|
||||
self.result_text.setReadOnly(True)
|
||||
self.result_text.setStyleSheet(
|
||||
"border: 1px solid #e2e8f0; border-radius: 6px; padding: 10px;"
|
||||
)
|
||||
|
||||
output_layout.addLayout(output_header_layout)
|
||||
output_layout.addWidget(self.result_text)
|
||||
|
||||
# 将所有组件添加到主布局
|
||||
main_layout.addWidget(title_label)
|
||||
main_layout.addLayout(model_layout)
|
||||
main_layout.addWidget(self.advanced_group)
|
||||
main_layout.addLayout(input_layout)
|
||||
main_layout.addWidget(self.translate_btn)
|
||||
main_layout.addLayout(output_layout)
|
||||
|
||||
# 状态栏
|
||||
self.status_bar = QStatusBar()
|
||||
self.setStatusBar(self.status_bar)
|
||||
self.status_label = QLabel()
|
||||
self.status_bar.addWidget(self.status_label)
|
||||
|
||||
def init_status_bar_timer(self):
|
||||
"""初始化状态栏定时器"""
|
||||
self.status_timer = QTimer()
|
||||
self.status_timer.setInterval(1000) # 每秒更新一次
|
||||
self.status_timer.timeout.connect(self.update_status_bar)
|
||||
self.status_timer.start()
|
||||
|
||||
def update_status_bar(self):
|
||||
"""更新状态栏显示"""
|
||||
status_text = self.system_monitor.get_status_text()
|
||||
self.status_label.setText(status_text)
|
||||
|
||||
def load_default_model(self):
|
||||
"""加载默认模型"""
|
||||
if os.path.exists(self.default_model_path):
|
||||
self.model_status_label.setText("模型加载中...")
|
||||
if self.translator.load_model(self.default_model_path):
|
||||
self.model_status_label.setText("● 模型就绪")
|
||||
self.model_status_label.setStyleSheet("color: #38a169;")
|
||||
self.model_info_label.setText(f"{self.translator.get_model_info()}")
|
||||
else:
|
||||
self.model_status_label.setText("● 模型加载失败")
|
||||
self.model_status_label.setStyleSheet("color: #e53e3e;")
|
||||
else:
|
||||
self.model_status_label.setText("● 模型文件不存在")
|
||||
self.model_status_label.setStyleSheet("color: #e53e3e;")
|
||||
|
||||
def change_model(self):
|
||||
"""更换模型"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择模型文件", "", "GGUF模型文件 (*.gguf)"
|
||||
)
|
||||
if file_path:
|
||||
self.model_status_label.setText("模型加载中...")
|
||||
if self.translator.load_model(file_path):
|
||||
self.model_status_label.setText("● 模型就绪")
|
||||
self.model_status_label.setStyleSheet("color: #38a169;")
|
||||
self.model_info_label.setText(f"{self.translator.get_model_info()}")
|
||||
QMessageBox.information(self, "成功", "模型加载成功")
|
||||
else:
|
||||
self.model_status_label.setText("● 模型加载失败")
|
||||
self.model_status_label.setStyleSheet("color: #e53e3e;")
|
||||
QMessageBox.critical(self, "错误", "模型加载失败")
|
||||
|
||||
def add_term(self):
|
||||
"""添加术语定义"""
|
||||
term_text = self.term_input.text().strip()
|
||||
if term_text:
|
||||
if "=" in term_text:
|
||||
self.terms.append(term_text)
|
||||
self.terms_list.addItem(term_text)
|
||||
self.term_input.clear()
|
||||
else:
|
||||
QMessageBox.warning(self, "警告", "术语格式不正确,请使用 'A = B' 格式")
|
||||
|
||||
def show_term_context_menu(self, pos):
|
||||
"""显示术语项右键菜单"""
|
||||
from PySide6.QtWidgets import QMenu
|
||||
menu = QMenu()
|
||||
delete_action = menu.addAction("删除")
|
||||
action = menu.exec_(self.terms_list.mapToGlobal(pos))
|
||||
if action == delete_action:
|
||||
selected_items = self.terms_list.selectedItems()
|
||||
for item in selected_items:
|
||||
self.terms.remove(item.text())
|
||||
self.terms_list.takeItem(self.terms_list.row(item))
|
||||
|
||||
def import_word(self):
|
||||
"""导入Word文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "导入Word文件", "", "Word文件 (*.docx)"
|
||||
)
|
||||
if file_path:
|
||||
try:
|
||||
text = self.word_handler.import_docx(file_path)
|
||||
self.source_text.setText(text)
|
||||
QMessageBox.information(self, "成功", "Word文件导入成功")
|
||||
except Exception as e:
|
||||
logger.error(f"导入Word文件失败: {e}")
|
||||
QMessageBox.critical(self, "错误", f"Word文件导入失败: {str(e)}")
|
||||
|
||||
def export_word(self):
|
||||
"""导出Word文件"""
|
||||
result_text = self.result_text.toPlainText()
|
||||
if not result_text.strip():
|
||||
QMessageBox.warning(self, "警告", "译文结果为空,无法导出")
|
||||
return
|
||||
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self, "导出Word文件", "", "Word文件 (*.docx)"
|
||||
)
|
||||
if file_path:
|
||||
try:
|
||||
self.word_handler.export_docx(file_path, result_text)
|
||||
QMessageBox.information(self, "成功", "Word文件导出成功")
|
||||
except Exception as e:
|
||||
logger.error(f"导出Word文件失败: {e}")
|
||||
QMessageBox.critical(self, "错误", f"Word文件导出失败: {str(e)}")
|
||||
|
||||
def copy_result(self):
|
||||
"""复制译文结果"""
|
||||
result_text = self.result_text.toPlainText()
|
||||
if result_text.strip():
|
||||
from PySide6.QtGui import QClipboard
|
||||
clipboard = QApplication.clipboard()
|
||||
clipboard.setText(result_text)
|
||||
QMessageBox.information(self, "成功", "译文已复制到剪贴板")
|
||||
else:
|
||||
QMessageBox.warning(self, "警告", "译文结果为空,无法复制")
|
||||
|
||||
def start_translation(self):
|
||||
"""开始翻译"""
|
||||
source_text = self.source_text.toPlainText().strip()
|
||||
if not source_text:
|
||||
QMessageBox.warning(self, "警告", "原文内容为空,无法翻译")
|
||||
return
|
||||
|
||||
if not self.translator.is_ready:
|
||||
QMessageBox.warning(self, "警告", "模型未就绪,无法翻译")
|
||||
return
|
||||
|
||||
# 禁用翻译按钮
|
||||
self.translate_btn.setEnabled(False)
|
||||
self.translate_btn.setText("翻译中...")
|
||||
|
||||
# 获取上下文和术语
|
||||
context = self.context_edit.toPlainText().strip()
|
||||
terms = self.terms if self.terms else None
|
||||
|
||||
# 执行翻译(在后台线程中执行,避免UI卡顿)
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
|
||||
class TranslationThread(QThread):
|
||||
finished = Signal(str)
|
||||
|
||||
def __init__(self, translator, text, context, terms):
|
||||
super().__init__()
|
||||
self.translator = translator
|
||||
self.text = text
|
||||
self.context = context
|
||||
self.terms = terms
|
||||
|
||||
def run(self):
|
||||
result = self.translator.translate(self.text, self.context, self.terms)
|
||||
self.finished.emit(result)
|
||||
|
||||
self.translation_thread = TranslationThread(self.translator, source_text, context, terms)
|
||||
self.translation_thread.finished.connect(self.on_translation_finished)
|
||||
self.translation_thread.start()
|
||||
|
||||
def on_translation_finished(self, result):
|
||||
"""翻译完成回调"""
|
||||
# 启用翻译按钮
|
||||
self.translate_btn.setEnabled(True)
|
||||
self.translate_btn.setText("开始翻译")
|
||||
|
||||
if result:
|
||||
self.result_text.setText(result)
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "翻译失败,请检查日志")
|
||||
|
||||
# 修复导入问题
|
||||
from PySide6.QtWidgets import QApplication
|
||||
27
utils/logger.py
Normal file
27
utils/logger.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from loguru import logger
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 创建logs目录
|
||||
logs_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs')
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
|
||||
# 配置日志
|
||||
logger.remove()
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level="INFO",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
|
||||
colorize=True
|
||||
)
|
||||
logger.add(
|
||||
os.path.join(logs_dir, "translate.log"),
|
||||
level="DEBUG",
|
||||
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}.{function}:{line} | {message}",
|
||||
rotation="10 MB",
|
||||
retention="7 days",
|
||||
compression="zip"
|
||||
)
|
||||
|
||||
# 导出logger实例
|
||||
__all__ = ["logger"]
|
||||
57
utils/system_monitor.py
Normal file
57
utils/system_monitor.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import psutil
|
||||
from .logger import logger
|
||||
|
||||
# 尝试导入GPUtil,如果失败则设置has_gpu为False
|
||||
try:
|
||||
import GPUtil
|
||||
GPUtil_available = True
|
||||
except ImportError:
|
||||
logger.warning("GPUtil模块未找到,将禁用GPU监控")
|
||||
GPUtil_available = False
|
||||
|
||||
class SystemMonitor:
|
||||
def __init__(self):
|
||||
self.cpu_usage = 0.0
|
||||
self.memory_usage = 0.0
|
||||
self.gpu_memory_usage = 0.0
|
||||
self.has_gpu = self._check_gpu()
|
||||
|
||||
def _check_gpu(self):
|
||||
"""检查是否有可用的GPU"""
|
||||
if not GPUtil_available:
|
||||
return False
|
||||
try:
|
||||
gpus = GPUtil.getGPUs()
|
||||
return len(gpus) > 0
|
||||
except Exception as e:
|
||||
logger.warning(f"GPU检查失败: {e}")
|
||||
return False
|
||||
|
||||
def update_metrics(self):
|
||||
"""更新系统资源使用情况"""
|
||||
try:
|
||||
# 更新CPU使用率
|
||||
self.cpu_usage = psutil.cpu_percent(interval=0.1)
|
||||
|
||||
# 更新内存使用率
|
||||
memory = psutil.virtual_memory()
|
||||
self.memory_usage = memory.percent
|
||||
|
||||
# 更新GPU内存使用率(如果有GPU)
|
||||
if self.has_gpu:
|
||||
try:
|
||||
gpus = GPUtil.getGPUs()
|
||||
if gpus:
|
||||
self.gpu_memory_usage = gpus[0].memoryUtil * 100
|
||||
except Exception as e:
|
||||
logger.warning(f"GPU内存使用率获取失败: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"系统资源监控更新失败: {e}")
|
||||
|
||||
def get_status_text(self):
|
||||
"""获取状态栏显示文本"""
|
||||
self.update_metrics()
|
||||
status = f"CPU: {self.cpu_usage:.1f}% | 内存: {self.memory_usage:.1f}%"
|
||||
if self.has_gpu:
|
||||
status += f" | GPU内存: {self.gpu_memory_usage:.1f}%"
|
||||
return status
|
||||
39
utils/word_handler.py
Normal file
39
utils/word_handler.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from docx import Document
|
||||
from .logger import logger
|
||||
|
||||
class WordHandler:
|
||||
@staticmethod
|
||||
def import_docx(file_path):
|
||||
"""从docx文件中提取文本内容"""
|
||||
try:
|
||||
doc = Document(file_path)
|
||||
text = []
|
||||
|
||||
for paragraph in doc.paragraphs:
|
||||
if paragraph.text.strip():
|
||||
text.append(paragraph.text)
|
||||
|
||||
logger.info(f"成功从 {file_path} 导入文本,共 {len(text)} 段")
|
||||
return "\n\n".join(text)
|
||||
except Exception as e:
|
||||
logger.error(f"导入Word文件失败: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def export_docx(file_path, content):
|
||||
"""将文本内容导出为docx文件"""
|
||||
try:
|
||||
doc = Document()
|
||||
|
||||
# 将内容按段落分割并添加到文档
|
||||
paragraphs = content.split("\n\n")
|
||||
for para_text in paragraphs:
|
||||
if para_text.strip():
|
||||
doc.add_paragraph(para_text.strip())
|
||||
|
||||
doc.save(file_path)
|
||||
logger.info(f"成功将文本导出到 {file_path},共 {len(paragraphs)} 段")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"导出Word文件失败: {e}")
|
||||
raise
|
||||
Reference in New Issue
Block a user