前言

随着深度学习和自然语言处理技术的不断进步,问答系统进入了一个新的发展阶段,能够更加精准地理解复杂问题,支持多种知识形式的表达与多轮对话,从而实现更智能的问答体验。传统的问答方法主要依赖规则库、信息检索技术和浅层机器学习模型,尽管在特定领域中表现较为出色且系统具有较好的解释性,但在处理复杂语义和多轮对话时却显得力不从心。近年来,随着人工智能技术的快速发展,特别是大规模模型的出现,如 chatgpt、DeepSeek 等,迅速火爆整个 AI 圈。本文将使用 PySide6 搭建 AI 聊天界面,模仿 chatgpt 聊天,能实现与 AI 对话,使用 DeepSeek 大模型接口,功能:实现实时聊天,支持流式输出,下文也会教你怎么使用源码,界面如下:
在这里插入图片描述

一、DeepSeek注册与使用

官网地址:
DeepSeek | 深度求索
api接口改到这里了,点击开发平台
在这里插入图片描述
下图应该是旧版本的网页,官网应该更新了
在这里插入图片描述
自行注册
在这里插入图片描述

登录后的样子,有免费的额度,演示应该够用了
在这里插入图片描述

参考官网文档,python 代码感觉没啥用,又不是流式输出,对于新手不是很友好,后面我通过这个接口搭建一个界面:
在这里插入图片描述

二、安装环境

安装 openai 库,命令如下:

pip install openai

在这里插入图片描述
安装 pyside6,在自己创建的虚拟环境安装即可,命令如下

pip install pyside6==6.4.2

在这里插入图片描述

三、界面设计

搭建界面前需要完成 QtDesigner 配置,参考下面的教程:

手把手教你在PyCharm配置PySide6和QtDesigner,实现python程序快速搭建可视化界面

设计好的界面如下:
在这里插入图片描述

之后转成 python 文件即可
在这里插入图片描述

四、后端实现

接下来,将重点介绍如何实现后端逻辑,包括如何处理用户输入、发送 API 请求并响应、以及如何更新 UI 界面。本文的代码实现了一个多轮对话的聊天界面,并结合 API 实现了智能回复功能。

1.QTextEdit 输入控件实现

先说一下用户输入的控件,使用的是 QTextEdit ,原本 QTextEdit 控件键盘回车键是换行,我将其修改成发送信息了,如果按下的是 Shift+Enter,则是换行;如果仅按下 Enter,提交用户输入的消息。
核心代码如下:

    def eventFilter(self, obj, event):
        if obj == self.textEdit_input and event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
                if event.modifiers() & Qt.ShiftModifier:
                    # 按下 Shift+Enter 插入换行
                    cursor = self.textEdit_input.textCursor()
                    cursor.insertText("\n")
                    return True  # 事件被处理,避免传递到其他控件
                else:
                    # 按下 Enter 键发送消息
                    self.on_pushButton_Submit_clicked()
                    return True
        return super().eventFilter(obj, event)

2.API 请求与响应处理

