382 lines
13 KiB
C++
382 lines
13 KiB
C++
#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();
|
||
}
|
||
} |