ctai/src/ctaiHistoryTextEdit.cpp
2025-03-10 17:09:01 +08:00

391 lines
14 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 "ctaiHistoryTextEdit.h"
ctaiHistoryTextEdit::ctaiHistoryTextEdit(QWidget *parent)
: QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
ctaiHistoryTextEdit::~ctaiHistoryTextEdit()
{
}
void ctaiHistoryTextEdit::init_layout(msg_type msg_type_mode)
{
// 主垂直布局
main_layout = new QVBoxLayout();
init_msg_line();
init_msg_header_layout(msg_type_mode);
init_msg_history_layout();
// 主布局
main_layout->addLayout(header_layout);
main_layout->addLayout(history_layout);
main_layout->addItem(bottom_spacer);
main_layout->setStretch(0, 1);
main_layout->setSpacing(1);
main_layout->setContentsMargins(0, 0, 0, 0);
setLayout(main_layout);
set_default_opts();
connect_signals(msg_type_mode);
}
void ctaiHistoryTextEdit::init_msg_line()
{
// 分割线区域
msg_line = new QFrame;
msg_line->setFrameShape(QFrame::HLine); // 关键属性
msg_line->setFrameShadow(QFrame::Sunken); // 凹陷效果
msg_line->setLineWidth(2); // 线宽
}
void ctaiHistoryTextEdit::init_msg_history_layout()
{
history_layout = new QVBoxLayout();
// 历史信息QTextEdit
m_msg_history = new QTextEdit();
m_math_convert = new ctaiMathConvert();
m_msg_history->setUndoRedoEnabled(false); // 关闭撤销历史以节省内存
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);
history_layout->addWidget(m_msg_history);
history_layout->addWidget(msg_line);
history_layout->setContentsMargins(0, 0, 0, 0);
}
void ctaiHistoryTextEdit::init_msg_header_layout(msg_type msg_type_mode)
{
// 消息头左区域布局,ico,id,fp,time
header_info_layout = new QHBoxLayout();
// 消息区头整体垂直
header_layout = new QVBoxLayout();
header_opts_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)
{
// 1.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);
// 2.SYSTEM消息头水平右信息区
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.SYSTEM消息头垂直第二排功能区
m_msg_tools = new ctaiHistoryTools();
header_opts_layout->addWidget(m_msg_tools);
// 4.添加到消息头主布局
header_layout->addLayout(header_info_layout);
header_layout->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);
// 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);
header_layout->addLayout(header_info_layout);
}
}
void ctaiHistoryTextEdit::set_default_opts()
{
m_msg_history->setFont(QFont(m_font, m_font_size));
}
void ctaiHistoryTextEdit::connect_signals(msg_type msg_type_mode)
{
if (msg_type_mode == SYSTEM)
{
connect(m_msg_system_del, SIGNAL(clicked()), this, SLOT(on_delete_slots()));
connect(m_msg_tools, SIGNAL(on_signals_copy()), this, SLOT(on_copy_slots()));
connect(m_msg_tools, SIGNAL(on_signals_fold()), this, SLOT(on_fold_slots()));
connect(m_msg_tokens, SIGNAL(clicked()), this, SLOT(on_tokens_slots()));
connect(m_msg_tools, SIGNAL(on_signals_display_mode(QString)), this, SLOT(on_display_changed(QString)));
connect(m_msg_tools, SIGNAL(on_signals_display_font(QString)), this, SLOT(on_display_font_changed(QString)));
connect(m_msg_tools, SIGNAL(on_signals_display_font_size(QString)), this, SLOT(on_display_font_size_changed(QString)));
// save菜单功能
connect(m_msg_tools, SIGNAL(on_signals_save_text()), this, SLOT(on_save_text()));
connect(m_msg_tools, SIGNAL(on_signals_save_html()), this, SLOT(on_save_html()));
connect(m_msg_tools, SIGNAL(on_signals_save_pdf()), this, SLOT(on_save_pdf()));
connect(m_msg_tools, SIGNAL(on_signals_save_markdown()), this, SLOT(on_save_markdown()));
// 其他功能菜单
connect(m_msg_tools, SIGNAL(on_signals_menu()), this, SLOT(on_menu_slots()));
}
else
{
connect(m_msg_user_del, SIGNAL(clicked()), this, SLOT(on_delete_slots()));
}
connect(m_msg_history, SIGNAL(textChanged()), this, SLOT(on_sync_text_height()));
}
void ctaiHistoryTextEdit::on_menu_slots()
{
}
void ctaiHistoryTextEdit::on_display_font_size_changed(QString font_size)
{
m_font_size = font_size.toInt();
m_msg_history->setFont(QFont(m_font, m_font_size));
}
void ctaiHistoryTextEdit::on_display_font_changed(QString font)
{
m_font = font;
m_msg_history->setFont(QFont(m_font, m_font_size));
}
void ctaiHistoryTextEdit::on_delete_slots()
{
// 发送删除请求信号
emit delete_requested(m_msg_sned_id);
}
void ctaiHistoryTextEdit::on_copy_slots()
{
// 获取文本并复制到剪贴板
QString text = m_msg_history->toPlainText();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
void ctaiHistoryTextEdit::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";
file.close();
}
void ctaiHistoryTextEdit::on_save_html()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("保存HTML"),
m_msg_sned_id + ".html",
tr("HTML 文件 (*.html)"));
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->toHtml();
file.close();
}
void ctaiHistoryTextEdit::on_save_markdown()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("保存 Markdown"),
m_msg_sned_id + ".md",
tr("Markdown 文件 (*.md)"));
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->toMarkdown();
file.close();
}
void ctaiHistoryTextEdit::on_save_pdf()
{
}
// 实现折叠功能的槽函数
void ctaiHistoryTextEdit::on_fold_slots()
{
m_is_folded = !m_is_folded;
m_msg_history->setFixedHeight(m_is_folded ? 0 : m_original_height);
m_msg_tools->setFoldIco(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 ctaiHistoryTextEdit::on_display_changed(QString mode)
{
// 根据显示模式设置文本
if (mode == "原始文本")
{
m_msg_history->setText(m_current_content);
}
else if (mode == "HTML源码")
{
}
else if (mode == "HTML渲染" || mode == "Markdown渲染")
{
m_msg_history->setHtml(m_math_convert->replace_tags_svg(m_current_content, m_font_size));
qDebug() << "LaTeX Markdown:" << m_msg_history->toMarkdown();
on_save_html();
on_save_markdown();
}
}
// tokens按钮实现功能的槽函数
void ctaiHistoryTextEdit::on_tokens_slots()
{
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());
}
void ctaiHistoryTextEdit::on_sync_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 ctaiHistoryTextEdit::add_user_message(const model_data &message)
{
QString disp_data;
disp_data = QSL(message.send_user_data);
m_msg_sned_id = QSL(message.send_user_id);
m_msg_history->setHtml(m_math_convert->replace_tags_svg(disp_data, m_font_size));
}
void ctaiHistoryTextEdit::add_system_message(const model_data &message)
{
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;
}
else
{
// 非流式模式直接设置全部内容
m_current_content = QSL(message.postback_model_data);
}
m_msg_history->setMarkdown(m_current_content);
}
// 增加tokens信息
void ctaiHistoryTextEdit::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 ctaiHistoryTextEdit::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);
}