ctai/src/ctaiHistoryTextEdit.cpp
JackLee aba2524edf 增加本地html浏览
修改大公式的图片显示方式,采用自动换行单独显示大公式图片
2025-03-11 21:09:31 +08:00

349 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 "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);
m_msg_tools = new ctaiHistoryTools();
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消息头垂直第二排功能区
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_msg_tools->getFont(), m_msg_tools->getFontSize().toInt()));
}
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_size()), this, SLOT(on_display_font_size_changed()));
connect(m_msg_tools, SIGNAL(on_signals_send_browser()), this, SLOT(on_send_browser()));
// save菜单功能
connect(m_msg_tools, SIGNAL(on_signals_save_file(QString,int)), this, SLOT(on_save_file(QString,int)));
// 其他功能菜单
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_send_browser(){
QString save_path=QDir::currentPath()+"/html/"+m_msg_sned_id+".html";
on_save_file(save_path,1);
QDesktopServices::openUrl(save_path);
}
void ctaiHistoryTextEdit::on_menu_slots()
{
}
void ctaiHistoryTextEdit::on_display_font_size_changed()
{
m_msg_history->setFont(QFont(m_msg_tools->getFont(), m_msg_tools->getFontSize().toInt()));
}
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_file(QString save_path,int index)
{
QString temp_current_content;
if(save_path.isEmpty()){
save_path = QFileDialog::getSaveFileName(this, tr("保存消息"), m_msg_sned_id + save_extend[index], save_extend_str[index]);
}
QFile file(save_path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::warning(this, tr("保存失败"),
tr("无法保存文件 %1:\n%2").arg(save_path).arg(file.errorString()));
return;
}
QTextStream out(&file);
switch (index)
{
case 0:
temp_current_content = m_msg_history->toPlainText();
break;
case 1:
temp_current_content = m_msg_history->toHtml();
break;
case 2:
temp_current_content = m_msg_history->toMarkdown();
break;
case 3:
break;
}
if (!temp_current_content.isEmpty())
{
out << temp_current_content << "\n\n";
}
file.close();
}
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_msg_tools->getFontSize().toInt()));
QTextDocument *doc = m_msg_history->document();
doc->adjustSize();
}
}
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->setText(disp_data);
}
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);
}