diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a6880baff19c77898949d06f6bedc1b68d449d88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/debian/files +/debian/nfs-ai.debhelper.log +/debian/nfs-ai.substvars +/debian/.debhelper +/debian/nfs-ai +.idea +__pycache__/ diff --git a/debian/postinst b/debian/postinst index d08ed3723afaaeb870a6285637c646d4a2b77181..a55c13f6caeb42aefb66551b63ba2dbc77f07616 100644 --- a/debian/postinst +++ b/debian/postinst @@ -5,3 +5,5 @@ set -e pip3 install dashscope pip3 install pyaudio pip3 install pyqt5 + +cp /usr/share/aiassistant/config.ini "/home/$SUDO_USER/.config/aiassistant/config.ini" diff --git a/usr/share/aiassistant/AboutPage.py b/usr/share/aiassistant/AboutPage.py new file mode 100644 index 0000000000000000000000000000000000000000..7d09cfb6b93370a95a6e37e6a2c990fcb6328f0b --- /dev/null +++ b/usr/share/aiassistant/AboutPage.py @@ -0,0 +1,222 @@ +from PyQt5 import QtCore +from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QFrame, QDesktopWidget, \ + QGraphicsBlurEffect, QGraphicsDropShadowEffect +from PyQt5.QtCore import Qt, QSize, QRectF, pyqtSignal +from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QRegion, QTransform, QPalette, QColor, QLinearGradient, QPen, QPixmap, QIcon + +from ApikeyConfigPage import ApiKeyConfigPage +from Config import Config + + +class AboutPage(QWidget): + # 定义关闭信号 + closed = pyqtSignal() + def __init__(self, parent=None): + super(AboutPage, self).__init__(parent) + layout = QVBoxLayout(self) # 包含顶栏和主要内容 + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint) #Qt.Dialog主要用于在任务栏隐藏图标 + self.resize(450, 350) + + # 背景透明 + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 顶部栏 + top_bar = self.create_top_bar() + layout.addWidget(top_bar) + + #主要内容 + self.setting_main_window = AboutMainWindow() + layout.addWidget(self.setting_main_window) + + + layout.setContentsMargins(0, 0, 0, 0) + + def openApiKeyConfigPage(self): + # 创建并显示 APIKeyConfigPage 窗口 + self.apiKeyConfigPage = ApiKeyConfigPage() + self.hide() + self.apiKeyConfigPage.show() + # 将窗口置顶 + self.apiKeyConfigPage.raise_() + # self.hide() + + def create_top_bar(self): + setting_page_title = BoxTitle(self) + + #创建顶部栏 + top_bar_layout = QHBoxLayout() + + title_label = QLabel("关于") + title_label.setStyleSheet("font-size: 16px; color: black;") + + close_btn = QPushButton() + close_btn.setFixedSize(24, 24) + close_btn.setStyleSheet( + ''' + QPushButton{background-color:transparent;border:none;color:white;} + ''' + ) + + close_btn.setIcon(QIcon('./data/close.png')) + close_btn.setIconSize(QSize(24, 24)); + close_btn.clicked.connect(self.close) + close_btn.setFixedSize(24, 24) + + top_bar_layout.addWidget(title_label) + top_bar_layout.addStretch(1) + top_bar_layout.addWidget(close_btn) + + setting_page_title.setLayout(top_bar_layout) + + return setting_page_title + + + def move_to_center(self): + #移动到窗口中心位置 + # 获取屏幕总数和屏幕几何数据 + desktop = QDesktopWidget() + screen_count = desktop.screenCount() + if screen_count > 1: + # 多屏情况下,用第一个屏幕 + target_screen_index = 0 + # 获取目标屏幕的几何信息 + target_screen_geometry = desktop.screenGeometry(target_screen_index) + else: + # 单屏情况下使用默认屏幕 + target_screen_geometry = desktop.screenGeometry() + + # 计算窗口移动位置到屏幕右下角 + right = target_screen_geometry.right() + bottom = target_screen_geometry.bottom() + self.move(right/2 - self.width()/2, bottom/2 - self.height()/2) + + #鼠标移动,拖拽窗口实现 + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton: + self.moving = True + self.offset = event.pos() + + def mouseMoveEvent(self, event): + if(self.moving): + self.move(event.globalPos() - self.offset) + + def mouseReleaseEvent(self, event): + if(event.button() == Qt.LeftButton): + self.moving = False + + def paintEvent(self, event): + #画圆角 + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) # 设置抗锯齿,让圆角更加平滑 + self.setAttribute(Qt.WA_TranslucentBackground, False) + painter.setPen(QColor(0, 0, 0, 10)) # 黑色,透明度设置为10 + painter.setBrush(self.palette().window().color())#设置画刷为窗口背景颜色 + radius = 15 #设置圆角半径 + + bounds = self.rect() # 获取窗口的边界 + # 绘制阴影 + shadow = QLinearGradient(bounds.topLeft(), bounds.bottomLeft()) + shadow.setColorAt(0, QColor(100, 100, 100, 50)) + + # 设置阴影的模糊半径 + painter.setPen(QPen(shadow, 1)) + painter.drawRoundedRect(self.rect(), radius, radius) + + def closeEvent(self, event): + # 重写关闭事件,以便在关闭时通知父窗口 + self.closed.emit() + super().closeEvent(event) + + +class AboutMainWindow(QWidget): + # 定义关闭信号 + closed = pyqtSignal() + def __init__(self, parent=None): + super(AboutMainWindow, self).__init__(parent) + self.moving = False + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(50, 20, 50, 50) + + #实现圆角 + self.setObjectName('mainwindow') + self.radius = 10 + + # 背景透明 + self.setAttribute(Qt.WA_TranslucentBackground, True) + + + # 检查并更新服务状态并创建对应模块 + titile_about_section = self.create_titile_about_section() + main_layout.addLayout(titile_about_section) + + titile_about_section = self.create_content_about_section() + main_layout.addLayout(titile_about_section) + + + def create_titile_about_section(self): + #ai大模型以及配置按钮 + title_layout = QHBoxLayout() + # ai_model_layout.setSpacing(10) + + word_layout = QVBoxLayout() + word_label_one = QLabel("NFS AI") + word_label_one.setStyleSheet("font-size: 16px; color: black;") + + word_label_two = QLabel("1.0.0") + word_label_two.setStyleSheet("font-size: 10px; color: black;") + + word_layout.addWidget(word_label_one) + word_layout.addWidget(word_label_two) + + icon_label = QLabel() + icon_pixmap = QPixmap('./data/logo_about.png') # 替换为你的图标文件路径 + icon_label.setPixmap(icon_pixmap) + + title_layout.addLayout(word_layout) + title_layout.addStretch(1) + title_layout.addWidget(icon_label) + + return title_layout + + def create_content_about_section(self): + content_layout = QVBoxLayout() + word_label_one = QLabel("""NFS AI是一款集成了自然语言处理、智能绘图及会议纪要等多种功能的AI代理\n智能助手, 以优质的AI大模型接入效果为用户打造丰富的服务体验。\n\n\n© 2024中科方德软件有限公司 + """) + + word_label_one.setStyleSheet("font-size: 14px; color: black;") + + content_layout.addWidget(word_label_one) + return content_layout + + + + + +class BoxTitle(QWidget): + def __init__(self, *args, **kwargs): + super(BoxTitle, self).__init__() + self.setAttribute(Qt.WA_StyledBackground) + self.setObjectName('box_title') + self.setStyleSheet( + ''' + #box_title{ + border-left: 2px solid transparent; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + background-color: white; + background-clip: padding-box; + } + ''' + ) + self.installEventFilter(self) + self.setFixedHeight(36) + + + +if __name__ == "__main__": + import sys + from PyQt5.QtWidgets import QApplication + app = QApplication(sys.argv) + settings_page = AboutPage() + settings_page.show() + sys.exit(app.exec_()) diff --git a/usr/share/aiassistant/ApikeyConfigPage.py b/usr/share/aiassistant/ApikeyConfigPage.py new file mode 100644 index 0000000000000000000000000000000000000000..23a9c339625abcc64da263110765caeabc8802fc --- /dev/null +++ b/usr/share/aiassistant/ApikeyConfigPage.py @@ -0,0 +1,321 @@ +from PyQt5.QtWidgets import ( + QWidget, QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QApplication, QMessageBox, QFrame, + QDesktopWidget, QGraphicsDropShadowEffect +) +from PyQt5.QtCore import Qt, QSize, QRectF, pyqtSignal +from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QTransform, QRegion, QColor, QLinearGradient, QPen +from Config import Config +from SettingStatusPage import SettingStatusPage + +class ApiKeyConfigPage(QWidget): + # 定义关闭信号 + closed = pyqtSignal() + def __init__(self, parent=None): + super(ApiKeyConfigPage, self).__init__(parent) + layout = QVBoxLayout(self) # 包含顶栏和主要内容 + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint) #Qt.Dialog主要用于在任务栏隐藏图标 + self.resize(450, 350) + + # 背景透明 + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 顶部栏 + top_bar = self.create_top_bar() + layout.addWidget(top_bar) + + #主要内容 + self.setting_main_window = ApiKeyConfigMainWindow() + layout.addWidget(self.setting_main_window) + + + layout.setContentsMargins(0, 0, 0, 0) + + + def create_top_bar(self): + setting_page_title = BoxTitle(self) + + #创建顶部栏 + top_bar_layout = QHBoxLayout() + + title_label = QLabel("设置") + title_label.setStyleSheet("font-size: 16px; color: black;") + + close_btn = QPushButton() + close_btn.setFixedSize(24, 24) + close_btn.setStyleSheet( + ''' + QPushButton{background-color:transparent;border:none;color:white;} + ''' + ) + + close_btn.setIcon(QIcon('./data/close.png')) + close_btn.setIconSize(QSize(24, 24)); + close_btn.clicked.connect(self.close) + close_btn.setFixedSize(24, 24) + + top_bar_layout.addWidget(title_label) + top_bar_layout.addStretch(1) + top_bar_layout.addWidget(close_btn) + + setting_page_title.setLayout(top_bar_layout) + + return setting_page_title + + + def move_to_center(self): + #移动到窗口中心位置 + # 获取屏幕总数和屏幕几何数据 + desktop = QDesktopWidget() + screen_count = desktop.screenCount() + if screen_count > 1: + # 多屏情况下,用第一个屏幕 + target_screen_index = 0 + # 获取目标屏幕的几何信息 + target_screen_geometry = desktop.screenGeometry(target_screen_index) + else: + # 单屏情况下使用默认屏幕 + target_screen_geometry = desktop.screenGeometry() + + # 计算窗口移动位置到屏幕右下角 + right = target_screen_geometry.right() + bottom = target_screen_geometry.bottom() + self.move(right/2 - self.width()/2, bottom/2 - self.height()/2) + + #鼠标移动,拖拽窗口实现 + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton: + self.moving = True + self.offset = event.pos() + + def mouseMoveEvent(self, event): + if(self.moving): + self.move(event.globalPos() - self.offset) + + def mouseReleaseEvent(self, event): + if(event.button() == Qt.LeftButton): + self.moving = False + + def paintEvent(self, event): + #画圆角 + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) # 设置抗锯齿,让圆角更加平滑 + self.setAttribute(Qt.WA_TranslucentBackground, False) + painter.setPen(QColor(0, 0, 0, 10)) # 黑色,透明度设置为10 + painter.setBrush(self.palette().window().color())#设置画刷为窗口背景颜色 + radius = 15 #设置圆角半径 + + bounds = self.rect() # 获取窗口的边界 + # 绘制阴影 + shadow = QLinearGradient(bounds.topLeft(), bounds.bottomLeft()) + shadow.setColorAt(0, QColor(100, 100, 100, 50)) + + # 设置阴影的模糊半径 + painter.setPen(QPen(shadow, 1)) + painter.drawRoundedRect(self.rect(), radius, radius) + + def closeEvent(self, event): + # 重写关闭事件,以便在关闭时通知父窗口 + self.closed.emit() + super().closeEvent(event) + + +class ApiKeyConfigMainWindow(QWidget): + def __init__(self, parent=None): + super(ApiKeyConfigMainWindow, self).__init__(parent) + self.resize(490, 500) + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint) #Qt.Dialog主要用于在任务栏隐藏图标 + + # 背景透明 + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 实现圆角 + self.setObjectName('mainwindow') + self.radius = 15 + + # 内容框 + main_layout = QVBoxLayout(self) + + + # 创建服务配置项的输入框,并存储它们的引用 + self.bailian_service_box, self.bailian_inputs = self.create_service_box( + "百炼-问答绘画服务", [("api-key", "API Key")] + ) + main_layout.addWidget(self.bailian_service_box) + + self.tongyi_service_box, self.tongyi_inputs = self.create_service_box( + "通义听悟·会议纪要服务", + [("app-key", "App Key"), ("access key id", "Access Key ID"), + ("access key secret", "Access Key Secret")]) + + #加入主布局中 + main_layout.addWidget(self.tongyi_service_box) + + # 确定和清空框 + button_layout = self.create_buttons() + main_layout.addLayout(button_layout) + + def create_ai_model_section(self): + # ai大模型 + ai_model_layout = QVBoxLayout() + ai_model_layout.setSpacing(15) + ai_model_layout.setContentsMargins(10, 10, 10, 10) + + ai_model_label = QLabel("AI大模型") + ai_model_label.setStyleSheet("font-weight: bold;") + ai_model_layout.addWidget(ai_model_label) + + + return ai_model_layout + + def create_service_box(self, service_name, fields): + # 服务配置项 + service_box = QWidget() + service_box_layout = QVBoxLayout(service_box) + service_box_layout.setSpacing(10) + + service_label = QLabel(service_name) + service_label.setStyleSheet("font-weight: bold; font-size:16px;") + service_box_layout.addWidget(service_label) + + + # 创建一个字典来保存字段名称与对应的 QLineEdit 控件 + field_inputs = {} + + for field_name, placeholder in fields: + field_label = QLabel(field_name) + field_input = QLineEdit() + + # 检查配置文件中是否存在该键 + existing_value = None + if field_name == "api-key": + existing_value = Config.get_api_key() + elif field_name == "app-key": + existing_value = Config.get_app_key() + elif field_name == "access key id": + existing_value = Config.get_ALIBABA_CLOUD_ACCESS_KEY_ID() + elif field_name == "access key secret": + existing_value = Config.get_ALIBABA_CLOUD_ACCESS_KEY_SECRET() + + # 根据是否存在键来设置 placeholder + if existing_value: + field_input.setPlaceholderText(f"已存在{field_name},输入将会覆盖原有{field_name}") + else: + field_input.setPlaceholderText(placeholder) + + # 保存输入框的引用 + field_inputs[field_name] = field_input + service_box_layout.setContentsMargins(20,0,20,0) + + service_box_layout.addWidget(field_input) + + service_box.setStyleSheet( + "QWidget { border: none; border-radius: 8px; padding: 10px; }" + ) + + return service_box, field_inputs + + def create_buttons(self): + # 确定和清空按钮 + button_layout = QHBoxLayout() + + clear_button = QPushButton("清空") + clear_button.setStyleSheet( + "background-color: #f0f0f0; color: #4d4f53; font-weight: bold; padding: 10px 20px; border-radius: 5px;border:0.5px solid #7d7d7d" + ) + clear_button.setFixedWidth(80) + clear_button.clicked.connect(self.clear_inputs) + + confirm_button = QPushButton("确认") + confirm_button.setStyleSheet( + "background-color: #00aaff; color: white; font-weight: bold; padding: 10px 20px; border-radius: 5px;" + ) + confirm_button.setFixedWidth(80) + confirm_button.clicked.connect(self.confirm) + + + button_layout.addStretch(1) + button_layout.addWidget(clear_button) + button_layout.addWidget(confirm_button) + button_layout.setContentsMargins(0, 150, 0, 0) + + return button_layout + + + def confirm(self): + # 从字段字典中获取 QLineEdit 控件的值 + bailian_api_key = self.bailian_inputs['api-key'].text() + tongyi_app_key = self.tongyi_inputs['app-key'].text() + tongyi_access_key_id = self.tongyi_inputs['access key id'].text() + tongyi_access_key_secret = self.tongyi_inputs['access key secret'].text() + + # 输出结果 + # print(f"百炼 API Key: {bailian_api_key}") + # print(f"通义 App Key: {tongyi_app_key}") + # print(f"通义 Access Key ID: {tongyi_access_key_id}") + # print(f"通义 Access Key Secret: {tongyi_access_key_secret}") + + if not bailian_api_key and not tongyi_app_key and not tongyi_access_key_id and not tongyi_access_key_secret: + self.openSettingStatusPage(False) + return + + # 检查并设置配置,记录成功或失败的状态 + success = True + + if bailian_api_key: + if not Config.set_api_key(bailian_api_key): + success = False + + if tongyi_app_key: + if not Config.set_app_key(tongyi_app_key): + success = False + + if tongyi_access_key_id: + if not Config.set_ALIBABA_CLOUD_ACCESS_KEY_ID(tongyi_access_key_id): + success = False + + if tongyi_access_key_secret: + if not Config.set_ALIBABA_CLOUD_ACCESS_KEY_SECRET(tongyi_access_key_secret): + success = False + + # 弹出结果提示框 + self.openSettingStatusPage(success) + + + + def clear_inputs(self): + # 清空按钮 + for widget in self.findChildren(QLineEdit): + widget.clear() + + def openSettingStatusPage(self,success): + # 创建并显示SettingStatusPage窗口 + self.settingStatusPage = SettingStatusPage(success,self) + self.settingStatusPage.show() + + +class BoxTitle(QWidget): + def __init__(self, *args, **kwargs): + super(BoxTitle, self).__init__() + self.setAttribute(Qt.WA_StyledBackground) + self.setObjectName('box_title') + self.setStyleSheet( + ''' + #box_title{ + border-left: 2px solid transparent; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + background-color: white; + background-clip: padding-box; + } + ''' + ) + self.installEventFilter(self) + self.setFixedHeight(36) + + +if __name__ == "__main__": + import sys + app = QApplication(sys.argv) + api_key_config_page = ApiKeyConfigPage() + api_key_config_page.show() + sys.exit(app.exec_()) diff --git a/usr/share/aiassistant/Config.py b/usr/share/aiassistant/Config.py index 613eb46bed0dc60b391299d1b69dc787d009644c..423ece357263fc4ef43ae48f5ecd14b863eea96f 100644 --- a/usr/share/aiassistant/Config.py +++ b/usr/share/aiassistant/Config.py @@ -1,13 +1,22 @@ import configparser import json +import dashscope +from PublicTypes import PublicTypes +public_types = PublicTypes() +import os +# 获取家目录路径 +home_dir = os.path.expanduser('~') + +# 指定配置文件路径 +CONFIG_PATH = os.path.join(home_dir, PublicTypes.CONFIG_PATH) class Config: @staticmethod def get_api_key(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) api_key = config.get('DEFAULT', 'api_key') # print(api_key) return api_key @@ -15,7 +24,7 @@ class Config: @staticmethod def get_app_list(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) data = config.get('DEFAULT', 'app_list') app_list = json.loads(data) return app_list @@ -23,7 +32,7 @@ class Config: @staticmethod def get_local_model(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) local_model = config.get('DEFAULT', 'local_model') # print(local_model) return local_model @@ -31,7 +40,7 @@ class Config: @staticmethod def get_ALIBABA_CLOUD_ACCESS_KEY_ID(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) access_key_id = config.get('DEFAULT', 'access_key_id') # print(local_model) return access_key_id @@ -39,7 +48,7 @@ class Config: @staticmethod def get_ALIBABA_CLOUD_ACCESS_KEY_SECRET(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) access_key_secret = config.get('DEFAULT', 'access_key_secret') # print(local_model) return access_key_secret @@ -47,7 +56,64 @@ class Config: @staticmethod def get_app_key(): config = configparser.ConfigParser() - config.read("/usr/share/aiassistant/config.ini") + config.read(CONFIG_PATH) app_key = config.get('DEFAULT', 'app_key') # print(local_model) - return app_key \ No newline at end of file + return app_key + + @staticmethod + def set_api_key(api_key): + try: + config = configparser.ConfigParser() + with open(CONFIG_PATH, "r") as configfile: + config.read_file(configfile) + config.set('DEFAULT', 'api_key', api_key) + with open(CONFIG_PATH, "w") as configfile: + config.write(configfile) + dashscope.api_key = Config.get_api_key() + return True + except Exception as e: + print(f"Failed to set API Key: {e}") + return False + + @staticmethod + def set_app_key(app_key): + try: + config = configparser.ConfigParser() + with open(CONFIG_PATH, "r") as configfile: + config.read_file(configfile) + config.set('DEFAULT', 'app_key', app_key) + with open(CONFIG_PATH, "w") as configfile: + config.write(configfile) + return True + except Exception as e: + print(f"Failed to set App Key: {e}") + return False + + @staticmethod + def set_ALIBABA_CLOUD_ACCESS_KEY_ID(access_key_id): + try: + config = configparser.ConfigParser() + with open(CONFIG_PATH, "r") as configfile: + config.read_file(configfile) + config.set('DEFAULT', 'access_key_id', access_key_id) + with open(CONFIG_PATH, "w") as configfile: + config.write(configfile) + return True + except Exception as e: + print(f"Failed to set Alibaba Cloud Access Key ID: {e}") + return False + + @staticmethod + def set_ALIBABA_CLOUD_ACCESS_KEY_SECRET(access_key_secret): + try: + config = configparser.ConfigParser() + with open(CONFIG_PATH, "r") as configfile: + config.read_file(configfile) + config.set('DEFAULT', 'access_key_secret', access_key_secret) + with open(CONFIG_PATH, "w") as configfile: + config.write(configfile) + return True + except Exception as e: + print(f"Failed to set Alibaba Cloud Access Key Secret: {e}") + return False \ No newline at end of file diff --git a/usr/share/aiassistant/MeetingBottomWgt.py b/usr/share/aiassistant/MeetingBottomWgt.py index fbbfbddaee1dd6a7a203700b95275566fa788557..7f0bcb9fbd22a6e0f4a385bdef42a43de74e0764 100644 --- a/usr/share/aiassistant/MeetingBottomWgt.py +++ b/usr/share/aiassistant/MeetingBottomWgt.py @@ -17,6 +17,9 @@ from PyQt5.QtGui import * from MeetingTask import MeetingTask from bubble_message import BubbleMessage, MessageType from MeetingFile import MeetingFile +from PublicTypes import PublicTypes + +public_types = PublicTypes() class MeetingBottomWidget(QWidget): switch_signal = pyqtSignal(bool) @@ -79,6 +82,17 @@ class MeetingBottomWidget(QWidget): # print(filename) if os.path.exists(filename): MeetingFile.open_file(filename) + + def switchViewType(self): + if PublicTypes.viewType == "sidebar": + self.ui.meetingSwitchBtn.setGeometry(183, 74, 114, 36) + self.ui.exportMeetingBtn.setGeometry(10, 140, 114, 36) + elif PublicTypes.viewType == "windows": + self.ui.meetingSwitchBtn.setGeometry(550, 34, 114, 36) + self.ui.exportMeetingBtn.setGeometry(10, 80, 114, 36) + else: + raise NotImplementedError("illegal type") + class Ui_meetingBottomWidget(QWidget): def setupUi(self, bottomWidget): diff --git a/usr/share/aiassistant/ModelGuidePage.html b/usr/share/aiassistant/ModelGuidePage.html new file mode 100644 index 0000000000000000000000000000000000000000..0b337f2aa18b559f867b6dca6c343081b0a36b24 --- /dev/null +++ b/usr/share/aiassistant/ModelGuidePage.html @@ -0,0 +1,26 @@ + + +
+ +欢迎使用NFS AI语音助手
+可以点击下方按钮唤醒
+ ''') + + self.textLabel.setAlignment(Qt.AlignCenter) + + # 状态标签 + + self.statusLabel = QLabel('当前状态:休眠中', self) + self.statusLabel.setStyleSheet(""" + QLabel { + font-size: 16px; + font-weight: bold; + color: black; + padding: 0px; + } + """) + self.statusLabel.setAlignment(Qt.AlignCenter) + + + # 按钮 + self.startButton = QPushButton('', self) + self.startButton.setIconSize(QSize(300, 150)) + self.startButton.setStyleSheet("QPushButton { border: none; }") + # AILight + self.image_folder = './data/AILight' + self.image_files = sorted([os.path.join(self.image_folder, f) for f in os.listdir(self.image_folder) if + f.endswith(('.png', '.jpg', '.jpeg'))]) + self.current_image_index = 0 + + # 定时器 + self.image_timer = QTimer(self) + self.image_timer.timeout.connect(self.updateButton) + self.image_timer.start(50) # 每500毫秒更换一次图片 + + self.verticalLayout.addStretch(1) + self.verticalLayout.addWidget(self.imageLabel) + self.verticalLayout.addWidget(self.textLabel) + self.verticalLayout.addStretch(1) + # self.verticalLayout.addWidget(self.opLabel) + self.verticalLayout.addWidget(self.statusLabel) + self.verticalLayout.addStretch(1) + self.verticalLayout.addWidget(self.startButton) + self.verticalLayout.addStretch(4) + + + self.startButton.clicked.connect(self.startConversation) + self.state = '休眠中' + + + def capture_voice(self): + self.collect_voice_signal.emit() + def updateButton(self): + # 更新按钮的图标 + if self.current_image_index < len(self.image_files): + icon = QIcon(self.image_files[self.current_image_index]) + self.startButton.setIcon(icon) + self.current_image_index = (self.current_image_index + 1) % len(self.image_files) + + def updateImage(self): + if self.current_image_index1 < len(self.image_files1): + pixmap = QPixmap(self.image_files1[self.current_image_index1]) + + self.imageLabel.setPixmap(pixmap) + self.current_image_index1 = (self.current_image_index1 + 1) % len(self.image_files1) + + def startConversation(self): + # if self.state == '聆听中': + # self.capture_voice() + if self.state == '休眠中': + self.updateStatus('聆听中') + QApplication.processEvents() + self.capture_voice() #获取声音信息 + # self.respondToUser() + #self.timer.start(5000) # 3秒 + + + def updateStatus(self, new_status): + self.state = new_status + print(self.state) + self.statusLabel.setText(f'当前状态:{self.state}') + self.statusLabel.repaint() + + def switchViewType(self): + if PublicTypes.viewType == "sidebar": + h = self.height - 380 + self.setFixedHeight(h) + self.setFixedWidth(480 - 36) + elif PublicTypes.viewType == "windows": + self.setFixedHeight(560) + self.setFixedWidth(1164) + else: + raise NotImplementedError("illegal type") + +# 测试布局和交互效果 +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +if __name__ == '__main__': + app = QApplication([]) + widget = exChange() + widget.show() + app.exec_() + + diff --git a/usr/share/aiassistant/mainwin.py b/usr/share/aiassistant/mainwin.py index bd634096a0770e2dd86ed5360c594a6bd2d39761..b835530a43bd3ae8ab455cc7c988eb102bbb420c 100644 --- a/usr/share/aiassistant/mainwin.py +++ b/usr/share/aiassistant/mainwin.py @@ -25,7 +25,14 @@ from ChatTalk import ChatTalk from MeetingBottomWgt import MeetingBottomWidget from MeetingTask import MeetingTask from MeetingFile import MeetingFile +from SettingPage import SettingsPage +from AboutPage import AboutPage +from PyQt5 import QtWidgets +from exchangePage import exChange +from ApikeyConfigPage import ApiKeyConfigPage + app_list = Config.get_app_list() +api_key = Config.get_api_key() public_types = PublicTypes() class ShadowWindow(QWidget): @@ -33,7 +40,19 @@ class ShadowWindow(QWidget): super().__init__() self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint) #Qt.Dialog主要用于在任务栏隐藏图标 self.setAttribute(Qt.WA_TranslucentBackground) + self.setObjectName('shadowindow') + self.setStyleSheet( + ''' + #shadowindow{ + border-top-left-radius: 15px; + border-top-right-radius: 15px; + border-bottom-left-radius: 15px; + border-bottom-right-radius: 15px; + } + ''' + ) self.resize(public_types.mainwin_width + 10, public_types.mainwin_height + 12) + self.setMaximumWidth(public_types.mainwin_width) layout = QVBoxLayout(self) layout.setContentsMargins(0,0,0,0) self.widget = Mainwin() @@ -42,46 +61,16 @@ class ShadowWindow(QWidget): self.widget.chatWgt.verticalScrollBar().setValue(200) self.setWindowTitle("NFS AI") # 设置窗口名 self.setWindowIcon(QIcon('./data/icon.png')) # 设置窗口图标 + + self.right_bottom_index = self.get_right_bottom_index() - #title - title = QWidget() - title.setObjectName('title') - title.setStyleSheet( - ''' - #title{ - border-top-left-radius: 5px; - border-top-right-radius: 5px; - background:transparent; - } - ''' - ) - title.installEventFilter(title) - title.setFixedHeight(36) - icon = QLabel(title) - icon.setFixedSize(16,16) - pixmap = QPixmap('./data/icon.png') - icon.setPixmap(pixmap) - icon.setScaledContents(True) - title_layout = QHBoxLayout(title) - title_layout.setContentsMargins(18, 12, 18, 0) - title_layout.addWidget(icon) - - title_name = QLabel() - title_name.setText('NFS AI') - title_name.setStyleSheet( - ''' - color:white; - font-size:16px; - font-weight:bold; - ''' - ) - - title_layout.addWidget(title_name) + self.stack = QStackedWidget(self) - title_spacer = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum) - title_layout.addItem(title_spacer) + #title + self.title = MianWinTitle() - home_btn = QPushButton(title) + # home button + home_btn = QPushButton(self.title) home_btn.setFixedSize(24, 24) home_btn.setFlat(True) home_btn.setStyleSheet( @@ -96,9 +85,56 @@ class ShadowWindow(QWidget): lambda: self.widget.shift_model(False) ) home_btn.setFixedSize(24, 24) - title_layout.addWidget(home_btn) + self.title.title_layout.addWidget(home_btn) - close_btn = QPushButton(title) + #切换按钮 + exchange_btn = QPushButton() + exchange_btn.setFixedSize(24, 24) + exchange_btn.setFlat(True) + exchange_btn.setStyleSheet( + ''' + QPushButton{background-color: transparent;border:none;color:white;} + ''' + ) + exchange_btn.setIcon(QIcon('./data/icon_audio_window.png')) + exchange_btn.setIconSize(QSize(24, 24)); + exchange_btn.clicked.connect(self.navigate_to_exchange) + + self.title.title_layout.addWidget(exchange_btn) + + # setting_ button + setting_btn = QPushButton(self.title) + setting_btn.setIcon(QIcon('./data/icon_setting.png')) + setting_btn.setIconSize(QSize(24, 24)); + setting_btn.setFixedSize(24, 24) + self.title.title_layout.addWidget(setting_btn) + setting_btn.setStyleSheet( + ''' + QPushButton{background-color:transparent;border:none;color:white;} + ''' + ) + + + menu = SettingMenu(self) + + # 添加二级菜单 + sub_menu = menu.addMenu("显示模式") + self.action_window = QAction( "窗口模式", self) + sub_menu.addAction(self.action_window) + self.action_window.triggered.connect(self.switchToWindow) + self.action_sidebar = QAction(QIcon('./data/menu_select.png'), "侧边栏模式", self) + sub_menu.addAction(self.action_sidebar) + self.action_sidebar.triggered.connect(self.switchToSidebar) + menu.sub_menu = sub_menu + menu.setSubMenu() + + menu.addAction("设置", self.openSettingPage) + menu.addSeparator() + menu.addAction("关于", self.openAboutPage) + setting_btn.setMenu(menu) + + # close button + close_btn = QPushButton(self.title) close_btn.setFixedSize(24, 24) close_btn.setStyleSheet( ''' @@ -110,31 +146,88 @@ class ShadowWindow(QWidget): close_btn.setIconSize(QSize(24, 24)); close_btn.clicked.connect(self.close) close_btn.setFixedSize(24, 24) - title_layout.addWidget(close_btn) + self.title.title_layout.addWidget(close_btn) - layout.addWidget(title) + layout.addWidget(self.title) layout.addWidget(self.widget) self.initDrag() + self.switchToSidebar() self.move_to_right_bottom() + + + print("2 self.widget", self.widget.width()) + print("2 self.widget.guidWgt", self.widget.guidWgt.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.width()) + print("2 self.widget.exchange", self.widget.exchange.width()) + print("2 self.widget.meetingWgt", self.widget.meetingWgt.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.scrollArea.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.scrollAreaWidgetContents.width()) + # 检测api-key是否存在,不存在,调用apikey配置页面 + # if api_key == "": + # self.openApiKeyConfigPage() + + + #切换功能跳转函数 + def navigate_to_exchange(self): + if self.widget.exchange.isHidden(): + self.widget.set_chat_model() + self.widget.contentStackWgt.setCurrentIndex(1) + self.widget.bottomStackWgt.setCurrentIndex(0) + self.widget.exchange.show() + self.widget.chatWgt.hide() + self.widget.bottom_ui.hide() + self.widget.meetingWgt.hide() + self.widget.meeting_bottom_ui.hide() + else: + self.widget.set_chat_model() + self.widget.shift_model(True) + + def get_right_bottom_index(self): + # 获取屏幕总数和屏幕几何数据 + desktop = QDesktopWidget() + screen_count = desktop.screenCount() + target_screen_index = 0 + if screen_count > 1: + # 多屏情况下,寻找最右下角的屏幕 + right_most = 0 + bottom_most = 0 + for i in range(screen_count): + screen_geo = desktop.screenGeometry(i) + if screen_geo.right() > right_most: + right_most = screen_geo.right() + target_screen_index = i + if screen_geo.bottom() > bottom_most: + bottom_most = screen_geo.bottom() + target_screen_index = i + else: + target_screen_index = 0 + + return target_screen_index + def move_to_right_bottom(self): # 获取屏幕总数和屏幕几何数据 desktop = QDesktopWidget() screen_count = desktop.screenCount() if screen_count > 1: - # 多屏情况下,寻找最右下角的屏幕 - right_most = 0 - bottom_most = 0 - target_screen_index = 0 - for i in range(screen_count): - screen_geo = desktop.screenGeometry(i) - if screen_geo.right() > right_most: - right_most = screen_geo.right() - target_screen_index = i - if screen_geo.bottom() > bottom_most: - bottom_most = screen_geo.bottom() - target_screen_index = i + # 获取目标屏幕的几何信息 + target_screen_geometry = desktop.screenGeometry(self.right_bottom_index) + else: + # 单屏情况下使用默认屏幕 + target_screen_geometry = desktop.screenGeometry() + # 计算窗口移动位置到屏幕右下角 + right = target_screen_geometry.right() + bottom = target_screen_geometry.bottom() + self.move(right - self.width(), bottom - self.height()) + + def move_to_center(self): + # 获取屏幕总数和屏幕几何数据 + desktop = QDesktopWidget() + screen_count = desktop.screenCount() + if screen_count > 1: + # 多屏情况下,用第一个屏幕 + target_screen_index = 0 # 获取目标屏幕的几何信息 target_screen_geometry = desktop.screenGeometry(target_screen_index) else: @@ -144,13 +237,100 @@ class ShadowWindow(QWidget): # 计算窗口移动位置到屏幕右下角 right = target_screen_geometry.right() bottom = target_screen_geometry.bottom() - self.move(right - self.width(), bottom - self.height()) + self.move(right/2 - self.width()/2, bottom/2 - self.height()/2) + + # 切换到窗口模式 + def switchToWindow(self): + if PublicTypes.viewType != "windows": + PublicTypes.viewType = "windows" + self.action_window.setIcon(QIcon('./data/menu_select.png')) + self.action_sidebar.setIcon(QIcon("")) + self.setMaximumWidth(1200) + + self.widget.resize(public_types.mainwin_windows_width, public_types.mainwin_windows_height) + self.widget.setMaximumHeight(public_types.mainwin_windows_height) + self.resize(public_types.mainwin_windows_width, public_types.mainwin_windows_height) + self.setMaximumHeight(public_types.mainwin_windows_height) + self.move_to_center() + self.widget.guidWgt.switchViewType() + self.widget.chatWgt.switchViewType() + self.widget.exchange.switchViewType() + self.widget.meetingWgt.switchViewType() + self.widget.meeting_bottom_ui.switchViewType() + self.title.switchViewType() + + print("1 self.widget", self.width()) + print("2 self.widget", self.widget.width()) + print("2 self.widget.guidWgt", self.widget.guidWgt.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.width()) + print("2 self.widget.exchange", self.widget.exchange.width()) + print("2 self.widget.meetingWgt", self.widget.meetingWgt.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.scrollArea.width()) + print("2 self.widget.chatWgt", self.widget.chatWgt.scrollAreaWidgetContents.width()) + + self.widget.bottom_ui.setFixedHeight(120) + self.widget.meeting_bottom_ui.setFixedHeight(120) + self.widget.bottomStackWgt.setFixedHeight(120) + + else: + pass + + # 切换到侧边栏模式 + def switchToSidebar(self): + if PublicTypes.viewType != "sidebar": + PublicTypes.viewType = "sidebar" + self.action_window.setIcon(QIcon("")) + self.action_sidebar.setIcon(QIcon('./data/menu_select.png')) + self.widget.guidWgt.switchViewType() + self.widget.chatWgt.switchViewType() + self.widget.exchange.switchViewType() + self.widget.meetingWgt.switchViewType() + self.widget.meeting_bottom_ui.switchViewType() + self.widget.setMaximumHeight(public_types.mainwin_height) + self.widget.resize(public_types.mainwin_width, public_types.mainwin_height) + + + self.widget.bottomStackWgt.setFixedHeight(180) + self.widget.bottom_ui.setFixedHeight(180) + self.widget.meeting_bottom_ui.setFixedHeight(180) + + self.setMaximumHeight(public_types.mainwin_height) + self.resize(public_types.mainwin_width, public_types.mainwin_height) + self.title.switchViewType() + self.setMaximumWidth(public_types.mainwin_width) + print("1 self.widget", self.width()) + print("1 self.widget", self.widget.width()) + print("1 self.widget.guidWgt", self.widget.guidWgt.width()) + print("1 self.widget.chatWgt", self.widget.chatWgt.width()) + print("1 self.widget.exchange", self.widget.exchange.width()) + print("1 self.widget.meetingWgt", self.widget.meetingWgt.width()) + print("1 self.widget.chatWgt", self.widget.chatWgt.scrollArea.width()) + print("1 self.widget.chatWgt", self.widget.chatWgt.scrollAreaWidgetContents.width()) + + self.move_to_right_bottom() + + else: + pass + def paintEvent(self, event): # 创建一个QPainter对象,并为当前窗口提供绘图功能 - painter = QPainter(self) - pixmap = QPixmap('./data/bg.png').scaled(self.size()) - painter.drawPixmap(0, 0, pixmap) + if PublicTypes.viewType == "sidebar": + #侧边栏模式背景 + painter = QPainter(self) + pixmap = QPixmap('./data/bg.png').scaled(self.size()) + painter.drawPixmap(0, 0, pixmap) + elif PublicTypes.viewType == "windows": + #窗口模式背景 + painter = QPainter(self) + self.setAttribute(Qt.WA_TranslucentBackground, False) + painter.setBrush(self.palette().window().color()) # 设置画刷为窗口背景颜色 + + radius = 15 # 设置圆角半径 + painter.drawRoundedRect(self.rect(), radius, radius) + else: + raise NotImplementedError("illegal type") + def initDrag(self): # 设置鼠标跟踪判断默认值 @@ -182,6 +362,45 @@ class ShadowWindow(QWidget): # 鼠标释放后,各扳机复位 self._move_drag = False + def openApiKeyConfigPage(self): + self.apiKeyConfigPage = ApiKeyConfigPage() + self.apiKeyConfigPage.show() + + def openSettingPage(self): + #创建并显示设置页面 + self.settingPage = SettingsPage(self) + self.settingPage.show() + # 绑定设置页面的关闭信号到槽函数 + self.settingPage.closed.connect(self.onSettingPageClosed) + # 设置窗口置顶 + self.settingPage.raise_() + # #修改一直置顶 + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog) + # 移到画面中间 + self.settingPage.move_to_center() + self.show() + + def openAboutPage(self): + #创建并显示设置页面 + self.aboutPage = AboutPage(self) + self.aboutPage.show() + # 绑定设置页面的关闭信号到槽函数 + self.aboutPage.closed.connect(self.onSettingPageClosed) + # 设置窗口置顶 + self.aboutPage.raise_() + # #修改一直置顶 + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog) + # 移到画面中间 + self.aboutPage.move_to_center() + self.show() + + def onSettingPageClosed(self): + # 设置窗口置顶标志 + self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint) + # 重新置顶主窗口 + self.raise_() + self.show() + class Mainwin(QWidget): def __init__(self): super().__init__() @@ -189,7 +408,7 @@ class Mainwin(QWidget): self.setStyleSheet( ''' #mainwindow{ - border-top-left-radius: 15px; + border-top-left-radius: 15px; border-top-right-radius: 15px; border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; @@ -206,6 +425,9 @@ class Mainwin(QWidget): self.chatWgt = ChatWidget() self.chatWgt.hide() self.guidWgt = PreWidget() + #新建exchage对象 + self.exchange = exChange() + self.exchange.hide() self.meetingWgt = MeetingWidget() self.meetingWgt.hide() TEXT = MessageType.Text @@ -264,6 +486,8 @@ class Mainwin(QWidget): self.bottom_ui.send_text_signal.connect(self.send_message_to_ai) # self.bottom_ui.send_text_signal.connect(self.send_message_to_ai) + self.exchange.collect_voice_signal.connect(self.collect_voice_handle) + self.guidWgt.chat_model_signal.connect(self.set_chat_model) self.guidWgt.paint_model_signal.connect(self.set_paint_model) self.guidWgt.meeting_minuts_model_signal.connect(self.set_meeting_minuts_model) @@ -273,9 +497,11 @@ class Mainwin(QWidget): bottom_spacer = QSpacerItem(10, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.contentStackWgt = QStackedWidget() self.contentStackWgt.addWidget(self.guidWgt) + self.contentStackWgt.addWidget(self.exchange) self.contentStackWgt.addWidget(self.chatWgt) self.contentStackWgt.addWidget(self.meetingWgt) self.contentStackWgt.setCurrentIndex(0) + layout.addWidget(self.contentStackWgt) # layout.addWidget(self.meetingWgt) # layout.addWidget(self.guidWgt) @@ -293,6 +519,19 @@ class Mainwin(QWidget): self.timer = QTimer(self) # 定义定时器 self.timer.timeout.connect(self.start_meeting) # 定时器信号连接到updateImage方法 + # # 聆听中到等待中的自动转换 + self.last_voice_message = "" + self.timerSend = QTimer(self) + self.timerSend.timeout.connect(self.listeningToWaiting) + + + # # 聆听中到等待中的自动转换函数 + def listeningToWaiting(self): + if self.voice_message != self.last_voice_message: + self.last_voice_message = self.voice_message + else: + self.exchange.capture_voice() + # self.exchange.updateStatus('等待中') def show_meeting_waiting_message(self, show): #总是和BubbleMessage成对出现,send为True则显示,否则隐藏,待优化 if show: @@ -380,6 +619,7 @@ class Mainwin(QWidget): def speechTaskCallback(self, bubble_message): print("speechTaskCallback") bubble_message.playComplete() + self.exchange.updateStatus('休眠中') def showExample(self): print('show example') @@ -409,18 +649,20 @@ class Mainwin(QWidget): def shift_model(self, toModel): if toModel == True: if self.modeType == ModelType.MeetingMinuts: - self.contentStackWgt.setCurrentIndex(2) + self.contentStackWgt.setCurrentIndex(3) self.bottomStackWgt.setCurrentIndex(1) self.meetingWgt.show() self.meeting_bottom_ui.show() self.showExample() + self.exchange.hide() self.bottom_ui.hide() self.chatWgt.hide() else: - self.contentStackWgt.setCurrentIndex(1) + self.contentStackWgt.setCurrentIndex(2) self.bottomStackWgt.setCurrentIndex(0) self.chatWgt.show() + self.exchange.hide() self.showExample() self.bottom_ui.setEnabled(True) self.bottom_ui.show() @@ -432,6 +674,8 @@ class Mainwin(QWidget): self.bottomStackWgt.setCurrentIndex(0) self.guidWgt.show() self.chatWgt.hide() + + self.exchange.hide() # self.bottom_ui.setEnabled(False) self.bottom_ui.hide() self.meetingWgt.hide() @@ -439,18 +683,30 @@ class Mainwin(QWidget): def set_chat_model (self): print("set_chat_model") + if Config.get_api_key() == "": + self.parent().openSettingPage() self.modeType = ModelType.Chat self.shift_model(True) def set_paint_model (self): print("set_paint_model") + if Config.get_api_key() == "": + self.parent().openSettingPage() self.modeType = ModelType.Paint self.shift_model(True) def set_meeting_minuts_model (self): print("set_meeting_minuts_model") + if Config.get_ALIBABA_CLOUD_ACCESS_KEY_ID() == "" or Config.get_app_key == "" or Config.get_ALIBABA_CLOUD_ACCESS_KEY_SECRET() == "": + self.parent().openSettingPage() self.modeType = ModelType.MeetingMinuts self.shift_model(True) + + def set_AiAss_model(self): + print("set_AiAss_model") + self.modeType = ModelType.AiAss + self.shift_model(True) + def show_waiting_message(self,show): #总是和BubbleMessage成对出现,send为True则显示,否则隐藏,待优化 if show: @@ -480,6 +736,7 @@ class Mainwin(QWidget): self.voiceInput.stop_recognition() self.bottom_ui.stop_timer() self.send_voice_message_to_ai(self.voice_message) + self.timerSend.stop() self.voice_message = "" self.bottom_ui.ui.inputLineEdit.setPlainText("") else: @@ -488,6 +745,7 @@ class Mainwin(QWidget): self.bottom_ui.collect_voice_flag = True self.bottom_ui.start_timer() self.voiceInput.start() + self.timerSend.start(3000) def start_local_app(self, text): flag = False @@ -534,6 +792,7 @@ class Mainwin(QWidget): self.bottom_ui.set_send_button_status(True) def send_quest_to_ai(self, message): + self.exchange.updateStatus('等待中') if self.serverCheck.internet_status(): Speech.short_text_play("好的,请稍等") self.sendTask.set_topic(message) @@ -547,9 +806,12 @@ class Mainwin(QWidget): bubble_message.playBtn.hide() self.show_waiting_message(False) self.bottom_ui.set_send_button_status(True) + self.exchange.updateStatus('休眠中') def send_voice_message_to_ai(self, message): if len(message) == 0: + self.exchange.updateStatus('休眠中') + self.bottom_ui.collect_voice_flag = False return bubble_message = BubbleMessage(message, '', Type=MessageType.Text, font_size=12, is_send=True) @@ -587,6 +849,7 @@ class Mainwin(QWidget): def ai_callback(self, result): self.show_waiting_message(False) bubble_message = BubbleMessage(result, '', Type=MessageType.Text, font_size=12, is_send=False) + self.exchange.updateStatus('应答中') bubble_message.switch_signal.connect(self.switch_signal_handle) self.chatWgt.add_message_item(bubble_message) @@ -595,8 +858,10 @@ class Mainwin(QWidget): self.speechTask.start() if "Invalid API-key provided" in result: bubble_message.playBtn.hide() + self.exchange.updateStatus('休眠中') else: bubble_message.playBtn.hide() + self.exchange.updateStatus('休眠中') # if not self.voiceStatus: self.bottom_ui.set_send_button_status(True) @@ -669,9 +934,157 @@ class Mainwin(QWidget): print('pos:', val) print('滚动条最大值', self.chatWgt.verticalScrollBar().maximum()) + +#菜单类 +class SettingMenu(QMenu): + def __init__(self, *args, **kwargs): + super(SettingMenu, self).__init__() + self.radius = 10 + self.setting_menu_sytle = ''' + QMenu {{ + /* 半透明效果 */ + border-radius: {radius}; + border: 2px solid rgb(255, 255, 255); + background-color: rgba(255, 255, 255, 230); + }} + QMenu::item {{ + border-radius: {radius}; + /* 这个距离很麻烦需要根据菜单的长度和图标等因素微调 */ + padding: 8px 48px 8px 12px; /* 12px是文字距离左侧距离*/ + background-color: transparent; + }} + + /* 鼠标悬停和按下效果 */ + QMenu::item:selected {{ + border-radius: {radius}; + /* 半透明效果 */ + background-color: rgba(230, 240, 255, 232); + }} + + /* 禁用效果 */ + QMenu::item:disabled {{ + border-radius: {radius}; + background-color: transparent; + }} + + /* 图标距离左侧距离 */ + QMenu::icon {{ + left: 15px; + }} + + /* 分割线效果 */ + QMenu::separator {{ + height: 1px; + background-color: rgb(232, 236, 243); + }}'''.format(radius=self.radius) + self.setStyleSheet(self.setting_menu_sytle) + self.setAttribute(Qt.WA_TranslucentBackground, True) + self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint ) + + def setSubMenu(self): + self.sub_menu.setStyleSheet(self.setting_menu_sytle) + self.sub_menu.setAttribute(Qt.WA_TranslucentBackground, True) + self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint ) + +# 上方标题栏 +class MianWinTitle(QWidget): + def __init__(self, *args, **kwargs): + super(MianWinTitle, self).__init__() + self.setAttribute(Qt.WA_StyledBackground) + self.setObjectName('title') + self.setStyleSheet( + ''' + #title{ + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background:transparent; + } + ''' + ) + self.installEventFilter(self) + self.setFixedHeight(36) + self.icon = QLabel(self) + self.icon.setFixedSize(16,16) + pixmap = QPixmap('./data/icon.png') + self.icon.setPixmap(pixmap) + self.icon.setScaledContents(True) + self.title_layout = QHBoxLayout(self) + self.title_layout.setContentsMargins(18, 12, 18, 0) + self.title_layout.addWidget(self.icon) + + self.title_name = QLabel() + self.title_name.setText('NFS AI') + self.title_name.setStyleSheet( + ''' + color:white; + font-size:16px; + font-weight:bold; + ''' + ) + + self.title_layout.addWidget(self.title_name) + + title_spacer = QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.title_layout.addItem(title_spacer) + + def switchViewType(self): + if PublicTypes.viewType == "sidebar": + self.title_layout.setContentsMargins(18, 12, 18, 0) + self.setStyleSheet( + ''' + #title{ + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background:transparent; + } + ''' + ) + self.icon.setFixedSize(16,16) + pixmap = QPixmap('./data/icon.png') + self.icon.setPixmap(pixmap) + self.icon.setScaledContents(True) + self.title_name.setStyleSheet( + ''' + color:white; + font-size:16px; + font-weight:bold; + ''' + ) + self.title_layout.setSpacing(10) + + elif PublicTypes.viewType == "windows": + self.title_layout.setContentsMargins(18, 6, 18, 6) + self.setStyleSheet( + ''' + #title{ + border-left: 2px solid transparent; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + background-color: white; + background-clip: padding-box; + } + ''' + ) + self.icon.setFixedSize(20,20) + pixmap = QPixmap('./data/icon_win.png') + self.icon.setPixmap(pixmap) + self.icon.setScaledContents(True) + + self.title_name.setStyleSheet( + ''' + color:black; + font-size: 16px; + font-weight: 400; + ''' + ) + self.title_layout.setSpacing(10) + + + else: + raise NotImplementedError("illegal type") + # if __name__ == '__main__': # app = QApplication([]) # widget = ShadowWindow() # widget.show() # app.exec_() - diff --git a/usr/share/aiassistant/preGuidWidget.py b/usr/share/aiassistant/preGuidWidget.py index 6122324b8c04e443e857b0549e27539e84bfa4f2..911b275efc2d7c3ad6a474b359573303f84792f9 100644 --- a/usr/share/aiassistant/preGuidWidget.py +++ b/usr/share/aiassistant/preGuidWidget.py @@ -13,6 +13,10 @@ from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * from bubble_message import BubbleMessage,MessageType +from PublicTypes import PublicTypes +import os + +public_types = PublicTypes() class ModelType: Chat = 1 @@ -45,15 +49,34 @@ class PreWidget(QWidget): self.setFixedHeight(h) # self.setWindowFlags(Qt.FramelessWindowHint) + + # AIFace + self.imageLabel = QLabel(self) + self.imageLabel.setAlignment(Qt.AlignCenter) + self.imageLabel.setStyleSheet("QLabel { padding: 0px;}") + self.imageLabel.setAlignment(Qt.AlignCenter) + + # 图片列表和索引 + self.image_folder1 = './data/AIFace' + self.image_files1 = [os.path.join(self.image_folder1, f) for f in os.listdir(self.image_folder1) if + f.endswith(('.png', '.jpg', '.jpeg'))] + self.current_image_index1 = 0 + self.image_timer1 = QTimer(self) + self.image_timer1.timeout.connect(self.updateImage) + self.image_timer1.start(500) ## 调用气泡信息 - self.guid_title = BubbleMessage('Hi, 我是NFS AI', '', MessageType.Guid, 16, is_send=False) - self.guid_content = BubbleMessage('作为您的智能助手,我将为您提供个性化的操作系统体验,帮助您完成各种任务、回答问题、提供实用信息,以及提供智能建议和提示。', '',MessageType.Guid, 12, is_send=False) + self.guid_title = BubbleMessage('欢迎使用NFS AI', '', MessageType.Guid, 14, is_send=False) + self.guid_content = BubbleMessage('作为您的智能助手,我将为您提供个性化的操作系统体验,帮助您完成各种任务、回答问题、提供实用信息,以及提供智能建议和提示。', '',MessageType.Guid, 11, is_send=False) + + self.guid_title.setFixedHeight(40) + self.guid_content.setFixedHeight(85) self.verticalLayout = QtWidgets.QVBoxLayout(self) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setContentsMargins(0, 20, 0, 0) self.verticalLayout.setSpacing(10) + self.verticalLayout.addWidget(self.imageLabel) self.verticalLayout.addWidget(self.guid_title) self.verticalLayout.addWidget(self.guid_content) @@ -66,10 +89,69 @@ class PreWidget(QWidget): self.meetingMinutsGuidWgt = OptionWidget() self.meetingMinutsGuidWgt.set_ui('./data/icon_meeting minutes.png', 'NFS AI秘书', '帮助您智能管理会议,不漏掉工作中每一个细节。') self.verticalLayout.addWidget(self.meetingMinutsGuidWgt) + self.chatGuidWgt.clicked.connect(self.chat_model_signal) self.paintGuidWgt.clicked.connect(self.paint_model_signal) self.meetingMinutsGuidWgt.clicked.connect(self.meeting_minuts_model_signal) + + + def switchViewType(self): + if PublicTypes.viewType == "sidebar": + self.verticalLayout.setSpacing(10) + # self.chatGuidWgt.setFixedWidth(440) + # self.paintGuidWgt.setFixedWidth(440) + # self.meetingMinutsGuidWgt.setFixedWidth(440) + self.guid_title.setFixedWidth(440) + self.guid_content.setFixedWidth(440) + + self.guid_title.setFixedHeight(40) + self.guid_content.setFixedHeight(85) + + self.verticalLayout.setAlignment(self.chatGuidWgt,Qt.AlignLeft) + self.verticalLayout.setAlignment(self.paintGuidWgt,Qt.AlignLeft) + self.verticalLayout.setAlignment(self.meetingMinutsGuidWgt,Qt.AlignLeft) + self.verticalLayout.setAlignment(self.guid_title,Qt.AlignLeft) + self.verticalLayout.setAlignment(self.guid_content,Qt.AlignLeft) + # self.setFixedHeight(600) + h = self.height - 380 + self.setFixedHeight(h) + self.setFixedWidth(480 - 36) + + + + elif PublicTypes.viewType == "windows": + self.verticalLayout.setSpacing(10) + self.chatGuidWgt.setFixedWidth(440) + self.paintGuidWgt.setFixedWidth(440) + self.meetingMinutsGuidWgt.setFixedWidth(440) + self.guid_title.setFixedWidth(480) + self.guid_content.setFixedWidth(480) + + self.guid_title.setFixedHeight(40) + self.guid_content.setFixedHeight(80) + + self.verticalLayout.setAlignment(self.chatGuidWgt,Qt.AlignCenter) + self.verticalLayout.setAlignment(self.paintGuidWgt,Qt.AlignCenter) + self.verticalLayout.setAlignment(self.meetingMinutsGuidWgt,Qt.AlignCenter) + self.verticalLayout.setAlignment(self.guid_title,Qt.AlignCenter) + self.verticalLayout.setAlignment(self.guid_content,Qt.AlignCenter) + self.setFixedHeight(560) + self.setFixedWidth(1164) + + + else: + raise NotImplementedError("illegal type") + + def updateImage(self): + if self.current_image_index1 < len(self.image_files1): + pixmap = QPixmap(self.image_files1[self.current_image_index1]) + + self.imageLabel.setPixmap(pixmap) + self.current_image_index1 = (self.current_image_index1 + 1) % len(self.image_files1) + + + class OptionWidget(QWidget): clicked = pyqtSignal() @@ -93,8 +175,14 @@ class OptionWidget(QWidget): painter.setOpacity(0.80) painter.setRenderHint(QPainter.Antialiasing) # 设置抗锯齿,让圆角更加平滑 # # 定义画笔和填充 - painter.setBrush(QBrush(QColor(235,235,235))) # 画刷颜色设置为白色 - painter.setPen(QPen(QColor(235, 235, 235), 0)) # 画笔颜色和粗细 + if PublicTypes.viewType == "sidebar": + painter.setBrush(QBrush(QColor(235,235,235))) # 画刷颜色设置为白色 + painter.setPen(QPen(QColor(235, 235, 235), 0)) # 画笔颜色和粗细 + elif PublicTypes.viewType == "windows": + painter.setBrush(QBrush(QColor(255,255,255))) # 画刷颜色设置为白色 + painter.setPen(QPen(QColor(255, 255, 255), 0)) # 画笔颜色和粗细 + else: + raise NotImplementedError("illegal type") # # 绘制圆角矩形 painter.drawRoundedRect(self.rect(), 10, 10) # 设置边角为10像素的圆度 @@ -102,7 +190,7 @@ class OptionWidget(QWidget): class Ui_optWgt(object): def setupUi(self, optWgt): # optWgt.resize(492, 108) - optWgt.setMinimumSize(QtCore.QSize(360, 108)) + # optWgt.setMinimumSize(QtCore.QSize(360, 108)) self.horizontalLayout = QtWidgets.QHBoxLayout(optWgt) self.horizontalLayout.setContentsMargins(24, 10, 24, 18) self.horizontalLayout.setObjectName("horizontalLayout")