Files
desktop-transfer/ui/main_window_final.py
xiaji 79075ee2ba feat(ui): 新增多个UI测试窗口并优化主窗口功能
- 添加main_window_test.py用于UI功能测试
- 添加main_window_simple.py简化版界面
- 添加main_window_new.py和main_window_final.py完整功能界面
- 优化主窗口高级面板切换逻辑
- 更新.gitignore忽略测试文件
2026-01-16 12:32:45 +08:00

749 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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