#include "coze.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>
#include <QComboBox>
#include <QProgressBar>
#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 <QLabel>
#include <QRegularExpression>
#include <QListWidget>
#include <QDialog>
#include <QListWidgetItem>
#include <QTimer>
#include <QUrlQuery>
// ------------------------- 构造函数 -------------------------
coze::coze(QWidget *parent)
: QWidget(parent)
, networkManager(new QNetworkAccessManager(this))
, apiToken("")
, botId("")
, userId("123456789")
, conversationId("")
, chatId("")
, isStreaming(false) // 默认关闭流式输出
{
setupUI();
loadSettings();
// 初始化进度条计时器
progressTimer = new QTimer(this);
progressValue = 0;
connect(progressTimer, &QTimer::timeout, [this]() {
progressValue = (progressValue + 5) % 100;
progressBar->setValue(progressValue);
});
// 初始化消息轮询定时器
messagePollTimer = new QTimer(this);
connect(messagePollTimer, &QTimer::timeout, this, &coze::pollMessages);
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"] = isStreaming;
requestBody["auto_save_history"] = true;
QJsonArray additionalMessages = convertToAdditionalMessages();
requestBody["additional_messages"] = additionalMessages;
QString model = modelComboBox->currentData().toString();
if (!model.isEmpty()) {
requestBody["model"] = model;
}
progressTimer->start(100);
progressBar->setRange(0, 0);
progressBar->setVisible(true);
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)
{
progressTimer->stop();
progressBar->setVisible(false);
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();
QString lastHtml = conversationArea->toHtml();
if (lastHtml.contains("正在思考")) {
conversationArea->setHtml(lastHtml.replace(
QRegularExpression("<b>AI:</b> 正在思考,请稍候\\.\\.\\.<br>$"), ""
));
}
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
showError("JSON解析错误: " + parseError.errorString());
return;
}
if (!jsonDoc.isObject()) {
showError("API返回格式错误");
return;
}
QJsonObject responseObj = jsonDoc.object();
if (responseObj.contains("code")) {
int code = responseObj["code"].toInt();
if (code != 0) {
QString errorMsg = "API错误: " + responseObj["msg"].toString();
if (responseObj.contains("detail")) {
errorMsg += "\n详情: " + responseObj["detail"].toString();
}
showError(errorMsg);
return;
}
}
if (!responseObj.contains("data") || !responseObj["data"].isObject()) {
showError("API返回缺少data字段");
return;
}
QJsonObject dataObj = responseObj["data"].toObject();
if (dataObj.contains("status")) {
QString status = dataObj["status"].toString();
if (status == "completed") {
if (dataObj.contains("messages") && dataObj["messages"].isArray()) {
QJsonArray messages = dataObj["messages"].toArray();
for (const QJsonValue &message : messages) {
QJsonObject msgObj = message.toObject();
if (msgObj["role"].toString() == "assistant" &&
msgObj.contains("content")) {
QString aiResponse = msgObj["content"].toString();
if (dataObj.contains("conversation_id")) {
conversationId = dataObj["conversation_id"].toString();
}
QJsonObject aiMessage;
aiMessage["role"] = "assistant";
aiMessage["content"] = aiResponse;
conversationHistory.append(aiMessage);
appendMessage("AI", aiResponse);
saveConversationHistory();
resetInputState();
return;
}
}
}
showError("完成状态但未找到回复内容");
}
else if (status == "in_progress") {
if (dataObj.contains("conversation_id")) {
conversationId = dataObj["conversation_id"].toString();
messagePollTimer->start(2000); // 开始轮询消息
} else {
showError("处理中状态但缺少conversation_id");
}
}
else if (status == "failed") {
QString errorMsg = "请求失败";
if (dataObj.contains("last_error") && dataObj["last_error"].isObject()) {
QJsonObject errorObj = dataObj["last_error"].toObject();
errorMsg = errorObj["msg"].toString();
}
showError(errorMsg);
}
else {
showError("未知状态: " + status);
}
} else {
showError("API返回缺少status字段");
}
}
// ------------------------- 轮询消息 -------------------------
void coze::pollMessages()
{
if (apiToken.isEmpty() || botId.isEmpty() || conversationId.isEmpty()) {
messagePollTimer->stop();
return;
}
QUrl url("https://api.coze.cn/v3/chat/message/list");
QUrlQuery query;
query.addQueryItem("conversation_id", conversationId);
url.setQuery(query);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", ("Bearer " + apiToken).toUtf8());
qDebug() << "Polling messages for conversation ID:" << conversationId;
networkManager->get(request);
}
// ------------------------- 处理消息列表响应 -------------------------
void coze::handleMessageListResponse(QNetworkReply *reply)
{
QByteArray responseData = reply->readAll();
reply->deleteLater();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qDebug() << "JSON解析失败:" << parseError.errorString();
return;
}
QJsonObject obj = jsonDoc.object();
if (!obj.contains("data") || !obj["data"].isArray()) {
qDebug() << "数据格式错误或为空";
return;
}
QJsonArray messages = obj["data"].toArray();
for (const QJsonValue &val : messages) {
QJsonObject msg = val.toObject();
if (msg["role"].toString() == "assistant" && !msg["content"].toString().isEmpty()) {
QString aiResponse = msg["content"].toString();
appendMessage("AI", aiResponse);
QJsonObject aiMessage;
aiMessage["role"] = "assistant";
aiMessage["content"] = aiResponse;
conversationHistory.append(aiMessage);
saveConversationHistory();
messagePollTimer->stop();
resetInputState();
return;
}
}
qDebug() << "未收到AI回复,继续轮询...";
}
// ------------------------- 添加消息到对话区域 -------------------------
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("系统", "历史对话已加载。");
}
}
}
这个函数设置apiToken和botId的位置在哪?