<think>我们需要将之前的代码进行整合,提供一个完整的头文件(coze.h)和实现文件(coze.cpp)。根据之前的讨论,我们将实现流式响应的处理,并且修复一些可能的问题。以下是完整的头文件(coze.h):```cpp#ifndefCOZE_H#defineCOZE_H#include<QWidget>#include<QNetworkAccessManager>#include<QNetworkReply>#include<QJsonArray>#include<QJsonObject>#include<QJsonDocument>#include<QSettings>#include<QRegularExpression>classQVBoxLayout;classQHBoxLayout;classQLineEdit;classQTextEdit;classQPushButton;classQComboBox;classQProgressBar;classQLabel;classcoze:publicQWidget{Q_OBJECTpublic:explicitcoze(QWidget*parent=nullptr);~coze();voidsetApiCredentials(constQString&token,constQString&id);privateslots:voidonSendClicked();voidonInputFinished();voidonClearClicked();voidonHistoryClicked();voidonModelChanged(intindex);voidhandleCozeResponse(QNetworkReply*reply);private:voidsetupUI();voidloadSettings();voidsaveSettings();voidresetInputState();voidshowError(constQString&message);voidappendMessage(constQString&sender,constQString&message,boolisError=false);voidsendQueryToCoze(constQString&query);QJsonArrayconvertToAdditionalMessages();voidsaveConversationHistory();voidloadConversationHistory(constQString&filePath=QString());voidprocessStreamEvent(constQString&eventName,constQJsonObject&data);voidhandleDeltaMessage(constQJsonObject&messageObj);voidhandleCompletedMessage(constQJsonObject&messageObj);voidhandleChatEvent(constQJsonObject&chatObj);private:QNetworkAccessManager*networkManager;QStringapiToken;QStringbotId;QStringuserId;QStringconversationId;//会话IDQStringchatId;//对话ID//对话历史QJsonArrayconversationHistory;//UIElementsQLabel*titleLabel;QComboBox*modelComboBox;QTextEdit*conversationArea;QLineEdit*inputBox;QPushButton*sendButton;QPushButton*clearButton;QPushButton*historyButton;QProgressBar*progressBar;//流式响应处理QStringcurrentAssistantMessageId;QStringcurrentAssistantContent;};#endif//COZE_H```以下是完整的实现文件(coze.cpp):```cpp#include"coze.h"#include<QVBoxLayout>#include<QHBoxLayout>#include<QLineEdit>#include<QTextEdit>#include<QPushButton>#include<QComboBox>#include<QProgressBar>#include<QLabel>#include<QNetworkRequest>#include<QNetworkReply>#include<QJsonDocument>#include<QJsonObject>#include<QJsonArray>#include<QUrl>#include<QMessageBox>#include<QDateTime>#include<QDebug>#include<QSettings>#include<QScrollBar>#include<QShortcut>#include<QFont>#include<QFile>#include<QDir>#include<QStandardPaths>#include<QRegularExpression>#include<QListWidget>#include<QDialog>#include<QListWidgetItem>#include<QUrlQuery>//-------------------------构造函数-------------------------coze::coze(QWidget*parent):QWidget(parent),networkManager(newQNetworkAccessManager(this)),apiToken(""),botId(""),userId("123456789"),conversationId(""),chatId(""),currentAssistantMessageId(""),currentAssistantContent(""){setupUI();loadSettings();connect(networkManager,&QNetworkAccessManager::finished,this,&coze::handleCozeResponse);}//-------------------------析构函数-------------------------coze::~coze(){saveSettings();}//-------------------------设置API凭证-------------------------voidcoze::setApiCredentials(constQString&token,constQString&id){apiToken=token;botId=id;saveSettings();}//-------------------------创建UI界面-------------------------voidcoze::setupUI(){//主布局QVBoxLayout*mainLayout=newQVBoxLayout(this);//标题titleLabel=newQLabel("CozeAI助手",this);titleLabel->setAlignment(Qt::AlignCenter);titleLabel->setStyleSheet("font-size:18px;font-weight:bold;margin:10px;");mainLayout->addWidget(titleLabel);//模型选择QHBoxLayout*modelLayout=newQHBoxLayout();QLabel*modelLabel=newQLabel("选择模型:",this);modelComboBox=newQComboBox(this);modelComboBox->addItem("默认模型","");modelComboBox->addItem("GPT-4","gpt-4");modelComboBox->addItem("GPT-3.5","gpt-3.5-turbo");modelComboBox->addItem("DeepSeek-R1","deepseek-r1");modelLayout->addWidget(modelLabel);modelLayout->addWidget(modelComboBox);mainLayout->addLayout(modelLayout);//对话区域conversationArea=newQTextEdit(this);conversationArea->setReadOnly(true);conversationArea->setFont(QFont("Arial",11));conversationArea->setStyleSheet("background-color:white;border:1pxsolid#ddd;");mainLayout->addWidget(conversationArea,1);//输入区域QHBoxLayout*inputLayout=newQHBoxLayout();inputBox=newQLineEdit(this);inputBox->setPlaceholderText("输入您的问题...");sendButton=newQPushButton("发送",this);clearButton=newQPushButton("清除",this);historyButton=newQPushButton("历史",this);//设置快捷键QShortcut*enterShortcut=newQShortcut(QKeySequence(Qt::Key_Return),inputBox);connect(enterShortcut,&QShortcut::activated,this,&coze::onInputFinished);inputLayout->addWidget(inputBox,1);inputLayout->addWidget(sendButton);inputLayout->addWidget(clearButton);inputLayout->addWidget(historyButton);mainLayout->addLayout(inputLayout);//进度条progressBar=newQProgressBar(this);progressBar->setVisible(false);progressBar->setRange(0,0);//不确定进度progressBar->setTextVisible(false);mainLayout->addWidget(progressBar);//连接信号槽connect(sendButton,&QPushButton::clicked,this,&coze::onSendClicked);connect(clearButton,&QPushButton::clicked,this,&coze::onClearClicked);connect(historyButton,&QPushButton::clicked,this,&coze::onHistoryClicked);connect(modelComboBox,QOverload<int>::of(&QComboBox::currentIndexChanged),this,&coze::onModelChanged);//初始化对话appendMessage("系统","欢迎使用CozeAI助手!请输入您的问题开始对话。");}//-------------------------发送消息-------------------------voidcoze::onSendClicked(){if(apiToken.isEmpty()||botId.isEmpty()){QMessageBox::warning(this,"API凭证缺失","请先设置有效的APIToken和BotID\n""点击确定打开设置对话框");return;}QStringquery=inputBox->text().trimmed();if(query.isEmpty()){showError("请输入问题内容");return;}QJsonObjectuserMessage;userMessage["role"]="user";userMessage["content"]=query;userMessage["content_type"]="text";conversationHistory.append(userMessage);appendMessage("用户",query);inputBox->clear();appendMessage("AI","正在思考,请稍候...");inputBox->setEnabled(false);sendButton->setEnabled(false);progressBar->setVisible(true);currentAssistantMessageId="";currentAssistantContent="";sendQueryToCoze(query);}//-------------------------输入完成(回车键)-------------------------voidcoze::onInputFinished(){onSendClicked();}//-------------------------清除对话-------------------------voidcoze::onClearClicked(){conversationArea->clear();conversationHistory=QJsonArray();conversationId="";chatId="";appendMessage("系统","对话已清除。请输入新问题开始对话。");saveConversationHistory();}//-------------------------显示历史记录-------------------------voidcoze::onHistoryClicked(){QDialoghistoryDialog(this);historyDialog.setWindowTitle("对话历史");historyDialog.resize(600,400);QVBoxLayout*dialogLayout=newQVBoxLayout(&historyDialog);QListWidget*historyList=newQListWidget(&historyDialog);dialogLayout->addWidget(historyList);QStringhistoryPath=QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+"/history";QDirhistoryDir(historyPath);if(historyDir.exists()){QStringListfiles=historyDir.entryList(QStringList()<<"*.json",QDir::Files,QDir::Time);for(constQString&file:files){historyList->addItem(file);}}connect(historyList,&QListWidget::itemDoubleClicked,[&](QListWidgetItem*item){loadConversationHistory(historyPath+"/"+item->text());historyDialog.accept();});historyDialog.exec();}//-------------------------模型改变-------------------------voidcoze::onModelChanged(intindex){Q_UNUSED(index);saveSettings();}//-------------------------转换对话历史为additional_messages格式-------------------------QJsonArraycoze::convertToAdditionalMessages(){QJsonArrayadditionalMessages;for(constQJsonValue&value:conversationHistory){QJsonObjectmessage=value.toObject();QJsonObjectadditionalMessage;additionalMessage["role"]=message["role"].toString();additionalMessage["content"]=message["content"].toString();additionalMessage["content_type"]="text";if(message["role"].toString()=="user"){additionalMessage["type"]="question";}elseif(message["role"].toString()=="assistant"){additionalMessage["type"]="answer";}additionalMessages.append(additionalMessage);}returnadditionalMessages;}//-------------------------发送请求到CozeAPI-------------------------voidcoze::sendQueryToCoze(constQString&query){if(apiToken.isEmpty()||botId.isEmpty()){showError("API凭证未设置,请先配置API密钥");resetInputState();return;}QNetworkRequestrequest;QUrlurl("https://api.coze.cn/v3/chat");if(!conversationId.isEmpty()){url.setQuery("conversation_id="+conversationId);}request.setUrl(url);request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");request.setRawHeader("Authorization",("Bearer"+apiToken).toUtf8());QJsonObjectrequestBody;requestBody["bot_id"]=botId;requestBody["user_id"]=userId;requestBody["stream"]=true;//使用流式响应requestBody["auto_save_history"]=true;QJsonArrayadditionalMessages=convertToAdditionalMessages();requestBody["additional_messages"]=additionalMessages;QStringmodel=modelComboBox->currentData().toString();if(!model.isEmpty()){requestBody["model"]=model;}qDebug()<<"AuthorizationHeader:"<<("Bearer"+apiToken);qDebug()<<"RequestURL:"<<request.url().toString();QJsonDocumentreqDoc(requestBody);qDebug()<<"RequestBody:"<<reqDoc.toJson(QJsonDocument::Indented);networkManager->post(request,reqDoc.toJson());}//-------------------------处理API响应-------------------------voidcoze::handleCozeResponse(QNetworkReply*reply){if(reply->error()!=QNetworkReply::NoError){inthttpStatus=reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();QStringerrorDetails=QString("HTTP状态码:%1\nURL:%2\n错误:%3").arg(httpStatus).arg(reply->url().toString()).arg(reply->errorString());QByteArrayerrorResponse=reply->readAll();if(!errorResponse.isEmpty()){errorDetails+="\n服务器响应:"+QString(errorResponse);}showError("网络请求失败\n"+errorDetails);resetInputState();reply->deleteLater();return;}QByteArrayresponseData=reply->readAll();reply->deleteLater();//调试输出原始响应qDebug()<<"RawAPIResponse:"<<QString(responseData);//处理流式响应(SSE格式)QStringresponseText=QString::fromUtf8(responseData);QStringListlines=responseText.split("\n",Qt::SkipEmptyParts);for(constQString&line:lines){if(line.startsWith("event:")){QStringeventName=line.mid(6).trimmed();//查找对应的data行(在event行之后)intindex=lines.indexOf(line)+1;if(index<lines.size()&&lines[index].startsWith("data:")){QStringdataLine=lines[index];QStringjsonStr=dataLine.mid(5).trimmed();if(jsonStr=="[DONE]"){qDebug()<<"StreamendedwithDONEevent";resetInputState();return;}QJsonParseErrorparseError;QJsonDocumentjsonDoc=QJsonDocument::fromJson(jsonStr.toUtf8(),&parseError);if(parseError.error!=QJsonParseError::NoError){qDebug()<<"JSONparseerror:"<<parseError.errorString();continue;}if(jsonDoc.isObject()){QJsonObjectdataObj=jsonDoc.object();processStreamEvent(eventName,dataObj);}}}}}//-------------------------处理流式事件-------------------------voidcoze::processStreamEvent(constQString&eventName,constQJsonObject&data){qDebug()<<"Processingevent:"<<eventName;if(eventName=="conversation.chat.created"){//保存对话IDif(data.contains("id")){chatId=data["id"].toString();qDebug()<<"Chatcreated,ID:"<<chatId;}//保存会话IDif(data.contains("conversation_id")&&conversationId.isEmpty()){conversationId=data["conversation_id"].toString();qDebug()<<"ConversationID:"<<conversationId;}handleChatEvent(data);}elseif(eventName=="conversation.chat.in_progress"){handleChatEvent(data);}elseif(eventName=="conversation.message.delta"){handleDeltaMessage(data);}elseif(eventName=="conversation.message.completed"){handleCompletedMessage(data);}elseif(eventName=="conversation.chat.completed"){handleChatEvent(data);}elseif(eventName=="conversation.chat.failed"){showError("对话处理失败:"+data["last_error"].toObject()["msg"].toString());}elseif(eventName=="error"){showError("API错误:"+data["msg"].toString());}else{qDebug()<<"Unhandledevent:"<<eventName<<"Data:"<<data;}}//-------------------------处理增量消息-------------------------voidcoze::handleDeltaMessage(constQJsonObject&messageObj){//检查是否为AI消息if(messageObj["role"].toString()!="assistant"||messageObj["type"].toString()!="answer"){return;}//获取消息IDQStringmessageId=messageObj["id"].toString();//如果是新消息,重置内容if(messageId!=currentAssistantMessageId){currentAssistantMessageId=messageId;currentAssistantContent="";//移除"正在思考"消息QStringlastHtml=conversationArea->toHtml();if(lastHtml.contains("正在思考")){conversationArea->setHtml(lastHtml.replace(QRegularExpression("<b>AI:</b>正在思考,请稍候\\.\\.\\.<br>$"),""));}}//添加增量内容QStringdeltaContent=messageObj["content"].toString();currentAssistantContent+=deltaContent;//更新UIQStringlastHtml=conversationArea->toHtml();if(lastHtml.contains("<b>AI:</b>"+currentAssistantContent)){//如果内容已显示,只更新最后一行QRegularExpressionre("<b>AI:</b>(.*?)<br>$");QRegularExpressionMatchmatch=re.match(lastHtml);if(match.hasMatch()){QStringoldContent=match.captured(1);conversationArea->setHtml(lastHtml.replace(oldContent,currentAssistantContent));}}else{//否则添加新消息appendMessage("AI",currentAssistantContent);}}//-------------------------处理完成的消息-------------------------voidcoze::handleCompletedMessage(constQJsonObject&messageObj){//检查是否为AI消息if(messageObj["role"].toString()!="assistant"||messageObj["type"].toString()!="answer"){return;}//获取完整消息内容QStringfullContent=messageObj["content"].toString();//更新对话历史if(!currentAssistantContent.isEmpty()){//更新UI中的消息QStringlastHtml=conversationArea->toHtml();QRegularExpressionre("<b>AI:</b>(.*?)<br>$");QRegularExpressionMatchmatch=re.match(lastHtml);if(match.hasMatch()){QStringoldContent=match.captured(1);conversationArea->setHtml(lastHtml.replace(oldContent,fullContent));}//添加到对话历史QJsonObjectaiMessage;aiMessage["role"]="assistant";aiMessage["content"]=fullContent;conversationHistory.append(aiMessage);//保存对话历史saveConversationHistory();}}//-------------------------处理对话事件-------------------------voidcoze::handleChatEvent(constQJsonObject&chatObj){//更新状态信息if(chatObj.contains("status")){QStringstatus=chatObj["status"].toString();QStringstatusText;if(status=="created")statusText="对话已创建";elseif(status=="in_progress")statusText="处理中";elseif(status=="completed")statusText="已完成";elseif(status=="failed")statusText="失败";elseif(status=="requires_action")statusText="需要操作";qDebug()<<"对话状态:"<<statusText;}//更新会话ID(如果为空)if(conversationId.isEmpty()&&chatObj.contains("conversation_id")){conversationId=chatObj["conversation_id"].toString();qDebug()<<"更新会话ID:"<<conversationId;}//更新对话IDif(chatObj.contains("id")){chatId=chatObj["id"].toString();}}//-------------------------添加消息到对话区域-------------------------voidcoze::appendMessage(constQString&sender,constQString&message,boolisError){QStringformattedMessage;if(isError){formattedMessage=QString("<bstyle='color:red;'>%1:</b><spanstyle='color:black;'>%2</span><br>").arg(sender,message);}else{formattedMessage=QString("<b>%1:</b><spanstyle='color:black;'>%2</span><br>").arg(sender,message);}conversationArea->append(formattedMessage);QScrollBar*scrollbar=conversationArea->verticalScrollBar();scrollbar->setValue(scrollbar->maximum());}//-------------------------显示错误消息-------------------------voidcoze::showError(constQString&message){appendMessage("错误",message,true);qDebug()<<"错误:"<<message;resetInputState();}//-------------------------重置输入状态-------------------------voidcoze::resetInputState(){inputBox->setEnabled(true);sendButton->setEnabled(true);progressBar->setVisible(false);inputBox->setFocus();}//-------------------------保存设置-------------------------voidcoze::saveSettings(){QSettingssettings("MyCompany","CozeAI");settings.setValue("apiToken",apiToken);settings.setValue("botId",botId);settings.setValue("conversationId",conversationId);settings.setValue("modelIndex",modelComboBox->currentIndex());}//-------------------------加载设置-------------------------voidcoze::loadSettings(){QSettingssettings("MyCompany","CozeAI");apiToken=settings.value("apiToken","").toString();botId=settings.value("botId","").toString();conversationId=settings.value("conversationId","").toString();intmodelIndex=settings.value("modelIndex",0).toInt();if(modelIndex>=0&&modelIndex<modelComboBox->count()){modelComboBox->setCurrentIndex(modelIndex);}loadConversationHistory();}//-------------------------保存对话历史-------------------------voidcoze::saveConversationHistory(){if(conversationHistory.isEmpty())return;QStringhistoryPath=QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+"/history";QDir().mkpath(historyPath);QStringtimestamp=QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");QStringfileName=historyPath+"/conversation_"+timestamp+".json";QFilefile(fileName);if(file.open(QIODevice::WriteOnly)){QJsonDocumentdoc(conversationHistory);file.write(doc.toJson());file.close();}}//-------------------------加载对话历史-------------------------voidcoze::loadConversationHistory(constQString&filePath){QStringloadPath=filePath;if(loadPath.isEmpty()){QStringhistoryPath=QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)+"/history";QDirhistoryDir(historyPath);if(historyDir.exists()){QStringListfiles=historyDir.entryList(QStringList()<<"*.json",QDir::Files,QDir::Time);if(!files.isEmpty()){loadPath=historyPath+"/"+files.first();}}}if(loadPath.isEmpty())return;QFilefile(loadPath);if(file.open(QIODevice::ReadOnly)){QByteArraydata=file.readAll();QJsonParseErrorparseError;QJsonDocumentdoc=QJsonDocument::fromJson(data,&parseError);if(parseError.error==QJsonParseError::NoError&&doc.isArray()){conversationHistory=doc.array();conversationArea->clear();for(constQJsonValue&value:conversationHistory){QJsonObjectmessage=value.toObject();QStringrole=message["role"].toString();QStringcontent=message["content"].toString();if(role=="user"){appendMessage("用户",content);}elseif(role=="assistant"){appendMessage("AI",content);}}appendMessage("系统","历史对话已加载。");}}}```###代码说明:1.**头文件(coze.h)**:声明了类成员和槽函数,包括网络管理、API凭证、UI元素以及流式处理相关的变量。2.**实现文件(coze.cpp)**:-构造函数:初始化成员变量和UI,加载设置。-`setupUI`:创建用户界面,包括标题、模型选择、对话区域、输入框和按钮。-消息处理:实现了发送消息、清除对话、显示历史记录等功能。-流式响应处理:通过解析SSE(Server-SentEvents)格式的响应,处理增量消息和完成消息。-错误处理:显示网络错误和API错误。-设置保存与加载:使用`QSettings`保存API凭证、会话ID和模型选择。-对话历史管理:将对话历史保存为JSON文件,并支持从文件加载历史对话。###注意事项:-流式响应处理依赖于CozeAPI返回的SSE格式。-在`handleCozeResponse`中,我们按行解析响应,查找`event`和`data`行。-增量消息会实时更新UI,完成消息会更新对话历史并保存。-会话ID(`conversationId`)用于保持对话连续性。§§相关问题§§:1.如何优化流式响应处理,避免UI频繁刷新导致的性能问题?2.在对话历史记录中,如何实现分页加载以避免内存占用过大?3.如何增加对多语言的支持,使界面和消息可以切换语言?4.如何实现图片、文件等富媒体消息的发送和显示?</think>以下是根据您的需求整理后的完整 `.h` 和 `.cpp` 文件,包含流式响应处理的完整实现:
### coze.h 头文件
```cpp
#ifndef COZE_H
#define COZE_H
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QSettings>
#include <QRegularExpression>
class QVBoxLayout;
class QHBoxLayout;
class QLineEdit;
class QTextEdit;
class QPushButton;
class QComboBox;
class QProgressBar;
class QLabel;
class coze : public QWidget
{
Q_OBJECT
public:
explicit coze(QWidget *parent = nullptr);
~coze();
void setApiCredentials(const QString& token, const QString& id);
private slots:
void onSendClicked();
void onInputFinished();
void onClearClicked();
void onHistoryClicked();
void onModelChanged(int index);
void handleCozeResponse(QNetworkReply* reply);
private:
void setupUI();
void loadSettings();
void saveSettings();
void resetInputState();
void showError(const QString& message);
void appendMessage(const QString& sender, const QString& message, bool isError = false);
void sendQueryToCoze(const QString& query);
QJsonArray convertToAdditionalMessages();
void saveConversationHistory();
void loadConversationHistory(const QString& filePath = QString());
void processStreamEvent(const QString& eventName, const QJsonObject& data);
void handleDeltaMessage(const QJsonObject& messageObj);
void handleCompletedMessage(const QJsonObject& messageObj);
void handleChatEvent(const QJsonObject& chatObj);
private:
QNetworkAccessManager *networkManager;
QString apiToken;
QString botId;
QString userId;
QString conversationId; // 会话ID
QString chatId; // 对话ID
// 对话历史
QJsonArray conversationHistory;
// UI Elements
QLabel *titleLabel;
QComboBox *modelComboBox;
QTextEdit *conversationArea;
QLineEdit *inputBox;
QPushButton *sendButton;
QPushButton *clearButton;
QPushButton *historyButton;
QProgressBar *progressBar;
// 流式响应处理
QString currentAssistantMessageId;
QString currentAssistantContent;
};
#endif // COZE_H
```
### coze.cpp 实现文件
```cpp
#include "coze.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>
#include <QComboBox>
#include <QProgressBar>
#include <QLabel>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrl>
#include <QMessageBox>
#include <QDateTime>
#include <QDebug>
#include <QSettings>
#include <QScrollBar>
#include <QShortcut>
#include <QFont>
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#include <QRegularExpression>
#include <QListWidget>
#include <QDialog>
#include <QListWidgetItem>
#include <QUrlQuery>
// ------------------------- 构造函数 -------------------------
coze::coze(QWidget *parent)
: QWidget(parent)
, networkManager(new QNetworkAccessManager(this))
, apiToken("")
, botId("")
, userId("123456789")
, conversationId("")
, chatId("")
, currentAssistantMessageId("")
, currentAssistantContent("")
{
setupUI();
loadSettings();
connect(networkManager, &QNetworkAccessManager::finished,
this, &coze::handleCozeResponse);
}
// ------------------------- 析构函数 -------------------------
coze::~coze()
{
saveSettings();
}
// ------------------------- 设置API凭证 -------------------------
void coze::setApiCredentials(const QString& token, const QString& id)
{
apiToken = token;
botId = id;
saveSettings();
}
// ------------------------- 创建UI界面 -------------------------
void coze::setupUI()
{
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
// 标题
titleLabel = new QLabel("Coze AI 助手", this);
titleLabel->setAlignment(Qt::AlignCenter);
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; margin: 10px;");
mainLayout->addWidget(titleLabel);
// 模型选择
QHBoxLayout *modelLayout = new QHBoxLayout();
QLabel *modelLabel = new QLabel("选择模型:", this);
modelComboBox = new QComboBox(this);
modelComboBox->addItem("默认模型", "");
modelComboBox->addItem("GPT-4", "gpt-4");
modelComboBox->addItem("GPT-3.5", "gpt-3.5-turbo");
modelComboBox->addItem("DeepSeek-R1", "deepseek-r1");
modelLayout->addWidget(modelLabel);
modelLayout->addWidget(modelComboBox);
mainLayout->addLayout(modelLayout);
// 对话区域
conversationArea = new QTextEdit(this);
conversationArea->setReadOnly(true);
conversationArea->setFont(QFont("Arial", 11));
conversationArea->setStyleSheet("background-color: white; border: 1px solid #ddd;");
mainLayout->addWidget(conversationArea, 1);
// 输入区域
QHBoxLayout *inputLayout = new QHBoxLayout();
inputBox = new QLineEdit(this);
inputBox->setPlaceholderText("输入您的问题...");
sendButton = new QPushButton("发送", this);
clearButton = new QPushButton("清除", this);
historyButton = new QPushButton("历史", this);
// 设置快捷键
QShortcut *enterShortcut = new QShortcut(QKeySequence(Qt::Key_Return), inputBox);
connect(enterShortcut, &QShortcut::activated, this, &coze::onInputFinished);
inputLayout->addWidget(inputBox, 1);
inputLayout->addWidget(sendButton);
inputLayout->addWidget(clearButton);
inputLayout->addWidget(historyButton);
mainLayout->addLayout(inputLayout);
// 进度条
progressBar = new QProgressBar(this);
progressBar->setVisible(false);
progressBar->setRange(0, 0); // 不确定进度
progressBar->setTextVisible(false);
mainLayout->addWidget(progressBar);
// 连接信号槽
connect(sendButton, &QPushButton::clicked, this, &coze::onSendClicked);
connect(clearButton, &QPushButton::clicked, this, &coze::onClearClicked);
connect(historyButton, &QPushButton::clicked, this, &coze::onHistoryClicked);
connect(modelComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &coze::onModelChanged);
// 初始化对话
appendMessage("系统", "欢迎使用 Coze AI 助手!请输入您的问题开始对话。");
}
// ------------------------- 发送消息 -------------------------
void coze::onSendClicked()
{
if (apiToken.isEmpty() || botId.isEmpty()) {
QMessageBox::warning(this, "API凭证缺失",
"请先设置有效的API Token和Bot ID\n"
"点击确定打开设置对话框");
return;
}
QString query = inputBox->text().trimmed();
if (query.isEmpty()) {
showError("请输入问题内容");
return;
}
QJsonObject userMessage;
userMessage["role"] = "user";
userMessage["content"] = query;
userMessage["content_type"] = "text";
conversationHistory.append(userMessage);
appendMessage("用户", query);
inputBox->clear();
appendMessage("AI", "正在思考,请稍候...");
inputBox->setEnabled(false);
sendButton->setEnabled(false);
progressBar->setVisible(true);
currentAssistantMessageId = "";
currentAssistantContent = "";
sendQueryToCoze(query);
}
// ------------------------- 输入完成(回车键) -------------------------
void coze::onInputFinished()
{
onSendClicked();
}
// ------------------------- 清除对话 -------------------------
void coze::onClearClicked()
{
conversationArea->clear();
conversationHistory = QJsonArray();
conversationId = "";
chatId = "";
appendMessage("系统", "对话已清除。请输入新问题开始对话。");
saveConversationHistory();
}
// ------------------------- 显示历史记录 -------------------------
void coze::onHistoryClicked()
{
QDialog historyDialog(this);
historyDialog.setWindowTitle("对话历史");
historyDialog.resize(600, 400);
QVBoxLayout *dialogLayout = new QVBoxLayout(&historyDialog);
QListWidget *historyList = new QListWidget(&historyDialog);
dialogLayout->addWidget(historyList);
QString historyPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/history";
QDir historyDir(historyPath);
if (historyDir.exists()) {
QStringList files = historyDir.entryList(QStringList() << "*.json", QDir::Files, QDir::Time);
for (const QString &file : files) {
historyList->addItem(file);
}
}
connect(historyList, &QListWidget::itemDoubleClicked, [&](QListWidgetItem *item) {
loadConversationHistory(historyPath + "/" + item->text());
historyDialog.accept();
});
historyDialog.exec();
}
// ------------------------- 模型改变 -------------------------
void coze::onModelChanged(int index)
{
Q_UNUSED(index);
saveSettings();
}
// ------------------------- 转换对话历史为additional_messages格式 -------------------------
QJsonArray coze::convertToAdditionalMessages()
{
QJsonArray additionalMessages;
for (const QJsonValue &value : conversationHistory) {
QJsonObject message = value.toObject();
QJsonObject additionalMessage;
additionalMessage["role"] = message["role"].toString();
additionalMessage["content"] = message["content"].toString();
additionalMessage["content_type"] = "text";
if (message["role"].toString() == "user") {
additionalMessage["type"] = "question";
} else if (message["role"].toString() == "assistant") {
additionalMessage["type"] = "answer";
}
additionalMessages.append(additionalMessage);
}
return additionalMessages;
}
// ------------------------- 发送请求到Coze API -------------------------
void coze::sendQueryToCoze(const QString& query)
{
if (apiToken.isEmpty() || botId.isEmpty()) {
showError("API凭证未设置,请先配置API密钥");
resetInputState();
return;
}
QNetworkRequest request;
QUrl url("https://api.coze.cn/v3/chat");
if (!conversationId.isEmpty()) {
url.setQuery("conversation_id=" + conversationId);
}
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", ("Bearer " + apiToken).toUtf8());
QJsonObject requestBody;
requestBody["bot_id"] = botId;
requestBody["user_id"] = userId;
requestBody["stream"] = true; // 使用流式响应
requestBody["auto_save_history"] = true;
QJsonArray additionalMessages = convertToAdditionalMessages();
requestBody["additional_messages"] = additionalMessages;
QString model = modelComboBox->currentData().toString();
if (!model.isEmpty()) {
requestBody["model"] = model;
}
qDebug() << "Authorization Header:" << ("Bearer " + apiToken);
qDebug() << "Request URL:" << request.url().toString();
QJsonDocument reqDoc(requestBody);
qDebug() << "Request Body:" << reqDoc.toJson(QJsonDocument::Indented);
networkManager->post(request, reqDoc.toJson());
}
// ------------------------- 处理API响应 -------------------------
void coze::handleCozeResponse(QNetworkReply* reply)
{
if (reply->error() != QNetworkReply::NoError) {
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString errorDetails = QString("HTTP状态码: %1\nURL: %2\n错误: %3")
.arg(httpStatus)
.arg(reply->url().toString())
.arg(reply->errorString());
QByteArray errorResponse = reply->readAll();
if (!errorResponse.isEmpty()) {
errorDetails += "\n服务器响应: " + QString(errorResponse);
}
showError("网络请求失败\n" + errorDetails);
resetInputState();
reply->deleteLater();
return;
}
QByteArray responseData = reply->readAll();
reply->deleteLater();
// 调试输出原始响应
qDebug() << "Raw API Response:" << QString(responseData);
// 处理流式响应(SSE格式)
QString responseText = QString::fromUtf8(responseData);
QStringList lines = responseText.split("\n", Qt::SkipEmptyParts);
for (const QString &line : lines) {
if (line.startsWith("event:")) {
QString eventName = line.mid(6).trimmed();
// 查找对应的data行(在event行之后)
int index = lines.indexOf(line) + 1;
if (index < lines.size() && lines[index].startsWith("data:")) {
QString dataLine = lines[index];
QString jsonStr = dataLine.mid(5).trimmed();
if (jsonStr == "[DONE]") {
qDebug() << "Stream ended with DONE event";
resetInputState();
return;
}
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError) {
qDebug() << "JSON parse error:" << parseError.errorString();
continue;
}
if (jsonDoc.isObject()) {
QJsonObject dataObj = jsonDoc.object();
processStreamEvent(eventName, dataObj);
}
}
}
}
}
// ------------------------- 处理流式事件 -------------------------
void coze::processStreamEvent(const QString& eventName, const QJsonObject& data)
{
qDebug() << "Processing event:" << eventName;
if (eventName == "conversation.chat.created") {
// 保存对话ID
if (data.contains("id")) {
chatId = data["id"].toString();
qDebug() << "Chat created, ID:" << chatId;
}
// 保存会话ID
if (data.contains("conversation_id") && conversationId.isEmpty()) {
conversationId = data["conversation_id"].toString();
qDebug() << "Conversation ID:" << conversationId;
}
handleChatEvent(data);
}
else if (eventName == "conversation.chat.in_progress") {
handleChatEvent(data);
}
else if (eventName == "conversation.message.delta") {
handleDeltaMessage(data);
}
else if (eventName == "conversation.message.completed") {
handleCompletedMessage(data);
}
else if (eventName == "conversation.chat.completed") {
handleChatEvent(data);
}
else if (eventName == "conversation.chat.failed") {
showError("对话处理失败: " + data["last_error"].toObject()["msg"].toString());
}
else if (eventName == "error") {
showError("API错误: " + data["msg"].toString());
}
else {
qDebug() << "Unhandled event:" << eventName << "Data:" << data;
}
}
// ------------------------- 处理增量消息 -------------------------
void coze::handleDeltaMessage(const QJsonObject& messageObj)
{
// 检查是否为AI消息
if (messageObj["role"].toString() != "assistant" ||
messageObj["type"].toString() != "answer") {
return;
}
// 获取消息ID
QString messageId = messageObj["id"].toString();
// 如果是新消息,重置内容
if (messageId != currentAssistantMessageId) {
currentAssistantMessageId = messageId;
currentAssistantContent = "";
// 移除"正在思考"消息
QString lastHtml = conversationArea->toHtml();
if (lastHtml.contains("正在思考")) {
conversationArea->setHtml(lastHtml.replace(
QRegularExpression("<b>AI:</b> 正在思考,请稍候\\.\\.\\.<br>$"), ""
));
}
}
// 添加增量内容
QString deltaContent = messageObj["content"].toString();
currentAssistantContent += deltaContent;
// 更新UI
QString lastHtml = conversationArea->toHtml();
if (lastHtml.contains("<b>AI:</b> " + currentAssistantContent)) {
// 如果内容已显示,只更新最后一行
QRegularExpression re("<b>AI:</b> (.*?)<br>$");
QRegularExpressionMatch match = re.match(lastHtml);
if (match.hasMatch()) {
QString oldContent = match.captured(1);
conversationArea->setHtml(lastHtml.replace(
oldContent, currentAssistantContent
));
}
} else {
// 否则添加新消息
appendMessage("AI", currentAssistantContent);
}
}
// ------------------------- 处理完成的消息 -------------------------
void coze::handleCompletedMessage(const QJsonObject& messageObj)
{
// 检查是否为AI消息
if (messageObj["role"].toString() != "assistant" ||
messageObj["type"].toString() != "answer") {
return;
}
// 获取完整消息内容
QString fullContent = messageObj["content"].toString();
// 更新对话历史
if (!currentAssistantContent.isEmpty()) {
// 更新UI中的消息
QString lastHtml = conversationArea->toHtml();
QRegularExpression re("<b>AI:</b> (.*?)<br>$");
QRegularExpressionMatch match = re.match(lastHtml);
if (match.hasMatch()) {
QString oldContent = match.captured(1);
conversationArea->setHtml(lastHtml.replace(
oldContent, fullContent
));
}
// 添加到对话历史
QJsonObject aiMessage;
aiMessage["role"] = "assistant";
aiMessage["content"] = fullContent;
conversationHistory.append(aiMessage);
// 保存对话历史
saveConversationHistory();
}
}
// ------------------------- 处理对话事件 -------------------------
void coze::handleChatEvent(const QJsonObject& chatObj)
{
// 更新状态信息
if (chatObj.contains("status")) {
QString status = chatObj["status"].toString();
QString statusText;
if (status == "created") statusText = "对话已创建";
else if (status == "in_progress") statusText = "处理中";
else if (status == "completed") statusText = "已完成";
else if (status == "failed") statusText = "失败";
else if (status == "requires_action") statusText = "需要操作";
qDebug() << "对话状态:" << statusText;
}
// 更新会话ID(如果为空)
if (conversationId.isEmpty() && chatObj.contains("conversation_id")) {
conversationId = chatObj["conversation_id"].toString();
qDebug() << "更新会话ID:" << conversationId;
}
// 更新对话ID
if (chatObj.contains("id")) {
chatId = chatObj["id"].toString();
}
}
// ------------------------- 添加消息到对话区域 -------------------------
void coze::appendMessage(const QString& sender, const QString& message, bool isError)
{
QString formattedMessage;
if (isError) {
formattedMessage = QString("<b style='color:red;'>%1:</b> <span style='color:black;'>%2</span><br>")
.arg(sender, message);
} else {
formattedMessage = QString("<b>%1:</b> <span style='color:black;'>%2</span><br>")
.arg(sender, message);
}
conversationArea->append(formattedMessage);
QScrollBar *scrollbar = conversationArea->verticalScrollBar();
scrollbar->setValue(scrollbar->maximum());
}
// ------------------------- 显示错误消息 -------------------------
void coze::showError(const QString& message)
{
appendMessage("错误", message, true);
qDebug() << "错误:" << message;
resetInputState();
}
// ------------------------- 重置输入状态 -------------------------
void coze::resetInputState()
{
inputBox->setEnabled(true);
sendButton->setEnabled(true);
progressBar->setVisible(false);
inputBox->setFocus();
}
// ------------------------- 保存设置 -------------------------
void coze::saveSettings()
{
QSettings settings("MyCompany", "CozeAI");
settings.setValue("apiToken", apiToken);
settings.setValue("botId", botId);
settings.setValue("conversationId", conversationId);
settings.setValue("modelIndex", modelComboBox->currentIndex());
}
// ------------------------- 加载设置 -------------------------
void coze::loadSettings()
{
QSettings settings("MyCompany", "CozeAI");
apiToken = settings.value("apiToken", "").toString();
botId = settings.value("botId", "").toString();
conversationId = settings.value("conversationId", "").toString();
int modelIndex = settings.value("modelIndex", 0).toInt();
if (modelIndex >= 0 && modelIndex < modelComboBox->count()) {
modelComboBox->setCurrentIndex(modelIndex);
}
loadConversationHistory();
}
// ------------------------- 保存对话历史 -------------------------
void coze::saveConversationHistory()
{
if (conversationHistory.isEmpty()) return;
QString historyPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/history";
QDir().mkpath(historyPath);
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString fileName = historyPath + "/conversation_" + timestamp + ".json";
QFile file(fileName);
if (file.open(QIODevice::WriteOnly)) {
QJsonDocument doc(conversationHistory);
file.write(doc.toJson());
file.close();
}
}
// ------------------------- 加载对话历史 -------------------------
void coze::loadConversationHistory(const QString& filePath)
{
QString loadPath = filePath;
if (loadPath.isEmpty()) {
QString historyPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/history";
QDir historyDir(historyPath);
if (historyDir.exists()) {
QStringList files = historyDir.entryList(QStringList() << "*.json", QDir::Files, QDir::Time);
if (!files.isEmpty()) {
loadPath = historyPath + "/" + files.first();
}
}
}
if (loadPath.isEmpty()) return;
QFile file(loadPath);
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isArray()) {
conversationHistory = doc.array();
conversationArea->clear();
for (const QJsonValue &value : conversationHistory) {
QJsonObject message = value.toObject();
QString role = message["role"].toString();
QString content = message["content"].toString();
if (role == "user") {
appendMessage("用户", content);
} else if (role == "assistant") {
appendMessage("AI", content);
}
}
appendMessage("系统", "历史对话已加载。");
}
}
}
```
### 关键功能说明:
1. **流式响应处理**:
- 使用 SSE (Server-Sent Events) 格式解析API响应
- 处理 `conversation.message.delta` 事件实时更新消息
- 处理 `conversation.message.completed` 事件保存完整回复
2. **会话管理**:
- 维护 `conversationId` 保持连续对话
- 使用 `conversationHistory` 存储对话上下文
- 自动保存对话历史到本地文件
3. **UI优化**:
- 实时更新消息显示(逐字显示效果)
- 自动滚动到最新消息
- 错误消息高亮显示
4. **设置持久化**:
- 使用 `QSettings` 保存API凭证和会话状态
- 模型选择自动保存和加载
5. **错误处理**:
- 详细的网络错误报告
- API错误消息解析和显示
- 输入状态自动重置