ctai/src/ctai_history_textedit.cpp
2025-03-05 19:39:50 +08:00

382 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ctai_history_textedit.h"
ctai_history_textedit::ctai_history_textedit(QWidget *parent)
: QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
ctai_history_textedit::~ctai_history_textedit()
{
}
void ctai_history_textedit::init_layout(msg_type msg_type_mode)
{
// 主垂直布局
mainLayout = new QVBoxLayout();
// 1. 消息头垂直
headerLayout = new QVBoxLayout();
header_info_Layout = new QHBoxLayout();
m_msg_header = new QLineEdit();
m_msg_header->setObjectName("m_msg_header");
m_msg_header->setReadOnly(true);
if (msg_type_mode == SYSTEM)
{
// SYSTEM消息头水平
m_msg_system_header_ico = new QPushButton();
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_header);
header_info_Layout->setContentsMargins(0, 0, 0, 0);
headerLayout->addLayout(header_info_Layout);
// 2.tokens功能按钮
m_msg_tokens = new QPushButton();
m_history_to_send = new QPushButton();
m_msg_system_del = new QPushButton();
m_restart_to_send = new QPushButton();
m_msg_tokens->setObjectName("m_msg_tokens");
m_history_to_send->setObjectName("m_history_to_send");
m_msg_system_del->setObjectName("m_msg_system_del");
m_restart_to_send->setObjectName("m_restart_to_send");
header_info_Layout->addWidget(m_history_to_send);
header_info_Layout->addWidget(m_msg_tokens);
header_info_Layout->addWidget(m_restart_to_send);
header_info_Layout->addWidget(m_msg_system_del);
// 3.消息功能区
header_opts_Layout = new QHBoxLayout();
m_msg_copy = new QPushButton();
m_msg_save = new QPushButton();
m_msg_menu = new QPushButton();
m_msg_fold = new QPushButton();
m_msg_copy->setObjectName("m_msg_copy");
m_msg_save->setObjectName("m_msg_save");
m_msg_menu->setObjectName("m_msg_menu");
m_msg_fold->setObjectName("m_msg_fold");
m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png"));
m_msg_fold->setIconSize(QSize(25, 25));
header_opts_Layout->addItem(sparcer_item);
header_opts_Layout->addWidget(m_msg_copy);
header_opts_Layout->addWidget(m_msg_save);
header_opts_Layout->addWidget(m_msg_menu);
header_opts_Layout->addWidget(m_msg_fold);
header_opts_Layout->addItem(sparcer_item);
header_opts_Layout->setContentsMargins(0, 0, 0, 0);
headerLayout->addLayout(header_opts_Layout);
}
else
{
// SYSTEM MSG ICO
m_msg_user_header_ico = new QPushButton();
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_header);
header_info_Layout->setContentsMargins(0, 0, 0, 0);
headerLayout->addLayout(header_info_Layout);
// USER DELETE BUTTON
m_msg_user_del = new QPushButton();
m_msg_user_del->setObjectName("m_msg_user_del");
m_history_to_send = new QPushButton();
m_history_to_send->setObjectName("m_history_to_send");
m_restart_to_send = new QPushButton();
m_restart_to_send->setObjectName("m_restart_to_send");
header_info_Layout->addWidget(m_history_to_send);
header_info_Layout->addWidget(m_restart_to_send);
header_info_Layout->addWidget(m_msg_user_del);
}
// 4.分割线区域
hLine = new QFrame;
hLine->setFrameShape(QFrame::HLine); // 关键属性
hLine->setFrameShadow(QFrame::Sunken); // 凹陷效果
hLine->setLineWidth(2); // 线宽
// 5. 历史信息QTextEdit
historyLayout = new QVBoxLayout();
m_msg_history = new QTextEdit();
m_msg_history->setAcceptRichText(true);
m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_msg_history->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_msg_history->setObjectName("m_msg_history");
m_msg_history->setReadOnly(true);
historyLayout->addWidget(m_msg_history);
historyLayout->addWidget(hLine);
historyLayout->setContentsMargins(0, 0, 0, 0);
// 组合布局
mainLayout->addLayout(headerLayout);
mainLayout->addLayout(historyLayout);
mainLayout->addItem(bottom_spacer);
mainLayout->setStretch(0, 1);
mainLayout->setSpacing(1);
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
connect_signals(msg_type_mode);
}
void ctai_history_textedit::connect_signals(msg_type msg_type_mode)
{
if (msg_type_mode == SYSTEM)
{
connect(m_msg_system_del, SIGNAL(clicked()), this, SLOT(on_delete_clicked()));
connect(m_msg_copy, SIGNAL(clicked()), this, SLOT(on_copy_clicked()));
connect(m_msg_save, SIGNAL(clicked()), this, SLOT(on_save_clicked()));
connect(m_msg_menu, SIGNAL(clicked()), this, SLOT(on_menu_clicked()));
connect(m_msg_fold, SIGNAL(clicked()), this, SLOT(on_fold_clicked()));
connect(m_msg_tokens, SIGNAL(clicked()), this, SLOT(on_tokens_clicked()));
}
else
{
connect(m_msg_user_del, SIGNAL(clicked()), this, SLOT(on_delete_clicked()));
}
connect(m_msg_history->document(), SIGNAL(contentsChanged()), this, SLOT(on_text_height()));
}
// tokens按钮实现功能的槽函数
void ctai_history_textedit::on_tokens_clicked()
{
if (!m_msg_tokens_menu)
{
m_msg_tokens_menu = new QMenu();
}
if (!m_menu_prompt_tokens)
{
m_menu_prompt_tokens = new QAction(m_tokens_data.prompt_tokens);
}
if (!m_menu_completion_tokens)
{
m_menu_completion_tokens = new QAction(m_tokens_data.completion_tokens);
}
if (!m_menu_total_tokens)
{
m_menu_total_tokens = new QAction(m_tokens_data.total_tokens);
}
if (!m_menu_cache_hit_tokens)
{
m_menu_cache_hit_tokens = new QAction(m_tokens_data.cache_hit_tokens);
}
if (!m_menu_cache_miss_tokens)
{
m_menu_cache_miss_tokens = new QAction(m_tokens_data.cache_miss_tokens);
}
m_msg_tokens_menu->addAction(m_menu_prompt_tokens);
m_msg_tokens_menu->addAction(m_menu_completion_tokens);
m_msg_tokens_menu->addAction(m_menu_cache_hit_tokens);
m_msg_tokens_menu->addAction(m_menu_cache_miss_tokens);
m_msg_tokens_menu->addAction(m_menu_total_tokens);
m_msg_tokens_menu->exec(QCursor::pos());
}
// save按钮实现功能的槽函数
void ctai_history_textedit::on_save_clicked()
{
if (!m_msg_save_menu)
{
m_msg_save_menu = new QMenu();
}
if (!m_msg_save_text)
{
m_msg_save_text = new QAction(tr("另存为文本"));
connect(m_msg_save_text, SIGNAL(triggered(bool)), this, SLOT(on_save_text()));
}
if (!m_msg_save_html)
{
m_msg_save_html = new QAction(tr("另存为HTML"));
connect(m_msg_save_html, SIGNAL(triggered(bool)), this, SLOT(on_save_html()));
}
if (!m_msg_save_markdown)
{
m_msg_save_markdown = new QAction(tr("另存为Markdown"));
connect(m_msg_save_markdown, SIGNAL(triggered(bool)), this, SLOT(on_save_markdown()));
}
if (!m_msg_save_pdf)
{
m_msg_save_pdf = new QAction(tr("另存为PDF"));
connect(m_msg_save_pdf, SIGNAL(triggered(bool)), this, SLOT(on_save_pdf()));
}
m_msg_save_menu->addAction(m_msg_save_text);
m_msg_save_menu->addAction(m_msg_save_html);
m_msg_save_menu->addAction(m_msg_save_markdown);
m_msg_save_menu->addAction(m_msg_save_pdf);
m_msg_save_menu->exec(QCursor::pos());
}
// 实现折叠功能的槽函数
void ctai_history_textedit::on_fold_clicked()
{
m_is_folded = !m_is_folded;
m_msg_history->setFixedHeight(m_is_folded ? 0 : m_original_height);
m_msg_fold->setIcon(QIcon(m_is_folded ? ":res/img/btn/btn_info_down.png" : ":res/img/btn/btn_info_up.png"));
updateGeometry();
emit row_height_changed(m_is_folded);
}
void ctai_history_textedit::on_text_height()
{
// 防抖动处理如果上次更新在100ms内则延迟处理
static QTimer debounceTimer;
debounceTimer.setSingleShot(true);
if (debounceTimer.isActive())
{
return;
}
// 直接使用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 row_height_changed(false);
// 延迟发送布局更新请求
if (QWidget *parent = parentWidget())
{
QTimer::singleShot(100, [parent]()
{
QEvent e(QEvent::LayoutRequest);
QApplication::sendEvent(parent, &e); });
}
}
}
// 设置下一次检查的延迟
debounceTimer.start(100);
}
void ctai_history_textedit::add_user_message(const model_data &message)
{
QString disp_data;
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.postback_send_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)
{
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.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_miss_tokens = "提示词缓存未命中消耗tokens:" + QSN(message.postback_prompt_cache_miss_tokens);
}
}
// 增加头信息
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
{
disp_header = "ID:" + QSL(message.send_user_id) + " | Time:" + QSL(message.send_user_time);
}
m_msg_header->setText(disp_header);
}
void ctai_history_textedit::on_delete_clicked()
{
// 发送删除请求信号
emit delete_requested(m_msg_sned_id);
}
void ctai_history_textedit::on_copy_clicked()
{
// 获取文本并复制到剪贴板
QString text = m_msg_history->toPlainText();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
void ctai_history_textedit::on_save_text()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("保存消息"), m_msg_sned_id + ".txt",
tr("文本文件 (*.txt)"));
if (fileName.isEmpty())
return;
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this, tr("保存失败"),
tr("无法保存文件 %1:\n%2").arg(fileName).arg(file.errorString()));
return;
}
QTextStream out(&file);
out << m_msg_history->toPlainText() << "\n\n";
}
void ctai_history_textedit::on_save_html()
{
}
void ctai_history_textedit::on_save_markdown()
{
}
void ctai_history_textedit::on_save_pdf()
{
}
void ctai_history_textedit::on_menu_clicked()
{
QMenu menu(this);
// 添加菜单项
QAction *actCopy = menu.addAction(tr("复制"));
QAction *actSave = menu.addAction(tr("保存"));
menu.addSeparator();
QAction *actDelete = menu.addAction(tr("删除"));
// 显示菜单
QPoint pos = m_msg_menu->mapToGlobal(m_msg_menu->rect().bottomRight());
QAction *act = menu.exec(pos);
// 处理菜单选择
if (act == actCopy)
{
on_copy_clicked();
}
else if (actSave)
{
on_save_clicked();
}
else if (act == actDelete)
{
on_delete_clicked();
}
}