Compare commits
No commits in common. "3c3d360b55199fe6cf2acf77eb676806f8ccc31a" and "1113c5332a4aae1f95a70e96ad0b4e8e59fb9cf2" have entirely different histories.
3c3d360b55
...
1113c5332a
@ -62,15 +62,12 @@ include_directories(${JSON}/include)
|
|||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
|
||||||
#cmark
|
#cmark
|
||||||
#find_package(cmark CONFIG REQUIRED)
|
find_package(cmark CONFIG REQUIRED)
|
||||||
|
|
||||||
#cmark-gfm
|
#cmark-gfm
|
||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/3rdparty/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
|
#MicroTeX
|
||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/3rdparty/MicroTeX)
|
add_subdirectory(${PROJECT_SOURCE_DIR}/3rdparty/MicroTeX)
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/MicroTeX/lib)
|
|
||||||
|
|
||||||
#查找QT模块
|
#查找QT模块
|
||||||
FIND_PACKAGE(Qt6 REQUIRED Core Gui Widgets)
|
FIND_PACKAGE(Qt6 REQUIRED Core Gui Widgets)
|
||||||
@ -151,7 +148,7 @@ target_link_libraries(
|
|||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
#cmark::cmark
|
cmark::cmark
|
||||||
libcmark-gfm-extensions_static
|
libcmark-gfm-extensions_static
|
||||||
libcmark-gfm_static
|
libcmark-gfm_static
|
||||||
microtex
|
microtex
|
||||||
|
|||||||
111
ctai_cmark-gfm.cpp
Normal file
111
ctai_cmark-gfm.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
43
ctai_cmark-gfm.h
Normal file
43
ctai_cmark-gfm.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#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
|
||||||
@ -1,16 +0,0 @@
|
|||||||
#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)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -91,7 +91,6 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
|
|||||||
// 5. 历史信息QTextEdit
|
// 5. 历史信息QTextEdit
|
||||||
historyLayout = new QVBoxLayout();
|
historyLayout = new QVBoxLayout();
|
||||||
m_msg_history = new QTextEdit();
|
m_msg_history = new QTextEdit();
|
||||||
m_cmark_gfm=new ctai_cmark_gfm(m_msg_history);
|
|
||||||
m_msg_history->setAcceptRichText(true);
|
m_msg_history->setAcceptRichText(true);
|
||||||
m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
m_msg_history->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
m_msg_history->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
m_msg_history->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
@ -127,7 +126,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_user_del, SIGNAL(clicked()), this, SLOT(on_delete_clicked()));
|
||||||
}
|
}
|
||||||
connect(m_msg_history, SIGNAL(textChanged()), this, SLOT(on_sync_text_height()));
|
connect(m_msg_history->document(), SIGNAL(contentsChanged()), this, SLOT(on_text_height()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// tokens按钮实现功能的槽函数
|
// tokens按钮实现功能的槽函数
|
||||||
@ -197,7 +196,6 @@ void ctai_history_textedit::on_save_clicked()
|
|||||||
m_msg_save_menu->addAction(m_msg_save_pdf);
|
m_msg_save_menu->addAction(m_msg_save_pdf);
|
||||||
m_msg_save_menu->exec(QCursor::pos());
|
m_msg_save_menu->exec(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实现折叠功能的槽函数
|
// 实现折叠功能的槽函数
|
||||||
void ctai_history_textedit::on_fold_clicked()
|
void ctai_history_textedit::on_fold_clicked()
|
||||||
{
|
{
|
||||||
@ -208,7 +206,7 @@ void ctai_history_textedit::on_fold_clicked()
|
|||||||
updateGeometry();
|
updateGeometry();
|
||||||
emit row_height_changed(m_is_folded);
|
emit row_height_changed(m_is_folded);
|
||||||
}
|
}
|
||||||
void ctai_history_textedit::on_sync_text_height()
|
void ctai_history_textedit::on_text_height()
|
||||||
{
|
{
|
||||||
// 防抖动处理:如果上次更新在100ms内,则延迟处理
|
// 防抖动处理:如果上次更新在100ms内,则延迟处理
|
||||||
static QTimer debounceTimer;
|
static QTimer debounceTimer;
|
||||||
@ -245,8 +243,8 @@ void ctai_history_textedit::on_sync_text_height()
|
|||||||
{
|
{
|
||||||
QTimer::singleShot(100, [parent]()
|
QTimer::singleShot(100, [parent]()
|
||||||
{
|
{
|
||||||
QEvent e(QEvent::LayoutRequest);
|
QEvent e(QEvent::LayoutRequest);
|
||||||
QApplication::sendEvent(parent, &e); });
|
QApplication::sendEvent(parent, &e); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,7 +254,7 @@ void ctai_history_textedit::on_sync_text_height()
|
|||||||
void ctai_history_textedit::add_user_message(const model_data &message)
|
void ctai_history_textedit::add_user_message(const model_data &message)
|
||||||
{
|
{
|
||||||
QString disp_data;
|
QString disp_data;
|
||||||
disp_data = QSL(message.send_user_data);
|
disp_data = ctai_markdown::md_to_html(QSL(message.send_user_data));
|
||||||
m_msg_sned_id = QSL(message.send_user_id);
|
m_msg_sned_id = QSL(message.send_user_id);
|
||||||
m_msg_history->setHtml(disp_data);
|
m_msg_history->setHtml(disp_data);
|
||||||
}
|
}
|
||||||
@ -271,12 +269,12 @@ void ctai_history_textedit::add_system_message(const model_data &message)
|
|||||||
{
|
{
|
||||||
// 流式模式下追加内容
|
// 流式模式下追加内容
|
||||||
m_current_content += message.postback_model_data;
|
m_current_content += message.postback_model_data;
|
||||||
m_cmark_gfm->appendMarkdown(QSL(message.postback_model_data));
|
m_msg_history->setHtml(ctai_markdown::md_to_html(m_current_content));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 非流式模式直接设置全部内容
|
// 非流式模式直接设置全部内容
|
||||||
disp_data = QSL(message.postback_model_data);
|
disp_data = ctai_markdown::md_to_html(QSL(message.postback_model_data));
|
||||||
m_msg_history->setHtml(disp_data);
|
m_msg_history->setHtml(disp_data);
|
||||||
m_current_content = QSL(message.postback_model_data);
|
m_current_content = QSL(message.postback_model_data);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
// 项目相关头文件
|
// 项目相关头文件
|
||||||
#include "ctai_base.h"
|
#include "ctai_base.h"
|
||||||
#include "ctai_cmark_gfm.h"
|
#include "ctai_markdown.h"
|
||||||
|
|
||||||
typedef struct tokens_args{
|
typedef struct tokens_args{
|
||||||
QString prompt_tokens={};
|
QString prompt_tokens={};
|
||||||
@ -63,14 +63,13 @@ private slots:
|
|||||||
void on_save_html(); // 保存html
|
void on_save_html(); // 保存html
|
||||||
void on_save_markdown(); // 保存markdown
|
void on_save_markdown(); // 保存markdown
|
||||||
void on_save_pdf(); // 保存pdf
|
void on_save_pdf(); // 保存pdf
|
||||||
void on_sync_text_height();
|
void on_text_height();
|
||||||
signals:
|
signals:
|
||||||
void delete_requested(QString); // 请求删除此消息
|
void delete_requested(QString); // 请求删除此消息
|
||||||
void row_height_changed(bool);
|
void row_height_changed(bool);
|
||||||
private:
|
private:
|
||||||
void connect_signals(msg_type msg_type_mode);// 连接信号和槽
|
void connect_signals(msg_type msg_type_mode); // 连接信号和槽
|
||||||
private:
|
private:
|
||||||
ctai_cmark_gfm* m_cmark_gfm;
|
|
||||||
QFrame *hLine;
|
QFrame *hLine;
|
||||||
QSpacerItem *sparcer_item = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed);
|
QSpacerItem *sparcer_item = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
QVBoxLayout *mainLayout={};
|
QVBoxLayout *mainLayout={};
|
||||||
|
|||||||
40
src/ctai_markdown.cpp
Normal file
40
src/ctai_markdown.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#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
|
||||||
14
src/ctai_markdown.h
Normal file
14
src/ctai_markdown.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#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
|
||||||
Loading…
Reference in New Issue
Block a user