From 79075ee2ba5c8278907cfec5f4633fcd89c908df Mon Sep 17 00:00:00 2001 From: xiaji Date: Fri, 16 Jan 2026 12:32:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=96=B0=E5=A2=9E=E5=A4=9A?= =?UTF-8?q?=E4=B8=AAUI=E6=B5=8B=E8=AF=95=E7=AA=97=E5=8F=A3=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=BB=E7=AA=97=E5=8F=A3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加main_window_test.py用于UI功能测试 - 添加main_window_simple.py简化版界面 - 添加main_window_new.py和main_window_final.py完整功能界面 - 优化主窗口高级面板切换逻辑 - 更新.gitignore忽略测试文件 --- .gitignore | 8 +- ui/main_window.py | 7 +- ui/main_window_final.py | 749 +++++++++++++++++++++++++++++++++++++++ ui/main_window_new.py | 635 +++++++++++++++++++++++++++++++++ ui/main_window_simple.py | 475 +++++++++++++++++++++++++ ui/main_window_test.py | 264 ++++++++++++++ 6 files changed, 2133 insertions(+), 5 deletions(-) create mode 100644 ui/main_window_final.py create mode 100644 ui/main_window_new.py create mode 100644 ui/main_window_simple.py create mode 100644 ui/main_window_test.py diff --git a/.gitignore b/.gitignore index cc2e270..cf48f15 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,10 @@ logs/ Thumbs.db # 模型文件 -models/*.gguf \ No newline at end of file +models/*.gguf + +# 测试文件 +test* + +# 自己 +.gitignore \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index 8163117..35a81f7 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -310,11 +310,10 @@ class MainWindow(QMainWindow): else: QMessageBox.warning(self, "警告", "术语格式不正确,请使用 'A = B' 格式") - def toggle_advanced_panel(self): + def toggle_advanced_panel(self, checked): """切换高级辅助面板的显示/隐藏状态""" - is_visible = not self.advanced_content.isVisible() - self.advanced_content.setVisible(is_visible) - self.advanced_toggle.setText("▲" if is_visible else "▼") + self.advanced_content.setVisible(checked) + self.advanced_toggle.setText("▲" if checked else "▼") def show_term_context_menu(self, pos): """显示术语项右键菜单""" diff --git a/ui/main_window_final.py b/ui/main_window_final.py new file mode 100644 index 0000000..2e2d353 --- /dev/null +++ b/ui/main_window_final.py @@ -0,0 +1,749 @@ +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 \ No newline at end of file diff --git a/ui/main_window_new.py b/ui/main_window_new.py new file mode 100644 index 0000000..96ca97e --- /dev/null +++ b/ui/main_window_new.py @@ -0,0 +1,635 @@ +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(500, 600) + self.setModal(True) + + # 从父窗口获取数据 + self.parent_window = parent + self.terms = parent.terms if parent else [] + + # 初始化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(18) + title_font.setBold(True) + title_label.setFont(title_font) + title_label.setStyleSheet("color: #1a365d; margin-bottom: 10px;") + + # 文本背景/场景介绍 + 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) + + # 按钮区域 + 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(context_group) + main_layout.addWidget(terms_group) + main_layout.addStretch() + main_layout.addLayout(buttons_layout) + + def load_existing_data(self): + """加载现有数据""" + if self.parent_window: + # 加载上下文 + 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 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;") + + self.change_model_btn = QPushButton("更换模型") + self.change_model_btn.setFixedSize(100, 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) + + # 原文输入区域 + 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 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 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 \ No newline at end of file diff --git a/ui/main_window_simple.py b/ui/main_window_simple.py new file mode 100644 index 0000000..83a03d0 --- /dev/null +++ b/ui/main_window_simple.py @@ -0,0 +1,475 @@ +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.logger import logger +import os + +class SettingsDialog(QDialog): + """设置对话框,包含高级辅助功能""" + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("设置 - 高级辅助") + self.setMinimumSize(500, 600) + self.setModal(True) + + # 从父窗口获取数据 + self.parent_window = parent + self.terms = parent.terms if parent else [] + + # 初始化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(18) + title_font.setBold(True) + title_label.setFont(title_font) + title_label.setStyleSheet("color: #1a365d; margin-bottom: 10px;") + + # 文本背景/场景介绍 + 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) + + # 按钮区域 + 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(context_group) + main_layout.addWidget(terms_group) + main_layout.addStretch() + main_layout.addLayout(buttons_layout) + + def load_existing_data(self): + """加载现有数据""" + if self.parent_window: + # 加载上下文 + 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 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 MainWindowSimple(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("PrivaTrans - 简化测试版") + self.setMinimumSize(800, 600) + + # 术语列表 + self.terms = [] + + # 倒计时相关 + self.countdown_timer = None + self.countdown_seconds = 60 + self.countdown_label = None + + # 初始化UI + self.init_ui() + + 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: #38a169;") + + 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;") + + input_header_layout.addWidget(input_label) + input_header_layout.addStretch() + + self.source_text = QTextEdit() + self.source_text.setPlaceholderText("在此输入要翻译的文本...") + 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_test) + + # 倒计时提示区域(初始隐藏) + 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_header_layout.addWidget(output_label) + output_header_layout.addStretch() + + self.result_text = QTextEdit() + self.result_text.setReadOnly(True) + self.result_text.setPlaceholderText("翻译结果将显示在这里...") + 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 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 start_translation_test(self): + """开始翻译测试""" + source_text = self.source_text.toPlainText().strip() + if not source_text: + 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 + + # 模拟翻译过程(5秒后完成) + from PySide6.QtCore import QTimer + self.simulation_timer = QTimer() + self.simulation_timer.setSingleShot(True) + self.simulation_timer.setInterval(5000) # 5秒后完成 + self.simulation_timer.timeout.connect(lambda: self.on_translation_finished(f"【测试翻译结果】\n\n原文:{source_text}\n\n译文:这是模拟的翻译结果\n\n上下文:{context if context else '无'}\n术语:{', '.join(terms) if terms else '无'}")) + self.simulation_timer.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) + QMessageBox.information(self, "测试完成", "翻译测试完成!\n\n✓ 设置按钮功能正常\n✓ 倒计时功能正常\n✓ 界面布局优化完成") + +# 修复导入问题 +from PySide6.QtWidgets import QApplication \ No newline at end of file diff --git a/ui/main_window_test.py b/ui/main_window_test.py new file mode 100644 index 0000000..fe7309e --- /dev/null +++ b/ui/main_window_test.py @@ -0,0 +1,264 @@ +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.logger import logger +import os + +class MainWindowTest(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("PrivaTrans - UI测试") + self.setMinimumSize(800, 600) + + # 初始化组件 + self.terms = [] + + # 初始化UI + self.init_ui() + + # 初始化状态栏定时器 + self.init_status_bar_timer() + + 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 - UI测试") + 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("● UI测试模式 - 跳过模型加载") + self.model_status_label.setStyleSheet("color: #38a169;") + + 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() + + # 高级辅助面板(可折叠) + advanced_panel = QWidget() + advanced_layout = QVBoxLayout(advanced_panel) + advanced_layout.setSpacing(10) + advanced_layout.setContentsMargins(0, 0, 0, 0) + + # 高级辅助标题和折叠按钮 + advanced_header = QWidget() + header_layout = QHBoxLayout(advanced_header) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.setSpacing(10) + + advanced_title = QLabel("高级辅助 (背景与术语)") + advanced_title.setStyleSheet("color: #4a5568; font-weight: bold;") + + self.advanced_toggle = QToolButton() + self.advanced_toggle.setText("▼") + self.advanced_toggle.setCheckable(True) + self.advanced_toggle.setChecked(False) + self.advanced_toggle.setFixedSize(20, 20) + self.advanced_toggle.setStyleSheet( + "border: none; background: none; color: #4a5568;" + ) + self.advanced_toggle.clicked.connect(self.toggle_advanced_panel) + + header_layout.addWidget(advanced_title) + header_layout.addStretch() + header_layout.addWidget(self.advanced_toggle) + + advanced_layout.addWidget(advanced_header) + + # 高级辅助内容区域 + self.advanced_content = QWidget() + content_layout = QVBoxLayout(self.advanced_content) + content_layout.setSpacing(15) + content_layout.setContentsMargins(0, 0, 0, 0) + + # 文本背景/场景介绍 + context_label = QLabel("文本背景 / 场景介绍") + context_label.setStyleSheet("color: #4a5568;") + + 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;") + + 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;" + ) + + 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) + + content_layout.addWidget(context_label) + content_layout.addWidget(self.context_edit) + content_layout.addWidget(terms_label) + content_layout.addWidget(self.terms_list) + content_layout.addLayout(terms_input_layout) + + advanced_layout.addWidget(self.advanced_content) + + # 默认隐藏高级辅助内容 + self.advanced_content.setVisible(False) + + # 将高级辅助面板添加到主布局 + advanced_panel.setStyleSheet( + "border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; margin-top: 10px;" + ) + + # 原文输入区域 + input_layout = QVBoxLayout() + + input_header_layout = QHBoxLayout() + input_label = QLabel("原文内容") + input_label.setStyleSheet("color: #4a5568; font-weight: bold;") + + input_header_layout.addWidget(input_label) + input_header_layout.addStretch() + + self.source_text = QTextEdit() + self.source_text.setPlaceholderText("在此输入要翻译的文本...") + 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_test) + + # 译文结果区域 + output_layout = QVBoxLayout() + + output_header_layout = QHBoxLayout() + output_label = QLabel("译文结果") + output_label.setStyleSheet("color: #4a5568; font-weight: bold;") + + output_header_layout.addWidget(output_label) + output_header_layout.addStretch() + + self.result_text = QTextEdit() + self.result_text.setReadOnly(True) + self.result_text.setPlaceholderText("翻译结果将显示在这里...") + 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(advanced_panel) + 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("UI测试模式 - 折叠/展开按钮功能正常") + 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): + """更新状态栏显示""" + # 在测试模式下显示固定状态 + pass + + 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 toggle_advanced_panel(self, checked): + """切换高级辅助面板的显示/隐藏状态""" + self.advanced_content.setVisible(checked) + self.advanced_toggle.setText("▲" if checked else "▼") + + # 更新状态栏显示当前状态 + state = "展开" if checked else "折叠" + self.status_label.setText(f"高级辅助面板已{state} - 按钮功能正常") + + 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 start_translation_test(self): + """开始翻译测试""" + source_text = self.source_text.toPlainText().strip() + if not source_text: + QMessageBox.warning(self, "警告", "原文内容为空") + return + + # 在测试模式下显示模拟结果 + self.result_text.setText(f"【测试模式】翻译结果预览:\n\n{source_text}\n\n(这是测试模式,实际翻译需要加载模型)") + QMessageBox.information(self, "测试成功", "UI功能测试完成!\n\n✓ 折叠/展开按钮功能正常\n✓ 术语添加功能正常\n✓ 界面交互正常") \ No newline at end of file