使用 PyQt5 和 DeepSeek API 构建一个多语言聊天助手-代码(一)

使用 PyQt5 和 DeepSeek API 构建一个多语言聊天助手

在现代软件开发中,图形用户界面(GUI)和人工智能(AI)的结合变得越来越普遍。本文将介绍如何使用 Python 的 PyQt5 库和 DeepSeek API 构建一个多语言聊天助手。这个助手不仅支持多语言切换,还能保存和加载聊天历史,为用户提供流畅的交互体验。

项目概述

我们的目标是构建一个桌面应用程序,用户可以通过输入 API Key 登录,选择偏好的语言,并与 DeepSeek 的 AI 模型进行交互。应用程序的主要功能包括:

  1. 登录验证:用户输入 API Key 进行验证。

  2. 语言选择:用户可以选择英文或中文作为界面语言。

  3. 聊天功能:用户可以与 AI 进行多轮对话。

  4. 历史记录:聊天记录可以保存和加载。

  5. 多标签页:支持多个独立的聊天会话。

技术栈

  • PyQt5:用于构建图形用户界面。

  • DeepSeek API:用于与 AI 模型进行交互。

  • JSON:用于保存和加载聊天历史。

  • 多线程:用于处理 API 请求,避免阻塞主线程。

代码解析

1. 登录窗口 (LoginDialog)

登录窗口负责验证用户的 API Key。用户输入 API Key 后,应用程序会尝试与 DeepSeek API 进行通信,验证 Key 的有效性。

python

复制

class LoginDialog(QDialog):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(LANGUAGES["en"]["login_title"])  # 默认英文
        self.setGeometry(200, 200, 300, 150)

        layout = QVBoxLayout()

        # API Key 输入框
        self.api_key_input = QLineEdit(self)
        self.api_key_input.setPlaceholderText(LANGUAGES["en"]["api_key_placeholder"])
        layout.addWidget(self.api_key_input)

        # 登录按钮
        self.login_button = QPushButton(LANGUAGES["en"]["login_button"], self)
        self.login_button.clicked.connect(self.validate_api_key)
        layout.addWidget(self.login_button)

        self.setLayout(layout)

    def validate_api_key(self):
        api_key = self.api_key_input.text()
        if not api_key:
            QMessageBox.warning(self, "Error", LANGUAGES["en"]["error_api_key_empty"])
            return

        # 测试 API Key 是否有效
        try:
            client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
            client.chat.completions.create(
                model="deepseek-chat",
                messages=[{"role": "user", "content": "Hello"}],
                stream=False,
                timeout=5
            )
            self.accept()  # 关闭登录窗口
            self.api_key = api_key  # 保存有效的 API Key
        except AuthenticationError:
            QMessageBox.critical(self, "Error", LANGUAGES["en"]["error_invalid_api_key"])
        except Exception as e:
            QMessageBox.critical(self, "Error", LANGUAGES["en"]["error_unknown"].format(str(e)))

2. 语言选择窗口 (LanguageDialog)

语言选择窗口允许用户选择应用程序的界面语言。用户可以选择英文或中文。

python

复制

class LanguageDialog(QDialog):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(LANGUAGES["en"]["language_title"])
        self.setGeometry(200, 200, 300, 150)

        layout = QVBoxLayout()

        # 语言选择提示
        self.prompt_label = QLabel(LANGUAGES["en"]["language_prompt"], self)
        layout.addWidget(self.prompt_label)

        # 语言选择按钮
        self.language_group = QButtonGroup(self)
        self.language_en = QRadioButton(LANGUAGES["en"]["language_en"], self)
        self.language_zh = QRadioButton(LANGUAGES["en"]["language_zh"], self)
        self.language_group.addButton(self.language_en, 1)
        self.language_group.addButton(self.language_zh, 2)
        layout.addWidget(self.language_en)
        layout.addWidget(self.language_zh)

        # 确认按钮
        self.confirm_button = QPushButton("OK", self)
        self.confirm_button.clicked.connect(self.confirm_language)
        layout.addWidget(self.confirm_button)

        self.setLayout(layout)

    def confirm_language(self):
        if self.language_group.checkedId() == 1:
            self.language = "en"
        elif self.language_group.checkedId() == 2:
            self.language = "zh"
        else:
            QMessageBox.warning(self, "Error", "Please select a language.")
            return
        self.accept()

3. 聊天页面 (ChatPage)

聊天页面是应用程序的核心部分,负责处理用户输入、显示聊天记录、调用 DeepSeek API 并显示 AI 的回复。

python

复制

