This commit is contained in:
JackLee 2025-03-04 15:58:23 +08:00
parent dcf90fe575
commit 4c03e4d794
6 changed files with 122 additions and 119 deletions

View File

@ -101,7 +101,8 @@
"qfiledialog": "cpp", "qfiledialog": "cpp",
"qtimer": "cpp", "qtimer": "cpp",
"qstring": "cpp", "qstring": "cpp",
"qabstracttextdocumentlayout": "cpp" "qabstracttextdocumentlayout": "cpp",
"qframe": "cpp"
}, },
"editor.gotoLocation.alternativeDeclarationCommand": "editor.action.revealDefinition", "editor.gotoLocation.alternativeDeclarationCommand": "editor.action.revealDefinition",
"editor.gotoLocation.alternativeDefinitionCommand": "editor.action.revealDefinition", "editor.gotoLocation.alternativeDefinitionCommand": "editor.action.revealDefinition",

View File

@ -27,12 +27,12 @@ typedef struct curl_opts
} curl_opts; } curl_opts;
typedef struct model_data{ typedef struct model_data{
std::string api_url = "https://api.deepseek.com/chat/completions"; //std::string api_url = "https://api.deepseek.com/chat/completions";
std::string api_key = "Authorization: Bearer sk-7e6932ed45674c389dea1cd3481e0ec2"; //std::string api_key = "Authorization: Bearer sk-7e6932ed45674c389dea1cd3481e0ec2";
std::string user_model = "deepseek-chat"; //std::string user_model = "deepseek-chat";
//std::string api_url="https://api.siliconflow.cn/v1/chat/completions"; std::string api_url="https://api.siliconflow.cn/v1/chat/completions";
//std::string api_key="Authorization: Bearer sk-oiphigpzqtmkkcoucyakrfevcvndroywvxhprvscjqhdykdb"; std::string api_key="Authorization: Bearer sk-oiphigpzqtmkkcoucyakrfevcvndroywvxhprvscjqhdykdb";
//std::string user_model = "deepseek-ai/DeepSeek-V3"; std::string user_model = "deepseek-ai/DeepSeek-V3";
std::string send_content_header = "Content-Type: application/json"; std::string send_content_header = "Content-Type: application/json";
std::string send_accept_header = "Accept: application/json"; std::string send_accept_header = "Accept: application/json";
std::string send_user_data; std::string send_user_data;

View File

@ -1,8 +1,8 @@
#include "ctai_history_textedit.h" #include "ctai_history_textedit.h"
ctai_history_textedit::ctai_history_textedit(msg_type msg_type_mode) ctai_history_textedit::ctai_history_textedit(QWidget *parent)
: QWidget(parent)
{ {
init_layout(msg_type_mode);
} }
ctai_history_textedit::~ctai_history_textedit() ctai_history_textedit::~ctai_history_textedit()
{ {
@ -19,9 +19,9 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_header->setReadOnly(true); m_msg_header->setReadOnly(true);
if (msg_type_mode == SYSTEM) if (msg_type_mode == SYSTEM)
{ {
//SYSTEM消息头水平 // SYSTEM消息头水平
m_msg_system_header_ico = new QPushButton(); m_msg_system_header_ico = new QPushButton();
m_msg_system_header_ico->setObjectName("m_msg_system_header_ico"); m_msg_system_header_ico->setObjectName("m_msg_system_header_ico");
header_info_Layout->addWidget(m_msg_system_header_ico); header_info_Layout->addWidget(m_msg_system_header_ico);
header_info_Layout->addWidget(m_msg_header); header_info_Layout->addWidget(m_msg_header);
header_info_Layout->setContentsMargins(0, 0, 0, 0); header_info_Layout->setContentsMargins(0, 0, 0, 0);
@ -29,7 +29,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
// 2.tokens功能按钮 // 2.tokens功能按钮
m_msg_tokens = new QPushButton(); m_msg_tokens = new QPushButton();
m_msg_tokens->setObjectName("m_msg_tokens"); m_msg_tokens->setObjectName("m_msg_tokens");
m_history_to_send=new QPushButton(); m_history_to_send = new QPushButton();
m_history_to_send->setObjectName("m_history_to_send"); m_history_to_send->setObjectName("m_history_to_send");
header_info_Layout->addWidget(m_history_to_send); header_info_Layout->addWidget(m_history_to_send);
header_info_Layout->addWidget(m_msg_tokens); header_info_Layout->addWidget(m_msg_tokens);
@ -41,7 +41,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_save = new QPushButton(); m_msg_save = new QPushButton();
m_msg_menu = new QPushButton(); m_msg_menu = new QPushButton();
m_msg_fold = new QPushButton(); m_msg_fold = new QPushButton();
m_restart_to_send= new QPushButton(); m_restart_to_send = new QPushButton();
m_msg_system_del->setObjectName("m_msg_system_del"); m_msg_system_del->setObjectName("m_msg_system_del");
m_restart_to_send->setObjectName("m_restart_to_send"); m_restart_to_send->setObjectName("m_restart_to_send");
m_msg_copy->setObjectName("m_msg_copy"); m_msg_copy->setObjectName("m_msg_copy");
@ -49,8 +49,8 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_menu->setObjectName("m_msg_menu"); m_msg_menu->setObjectName("m_msg_menu");
m_msg_fold->setObjectName("m_msg_fold"); m_msg_fold->setObjectName("m_msg_fold");
m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png")); m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png"));
m_msg_fold->setMinimumSize(QSize(25,25)); m_msg_fold->setMinimumSize(QSize(25, 25));
header_opts_Layout->addItem(sparcer_item); header_opts_Layout->addItem(sparcer_item);
header_opts_Layout->addWidget(m_restart_to_send); header_opts_Layout->addWidget(m_restart_to_send);
header_opts_Layout->addWidget(m_msg_system_del); header_opts_Layout->addWidget(m_msg_system_del);
header_opts_Layout->addWidget(m_msg_copy); header_opts_Layout->addWidget(m_msg_copy);
@ -63,19 +63,19 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
} }
else else
{ {
//SYSTEM MSG ICO // SYSTEM MSG ICO
m_msg_user_header_ico = new QPushButton(); m_msg_user_header_ico = new QPushButton();
m_msg_user_header_ico->setObjectName("m_msg_system_header_ico"); m_msg_user_header_ico->setObjectName("m_msg_system_header_ico");
header_info_Layout->addWidget(m_msg_user_header_ico); header_info_Layout->addWidget(m_msg_user_header_ico);
header_info_Layout->addWidget(m_msg_header); header_info_Layout->addWidget(m_msg_header);
header_info_Layout->setContentsMargins(0, 0, 0, 0); header_info_Layout->setContentsMargins(0, 0, 0, 0);
headerLayout->addLayout(header_info_Layout); headerLayout->addLayout(header_info_Layout);
//USER DELETE BUTTON // USER DELETE BUTTON
m_msg_user_del = new QPushButton(); m_msg_user_del = new QPushButton();
m_msg_user_del->setObjectName("m_msg_user_del"); m_msg_user_del->setObjectName("m_msg_user_del");
m_history_to_send=new QPushButton(); m_history_to_send = new QPushButton();
m_history_to_send->setObjectName("m_history_to_send"); m_history_to_send->setObjectName("m_history_to_send");
m_restart_to_send= new QPushButton(); m_restart_to_send = new QPushButton();
m_restart_to_send->setObjectName("m_restart_to_send"); m_restart_to_send->setObjectName("m_restart_to_send");
header_info_Layout->addWidget(m_restart_to_send); header_info_Layout->addWidget(m_restart_to_send);
header_info_Layout->addWidget(m_history_to_send); header_info_Layout->addWidget(m_history_to_send);
@ -83,7 +83,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
} }
// 4.分割线区域 // 4.分割线区域
hLine = new QFrame; hLine = new QFrame;
hLine->setFrameShape(QFrame::HLine); // 关键属性 hLine->setFrameShape(QFrame::HLine); // 关键属性
hLine->setFrameShadow(QFrame::Sunken); // 凹陷效果 hLine->setFrameShadow(QFrame::Sunken); // 凹陷效果
hLine->setLineWidth(2); // 线宽 hLine->setLineWidth(2); // 线宽
// 5. 历史信息QTextEdit // 5. 历史信息QTextEdit
@ -92,6 +92,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_history->setAcceptRichText(true); m_msg_history->setAcceptRichText(true);
m_msg_history->setLineWrapMode(QTextEdit::WidgetWidth); m_msg_history->setLineWrapMode(QTextEdit::WidgetWidth);
m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_msg_history->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// 添加性能优化设置 // 添加性能优化设置
m_msg_history->setUndoRedoEnabled(false); // 禁用撤销重做 m_msg_history->setUndoRedoEnabled(false); // 禁用撤销重做
m_msg_history->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); m_msg_history->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
@ -129,68 +130,7 @@ void ctai_history_textedit::connect_signals(msg_type msg_type_mode)
} }
connect(m_msg_history->document(), SIGNAL(contentsChanged()), this, SLOT(on_text_height())); connect(m_msg_history->document(), SIGNAL(contentsChanged()), this, SLOT(on_text_height()));
} }
void ctai_history_textedit::on_text_height()
{
// 防抖动处理如果上次更新在100ms内则延迟处理
static QTimer debounceTimer;
debounceTimer.setSingleShot(true);
if (debounceTimer.isActive())
{
return;
}
// 使用viewport宽度作为缓存
static int lastWidth = 0;
int currentWidth = m_msg_history->viewport()->width();
// 只在宽度变化时才重新计算
if (lastWidth != currentWidth)
{
m_msg_history->document()->setTextWidth(currentWidth);
lastWidth = currentWidth;
}
// 直接使用documentLayout获取高度避免多次计算
int textHeight = m_msg_history->document()->documentLayout()->documentSize().height();
if (textHeight > 0)
{
// 计算实际需要的高度 (包含边距和视口边框)
int newHeight = textHeight +
m_msg_history->document()->documentMargin() * 2 +
m_msg_history->frameWidth() * 2;
// 添加高度阈值判断,避免微小变化触发更新
static const int HEIGHT_THRESHOLD = 5; // 5像素的阈值
if (abs(m_msg_history->height() - newHeight) > HEIGHT_THRESHOLD)
{
m_original_height = newHeight;
// 批量更新布局
bool wasBlocked = m_msg_history->signalsBlocked();
m_msg_history->blockSignals(true);
m_msg_history->setFixedHeight(newHeight);
updateGeometry();
m_msg_history->blockSignals(wasBlocked);
emit height_changed();
// 延迟发送布局更新请求
if (QWidget *parent = parentWidget())
{
QTimer::singleShot(100, [parent]()
{
QEvent e(QEvent::LayoutRequest);
QApplication::sendEvent(parent, &e); });
}
}
}
// 设置下一次检查的延迟
debounceTimer.start(100);
}
// tokens按钮实现功能的槽函数 // tokens按钮实现功能的槽函数
void ctai_history_textedit::on_tokens_clicked() void ctai_history_textedit::on_tokens_clicked()
{ {
@ -267,31 +207,65 @@ void ctai_history_textedit::on_fold_clicked()
m_msg_fold->setIcon(QIcon(m_is_folded ? ":res/img/btn/btn_info_down.png" : ":res/img/btn/btn_info_up.png")); m_msg_fold->setIcon(QIcon(m_is_folded ? ":res/img/btn/btn_info_down.png" : ":res/img/btn/btn_info_up.png"));
updateGeometry(); updateGeometry();
emit height_changed();
} }
void ctai_history_textedit::add_message(const model_data &message) void ctai_history_textedit::on_text_height()
{
}
void ctai_history_textedit::add_user_message(const model_data &message)
{ {
std::lock_guard<std::mutex> lock(m_mutex);
QString disp_data; QString disp_data;
QString disp_header; disp_data = ctai_markdown::md_to_html(QSL(message.send_user_data));
m_msg_sned_id = QSL(message.send_user_id);
m_msg_history->setHtml(disp_data);
}
void ctai_history_textedit::add_system_message(const model_data &message)
{
QString disp_data;
m_msg_sned_id = QSL(message.send_user_id);
std::lock_guard<std::mutex> lock(m_mutex);
if (message.postback_stream_mode)
{
// 流式模式下追加内容
m_current_content += message.postback_model_data;
m_msg_history->setHtml(ctai_markdown::md_to_html(m_current_content));
}
else
{
// 非流式模式直接设置全部内容
disp_data = ctai_markdown::md_to_html(QSL(message.postback_model_data));
m_msg_history->setHtml(disp_data);
m_current_content = QSL(message.postback_model_data);
}
}
// 增加tokens信息
void ctai_history_textedit::update_tokens_message(const model_data &message)
{
if (message.msg_type_mode == SYSTEM) if (message.msg_type_mode == SYSTEM)
{ {
disp_header = "ID:" + QSL(message.postback_send_id) + " | FP:" + QSL(message.postback_system_fingerprint) + " | 时间:" + QSL(message.postback_time);
m_tokens_data.prompt_tokens = "提示词消耗tokens:" + QSN(message.postback_prompt_tokens); m_tokens_data.prompt_tokens = "提示词消耗tokens:" + QSN(message.postback_prompt_tokens);
m_tokens_data.completion_tokens = "生成信息消耗:tokens:" + QSN(message.postback_completion_tokens); m_tokens_data.completion_tokens = "生成信息消耗:tokens:" + QSN(message.postback_completion_tokens);
m_tokens_data.total_tokens = "总消耗tokens:" + QSN(message.postback_total_tokens); m_tokens_data.total_tokens = "总消耗tokens:" + QSN(message.postback_total_tokens);
m_tokens_data.cache_hit_tokens = "提示词缓存命中消耗tokens:" + QSN(message.postback_prompt_cache_hit_tokens); m_tokens_data.cache_hit_tokens = "提示词缓存命中消耗tokens:" + QSN(message.postback_prompt_cache_hit_tokens);
m_tokens_data.cache_miss_tokens = "提示词缓存未命中消耗tokens:" + QSN(message.postback_prompt_cache_miss_tokens); m_tokens_data.cache_miss_tokens = "提示词缓存未命中消耗tokens:" + QSN(message.postback_prompt_cache_miss_tokens);
disp_data =ctai_markdown::md_to_html(QSL(message.postback_model_data)); }
m_msg_sned_id = QSL(message.postback_send_id); }
// 增加头信息
void ctai_history_textedit::add_header_message(const model_data &message)
{
QString disp_header;
if (message.msg_type_mode == SYSTEM)
{
disp_header = "ID:" + QSL(message.postback_send_id) + " | FP:" + QSL(message.postback_system_fingerprint) + " | 时间:" + QSL(message.postback_time);
} }
else else
{ {
disp_header = "ID:" + QSL(message.send_user_id) + " | Time:" + QSL(message.send_user_time); disp_header = "ID:" + QSL(message.send_user_id) + " | Time:" + QSL(message.send_user_time);
disp_data = ctai_markdown::md_to_html(QSL(message.send_user_data));
m_msg_sned_id = QSL(message.send_user_id);
} }
m_msg_header->setText(disp_header); m_msg_header->setText(disp_header);
m_msg_history->setHtml(disp_data);
} }
void ctai_history_textedit::on_delete_clicked() void ctai_history_textedit::on_delete_clicked()
{ {
@ -359,7 +333,7 @@ void ctai_history_textedit::on_menu_clicked()
{ {
on_copy_clicked(); on_copy_clicked();
} }
else if (act == actSave) else if (actSave)
{ {
on_save_clicked(); on_save_clicked();
} }
@ -367,4 +341,4 @@ void ctai_history_textedit::on_menu_clicked()
{ {
on_delete_clicked(); on_delete_clicked();
} }
} }