将通过一个自定义的线程类 ApiThread 来实现与后端 API 的交互。用户输入的消息会通过这个线程发送请求,获取响应后再更新到界面上。on_pushButton_Submit_clicked 方法是获取用户输入的消息并启动 API 请求线程。on_api_response 方法会将响应数据显示在聊天窗口中,并根据时间戳处理消息的时间显示。核心代码如下:

        def on_pushButton_Submit_clicked(self):
        if self.is_generating:
            if self.api_thread and self.api_thread.isRunning():
                self.api_thread.stop()
            return

        message = self.textEdit_input.toPlainText().strip()
        if not message:
            return

        self.textEdit_input.clear()

        self.toggle_generating_state(True)
        time = str(int(QDateTime.currentDateTime().toSecsSinceEpoch()))
        self.updateMessageTimeDisplay(time)
        user_window = AIChatMessageWindow(self.listWidget_out.parentWidget())
        user_item = QListWidgetItem(self.listWidget_out)
        self.updateMessageDisplay(user_window, user_item, message, time, RoleType.user)

        # 3. 准备系统回复框
        self.current_message_window = AIChatMessageWindow(self.listWidget_out.parentWidget())
        self.current_item = QListWidgetItem(self.listWidget_out)

        # 4. 启动线程
        self.api_thread = ApiThread(message)
        self.api_thread.response_signal.connect(self.on_api_response)
        self.api_thread.complete_signal.connect(self.on_api_finished)  # 绑定结束信号
        self.api_thread.start()

        self.listWidget_out.scrollToBottom()

    def on_api_response(self, response):
        time = str(int(QDateTime.currentDateTime().toSecsSinceEpoch()))

        if hasattr(self, 'current_item') and self.current_item:
            current_message_window = self.current_message_window
            current_text = current_message_window.message_text
            current_text += response
            # 更新消息显示
            self.updateMessageDisplay(current_message_window, self.current_item, current_text, time, RoleType.system)
            # 滚动到底部
            self.listWidget_out.scrollToBottom()

3.消息显示与时间显示

动态地显示消息,并根据消息的时间间隔决定是否显示时间戳。每当一条消息发送或接收到响应时,都会更新消息显示,并在合适的时机显示时间戳。updateMessageDisplay 方法根据消息的文本内容调整消息项的高度,并将消息显示在聊天窗口中。updateMessageTimeDisplay 方法用来显示时间戳,当两条消息的时间间隔超过 60 秒时,会显示时间戳。核心代码如下:

    def updateMessageDisplay(self, message_window, current_item, text, time, userType):
        message_window.setFixedWidth(self.width())  # 设置消息窗口的宽度为主窗口的宽度
        size = message_window.font_rect(text)  # 获取文本的矩形区域
        current_item.setSizeHint(QSize(self.width(), size.height()))  # 设置列表项的高度为文本高度
        message_window.setText(text, time, size, userType)
        self.listWidget_out.setItemWidget(current_item, message_window)  # 将消息添加到消息列表中

    # 处理消息的时间显示
    def updateMessageTimeDisplay(self, curMsgTime):
        if self.listWidget_out.count() > 0:
            lastItem = self.listWidget_out.item(self.listWidget_out.count() - 1)
            message_window = self.listWidget_out.itemWidget(lastItem)
            lastTime = int(message_window.message_time)  # 获取最后一条消息的时间戳
            curTime = int(curMsgTime)  # 获取当前时间戳
            show_time = (curTime - lastTime) > 60  # 如果两条消息相差超过60秒,显示时间
        else:
            show_time = True

        if show_time:
            messageTime = AIChatMessageWindow(self.listWidget_out.parentWidget())
            itemTime = QListWidgetItem(self.listWidget_out)
            size = QSize(self.width(), 40)
            messageTime.resize(size)
            itemTime.setSizeHint(size)
            messageTime.setText(curMsgTime, curMsgTime, size, RoleType.current_time)
            self.listWidget_out.setItemWidget(itemTime, messageTime)

4.实现头像绘制和文本的绘制

重点是这些代码实现,每个聊天作为一个QWidget窗口,简单来说 QListWidget 可以加载多个QWidget窗口,不过也可以通过其他组件实现,讲一下绘制代码 paintEvent ,paintEvent 是 Qt 框架中窗口和控件的自带方法,它是 QWidget 类的一个事件处理函数。所有继承自 QWidget 的控件(如 QMainWindow、QDialog、QLabel 等)都具有 paintEvent 方法,并且当需要重绘时,Qt 会自动调用这个方法。
在代码中每次窗口重绘时,会根据消息类型绘制不同的内容:

  • RoleType.system:绘制系统消息,包含左侧头像、消息框和文本。
  • RoleType.user:绘制用户消息,包含右侧头像、消息框和文本。
  • RoleType.current_time:绘制时间消息,显示当前时间。

