ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Qt与DeepSeek API构建桌面AI助手:从网络请求到流式响应的完整实现

基于Qt与DeepSeek API构建桌面AI助手:从网络请求到流式响应的完整实现 最近在尝试将 AI 大模型能力集成到桌面应用中发现网上关于 Qt 结合 DeepSeek API 的完整实战案例非常零散要么是简单的 HTTP 请求示例要么是纯界面展示缺乏一个从零到一、功能闭环、可直接复用的项目教程。本文将分享一个基于 Qt 框架开发的DeepSeek AI Assistant 客户端的完整实现过程。这个项目不仅实现了基础的对话问答还涵盖了 API 密钥管理、对话历史持久化、流式响应显示、Markdown 渲染等实用功能适合有一定 C/Qt 基础的开发者学习如何将现代 AI 能力与传统桌面 GUI 开发相结合。无论你是想为现有工具添加智能问答模块还是学习 Qt 网络编程与异步 UI 更新这篇文章都能提供一套可直接运行的代码和清晰的实现思路。1. 背景与核心概念在深入代码之前我们有必要厘清几个核心概念并理解为什么选择 Qt 和 DeepSeek 来构建这样一个客户端。1.1 Qt 框架简介Qt 是一个跨平台的 C 应用程序开发框架。它不仅仅是一个 GUI 工具包更提供了网络、数据库、多线程、XML 处理等丰富的模块。其核心特性“信号与槽”机制为处理异步操作如网络请求提供了优雅的解决方案。对于需要开发高性能、原生体验桌面应用的场景Qt 是 C 开发者的首选之一。1.2 DeepSeek API 简介DeepSeek 是由深度求索公司开发的大语言模型。它提供了开放的 API 接口允许开发者通过 HTTP 请求调用其模型能力实现文本生成、对话、代码编写等功能。与直接使用 Web 页面相比API 调用可以更灵活地集成到自定义应用中并实现数据的本地化处理与存储。1.3 项目目标与价值本项目旨在构建一个独立的桌面客户端它将封装 DeepSeek API用户无需打开浏览器直接在客户端内完成对话。管理对话上下文保持多轮对话的连贯性。提供良好的用户体验支持流式输出打字机效果、Markdown 格式渲染、历史记录查看与加载。保障数据安全API 密钥本地加密存储对话历史可本地保存。通过这个项目你将掌握 Qt 中QNetworkAccessManager进行 HTTP 通信、QJson处理数据、自定义模型视图显示数据、以及如何将异步网络响应安全地更新到 UI 线程等关键技术点。2. 环境准备与版本说明在开始编码前请确保你的开发环境已就绪。以下版本是本文示例所基于的环境其他相近版本通常也兼容但需要注意 API 和库的细微差异。操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 22.04)。Qt 是跨平台的本文代码在三大主流桌面系统上均可编译运行。Qt 版本Qt 6.5.0或更高版本推荐使用 Qt 6 系列。Qt 5.15 也可运行但部分网络 API 和 CMake 配置略有不同。本文使用 Qt 6。编译器Windows: MinGW 11.2.0 或 MSVC 2019/2022。macOS: Clang (随 Xcode 安装)。Linux: GCC 11 或以上。集成开发环境 (IDE)Qt Creator是官方 IDE对 Qt 项目支持最好。你也可以使用 VS Code 或 CLion 配合 CMake。DeepSeek API你需要一个有效的 DeepSeek API Key。可以访问 DeepSeek 官方平台注册并获取。本文示例使用其deepseek-chat模型。第三方库本项目核心使用 Qt 内置模块。为了渲染 Markdown我们将使用一个轻量级的头文件库cmark-gpp它会以源码形式集成。项目结构预览 在创建项目后你的目录结构将大致如下DeepSeekAssistant/ ├── CMakeLists.txt # 项目构建文件 ├── src/ # 源代码目录 │ ├── main.cpp # 程序入口 │ ├── MainWindow.cpp/.h # 主窗口类 │ ├── ChatSession.cpp/.h # 对话会话管理类 │ ├── ApiClient.cpp/.h # 网络请求客户端类 │ ├── Settings.cpp/.h # 配置管理类 │ └── MarkdownHighlighter.cpp/.h # Markdown语法高亮类 ├── resources/ # 资源文件 │ └── styles.qss # 界面样式表 ├── 3rdparty/ # 第三方库 │ └── cmark-gpp/ # Markdown解析库 └── README.md3. 核心模块设计与原理拆解一个健壮的客户端需要良好的架构。我们将功能拆分为几个核心类每个类职责单一。3.1 ApiClient网络请求的封装这是与 DeepSeek 服务器通信的核心。我们使用 Qt 的QNetworkAccessManager(NAM) 来发送 HTTP POST 请求。为什么使用 NAMNAM 是 Qt 推荐的网络访问接口它基于事件循环支持异步操作不会阻塞 UI 线程。关键流程构造一个QNetworkRequest设置 URL (https://api.deepseek.com/chat/completions) 和必要的 HTTP 头Authorization: Bearer your_api_key,Content-Type: application/json。构建请求体JSON 格式包含model,messages(对话历史),stream(是否流式) 等参数。调用NAM-post(request, jsonData)发送请求。连接finished信号到槽函数处理响应。对于流式响应需要连接readyRead信号来逐块读取数据。3.2 ChatSession对话上下文管理大模型需要上下文来理解当前对话。此类负责维护一个QVectorMessage列表其中Message是一个结构体包含role(“user”, “assistant”, “system”) 和content。核心方法appendMessage(): 添加一条消息到会话历史。getMessagesForApi(): 将会话历史转换为 API 所需的 JSON 数组格式。这里通常需要管理上下文长度避免超出模型 Token 限制。clear(): 清空当前会话。saveToFile()/loadFromFile(): 将会话保存到本地文件或从文件加载实现历史记录持久化。3.3 MainWindow用户界面与逻辑协调这是 Qt 的主窗口类负责UI 布局使用QTextEdit作为输入框和对话显示框QListWidget或QListView显示历史会话列表按钮等控件。信号与槽连接将 UI 事件如点击发送按钮连接到业务逻辑如调用ApiClient::sendMessage。线程安全更新网络响应在非 UI 线程中到达需要通过信号槽机制或QMetaObject::invokeMethod将内容安全地传递到 UI 线程进行更新。3.4 流式响应与 UI 更新这是提升用户体验的关键。当 API 请求设置stream: true时服务器会返回一个 SSE (Server-Sent Events) 流。处理流程在ApiClient中当收到数据块readyRead时读取所有可用数据。数据格式为data: {...}\n\n。需要按行解析提取data字段。解析data中的 JSON获取choices[0].delta.content即本次流式返回的文本片段。发射一个自定义信号如textFragmentReceived(const QString fragment)。在MainWindow中连接此信号到一个槽函数该函数将片段追加到对话显示框并自动滚动到底部实现“打字机”效果。4. 完整实战一步步构建客户端现在让我们从零开始构建这个项目。我们将使用CMake作为构建系统这是 Qt 6 推荐的方式。4.1 创建 Qt CMake 项目首先在 Qt Creator 中新建项目选择 “CMake Build System” 和 “Qt Widgets Application”。项目名称为DeepSeekAssistant。CMakeLists.txt(根目录) 关键配置cmake_minimum_required(VERSION 3.16) project(DeepSeekAssistant VERSION 1.0.0 LANGUAGES CXX) # 查找所需的 Qt 组件 set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network) # 添加可执行文件目标 add_executable(DeepSeekAssistant src/main.cpp src/MainWindow.cpp src/MainWindow.h src/ApiClient.cpp src/ApiClient.h src/ChatSession.cpp src/ChatSession.h src/Settings.cpp src/Settings.h src/MarkdownHighlighter.cpp src/MarkdownHighlighter.h ) # 链接 Qt 库 target_link_libraries(DeepSeekAssistant Qt6::Core Qt6::Widgets Qt6::Network ) # 包含头文件目录 target_include_directories(DeepSeekAssistant PRIVATE src) # 复制资源文件如样式表 file(COPY resources DESTINATION ${CMAKE_CURRENT_BINARY_DIR})4.2 定义数据结构与 API 客户端src/ChatSession.h:#ifndef CHATSESSION_H #define CHATSESSION_H #include QObject #include QVector #include QJsonArray #include QFile struct Message { QString role; // system, user, assistant QString content; }; class ChatSession : public QObject { Q_OBJECT public: explicit ChatSession(QObject *parent nullptr); void appendMessage(const QString role, const QString content); QJsonArray getMessagesForApi() const; void clear(); bool saveToFile(const QString filePath); bool loadFromFile(const QString filePath); const QVectorMessage messages() const { return m_messages; } signals: void sessionUpdated(); private: QVectorMessage m_messages; }; #endif // CHATSESSION_Hsrc/ApiClient.h:#ifndef APICLIENT_H #define APICLIENT_H #include QObject #include QNetworkAccessManager #include QNetworkReply class ApiClient : public QObject { Q_OBJECT public: explicit ApiClient(QObject *parent nullptr); void setApiKey(const QString apiKey); void sendMessage(const QJsonArray messages, bool stream true); signals: // 用于流式响应 void textFragmentReceived(const QString fragment); // 用于非流式响应或最终完成 void responseReceived(const QString fullResponse); void errorOccurred(const QString errorString); private slots: void onReplyFinished(); void onReadyRead(); private: QNetworkAccessManager *m_networkManager; QString m_apiKey; QNetworkReply *m_currentReply nullptr; void processStreamData(const QByteArray data); }; #endif // APICLIENT_Hsrc/ApiClient.cpp核心实现 (发送请求与处理流):#include ApiClient.h #include QJsonDocument #include QJsonObject #include QNetworkRequest #include QUrl ApiClient::ApiClient(QObject *parent) : QObject(parent), m_networkManager(new QNetworkAccessManager(this)) { // 可以在这里设置代理等网络配置 } void ApiClient::setApiKey(const QString apiKey) { m_apiKey apiKey; } void ApiClient::sendMessage(const QJsonArray messages, bool stream) { if (m_apiKey.isEmpty()) { emit errorOccurred(API Key 未设置。请在设置中配置。); return; } QUrl url(https://api.deepseek.com/chat/completions); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, application/json); request.setRawHeader(Authorization, QString(Bearer %1).arg(m_apiKey).toUtf8()); QJsonObject jsonBody; jsonBody[model] deepseek-chat; jsonBody[messages] messages; jsonBody[stream] stream; // 可以添加其他参数如 temperature, max_tokens 等 // jsonBody[temperature] 0.7; // jsonBody[max_tokens] 2048; QJsonDocument doc(jsonBody); QByteArray data doc.toJson(); if (m_currentReply) { m_currentReply-abort(); m_currentReply-deleteLater(); } m_currentReply m_networkManager-post(request, data); if (stream) { // 连接 readyRead 信号以处理流式数据块 connect(m_currentReply, QNetworkReply::readyRead, this, ApiClient::onReadyRead); } // 连接 finished 信号以处理最终结果或错误 connect(m_currentReply, QNetworkReply::finished, this, ApiClient::onReplyFinished); } void ApiClient::onReadyRead() { if (!m_currentReply) return; QByteArray newData m_currentReply-readAll(); processStreamData(newData); } void ApiClient::processStreamData(const QByteArray data) { // SSE 格式解析数据行以 data: 开头以两个换行符分隔事件 QString buffer QString::fromUtf8(data); QStringList lines buffer.split(\n, Qt::SkipEmptyParts); for (const QString line : lines) { if (line.startsWith(data: )) { QString jsonStr line.mid(6).trimmed(); // 去掉 data: if (jsonStr [DONE]) { // 流式传输结束 emit responseReceived(); // 或发射一个完成信号 continue; } QJsonParseError parseError; QJsonDocument doc QJsonDocument::fromJson(jsonStr.toUtf8(), parseError); if (parseError.error ! QJsonParseError::NoError) { qWarning() JSON parse error in stream: parseError.errorString(); continue; } QJsonObject obj doc.object(); QJsonArray choices obj[choices].toArray(); if (!choices.isEmpty()) { QJsonObject choice choices[0].toObject(); QJsonObject delta choice[delta].toObject(); if (delta.contains(content)) { QString fragment delta[content].toString(); emit textFragmentReceived(fragment); } } } } } void ApiClient::onReplyFinished() { if (!m_currentReply) return; if (m_currentReply-error() ! QNetworkReply::NoError) { emit errorOccurred(m_currentReply-errorString()); } else { // 对于非流式请求在这里处理完整响应 // QByteArray responseData m_currentReply-readAll(); // ... 解析并发射 responseReceived } m_currentReply-deleteLater(); m_currentReply nullptr; }4.3 构建主界面与逻辑连接src/MainWindow.h(部分关键成员):#include QMainWindow #include QTextEdit #include QListWidget #include QPushButton #include QSplitter #include ApiClient.h #include ChatSession.h #include Settings.h class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); private slots: void onSendButtonClicked(); void onApiTextFragmentReceived(const QString fragment); void onApiResponseReceived(const QString response); void onApiErrorOccurred(const QString error); void appendMessageToDisplay(const QString role, const QString content, bool isAssistantThinking false); private: void setupUI(); void loadSettings(); void saveSettings(); // UI 组件 QTextEdit *m_inputEdit; QTextEdit *m_chatDisplay; QListWidget *m_historyList; QPushButton *m_sendButton; QPushButton *m_clearButton; QPushButton *m_settingsButton; // 业务逻辑组件 ApiClient *m_apiClient; ChatSession *m_chatSession; Settings *m_settings; // 用于流式响应的临时缓冲区 QString m_currentAssistantResponse; };src/MainWindow.cpp核心交互逻辑:#include MainWindow.h #include QVBoxLayout #include QHBoxLayout #include QMessageBox #include QScrollBar #include QDateTime MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_apiClient(new ApiClient(this)), m_chatSession(new ChatSession(this)), m_settings(new Settings(this)) { setupUI(); loadSettings(); // 连接信号与槽 connect(m_sendButton, QPushButton::clicked, this, MainWindow::onSendButtonClicked); connect(m_apiClient, ApiClient::textFragmentReceived, this, MainWindow::onApiTextFragmentReceived); connect(m_apiClient, ApiClient::responseReceived, this, MainWindow::onApiResponseReceived); connect(m_apiClient, ApiClient::errorOccurred, this, MainWindow::onApiErrorOccurred); connect(m_chatSession, ChatSession::sessionUpdated, this, [this](){ // 当会话更新时可以刷新历史列表显示简化示例未实现完整历史列表 }); // 设置 API Key m_apiClient-setApiKey(m_settings-apiKey()); } void MainWindow::onSendButtonClicked() { QString userInput m_inputEdit-toPlainText().trimmed(); if (userInput.isEmpty()) return; // 1. 将用户输入添加到会话并显示 m_chatSession-appendMessage(user, userInput); appendMessageToDisplay(user, userInput); // 2. 清空输入框 m_inputEdit-clear(); // 3. 准备发送给 API 的消息历史 QJsonArray messagesForApi m_chatSession-getMessagesForApi(); // 4. 在界面上显示“思考中...”的占位符 appendMessageToDisplay(assistant, , true); // isAssistantThinking true m_currentAssistantResponse.clear(); // 5. 发送请求 m_apiClient-sendMessage(messagesForApi, true); // 使用流式 } void MainWindow::onApiTextFragmentReceived(const QString fragment) { // 累积片段 m_currentAssistantResponse fragment; // 更新最后一条消息即“思考中...”那条的内容 // 这里需要一种机制来更新 QTextEdit 中最后一段特定内容。 // 一种简单实现在 appendMessageToDisplay 时为助理消息设置一个特殊的 HTML 锚点或标记 // 然后在此处通过查找和替换来更新。 // 简化示例我们直接替换整个最后一段的 HTML效率较低仅作演示。 // 更优方案是使用 QTextDocument 的 cursor 进行精细操作。 // 示例获取聊天显示框的 QTextDocument QTextDocument *doc m_chatDisplay-document(); QTextCursor cursor(doc); cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); // ... 复杂的更新逻辑此处省略。实际项目中可使用 QTextBrowser 并配合自定义的 Model/Delegate。 // 临时简化直接更新整个最后一条消息 // 注意这会导致每次片段都重绘整个最后一条消息仅用于演示原理。 // 移除最后的“思考中...”占位符行重新添加累积的响应。 QString plainText m_chatDisplay-toPlainText(); QStringList lines plainText.split(\n, Qt::SkipEmptyParts); if (!lines.isEmpty() lines.last().startsWith(DeepSeek:)) { lines.removeLast(); // 重新组装文本这里应该用更结构化的方式存储消息 } lines DeepSeek: m_currentAssistantResponse; m_chatDisplay-setPlainText(lines.join(\n)); // 自动滚动到底部 QScrollBar *scrollbar m_chatDisplay-verticalScrollBar(); scrollbar-setValue(scrollbar-maximum()); } void MainWindow::onApiResponseReceived(const QString response) { // 流式传输结束或非流式请求完成 if (!response.isEmpty()) { // 非流式响应一次性添加 m_chatSession-appendMessage(assistant, response); appendMessageToDisplay(assistant, response); } else { // 流式响应结束将最终内容正式添加到会话 if (!m_currentAssistantResponse.isEmpty()) { m_chatSession-appendMessage(assistant, m_currentAssistantResponse); // 界面已在流式过程中更新这里可能只需要做一些清理工作 m_currentAssistantResponse.clear(); } } } void MainWindow::appendMessageToDisplay(const QString role, const QString content, bool isAssistantThinking) { QString formattedLine; QDateTime currentTime QDateTime::currentDateTime(); QString timeStr currentTime.toString(hh:mm:ss); if (role user) { formattedLine QString([%1] 你: %2).arg(timeStr, content); } else if (role assistant) { QString displayContent isAssistantThinking ? 思考中... : content; formattedLine QString([%1] DeepSeek: %2).arg(timeStr, displayContent); // 可以为助理消息设置不同的样式例如颜色 } m_chatDisplay-append(formattedLine); // append 会自动换行 }4.4 实现设置管理与数据持久化src/Settings.h:#ifndef SETTINGS_H #define SETTINGS_H #include QObject #include QSettings class Settings : public QObject { Q_OBJECT public: explicit Settings(QObject *parent nullptr); QString apiKey() const; void setApiKey(const QString apiKey); // 可以扩展其他设置如模型选择、温度、历史保存路径等 private: QSettings m_settings; }; #endifsrc/Settings.cpp:#include Settings.h Settings::Settings(QObject *parent) : QObject(parent), m_settings(MyCompany, DeepSeekAssistant) { // QSettings 会根据平台自动选择存储位置Windows注册表macOS plistLinux ini文件 } QString Settings::apiKey() const { // 简单示例实际应用中应考虑加密存储 return m_settings.value(Api/Key, ).toString(); } void Settings::setApiKey(const QString apiKey) { m_settings.setValue(Api/Key, apiKey); m_settings.sync(); }4.5 运行与验证编译项目在 Qt Creator 中配置好 Kit (选择你的 Qt 版本和编译器)点击“构建”按钮。配置 API Key首次运行程序会因缺少 API Key 而报错。你需要在代码中暂时硬编码一个测试 Key或者快速实现一个简单的设置对话框QDialog让用户输入并保存 Key。为了演示我们可以在MainWindow构造函数中添加一个临时检查if (m_settings-apiKey().isEmpty()) { bool ok; QString key QInputDialog::getText(this, tr(API 配置), tr(请输入您的 DeepSeek API Key:), QLineEdit::Normal, , ok); if (ok !key.isEmpty()) { m_settings-setApiKey(key); m_apiClient-setApiKey(key); } }测试对话运行程序在输入框中键入问题例如“用C写一个Hello World程序”点击发送。你应该能在聊天显示框中看到流式返回的回答一个字一个字地显示出来。检查功能多轮对话是否连贯上下文是否正确。流式显示是否流畅。错误处理如网络断开、API Key 无效是否有提示。5. 常见问题与排查思路在开发和使用过程中你可能会遇到以下问题问题现象可能原因排查与解决思路编译错误找不到 Qt 网络模块CMake 未正确链接Qt6::Network或 Qt 安装不完整。1. 检查CMakeLists.txt中的find_package和target_link_libraries是否包含Network。2. 在 Qt Creator 的“项目”设置中确保已勾选Qt Network模块。运行时错误no qt platform plugin could be init应用程序运行时找不到 Qt 的平台插件通常是windows、cocoa或xcb。1. 确保程序在正确的 Qt 环境下运行。在 Qt Creator 中直接运行通常没问题。2. 独立发布时需要将plugins/platforms目录及其依赖的 DLL 复制到可执行文件同级目录。使用windeployqt(Windows) 或macdeployqt(macOS) 工具自动化此过程。发送请求后无任何反应也无错误1. API Key 未设置或为空。2. 网络请求被防火墙或代理拦截。3.QNetworkAccessManager的信号槽未正确连接。1. 在ApiClient::sendMessage开始处添加qDebug() Sending request...;并检查控制台输出。2. 检查m_apiKey是否已正确从Settings加载并设置。3. 使用抓包工具如 Wireshark、Fiddler查看是否有 HTTP 请求发出。流式响应不显示“打字机”效果一次性全部显示onReadyRead槽函数未被触发或者数据累积到finished信号后才被处理。1. 确认sendMessage中stream参数设为true。2. 确认connect(m_currentReply, QNetworkReply::readyRead, ...)这行代码被执行。3. 检查processStreamData函数是否能正确解析data:开头的行。中文显示乱码字符串编码问题。Qt 内部使用 UTF-16网络传输和文件读写时编码不一致。1. 在 HTTP 请求头中明确指定charset虽然 API 通常返回 UTF-8。2. 使用QString::fromUtf8()将接收到的QByteArray转换为QString。3. 确保源代码文件保存为 UTF-8 编码在 Qt Creator 中设置。程序崩溃特别是在更新 UI 时在非 UI 线程中直接操作 GUI 组件。Qt 的 GUI 操作必须在主线程。1. 确保所有对QTextEdit::append()、QListWidget::addItem()等的调用都发生在主线程。2.ApiClient发出的信号如textFragmentReceived会被 Qt 的事件系统自动安排到接收对象所在的线程通常是主线程执行对应的槽函数这是安全的。如果手动创建了工作线程需使用QMetaObject::invokeMethod或信号槽进行跨线程调用。对话历史上下文过长导致 API 调用失败或响应慢模型有 Token 数量限制超出会截断或报错。1. 在ChatSession::getMessagesForApi()中实现一个简单的上下文窗口管理。例如只保留最近 N 轮对话或者当总 Token 估计值超过阈值时从最旧的消息开始移除。2. 可以使用tiktoken等库进行精确的 Token 计数但会增加复杂性。简单方案是按消息条数或字符数进行粗略限制。6. 最佳实践与工程建议将一个小 demo 变成可维护、可扩展的工程化项目还需要考虑以下几点6.1 配置管理加密存储 API Key不要明文存储在配置文件中。可以使用 Qt 的QCryptographicHash进行简单的哈希但需注意对称加密也需要存储密钥。更安全的方式是使用操作系统提供的凭据存储如 Windows Credential Manager, macOS Keychain, Linux libsecret。对于开源项目通常建议用户自行配置环境变量或启动参数。分离配置与代码将所有可配置项API 端点、模型名称、超时时间、代理设置放入Settings类并通过 UI 进行管理。6.2 网络与错误处理设置超时QNetworkRequest可以设置超时属性避免请求无限期挂起。request.setTransferTimeout(30000); // 30秒超时更健壮的错误处理区分网络错误超时、主机不可达、HTTP 错误401 未授权、429 限速、500 服务器错误和 API 业务错误在返回的 JSON 中。在onReplyFinished中根据QNetworkReply::error()和 HTTP 状态码进行精细化提示。实现重试机制对于网络波动导致的临时错误可以实现带退避策略的重试逻辑。6.3 用户体验优化实现 Markdown 渲染使用cmark-gpp或hoedown等库将 API 返回的 Markdown 文本转换为 HTML然后在QTextBrowser中显示支持代码高亮、表格、列表等丰富格式。这需要将QTextEdit替换为QTextBrowser并设置setOpenExternalLinks(true)。代码高亮对于 Markdown 中的代码块可以集成QSyntaxHighlighter或使用highlight.js的 Qt 端口实现语法高亮。对话历史管理实现一个完整的会话列表QListWidget支持创建新会话、重命名会话、删除会话、在不同会话间切换。每个会话对应一个ChatSession实例和本地存储文件。输入框优化支持快捷键如 CtrlEnter 发送、自动补全、历史输入记录。6.4 性能与资源避免 UI 阻塞所有耗时的操作网络请求、文件读写、Markdown 解析都应在后台线程进行。可以使用QtConcurrent或QThread。管理内存及时销毁不再需要的QNetworkReply对象调用deleteLater()。对于超长的聊天记录考虑分页加载或虚拟化显示。6.5 可维护性使用日志系统集成一个简单的日志库如spdlog或自定义的QFile日志记录关键操作、请求响应和错误便于线上问题排查。国际化 (i18n)使用 Qt 的翻译系统tr()宏、.ts文件、lrelease工具为界面文字提供多语言支持。单元测试为ApiClient、ChatSession等核心业务逻辑编写单元测试使用 Qt Test 框架。通过以上步骤你已经拥有了一个功能相对完整、架构清晰的 DeepSeek AI Assistant 客户端原型。你可以在此基础上继续深化例如添加插件系统、支持更多模型OpenAI、Ollama 等、实现函数调用Function Calling功能或者将其封装为一个可复用的 Qt 组件库供其他项目集成。
返回列表