from PySide6.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QPushButton, QLineEdit, QLabel, QGroupBox, QScrollArea, QToolButton, QStatusBar, QFileDialog, QMessageBox, QListWidget, QListWidgetItem, QFrame, QSplitter, QDialog, QProgressBar, QGridLayout ) from PySide6.QtCore import Qt, QTimer, QSize, QPropertyAnimation, QEasingCurve 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 SettingsDialog(QDialog): """设置对话框,包含高级辅助功能和模型管理""" def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("设置") self.setMinimumSize(600, 700) self.setModal(True) # 从父窗口获取数据 self.parent_window = parent self.terms = parent.terms if parent else [] self.translator = parent.translator if parent else None # 初始化UI self.init_ui() # 加载现有数据 self.load_existing_data() def init_ui(self): """初始化设置对话框UI""" main_layout = QVBoxLayout(self) main_layout.setSpacing(20) main_layout.setContentsMargins(20, 20, 20, 20) # 标题 title_label = QLabel("设置") title_font = QFont() title_font.setPointSize(20) title_font.setBold(True) title_label.setFont(title_font) title_label.setStyleSheet("color: #1a365d; margin-bottom: 10px;") # 创建滚动区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll_content = QWidget() scroll_layout = QVBoxLayout(scroll_content) scroll_layout.setSpacing(20) # ====== 模型管理部分 ====== model_group = QGroupBox("模型管理") model_group.setStyleSheet(""" QGroupBox { font-weight: bold; color: #4a5568; border: 1px solid #e2e8f0; border-radius: 8px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } """) model_layout = QVBoxLayout(model_group) # 当前模型信息 current_model_layout = QHBoxLayout() current_model_label = QLabel("当前模型:") current_model_label.setStyleSheet("font-weight: bold; color: #4a5568;") self.current_model_info = QLabel("未加载") self.current_model_info.setStyleSheet("color: #2d3748;") current_model_layout.addWidget(current_model_label) current_model_layout.addWidget(self.current_model_info) current_model_layout.addStretch() # 模型状态 model_status_layout = QHBoxLayout() model_status_label = QLabel("模型状态:") model_status_label.setStyleSheet("font-weight: bold; color: #4a5568;") self.model_status_display = QLabel("● 未知") self.model_status_display.setStyleSheet("color: #a0aec0;") model_status_layout.addWidget(model_status_label) model_status_layout.addWidget(self.model_status_display) model_status_layout.addStretch() # 更换模型按钮 change_model_layout = QHBoxLayout() self.change_model_btn = QPushButton("更换模型") self.change_model_btn.setFixedSize(120, 36) self.change_model_btn.setStyleSheet( "background-color: #3182ce; color: white; border: none; border-radius: 6px; font-weight: bold;" ) self.change_model_btn.clicked.connect(self.change_model) change_model_layout.addStretch() change_model_layout.addWidget(self.change_model_btn) model_layout.addLayout(current_model_layout) model_layout.addLayout(model_status_layout) model_layout.addLayout(change_model_layout) # ====== 文本背景/场景介绍 ====== context_group = QGroupBox("文本背景 / 场景介绍") context_group.setStyleSheet(""" QGroupBox { font-weight: bold; color: #4a5568; border: 1px solid #e2e8f0; border-radius: 8px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } """) context_layout = QVBoxLayout(context_group) self.context_edit = QTextEdit() self.context_edit.setPlaceholderText("例如:这是一份关于建筑工程的合同...") self.context_edit.setFixedHeight(100) self.context_edit.setStyleSheet( "border: 1px solid #e2e8f0; border-radius: 6px; padding: 8px;" ) context_layout.addWidget(self.context_edit) # ====== 术语定义 ====== terms_group = QGroupBox("术语簿 (定义 A=B)") terms_group.setStyleSheet(""" QGroupBox { font-weight: bold; color: #4a5568; border: 1px solid #e2e8f0; border-radius: 8px; margin-top: 10px; padding-top: 10px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; } """) terms_layout = QVBoxLayout(terms_group) # 术语列表 self.terms_list = QListWidget() self.terms_list.setStyleSheet( "border: 1px solid #e2e8f0; border-radius: 6px; min-height: 150px;" ) # 术语输入区域 terms_input_layout = QHBoxLayout() self.term_input = QLineEdit() self.term_input.setPlaceholderText("输入术语格式:原文 = 译文") self.term_input.setStyleSheet( "border: 1px solid #e2e8f0; border-radius: 6px; padding: 8px;" ) add_term_btn = QPushButton("添加") add_term_btn.setFixedSize(80, 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) # 术语操作按钮 terms_buttons_layout = QHBoxLayout() delete_term_btn = QPushButton("删除选中") delete_term_btn.setFixedSize(100, 32) delete_term_btn.setStyleSheet( "background-color: #e53e3e; color: white; border: none; border-radius: 6px;" ) delete_term_btn.clicked.connect(self.delete_selected_term) clear_terms_btn = QPushButton("清空所有") clear_terms_btn.setFixedSize(100, 32) clear_terms_btn.setStyleSheet( "background-color: #a0aec0; color: white; border: none; border-radius: 6px;" ) clear_terms_btn.clicked.connect(self.clear_all_terms) terms_buttons_layout.addWidget(delete_term_btn) terms_buttons_layout.addWidget(clear_terms_btn) terms_buttons_layout.addStretch() terms_layout.addWidget(self.terms_list) terms_layout.addLayout(terms_input_layout) terms_layout.addLayout(terms_buttons_layout) # 将所有组添加到滚动布局 scroll_layout.addWidget(model_group) scroll_layout.addWidget(context_group) scroll_layout.addWidget(terms_group) scroll_layout.addStretch() # 设置滚动区域内容 scroll_area.setWidget(scroll_content) # 按钮区域 buttons_layout = QHBoxLayout() save_btn = QPushButton("保存并应用") save_btn.setFixedSize(120, 40) save_btn.setStyleSheet( "background-color: #38a169; color: white; border: none; border-radius: 6px; font-weight: bold;" ) save_btn.clicked.connect(self.save_and_apply) cancel_btn = QPushButton("取消") cancel_btn.setFixedSize(120, 40) cancel_btn.setStyleSheet( "background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px;" ) cancel_btn.clicked.connect(self.reject) buttons_layout.addStretch() buttons_layout.addWidget(cancel_btn) buttons_layout.addWidget(save_btn) # 添加到主布局 main_layout.addWidget(title_label) main_layout.addWidget(scroll_area) main_layout.addLayout(buttons_layout) def load_existing_data(self): """加载现有数据""" if self.parent_window: # 加载模型信息 self.update_model_info() # 加载上下文 if hasattr(self.parent_window, 'context_edit'): self.context_edit.setText(self.parent_window.context_edit.toPlainText()) # 加载术语 for term in self.terms: self.terms_list.addItem(term) def update_model_info(self): """更新模型信息显示""" if self.parent_window and hasattr(self.parent_window, 'translator'): translator = self.parent_window.translator if translator.is_ready: self.model_status_display.setText("● 就绪") self.model_status_display.setStyleSheet("color: #38a169;") model_info = translator.get_model_info() if model_info: self.current_model_info.setText(model_info) else: self.current_model_info.setText("模型已加载") else: self.model_status_display.setText("● 未就绪") self.model_status_display.setStyleSheet("color: #e53e3e;") self.current_model_info.setText("模型未加载") def change_model(self): """更换模型""" if not self.parent_window: return file_path, _ = QFileDialog.getOpenFileName( self, "选择模型文件", "", "GGUF模型文件 (*.gguf)" ) if file_path: # 显示加载状态 self.model_status_display.setText("● 加载中...") self.model_status_display.setStyleSheet("color: #d69e2e;") self.current_model_info.setText("正在加载模型...") # 加载模型 if self.parent_window.translator.load_model(file_path): self.model_status_display.setText("● 就绪") self.model_status_display.setStyleSheet("color: #38a169;") model_info = self.parent_window.translator.get_model_info() if model_info: self.current_model_info.setText(model_info) else: self.current_model_info.setText(os.path.basename(file_path)) QMessageBox.information(self, "成功", "模型加载成功") # 更新父窗口的模型状态显示 if hasattr(self.parent_window, 'model_status_label'): self.parent_window.model_status_label.setText("● 模型就绪") self.parent_window.model_status_label.setStyleSheet("color: #38a169;") if hasattr(self.parent_window, 'model_info_label'): self.parent_window.model_info_label.setText(f"{model_info if model_info else os.path.basename(file_path)}") else: self.model_status_display.setText("● 加载失败") self.model_status_display.setStyleSheet("color: #e53e3e;") self.current_model_info.setText("模型加载失败") 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, "警告", "术语格式不正确,请使用 '原文 = 译文' 格式") def delete_selected_term(self): """删除选中的术语""" selected_items = self.terms_list.selectedItems() if not selected_items: QMessageBox.warning(self, "警告", "请先选择要删除的术语") return for item in selected_items: term_text = item.text() if term_text in self.terms: self.terms.remove(term_text) self.terms_list.takeItem(self.terms_list.row(item)) def clear_all_terms(self): """清空所有术语""" if self.terms_list.count() == 0: return reply = QMessageBox.question( self, "确认", "确定要清空所有术语吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: self.terms.clear() self.terms_list.clear() def save_and_apply(self): """保存并应用设置""" # 保存到父窗口 if self.parent_window: # 保存上下文 if hasattr(self.parent_window, 'context_edit'): self.parent_window.context_edit.setText(self.context_edit.toPlainText()) # 保存术语 self.parent_window.terms = self.terms.copy() # 更新术语列表显示(如果父窗口有显示的话) if hasattr(self.parent_window, 'terms_list'): self.parent_window.terms_list.clear() for term in self.terms: self.parent_window.terms_list.addItem(term) QMessageBox.information(self, "成功", "设置已保存并应用") self.accept() 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 = [] # 倒计时相关 self.countdown_timer = None self.countdown_seconds = 60 self.countdown_label = None # 初始化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_bar = QWidget() title_bar_layout = QHBoxLayout(title_bar) title_bar_layout.setContentsMargins(0, 0, 0, 0) title_bar_layout.setSpacing(0) # 应用标题 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;") # 右侧设置按钮 self.settings_btn = QPushButton("⚙ 设置") self.settings_btn.setFixedSize(80, 36) self.settings_btn.setStyleSheet(""" QPushButton { background-color: #e2e8f0; color: #2d3748; border: none; border-radius: 6px; font-weight: bold; } QPushButton:hover { background-color: #cbd5e0; } QPushButton:pressed { background-color: #a0aec0; } """) self.settings_btn.clicked.connect(self.open_settings) title_bar_layout.addWidget(title_label) title_bar_layout.addStretch() title_bar_layout.addWidget(self.settings_btn) # 模型信息显示(只显示状态,更换功能在设置中) 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;") model_layout.addWidget(self.model_status_label) model_layout.addWidget(self.model_info_label) model_layout.addStretch() # 原文输入区域 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; min-height: 150px;" ) 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) # 倒计时提示区域(初始隐藏) self.countdown_container = QWidget() self.countdown_container.setVisible(False) countdown_layout = QHBoxLayout(self.countdown_container) countdown_layout.setContentsMargins(0, 5, 0, 5) countdown_icon = QLabel("⏳") countdown_icon.setStyleSheet("font-size: 16px; color: #d69e2e;") self.countdown_label = QLabel("大模型正在翻译中,预计剩余时间:60秒") self.countdown_label.setStyleSheet("color: #d69e2e; font-weight: bold;") countdown_layout.addWidget(countdown_icon) countdown_layout.addWidget(self.countdown_label) countdown_layout.addStretch() # 译文结果区域 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; min-height: 150px;" ) output_layout.addLayout(output_header_layout) output_layout.addWidget(self.result_text) # 将所有组件添加到主布局 main_layout.addWidget(title_bar) main_layout.addLayout(model_layout) main_layout.addLayout(input_layout) main_layout.addWidget(self.translate_btn) main_layout.addWidget(self.countdown_container) 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) # 初始化上下文编辑框(用于存储设置) self.context_edit = QTextEdit() self.context_edit.setVisible(False) 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 open_settings(self): """打开设置对话框""" dialog = SettingsDialog(self) dialog.exec() def start_countdown(self): """开始60秒倒计时""" self.countdown_seconds = 60 self.countdown_container.setVisible(True) self.update_countdown_display() # 创建倒计时定时器 self.countdown_timer = QTimer() self.countdown_timer.setInterval(1000) # 每秒触发一次 self.countdown_timer.timeout.connect(self.update_countdown) self.countdown_timer.start() def update_countdown(self): """更新倒计时""" self.countdown_seconds -= 1 self.update_countdown_display() if self.countdown_seconds <= 0: self.stop_countdown() def update_countdown_display(self): """更新倒计时显示""" if self.countdown_label: self.countdown_label.setText(f"大模型正在翻译中,预计剩余时间:{self.countdown_seconds}秒") def stop_countdown(self): """停止倒计时""" if self.countdown_timer: self.countdown_timer.stop() self.countdown_timer = None self.countdown_container.setVisible(False) 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 from PySide6.QtWidgets import QApplication 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 hasattr(self.translator, 'llama_cpp_available') or not self.translator.llama_cpp_available: QMessageBox.warning(self, "警告", "llama-cpp-python库未安装,无法执行翻译功能") return if not self.translator.is_ready: QMessageBox.warning(self, "警告", "模型未就绪,无法翻译") return # 禁用翻译按钮 self.translate_btn.setEnabled(False) self.translate_btn.setText("翻译中...") # 开始倒计时 self.start_countdown() # 获取上下文和术语 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.stop_countdown() # 启用翻译按钮 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