绘制过程中,QPainter 用于绘制图形、文本等,QRect 用于定义矩形区域,QPen 设置画笔颜色。绘制步骤:

  1. 绘制头像:首先根据消息类型选择左侧或右侧的头像,确保其大小与设备的像素比例一致。
  2. 绘制消息框:为每条消息绘制一个背景框,左侧系统消息框背景为浅灰色,右侧用户消息框为蓝色。
  3. 绘制文本:根据消息内容绘制文本,如果有换行,文本会自动换行并适应消息框的宽度。
  4. 绘制时间消息:时间消息居中显示,字体较小且灰色。

paintEvent 方法全部代码如下:

        def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        dpr = self.devicePixelRatio()

        if self.message_userType == RoleType.system:
            self._draw_avatar(painter, self.leftPixmap, self.left_icon_rect, dpr)
            painter.setBrush(QColor(255, 255, 255))
            painter.setPen(QColor(230, 230, 230))
            painter.drawRoundedRect(self.left_frame_rect, 6, 6)

            path = QPainterPath()
            tip_x = self.left_frame_rect.x() - self.triangleW
            tip_y = self.left_frame_rect.y() + 20
            path.moveTo(self.left_frame_rect.x(), tip_y - 6)
            path.lineTo(tip_x, tip_y)
            path.lineTo(self.left_frame_rect.x(), tip_y + 6)
            painter.drawPath(path)

        elif self.message_userType == RoleType.user:
            self._draw_avatar(painter, self.rightPixmap, self.right_icon_rect, dpr)
            painter.setBrush(QColor(75, 164, 242))
            painter.setPen(Qt.NoPen)
            painter.drawRoundedRect(self.right_frame_rect, 6, 6)

            path = QPainterPath()
            # 右侧三角的位置也要基于 right_frame_rect 动态计算
            tip_x = self.right_frame_rect.x() + self.right_frame_rect.width() + self.triangleW
            tip_y = self.right_frame_rect.y() + 20
            path.moveTo(self.right_frame_rect.right(), tip_y - 6)
            path.lineTo(tip_x, tip_y)
            path.lineTo(self.right_frame_rect.right(), tip_y + 6)
            painter.drawPath(path)

        elif self.message_userType == RoleType.current_time:
            painter.setPen(QColor(153, 153, 153))
            painter.setFont(QFont("Microsoft YaHei", 10))
            painter.drawText(self.rect(), Qt.AlignCenter, self.current_time)
            self.text_edit.hide()

在代码实现过程出现绘制的头像模糊,我电脑设备高分辨率会出现模糊,为了确保在不同显示设备上,使用 devicePixelRatio 方法获取当前显示设备的像素比(DPI比例)之后通过 pixmap.scaled 方法调整图像的大小,这样就可以使图像能够按照正确的设备像素比(DPI)进行缩放和显示,因此能在高分辨率显示器上显示清晰,且不会模糊。核心代码如下:

    def _draw_avatar(self, painter, pixmap, rect, dpr):
        if pixmap.devicePixelRatio() != dpr:
            pixmap = pixmap.scaled(rect.size() * dpr, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            pixmap.setDevicePixelRatio(dpr)
        painter.drawPixmap(rect, pixmap)

5.更换头像

如需更换自己头像也是很简单的,在 leftPixmap 和 rightPixmap 填入图片路径即可。
在这里插入图片描述

五、完整源码下载和使用方法

完整源码公众号后台发送 AI聊天界面 关键字即可获取,自己配置一下环境即可运行起来,代码需要更换自己的 API keys,在 DeepSeek 平台创建即可,创建时候记住 keys 值,它只会出现一次
在这里插入图片描述

之后在 deepseek.py 文件中的 api_key 处的单引号里面填入你的 keys 就行
在这里插入图片描述


总结

本文到此结束,对你有帮助帮忙点个小爱心呗,完整源码公众号发送 AI聊天界面 关键字即可获取

参考文章: Qt 学习之路】Qt5气泡式聊天框——QListWidget+QPainter实现

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