View File

@ -2,24 +2,32 @@
#ifndef CTAI_HISTORY_TEXTEDIT_H #ifndef CTAI_HISTORY_TEXTEDIT_H
#define CTAI_HISTORY_TEXTEDIT_H #define CTAI_HISTORY_TEXTEDIT_H
// Qt核心组件
#include <QWidget> #include <QWidget>
#include <QString>
#include <QApplication>
#include <QClipboard>
// Qt布局相关
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QSpacerItem>
// Qt控件相关
#include <QPushButton> #include <QPushButton>
#include <QTextEdit> #include <QTextEdit>
#include <QLineEdit> #include <QLineEdit>
#include <QDateTime> #include <QFrame>
#include <QComboBox>
#include <QTextDocument>
#include <QApplication>
#include <QClipboard>
#include <QMenu> #include <QMenu>
#include <QAction>
#include <QAbstractTextDocumentLayout>
// Qt对话框
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QTimer>
// 系统相关
#include <mutex> #include <mutex>
#include <QAbstractTextDocumentLayout>
// 项目相关头文件
#include "ctai_base.h" #include "ctai_base.h"
#include "ctai_markdown.h" #include "ctai_markdown.h"
@ -37,9 +45,13 @@ class ctai_history_textedit : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
ctai_history_textedit(msg_type msg_type_mode); ctai_history_textedit(QWidget *parent = nullptr);
~ctai_history_textedit(); ~ctai_history_textedit();
void add_message(const model_data &message); void init_layout(msg_type msg_type_mode);
void add_user_message(const model_data &message);
void add_system_message(const model_data &message);
void update_tokens_message(const model_data &message);
void add_header_message(const model_data &message);
private slots: private slots:
void on_delete_clicked(); // 删除按钮 void on_delete_clicked(); // 删除按钮
void on_copy_clicked(); // 复制按钮 void on_copy_clicked(); // 复制按钮
@ -55,8 +67,7 @@ private slots:
signals: signals:
void delete_requested(QString); // 请求删除此消息 void delete_requested(QString); // 请求删除此消息
void height_changed(); // 新增高度变化信号 void height_changed(); // 新增高度变化信号
private: private:
void init_layout(msg_type msg_type_mode);
void connect_signals(msg_type msg_type_mode); // 连接信号和槽 void connect_signals(msg_type msg_type_mode); // 连接信号和槽
private: private:
QFrame *hLine; QFrame *hLine;
@ -100,7 +111,9 @@ private:
//本次会话的tokens消耗信息 //本次会话的tokens消耗信息
tokens_data m_tokens_data; tokens_data m_tokens_data;
//本次信息ID //本次信息ID
QString m_msg_sned_id={}; QString m_msg_sned_id={};
//本次信息
QString m_current_content;
QSpacerItem *bottom_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); QSpacerItem *bottom_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
}; };

