|
|
|
|
@ -12,6 +12,13 @@ from PyQt5.QtCore import Qt, pyqtSignal, QThread, QTimer, QSize, pyqtSlot
|
|
|
|
|
from PyQt5.QtGui import QFont, QColor, QTextCursor, QIcon, QPixmap
|
|
|
|
|
from PyQt5.QtGui import QTextDocument, QTextCharFormat
|
|
|
|
|
|
|
|
|
|
# 导入主题管理器
|
|
|
|
|
try:
|
|
|
|
|
from .theme_manager import theme_manager
|
|
|
|
|
except ImportError:
|
|
|
|
|
# 处理直接运行的情况
|
|
|
|
|
from ui.theme_manager import theme_manager
|
|
|
|
|
|
|
|
|
|
class AIChatPanel(QWidget):
|
|
|
|
|
"""AI对话面板"""
|
|
|
|
|
|
|
|
|
|
@ -34,6 +41,9 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 连接信号到槽
|
|
|
|
|
self.update_chat_display.connect(self.on_update_chat_display)
|
|
|
|
|
|
|
|
|
|
# 连接主题变化信号
|
|
|
|
|
theme_manager.theme_changed.connect(self.on_theme_changed)
|
|
|
|
|
|
|
|
|
|
def load_api_key(self):
|
|
|
|
|
"""从本地文件加载API密钥"""
|
|
|
|
|
@ -68,9 +78,45 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 清空历史按钮
|
|
|
|
|
clear_btn = QPushButton("清空")
|
|
|
|
|
clear_btn.setMaximumWidth(50)
|
|
|
|
|
clear_btn.setObjectName("clear_btn") # 设置对象名称
|
|
|
|
|
clear_btn.setMaximumWidth(60)
|
|
|
|
|
clear_btn.setFont(QFont("微软雅黑", 9))
|
|
|
|
|
clear_btn.clicked.connect(self.clear_history)
|
|
|
|
|
clear_btn.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
color: #333333;
|
|
|
|
|
border: 1px solid #d0d0d0;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
min-width: 50px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
border: 1px solid #c0c0c0;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:pressed {
|
|
|
|
|
background-color: #d0d0d0;
|
|
|
|
|
border: 1px solid #b0b0b0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 深色主题 */
|
|
|
|
|
QPushButton[darkTheme="true"] {
|
|
|
|
|
background-color: #3c3c3c;
|
|
|
|
|
color: #e0e0e0;
|
|
|
|
|
border: 1px solid #4c4c4c;
|
|
|
|
|
}
|
|
|
|
|
QPushButton[darkTheme="true"]:hover {
|
|
|
|
|
background-color: #4c4c4c;
|
|
|
|
|
border: 1px solid #5c5c5c;
|
|
|
|
|
}
|
|
|
|
|
QPushButton[darkTheme="true"]:pressed {
|
|
|
|
|
background-color: #5c5c5c;
|
|
|
|
|
border: 1px solid #6c6c6c;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
header_layout.addWidget(clear_btn)
|
|
|
|
|
|
|
|
|
|
main_layout.addLayout(header_layout)
|
|
|
|
|
@ -88,11 +134,19 @@ class AIChatPanel(QWidget):
|
|
|
|
|
QTextEdit {
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
border: 1px solid #d0d0d0;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-family: 'Segoe UI', '微软雅黑', sans-serif;
|
|
|
|
|
font-size: 10pt;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
|
|
|
|
|
font-size: 11pt;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
color: #333333;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 深色主题 */
|
|
|
|
|
QTextEdit[darkTheme="true"] {
|
|
|
|
|
background-color: #1e1e1e;
|
|
|
|
|
border: 1px solid #3c3c3c;
|
|
|
|
|
color: #e0e0e0;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
self.chat_display.setMinimumHeight(400)
|
|
|
|
|
@ -100,7 +154,7 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 输入区域
|
|
|
|
|
input_layout = QVBoxLayout()
|
|
|
|
|
input_layout.setSpacing(4)
|
|
|
|
|
input_layout.setSpacing(6)
|
|
|
|
|
|
|
|
|
|
# 输入框
|
|
|
|
|
self.input_field = QLineEdit()
|
|
|
|
|
@ -109,23 +163,34 @@ class AIChatPanel(QWidget):
|
|
|
|
|
QLineEdit {
|
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
|
border: 1px solid #d0d0d0;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-family: '微软雅黑', sans-serif;
|
|
|
|
|
font-size: 10pt;
|
|
|
|
|
padding: 6px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
|
|
|
|
|
font-size: 11pt;
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
color: #333333;
|
|
|
|
|
}
|
|
|
|
|
QLineEdit:focus {
|
|
|
|
|
border: 1px solid #0078d4;
|
|
|
|
|
border: 2px solid #0078d4;
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 深色主题 */
|
|
|
|
|
QLineEdit[darkTheme="true"] {
|
|
|
|
|
background-color: #2d2d2d;
|
|
|
|
|
border: 1px solid #3c3c3c;
|
|
|
|
|
color: #e0e0e0;
|
|
|
|
|
}
|
|
|
|
|
QLineEdit[darkTheme="true"]:focus {
|
|
|
|
|
border: 2px solid #0078d4;
|
|
|
|
|
background-color: #1e1e1e;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
self.input_field.returnPressed.connect(self.send_user_message)
|
|
|
|
|
input_layout.addWidget(self.input_field)
|
|
|
|
|
|
|
|
|
|
# 按钮区域
|
|
|
|
|
button_layout = QHBoxLayout()
|
|
|
|
|
button_layout.setSpacing(4)
|
|
|
|
|
button_layout.setSpacing(6)
|
|
|
|
|
|
|
|
|
|
# 发送按钮
|
|
|
|
|
self.send_btn = QPushButton("发送")
|
|
|
|
|
@ -135,9 +200,11 @@ class AIChatPanel(QWidget):
|
|
|
|
|
background-color: #0078d4;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
padding: 6px 16px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 8px 20px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
min-width: 70px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #0063b1;
|
|
|
|
|
@ -145,6 +212,17 @@ class AIChatPanel(QWidget):
|
|
|
|
|
QPushButton:pressed {
|
|
|
|
|
background-color: #005a9e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 深色主题 */
|
|
|
|
|
QPushButton[darkTheme="true"] {
|
|
|
|
|
background-color: #0078d4;
|
|
|
|
|
}
|
|
|
|
|
QPushButton[darkTheme="true"]:hover {
|
|
|
|
|
background-color: #0063b1;
|
|
|
|
|
}
|
|
|
|
|
QPushButton[darkTheme="true"]:pressed {
|
|
|
|
|
background-color: #005a9e;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
self.send_btn.clicked.connect(self.send_user_message)
|
|
|
|
|
button_layout.addStretch()
|
|
|
|
|
@ -161,7 +239,59 @@ class AIChatPanel(QWidget):
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
border-left: 1px solid #d0d0d0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 深色主题 */
|
|
|
|
|
AIChatPanel[darkTheme="true"] {
|
|
|
|
|
background-color: #2d2d2d;
|
|
|
|
|
border-left: 1px solid #3c3c3c;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 初始化主题
|
|
|
|
|
self.apply_theme()
|
|
|
|
|
|
|
|
|
|
def apply_theme(self):
|
|
|
|
|
"""应用当前主题"""
|
|
|
|
|
is_dark = theme_manager.is_dark_theme()
|
|
|
|
|
|
|
|
|
|
# 设置属性用于样式表选择器
|
|
|
|
|
self.setProperty("darkTheme", is_dark)
|
|
|
|
|
|
|
|
|
|
# 更新子控件的属性
|
|
|
|
|
self.chat_display.setProperty("darkTheme", is_dark)
|
|
|
|
|
self.input_field.setProperty("darkTheme", is_dark)
|
|
|
|
|
self.send_btn.setProperty("darkTheme", is_dark)
|
|
|
|
|
self.findChild(QPushButton, "clear_btn").setProperty("darkTheme", is_dark) if self.findChild(QPushButton, "clear_btn") else None
|
|
|
|
|
|
|
|
|
|
# 重新应用样式表
|
|
|
|
|
self.style().unpolish(self)
|
|
|
|
|
self.style().polish(self)
|
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
|
|
# 更新聊天显示样式
|
|
|
|
|
self.chat_display.style().unpolish(self.chat_display)
|
|
|
|
|
self.chat_display.style().polish(self.chat_display)
|
|
|
|
|
self.chat_display.update()
|
|
|
|
|
|
|
|
|
|
# 更新输入框样式
|
|
|
|
|
self.input_field.style().unpolish(self.input_field)
|
|
|
|
|
self.input_field.style().polish(self.input_field)
|
|
|
|
|
self.input_field.update()
|
|
|
|
|
|
|
|
|
|
# 更新按钮样式
|
|
|
|
|
self.send_btn.style().unpolish(self.send_btn)
|
|
|
|
|
self.send_btn.style().polish(self.send_btn)
|
|
|
|
|
self.send_btn.update()
|
|
|
|
|
|
|
|
|
|
clear_btn = self.findChild(QPushButton, "clear_btn")
|
|
|
|
|
if clear_btn:
|
|
|
|
|
clear_btn.style().unpolish(clear_btn)
|
|
|
|
|
clear_btn.style().polish(clear_btn)
|
|
|
|
|
clear_btn.update()
|
|
|
|
|
|
|
|
|
|
def on_theme_changed(self, is_dark):
|
|
|
|
|
"""主题变化处理"""
|
|
|
|
|
self.apply_theme()
|
|
|
|
|
|
|
|
|
|
def send_user_message(self):
|
|
|
|
|
"""发送用户消息"""
|
|
|
|
|
@ -211,20 +341,32 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 设置格式
|
|
|
|
|
char_format = QTextCharFormat()
|
|
|
|
|
char_format.setFont(QFont("微软雅黑", 10))
|
|
|
|
|
char_format.setFont(QFont("微软雅黑", 11))
|
|
|
|
|
|
|
|
|
|
if sender == "用户":
|
|
|
|
|
char_format.setForeground(QColor("#0078d4"))
|
|
|
|
|
char_format.setFontWeight(70)
|
|
|
|
|
# 根据主题设置用户消息颜色
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色
|
|
|
|
|
else:
|
|
|
|
|
char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色
|
|
|
|
|
char_format.setFontWeight(60) # 中等粗体
|
|
|
|
|
prefix = "你: "
|
|
|
|
|
else: # AI助手
|
|
|
|
|
char_format.setForeground(QColor("#333333"))
|
|
|
|
|
# 根据主题设置AI消息颜色
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
char_format.setForeground(QColor("#e0e0e0")) # 深色主题下的浅灰色
|
|
|
|
|
else:
|
|
|
|
|
char_format.setForeground(QColor("#000000")) # 浅色主题下的纯黑色,提高可读性
|
|
|
|
|
char_format.setFontWeight(50) # 正常粗体
|
|
|
|
|
prefix = "AI: "
|
|
|
|
|
|
|
|
|
|
# 插入时间戳
|
|
|
|
|
timestamp_format = QTextCharFormat()
|
|
|
|
|
timestamp_format.setForeground(QColor("#999999"))
|
|
|
|
|
timestamp_format.setFont(QFont("微软雅黑", 8))
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
timestamp_format.setForeground(QColor("#a0a0a0")) # 深色主题下的灰色
|
|
|
|
|
else:
|
|
|
|
|
timestamp_format.setForeground(QColor("#666666")) # 浅色主题下的深灰色,提高可读性
|
|
|
|
|
timestamp_format.setFont(QFont("微软雅黑", 9))
|
|
|
|
|
cursor.insertText(f"\n[{datetime.now().strftime('%H:%M:%S')}] ", timestamp_format)
|
|
|
|
|
|
|
|
|
|
# 插入前缀
|
|
|
|
|
@ -237,16 +379,7 @@ class AIChatPanel(QWidget):
|
|
|
|
|
self.chat_display.verticalScrollBar().setValue(
|
|
|
|
|
self.chat_display.verticalScrollBar().maximum()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def update_streaming_display(self):
|
|
|
|
|
"""更新流式显示"""
|
|
|
|
|
if self.is_streaming and self.current_streaming_content:
|
|
|
|
|
# 重新显示所有对话
|
|
|
|
|
self.rebuild_chat_display()
|
|
|
|
|
self.chat_display.verticalScrollBar().setValue(
|
|
|
|
|
self.chat_display.verticalScrollBar().maximum()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rebuild_chat_display(self):
|
|
|
|
|
"""重新构建聊天显示"""
|
|
|
|
|
self.chat_display.clear()
|
|
|
|
|
@ -258,14 +391,23 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 设置格式
|
|
|
|
|
char_format = QTextCharFormat()
|
|
|
|
|
char_format.setFont(QFont("微软雅黑", 10))
|
|
|
|
|
char_format.setFont(QFont("微软雅黑", 11))
|
|
|
|
|
|
|
|
|
|
if sender == "用户":
|
|
|
|
|
char_format.setForeground(QColor("#0078d4"))
|
|
|
|
|
char_format.setFontWeight(70)
|
|
|
|
|
# 根据主题设置用户消息颜色
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色
|
|
|
|
|
else:
|
|
|
|
|
char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色
|
|
|
|
|
char_format.setFontWeight(60) # 中等粗体
|
|
|
|
|
prefix = "你: "
|
|
|
|
|
else:
|
|
|
|
|
char_format.setForeground(QColor("#333333"))
|
|
|
|
|
# 根据主题设置AI消息颜色
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
char_format.setForeground(QColor("#e0e0e0")) # 深色主题下的浅灰色
|
|
|
|
|
else:
|
|
|
|
|
char_format.setForeground(QColor("#000000")) # 浅色主题下的纯黑色,提高可读性
|
|
|
|
|
char_format.setFontWeight(50) # 正常粗体
|
|
|
|
|
prefix = "AI: "
|
|
|
|
|
|
|
|
|
|
# 插入分隔符
|
|
|
|
|
@ -274,8 +416,11 @@ class AIChatPanel(QWidget):
|
|
|
|
|
|
|
|
|
|
# 插入时间戳
|
|
|
|
|
timestamp_format = QTextCharFormat()
|
|
|
|
|
timestamp_format.setForeground(QColor("#999999"))
|
|
|
|
|
timestamp_format.setFont(QFont("微软雅黑", 8))
|
|
|
|
|
if theme_manager.is_dark_theme():
|
|
|
|
|
timestamp_format.setForeground(QColor("#a0a0a0")) # 深色主题下的灰色
|
|
|
|
|
else:
|
|
|
|
|
timestamp_format.setForeground(QColor("#666666")) # 浅色主题下的深灰色,提高可读性
|
|
|
|
|
timestamp_format.setFont(QFont("微软雅黑", 9))
|
|
|
|
|
cursor.insertText(f"[{datetime.now().strftime('%H:%M:%S')}] ", timestamp_format)
|
|
|
|
|
|
|
|
|
|
# 插入前缀
|
|
|
|
|
@ -379,3 +524,12 @@ class AIChatPanel(QWidget):
|
|
|
|
|
self.conversation_history = []
|
|
|
|
|
self.chat_display.clear()
|
|
|
|
|
self.input_field.clear()
|
|
|
|
|
|
|
|
|
|
def update_streaming_display(self):
|
|
|
|
|
"""更新流式显示"""
|
|
|
|
|
if self.is_streaming and self.current_streaming_content:
|
|
|
|
|
# 重新显示所有对话
|
|
|
|
|
self.rebuild_chat_display()
|
|
|
|
|
self.chat_display.verticalScrollBar().setValue(
|
|
|
|
|
self.chat_display.verticalScrollBar().maximum()
|
|
|
|
|
)
|