ctai/src/ctaiHistoryTextEdit.cpp
2025-03-20 21:11:50 +08:00

370 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::initLayout(msg_type msg_type_mode)
{
// 主垂直布局
main_layout = new QVBoxLayout();
initMsgLine();
initMsgHeaderLayout(msg_type_mode);
initMsgHistoryLayout();
// 主布局
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::initMsgLine()
{
// 分割线区域
msg_line = new QFrame;
msg_line->setFrameShape(QFrame::HLine); // 关键属性
msg_line->setFrameShadow(QFrame::Sunken); // 凹陷效果
msg_line->setLineWidth(2); // 线宽
}
void ctaiHistoryTextEdit::initMsgHistoryLayout()
{
history_layout = new QVBoxLayout();
// 历史信息QTextEdit
m_mainwindow=new MainWindow();
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(m_mainwindow);
history_layout->addWidget(msg_line);
history_layout->setContentsMargins(0, 0, 0, 0);
}
void ctaiHistoryTextEdit::initMsgHeaderLayout(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();
m_history_to_send = new QPushButton();
m_restart_to_send = new QPushButton();
m_msg_fold = new QPushButton();
m_msg_delete = new QPushButton();
m_msg_fold->setObjectName("m_msg_fold");
m_msg_delete->setObjectName("m_msg_delete");
m_history_to_send->setObjectName("m_history_to_send");
m_restart_to_send->setObjectName("m_restart_to_send");
m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png"));
m_msg_fold->setIconSize(QSize(25, 25));
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_msg_tokens->setObjectName("m_msg_tokens");
header_info_layout->addWidget(m_msg_fold);
header_info_layout->addWidget(m_history_to_send);
header_info_layout->addWidget(m_restart_to_send);
header_info_layout->addWidget(m_msg_tokens);
header_info_layout->addWidget(m_msg_delete);
// 3.SYSTEM消息头垂直第二排功能区
header_opts_layout->addWidget(m_msg_tools);
// 4.添加到消息头主布局
header_layout->addLayout(header_info_layout);
header_layout->addLayout(header_opts_layout);
}
else
{
// USER 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->addWidget(m_msg_fold);
header_info_layout->addWidget(m_history_to_send);
header_info_layout->addWidget(m_restart_to_send);
header_info_layout->addWidget(m_msg_delete);
header_info_layout->setContentsMargins(0, 0, 0, 0);
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_tools, SIGNAL(signalsOnCopy()), this, SLOT(slotsOnCopy()));
//tokens显示功能
connect(m_msg_tokens, SIGNAL(clicked()), this, SLOT(slotsOnTokens()));
//显示模式功能
connect(m_msg_tools, SIGNAL(signalsDisplayMode(QString)), this, SLOT(slotsOnDisplay(QString)));
//字体大小功能
connect(m_msg_tools, SIGNAL(signalsDisplayFontSize()), this, SLOT(slotsOnDisplayFontSize()));
//浏览器功能
connect(m_msg_tools, SIGNAL(signalsSendBrowser()), this, SLOT(slotsOnSendBrowser()));
//保存菜单功能
connect(m_msg_tools, SIGNAL(signalsSaveFile(QString, int)), this, SLOT(slotsOnSave(QString, int)));
// 其他功能菜单
connect(m_msg_tools, SIGNAL(signalsOnOptsMenu()), this, SLOT(slotsOnOptsMenu()));
}
//删除信息
connect(m_msg_delete, SIGNAL(clicked()), this, SLOT(slotsOnDelete()));
//折叠信息功能
connect(m_msg_fold, SIGNAL(clicked()), this, SLOT(slotsOnFold()));
//同步行高
connect(m_msg_history, SIGNAL(textChanged()), this, SLOT(slotsOnSyncHeight()));
//插入msg到send
connect(m_history_to_send, SIGNAL(clicked()), this, SLOT(slotsHisResToSend()));
//重试send
connect(m_restart_to_send, SIGNAL(clicked()), this, SLOT(slotsHisResToSend()));
}
void ctaiHistoryTextEdit::slotsHisResToSend()
{
if (sender()->objectName() == "m_history_to_send")
{
emit signalsEditHisResToSend(m_current_content, HISTORY_SEND_INSERT);
}
else
{
emit signalsEditHisResToSend(m_current_content, HISTORY_SEND_RESTART);
}
}
void ctaiHistoryTextEdit::slotsOnSendBrowser()
{
QString save_path = QDir::currentPath() + "/html/" + m_msg_sned_id + ".html";
slotsOnSave(save_path, 1);
QDesktopServices::openUrl(save_path);
}
void ctaiHistoryTextEdit::slotsOnOptsMenu()
{
}
void ctaiHistoryTextEdit::slotsOnDisplayFontSize()
{
m_msg_history->setFont(QFont(m_msg_tools->getFont(), m_msg_tools->getFontSize().toInt()));
}
void ctaiHistoryTextEdit::slotsOnDelete()
{
// 发送删除请求信号
emit signalsDeleteMsg(m_msg_sned_id);
}
void ctaiHistoryTextEdit::slotsOnCopy()
{
// 获取文本并复制到剪贴板
QString text = m_msg_history->toPlainText();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text);
}
void ctaiHistoryTextEdit::slotsOnSave(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::slotsOnFold()
{
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 signalsRowHeightChanged(m_is_folded);
}
void ctaiHistoryTextEdit::slotsOnDisplay(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::slotsOnTokens()
{
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::slotsOnSyncHeight()
{
// 防抖动处理如果上次更新在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 signalsRowHeightChanged(false);
// 延迟发送布局更新请求
if (QWidget *parent = parentWidget())
{
QTimer::singleShot(100, [parent]()
{
QEvent e(QEvent::LayoutRequest);
QApplication::sendEvent(parent, &e); });
}
}
}
// 设置下一次检查的延迟
debounceTimer.start(100);
}
void ctaiHistoryTextEdit::addUserMessage(const model_data &message)
{
m_current_content = QSL(message.send_user_data);
m_msg_sned_id = QSL(message.send_user_id);
m_msg_history->setText(m_current_content);
}
void ctaiHistoryTextEdit::addSystemMessage(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::updateTokensMessage(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::addHeaderMessage(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);
}