View File

@ -48,29 +48,45 @@ void ctai_history_widget::add_message(const model_data &message)
model_data *stored_data = it->second.first; model_data *stored_data = it->second.first;
int row = it->second.second; int row = it->second.second;
//如果传入的对象属性为null或者[DONE]表示传输完毕或者其他情况
//此时需要将tokens更新掉所有其他属性不更新传输数据
QWidget *widget = cellWidget(row, 0);
if (!QSL(message.postback_model_data).isEmpty() || if (!QSL(message.postback_model_data).isEmpty() ||
!QSL(message.postback_model_data).contains("[]")) !QSL(message.postback_model_data).contains("[DONE]"))
{ {
//更新传入信息
stored_data->postback_model_data += message.postback_model_data; stored_data->postback_model_data += message.postback_model_data;
if (auto message_widget = qobject_cast<ctai_history_textedit *>(widget))
{
message_widget->add_system_message(message);
}
} }
else else
{ {
//更新tokens信息
update_stored_data(stored_data, message); update_stored_data(stored_data, message);
} if (auto message_widget = qobject_cast<ctai_history_textedit *>(widget))
{
// 更新显示 message_widget->update_tokens_message(message);
QWidget *widget = cellWidget(row, 0); }
if (auto message_widget = qobject_cast<ctai_history_textedit *>(widget)) }
{
message_widget->add_message(*stored_data);
}
} }
else else
{ {
// 添加新消息 // 添加新消息
model_data *new_data = new model_data(message); model_data *new_data = new model_data(message);
ctai_history_textedit *message_widget = new ctai_history_textedit(new_data->msg_type_mode); ctai_history_textedit *message_widget = new ctai_history_textedit(this);
message_widget->add_message(*new_data); message_widget->init_layout(new_data->msg_type_mode);
message_widget->show();
if(new_data->msg_type_mode==SYSTEM){
message_widget->add_header_message(*new_data);
message_widget->update_tokens_message(*new_data);
message_widget->add_system_message(*new_data);
}else{
message_widget->add_header_message(*new_data);
message_widget->add_user_message(*new_data);
}
// 创建新行 // 创建新行
int row = rowCount(); int row = rowCount();
insertRow(row); insertRow(row);

View File

@ -15,5 +15,4 @@ namespace ctai_markdown {
void savePDF(QString fileName); void savePDF(QString fileName);
} }
#endif // CTAI_PARSESTRING_H #endif // CTAI_PARSESTRING_H