From 136994db9031e01e21ef58b3cb535964e295040f Mon Sep 17 00:00:00 2001 From: xiaji Date: Wed, 14 Jan 2026 15:10:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=9F=BA=E4=BA=8EPyS?= =?UTF-8?q?ide6=E7=9A=84=E7=BF=BB=E8=AF=91GUI=E5=B7=A5=E5=85=B7=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加主程序入口、GUI界面、翻译核心逻辑、Word文件处理、系统监控和日志模块 --- .gitignore | 27 ++ .../documents/实现基于PySide6的翻译GUI工具.md | 88 ++++ main.py | 27 ++ requirements.txt | 6 + translator.py | 105 +++++ ui/main_window.py | 386 ++++++++++++++++++ utils/logger.py | 27 ++ utils/system_monitor.py | 57 +++ utils/word_handler.py | 39 ++ 9 files changed, 762 insertions(+) create mode 100644 .gitignore create mode 100644 .trae/documents/实现基于PySide6的翻译GUI工具.md create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 translator.py create mode 100644 ui/main_window.py create mode 100644 utils/logger.py create mode 100644 utils/system_monitor.py create mode 100644 utils/word_handler.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc2e270 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# 虚拟环境 +venv/ +env/ +*.venv/ +*.env/ + +# Python编译文件 +*.pyc +__pycache__/ + +# 日志文件 +logs/ +*.log + +# IDE配置 +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# 操作系统文件 +.DS_Store +Thumbs.db + +# 模型文件 +models/*.gguf \ No newline at end of file diff --git a/.trae/documents/实现基于PySide6的翻译GUI工具.md b/.trae/documents/实现基于PySide6的翻译GUI工具.md new file mode 100644 index 0000000..0ded97c --- /dev/null +++ b/.trae/documents/实现基于PySide6的翻译GUI工具.md @@ -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. 未来扩展考虑 + +- 支持更多模型格式 +- 实现批量翻译功能 +- 支持更多文档格式 +- 添加翻译历史记录 +- 实现翻译质量评估 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..9e6d4a5 --- /dev/null +++ b/main.py @@ -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() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..98dab45 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +PySide6 +llama-cpp-python +python-docx +loguru +psutil +GPUtil \ No newline at end of file diff --git a/translator.py b/translator.py new file mode 100644 index 0000000..232a64a --- /dev/null +++ b/translator.py @@ -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 "未加载模型" \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py new file mode 100644 index 0000000..ae1fbb3 --- /dev/null +++ b/ui/main_window.py @@ -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 \ No newline at end of file diff --git a/utils/logger.py b/utils/logger.py new file mode 100644 index 0000000..bc81f53 --- /dev/null +++ b/utils/logger.py @@ -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"] \ No newline at end of file diff --git a/utils/system_monitor.py b/utils/system_monitor.py new file mode 100644 index 0000000..5b8bb6d --- /dev/null +++ b/utils/system_monitor.py @@ -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 \ No newline at end of file diff --git a/utils/word_handler.py b/utils/word_handler.py new file mode 100644 index 0000000..e0b9353 --- /dev/null +++ b/utils/word_handler.py @@ -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 \ No newline at end of file