Compare commits

...

2 Commits

Author SHA1 Message Date
3c3d360b55 重新构建cmark-gfm 2025-03-06 15:38:38 +08:00
6890c4b71d 增加三方库的头文件 2025-03-06 14:16:59 +08:00
9 changed files with 58 additions and 220 deletions

View File

@ -62,12 +62,15 @@ include_directories(${JSON}/include)
find_package(CURL REQUIRED)
#cmark
find_package(cmark CONFIG REQUIRED)
#find_package(cmark CONFIG REQUIRED)
#cmark-gfm
add_subdirectory(${PROJECT_SOURCE_DIR}/3rdparty/cmark-gfm)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/cmark-gfm/src)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/cmark-gfm/extensions)
#MicroTeX
add_subdirectory(${PROJECT_SOURCE_DIR}/3rdparty/MicroTeX)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/MicroTeX/lib)
#QT
FIND_PACKAGE(Qt6 REQUIRED Core Gui Widgets)
@ -148,7 +151,7 @@ target_link_libraries(
Qt6::Gui
Qt6::Widgets
CURL::libcurl
cmark::cmark
#cmark::cmark
libcmark-gfm-extensions_static
libcmark-gfm_static
microtex

View File

@ -1,111 +0,0 @@
#include "ctai_cmark-gfm.h"
namespace ctai_cmark_gfm {
MarkdownStreamProcessor::MarkdownStreamProcessor(QTextEdit* textEdit)
: m_textEdit(textEdit)
, m_buffer()
, m_tempText() {
}
MarkdownStreamProcessor::~MarkdownStreamProcessor() {
// 确保在销毁时处理所有剩余的缓冲内容
if (!m_buffer.isEmpty()) {
handleBufferOverflow();
}
}
void MarkdownStreamProcessor::appendMarkdown(const QString& text) {
// 将新内容添加到缓冲区
QString newBuffer = m_buffer + text;
// 检查是否超出缓冲区大小
if (newBuffer.size() > BUFFER_SIZE) {
handleBufferOverflow();
newBuffer = text;
}
// 检查是否只包含开始标记
if (hasMarkdownStart(text) && !hasMarkdownEnd(text)) {
m_buffer = newBuffer;
m_tempText = text;
m_textEdit->append(text);
return;
}
// 检查是否包含结束标记
if (hasMarkdownEnd(text)) {
QString fullContent = m_buffer + text;
QString rendered = renderMarkdown(fullContent);
replaceTempText(rendered);
m_buffer.clear();
return;
}
m_buffer = newBuffer;
}
bool MarkdownStreamProcessor::hasMarkdownStart(const QString& text) {
// 检查常见的Markdown开始标记
static const QStringList startMarkers = {
"```", "~~~", "#", ">", "*", "_", "-", "+", "[", "|"
};
for (const QString& marker : startMarkers) {
if (text.contains(marker)) {
return true;
}
}
return false;
}
bool MarkdownStreamProcessor::hasMarkdownEnd(const QString& text) {
// 检查常见的Markdown结束标记
static const QStringList endMarkers = {
"```", "~~~", "\n\n", "]", "*", "_"
};
for (const QString& marker : endMarkers) {
if (text.contains(marker)) {
return true;
}
}
return false;
}
QString MarkdownStreamProcessor::renderMarkdown(const QString& text) {
QByteArray textUtf8 = text.toUtf8();
char* rendered = cmark_markdown_to_html(
textUtf8.constData(),
textUtf8.size(),
CMARK_OPT_DEFAULT
);
QString result = QString::fromUtf8(rendered);
free(rendered);
return result;
}
void MarkdownStreamProcessor::handleBufferOverflow() {
if (!m_buffer.isEmpty()) {
QString rendered = renderMarkdown(m_buffer);
replaceTempText(rendered);
m_buffer.clear();
}
}
void MarkdownStreamProcessor::replaceTempText(const QString& newContent) {
if (!m_tempText.isEmpty()) {
// 获取当前文本
QString currentText = m_textEdit->toPlainText();
// 替换临时文本
currentText.replace(m_tempText, newContent);
m_textEdit->setHtml(currentText);
m_tempText.clear();
} else {
m_textEdit->append(newContent);
}
}
}

View File

@ -1,43 +0,0 @@
#ifndef CTAI_CMARK_GFM_H
#define CTAI_CMARK_GFM_H
#include <cmark-gfm.h>
#include <QString>
#include <QTextEdit>
#include <memory>
namespace ctai_cmark_gfm {
class MarkdownStreamProcessor {
public:
explicit MarkdownStreamProcessor(QTextEdit* textEdit);
~MarkdownStreamProcessor();
// 追加内容并处理Markdown
void appendMarkdown(const QString& text);
private:
static constexpr size_t BUFFER_SIZE = 4096;
QTextEdit* m_textEdit;
QString m_buffer;
QString m_tempText;
// 检查是否包含Markdown开始标记
bool hasMarkdownStart(const QString& text);
// 检查是否包含Markdown结束标记
bool hasMarkdownEnd(const QString& text);
// 渲染Markdown内容
QString renderMarkdown(const QString& text);
// 处理缓冲区溢出
void handleBufferOverflow();
// 替换临时文本
void replaceTempText(const QString& newContent);
};
}
#endif

16
src/ctai_cmark_gfm.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "ctai_cmark_gfm.h"
ctai_cmark_gfm::ctai_cmark_gfm(QTextEdit *textEdit)
: m_textEdit(textEdit), m_buffer()
{
}
ctai_cmark_gfm::~ctai_cmark_gfm()
{
}
void ctai_cmark_gfm::appendMarkdown(const QString &markdown)
{
}

24
src/ctai_cmark_gfm.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef CTAI_CMARK_GFM_H
#define CTAI_CMARK_GFM_H
#include "cmark-gfm.h"
#include "cmark-gfm-extension_api.h"
#include "cmark-gfm-core-extensions.h"
#include <QString>
#include <QTextEdit>
class ctai_cmark_gfm {
public:
explicit ctai_cmark_gfm(QTextEdit* textEdit);
~ctai_cmark_gfm();
// 追加内容并处理Markdown
void appendMarkdown(const QString& text);
private:
static constexpr size_t BUFFER_SIZE = 4096;
QTextEdit* m_textEdit;
QString m_buffer;
};
#endif

View File

@ -91,6 +91,7 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
// 5. 历史信息QTextEdit
historyLayout = new QVBoxLayout();
m_msg_history = new QTextEdit();
m_cmark_gfm=new ctai_cmark_gfm(m_msg_history);
m_msg_history->setAcceptRichText(true);
m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_msg_history->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
@ -126,7 +127,7 @@ void ctai_history_textedit::connect_signals(msg_type msg_type_mode)
{
connect(m_msg_user_del, SIGNAL(clicked()), this, SLOT(on_delete_clicked()));
}
connect(m_msg_history->document(), SIGNAL(contentsChanged()), this, SLOT(on_text_height()));
connect(m_msg_history, SIGNAL(textChanged()), this, SLOT(on_sync_text_height()));
}
// tokens按钮实现功能的槽函数
@ -196,6 +197,7 @@ void ctai_history_textedit::on_save_clicked()
m_msg_save_menu->addAction(m_msg_save_pdf);
m_msg_save_menu->exec(QCursor::pos());
}
// 实现折叠功能的槽函数
void ctai_history_textedit::on_fold_clicked()
{
@ -206,7 +208,7 @@ void ctai_history_textedit::on_fold_clicked()
updateGeometry();
emit row_height_changed(m_is_folded);
}
void ctai_history_textedit::on_text_height()
void ctai_history_textedit::on_sync_text_height()
{
// 防抖动处理如果上次更新在100ms内则延迟处理
static QTimer debounceTimer;
@ -243,8 +245,8 @@ void ctai_history_textedit::on_text_height()
{
QTimer::singleShot(100, [parent]()
{
QEvent e(QEvent::LayoutRequest);
QApplication::sendEvent(parent, &e); });
QEvent e(QEvent::LayoutRequest);
QApplication::sendEvent(parent, &e); });
}
}
}
@ -254,7 +256,7 @@ void ctai_history_textedit::on_text_height()
void ctai_history_textedit::add_user_message(const model_data &message)
{
QString disp_data;
disp_data = ctai_markdown::md_to_html(QSL(message.send_user_data));
disp_data = QSL(message.send_user_data);
m_msg_sned_id = QSL(message.send_user_id);
m_msg_history->setHtml(disp_data);
}
@ -269,12 +271,12 @@ void ctai_history_textedit::add_system_message(const model_data &message)
{
// 流式模式下追加内容
m_current_content += message.postback_model_data;
m_msg_history->setHtml(ctai_markdown::md_to_html(m_current_content));
m_cmark_gfm->appendMarkdown(QSL(message.postback_model_data));
}
else
{
// 非流式模式直接设置全部内容
disp_data = ctai_markdown::md_to_html(QSL(message.postback_model_data));
disp_data = QSL(message.postback_model_data);
m_msg_history->setHtml(disp_data);
m_current_content = QSL(message.postback_model_data);
}

