#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 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); }