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