View File

@ -29,7 +29,7 @@
#include <mutex>
// 项目相关头文件
#include "ctai_base.h"
#include "ctai_markdown.h"
#include "ctai_cmark_gfm.h"
typedef struct tokens_args{
QString prompt_tokens={};
@ -63,13 +63,14 @@ private slots:
void on_save_html(); // 保存html
void on_save_markdown(); // 保存markdown
void on_save_pdf(); // 保存pdf
void on_text_height();
void on_sync_text_height();
signals:
void delete_requested(QString); // 请求删除此消息
void row_height_changed(bool);
private:
void connect_signals(msg_type msg_type_mode); // 连接信号和槽
void connect_signals(msg_type msg_type_mode);// 连接信号和槽
private:
ctai_cmark_gfm* m_cmark_gfm;
QFrame *hLine;
QSpacerItem *sparcer_item = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed);
QVBoxLayout *mainLayout={};

View File

@ -1,40 +0,0 @@
#include "ctai_markdown.h"
namespace ctai_markdown {
QString md_to_html(const QString& text) {
// 转换为UTF-8编码的字符串
QByteArray markdown = text.toUtf8();
// 使用cmark解析Markdown
char *html = cmark_markdown_to_html(
markdown.constData(),
markdown.size(),
CMARK_OPT_DEFAULT
);
// 转换回QString并处理代码块
QString result = QString::fromUtf8(html);
free(html);
//qDebug()<<"处理的块:"<<result;
return process_code_blocks(result.toUtf8());
}
QString process_code_blocks(QString html) {
// 添加代码块样式
html.replace(
"<pre><code>",
"<pre><code style='display:block; padding:1em; "
"background-color:#f6f8fa; border-radius:4px; "
"font-family:Consolas,monospace; font-size:13px;'>"
);
// 添加内联代码样式
html.replace(
"<code>",
"<code style='padding:0.2em 0.4em; background-color:#f6f8fa; "
"border-radius:3px; font-family:Consolas,monospace;'>"
);
return html;
}
} // namespace ctai_parse

View File

@ -1,14 +0,0 @@
#ifndef CTAI_MARKDOWN_H
#define CTAI_MARKDOWN_H
#include <cmark.h>
#include <QString>
#include <QDebug>
namespace ctai_markdown {
// Markdown 转 HTML
QString md_to_html(const QString& text);
// 处理代码块样式
QString process_code_blocks(QString html);
}
#endif // CTAI_PARSESTRING_H