class ChatPage(QWidget):
    def __init__(self, client, language, tab_id):
        super().__init__()
        self.client = client
        self.language = language
        self.tab_id = tab_id
        self.history_file = os.path.join(HISTORY_DIR, f"chat_history_{self.tab_id}.json")
        self.messages = self.load_history()  # 初始化对话历史
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # 聊天记录显示区域
        self.chat_display = QTextEdit(self)
        self.chat_display.setReadOnly(True)  # 设置为只读
        layout.addWidget(self.chat_display)

        # 用户输入框
        self.user_input = QLineEdit(self)
        self.user_input.setPlaceholderText(LANGUAGES[self.language]["send_button"])
        layout.addWidget(self.user_input)

        # 发送按钮
        self.send_button = QPushButton(LANGUAGES[self.language]["send_button"], self)
        self.send_button.clicked.connect(self.send_message)
        layout.addWidget(self.send_button)

        # 清除对话按钮
        self.clear_button = QPushButton(LANGUAGES[self.language]["clear_button"], self)
        self.clear_button.clicked.connect(self.clear_conversation)
        layout.addWidget(self.clear_button)

        self.setLayout(layout)

        # 初始化时显示历史记录
        self.update_chat_display()

    def send_message(self):
        user_message = self.user_input.text()
        if not user_message:
            return  # 如果输入为空,直接返回

        # 将用户输入添加到对话历史
        self.messages.append({"role": "user", "content": user_message})

        # 显示用户输入
        self.chat_display.append(f"User: {user_message}")

        # 清空输入框
        self.user_input.clear()

        # 禁用发送按钮,避免重复点击
        self.send_button.setEnabled(False)

        # 创建工作线程并启动
        self.worker = Worker(self.client, self.messages)
        self.worker.finished.connect(self.handle_reply, Qt.QueuedConnection)
        self.worker.error.connect(self.handle_error, Qt.QueuedConnection)
        self.worker.start()

    def handle_reply(self, reply):
        # 显示助手回复
        self.chat_display.append(f"Assistant: {reply}\n")

        # 将助手回复添加到对话历史
        self.messages.append({"role": "assistant", "content": reply})

        # 重新启用发送按钮
        self.send_button.setEnabled(True)

        # 释放工作线程资源
        self.worker.quit()
        self.worker.wait()
        self.worker = None

        # 自动保存历史
        self.save_history()

    def handle_error(self, error_message):
        # 显示错误信息
        self.chat_display.append(f"Assistant: {error_message}\n")
        QMessageBox.critical(self, "Error", error_message)

        # 重新启用发送按钮
        self.send_button.setEnabled(True)

        # 释放工作线程资源
        if self.worker:
            self.worker.quit()
            self.worker.wait()
            self.worker = None

    def clear_conversation(self):
        # 清除当前对话历史
        self.messages = [{"role": "system", "content": "You are a helpful assistant."}]
        self.chat_display.clear()
        QMessageBox.information(self, "Info", LANGUAGES[self.language]["info_conversation_cleared"])

        # 自动保存历史
        self.save_history()

    def save_history(self):
        # 保存对话历史到文件
        with open(self.history_file, "w") as f:
            json.dump(self.messages, f)

    def load_history(self):
        # 从文件加载对话历史
        try:
            with open(self.history_file, "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return [{"role": "system", "content": "You are a helpful assistant."}]

    def update_chat_display(self):
        # 更新聊天记录显示
        self.chat_display.clear()
        for msg in self.messages:
            if msg["role"] == "user":
                self.chat_display.append(f"User: {msg['content']}\n")
            elif msg["role"] == "assistant":
                self.chat_display.append(f"Assistant: {msg['content']}\n")

4. 主窗口 (ChatApp)

主窗口负责管理多个聊天页面,并提供添加新聊天页面的功能。

python

复制

class ChatApp(QWidget):
    def __init__(self, api_key, language):
        super().__init__()
        self.api_key = api_key
        self.language = language
        self.client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(LANGUAGES[self.language]["chat_title"])
        self.setGeometry(100, 100, 800, 600)

        layout = QVBoxLayout()

        # 选项卡控件
        self.tabs = QTabWidget(self)
        layout.addWidget(self.tabs)

        # 添加新页面按钮
        self.add_tab_button = QPushButton(LANGUAGES[self.language]["new_chat_button"], self)
        self.add_tab_button.clicked.connect(self.add_tab)
        layout.addWidget(self.add_tab_button)

        self.setLayout(layout)

        # 加载所有历史文件
        self.load_all_histories()

    def add_tab(self):
        # 添加一个新页面
        tab_id = self.tabs.count() + 1
        tab = ChatPage(self.client, self.language, tab_id)
        self.tabs.addTab(tab, f"Chat {tab_id}")

    def load_all_histories(self):
        # 加载所有历史文件
        history_files = [f for f in os.listdir(HISTORY_DIR) if f.startswith("chat_history_") and f.endswith(".json")]
        for file in history_files:
            tab_id = int(file.split("_")[2].split(".")[0])
            tab = ChatPage(self.client, self.language, tab_id)
            self.tabs.addTab(tab, f"Chat {tab_id}")

5. 多线程处理 (Worker)

为了确保 API 请求不会阻塞主线程,我们使用 QThread 来处理 API 请求。

python

复制

class Worker(QThread):
    finished = pyqtSignal(str)  # 用于发送请求结果的信号
    error = pyqtSignal(str)  # 用于发送错误信息的信号

    def __init__(self, client, messages):
        super().__init__()
        self.client = client
        self.messages = messages

    def run(self):
        max_retries = 3  # 最大重试次数
        retry_delay = 5  # 每次重试的延迟时间(秒)

        for attempt in range(max_retries):
            try:
                # 调用 DeepSeek API
                response = self.client.chat.completions.create(
                    model="deepseek-chat",  # 使用 DeepSeek 的模型
                    messages=self.messages,
                    stream=False,
                    timeout=30  # 设置超时时间为 30 秒
                )

                # 提取助手的回复
                assistant_reply = response.choices[0].message.content
                self.finished.emit(assistant_reply)  # 发送成功信号
                break  # 如果成功,跳出循环

            except Exception as e:
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                else:
                    self.error.emit(f"An error occurred: {str(e)}")
                    break

总结

通过本文的介绍,我们使用 PyQt5 和 DeepSeek API 构建了一个功能丰富的多语言聊天助手。这个应用程序不仅支持多语言切换,还能保存和加载聊天历史,为用户提供了流畅的交互体验。希望这篇文章能帮助你理解如何将 GUI 和 AI 技术结合起来,构建出更强大的应用程序。

如果你对这个项目感兴趣,可以尝试扩展更多功能,比如支持更多的语言、添加语音输入输出、或者集成其他 AI 模型。祝你在编程的旅程中取得更多成就!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值