增加公式转换

This commit is contained in:
JackLee 2025-03-08 20:25:38 +08:00
parent eda1c1a2e9
commit e0ffdbd010
9 changed files with 160 additions and 193 deletions

View File

@ -1,83 +0,0 @@
name: CI tests
on: [push, workflow_dispatch]
jobs:
linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
compiler:
- c: 'clang'
cpp: 'clang++'
- c: 'gcc'
cpp: 'g++'
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
CC: ${{ matrix.compiler.c }}
CXX: ${{ matrix.compiler.cpp }}
steps:
- uses: actions/checkout@v1
- name: Install valgrind
run: |
sudo apt install -y valgrind
- name: Build and test
run: |
make
make test
make leakcheck
macos:
runs-on: macOS-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
compiler:
- c: 'clang'
cpp: 'clang++'
- c: 'gcc'
cpp: 'g++'
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
CC: ${{ matrix.compiler.c }}
CXX: ${{ matrix.compiler.cpp }}
steps:
- uses: actions/checkout@v1
- name: Build and test
env:
CMAKE_OPTIONS: -DCMARK_SHARED=OFF
run: |
make
make test
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
cmake_opts:
- '-DCMARK_SHARED=ON'
- ''
env:
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
steps:
- uses: actions/checkout@v1
- uses: ilammy/msvc-dev-cmd@v1
- name: Build and test
run: |
chcp 65001
nmake.exe /nologo /f Makefile.nmake test
shell: cmd

View File

@ -1,77 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '45 14 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'javascript', 'python', 'ruby' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@ -30,7 +30,7 @@ SET(CMAKE_CXX_FLAGS_RELEASE"-O3 -s")
SET(CMAKE_BUILD_TYPE "Release")
MESSAGE(STATUS ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "----------编译模式 START-------------")
MESSAGE(STATUS "---------- START-------------")
if(NOT CMAKE_BUILD_TYPE)
MESSAGE(STATUS "[CMAKE_BUILD_TYPE]当前值[Debug]")
SET(CMAKE_BUILD_TYPE "Debug")
@ -40,7 +40,7 @@ else()
SET(CMAKE_BUILD_TYPE "Release")
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/release/)
endif()
MESSAGE(STATUS "----------编译模式 END---------------")
MESSAGE(STATUS "---------- END---------------")
#QT .cmake
#QT
@ -74,10 +74,10 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/MicroTeX/src)
FIND_PACKAGE(Qt6 REQUIRED Core Gui Widgets)
#
MESSAGE(STATUS "----------基础路径输出 START-------------")
MESSAGE(STATUS "---------- START-------------")
MESSAGE(STATUS "QT_DIR_PATH=${QT_DIR}")
MESSAGE(STATUS "JSON_DIR_PATH=${JSON}")
MESSAGE(STATUS "----------基础路径输出 END---------------")
MESSAGE(STATUS "---------- END---------------")
@ -148,8 +148,8 @@ target_link_libraries(
Qt6::Gui
Qt6::Widgets
CURL::libcurl
#libcmark-gfm-extensions_static
#libcmark-gfm_static
#libcmark-gfm-extensions_static
LaTeX
)

View File

@ -62,4 +62,30 @@ typedef struct model_data{
std::string request_body;
}model_data;
const QStringList latex_symbols = {
"\\Alpha",
"\\Beta",
"\\Gamma",
"\\Delta",
"\\Epsilon",
"\\Zeta",
"\\Eta",
"\\Theta",
"\\Iota",
"\\Kappa",
"\\Lambda",
"\\Mu",
"\\Nu",
"\\Xi",
"\\Omicron",
"\\Pi",
"\\Rho",
"\\Sigma",
"\\Tau",
"\\Upsilon",
"\\Phi",
"\\Chi",
"\\Psi",
"\\Omega"
};
#endif // CTAI_BASE_H

View File

@ -47,6 +47,11 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_save = new QPushButton();
m_msg_menu = new QPushButton();
m_msg_fold = new QPushButton();
m_msg_display_combobox=new QComboBox();
m_msg_display_label=new QLabel("显示模式:");
m_msg_display_label->setObjectName("m_msg_menu_label");
m_msg_display_combobox->setObjectName("m_msg_display_combobox");
m_msg_copy->setObjectName("m_msg_copy");
m_msg_save->setObjectName("m_msg_save");
@ -54,7 +59,11 @@ void ctai_history_textedit::init_layout(msg_type msg_type_mode)
m_msg_fold->setObjectName("m_msg_fold");
m_msg_fold->setIcon(QIcon(":res/img/btn/btn_info_up.png"));
m_msg_fold->setIconSize(QSize(25, 25));
m_msg_display_combobox->addItem("原始文本");
m_msg_display_combobox->addItem("LaTeX Markdown");
header_opts_Layout->addItem(sparcer_item);
header_opts_Layout->addWidget(m_msg_display_label);
header_opts_Layout->addWidget(m_msg_display_combobox);
header_opts_Layout->addWidget(m_msg_copy);
header_opts_Layout->addWidget(m_msg_save);
header_opts_Layout->addWidget(m_msg_menu);
@ -123,6 +132,7 @@ void ctai_history_textedit::connect_signals(msg_type msg_type_mode)
connect(m_msg_menu, SIGNAL(clicked()), this, SLOT(on_menu_clicked()));
connect(m_msg_fold, SIGNAL(clicked()), this, SLOT(on_fold_clicked()));
connect(m_msg_tokens, SIGNAL(clicked()), this, SLOT(on_tokens_clicked()));
connect(m_msg_display_combobox, SIGNAL(currentTextChanged(QString)), this, SLOT(on_display_changed(QString)));
}
else
{
@ -130,7 +140,19 @@ void ctai_history_textedit::connect_signals(msg_type msg_type_mode)
}
connect(m_msg_history, SIGNAL(textChanged()), this, SLOT(on_sync_text_height()));
}
void ctai_history_textedit::on_display_changed(QString mode)
{
// 根据显示模式设置文本
if (mode == "原始文本")
{
m_msg_history->setText(m_current_content);
}
else if (mode == "LaTeX Markdown")
{
m_msg_history->setMarkdown(m_math_convert->replace_tags_svg(m_current_content));
qDebug()<<"LaTeX Markdown:"<<m_msg_history->toMarkdown();
}
}
// tokens按钮实现功能的槽函数
void ctai_history_textedit::on_tokens_clicked()
{
@ -260,12 +282,10 @@ void ctai_history_textedit::add_user_message(const model_data &message)
disp_data = QSL(message.send_user_data);
m_msg_sned_id = QSL(message.send_user_id);
m_msg_history->setMarkdown(disp_data);
m_math_convert->math_convert_svg(disp_data);
}
void ctai_history_textedit::add_system_message(const model_data &message)
{
QString disp_data;
m_msg_sned_id = QSL(message.postback_send_id);
std::lock_guard<std::mutex> lock(m_mutex);
@ -274,13 +294,13 @@ void ctai_history_textedit::add_system_message(const model_data &message)
// 流式模式下追加内容
m_current_content += message.postback_model_data;
m_msg_history->setMarkdown(m_current_content);
//m_msg_history->setHtml(m_math_convert->replace_tags_svg(m_current_content));
}
else
{
// 非流式模式直接设置全部内容
disp_data = QSL(message.postback_model_data);
m_msg_history->setMarkdown(disp_data);
m_current_content = QSL(message.postback_model_data);
m_msg_history->setMarkdown(m_current_content);
}
}
// 增加tokens信息

View File

@ -22,6 +22,8 @@
#include <QAbstractTextDocumentLayout>
#include <QAbstractScrollArea>
#include <QTimer>
#include <QLabel>
#include <QComboBox>
// Qt对话框
#include <QFileDialog>
#include <QMessageBox>
@ -60,6 +62,7 @@ private slots:
void on_menu_clicked(); // 菜单按钮
void on_fold_clicked(); // 折叠按钮
void on_tokens_clicked(); // tokens按钮
void on_display_changed(QString); // 显示模式改变
void on_save_text(); // 保存文本
void on_save_html(); // 保存html
void on_save_markdown(); // 保存markdown
@ -93,6 +96,8 @@ private:
QPushButton *m_msg_tokens={};
QPushButton *m_history_to_send={};
QPushButton *m_restart_to_send={};
QLabel *m_msg_display_label={};
QComboBox *m_msg_display_combobox={};
//tokens消耗显示
QMenu *m_msg_tokens_menu={};
QAction *m_menu_prompt_tokens={};
@ -117,7 +122,6 @@ private:
//本次信息
QString m_current_content;
QSpacerItem *bottom_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding);
};
#endif // ctai_history_textedit_H

View File

@ -117,6 +117,7 @@ void ctai_history_widget::on_rows_height_changed(bool fold_state)
setRowHeight(row, 87);
resizeRowToContents(row);
}
scrollToBottom();
}
else
{
@ -128,7 +129,6 @@ void ctai_history_widget::on_rows_height_changed(bool fold_state)
resizeRowToContents(row);
}
}
scrollToBottom();
}
void ctai_history_widget::on_msg_remove(QString send_id)

View File

@ -11,7 +11,7 @@ ctai_math_convert::~ctai_math_convert()
void ctai_math_convert::set_convert_opts()
{
}
void ctai_math_convert::save_svg(QPixmap &img, const QString &svg)
void ctai_math_convert::save_svg(QPixmap &img)
{
QString uid = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString svgName = QDir::currentPath() + "/svg/" + uid + ".png";
@ -28,9 +28,9 @@ QByteArray ctai_math_convert::svg_to_base64(QPixmap &pix)
data = data.toBase64();
return data;
}
void ctai_math_convert::math_convert_svg(const QString &latex)
QString ctai_math_convert::math_convert_svg(const QString &math)
{
auto render = tex::LaTeX::parse(latex.toStdWString(), m_width, m_text_size, m_lineSpace, m_color);
auto render = tex::LaTeX::parse(math.toStdWString(), m_width, m_text_size, m_lineSpace, m_color);
qDebug() << render->getWidth() << render->getHeight();
QPixmap pix(render->getWidth(), render->getHeight());
pix.fill(Qt::white);
@ -38,13 +38,81 @@ void ctai_math_convert::math_convert_svg(const QString &latex)
painter.setRenderHint(QPainter::Antialiasing, true);
tex::Graphics2D_qt g2(&painter);
render->draw(g2, 0, 0);
if (save_mode)
{
save_svg(pix, latex);
}
else
{
//输出base64
qDebug() << "svg_to_base64:" << QString::fromUtf8(svg_to_base64(pix));
}
save_svg(pix);
return QString::fromUtf8(svg_to_base64(pix));
}
void ctai_math_convert::debug_latex_match(const QString &text, const QRegularExpressionMatch &match)
{
qDebug() << "=== LaTeX匹配信息 ===";
qDebug() << "匹配位置:" << match.capturedStart() << "-" << match.capturedEnd();
qDebug() << "匹配文本:" << match.captured(0);
qDebug() << "数学内容:" << match.captured(1);
// 显示匹配位置的上下文
int contextSize = 20;
int start = qMax(0, match.capturedStart() - contextSize);
int end = qMin(text.length(), match.capturedEnd() + contextSize);
QString context = text.mid(start, end - start);
qDebug() << "上下文:" << context;
qDebug() << "==================";
}
QString ctai_math_convert::clean_latex_for_mula(const QString &formula)
{
QString result = formula;
// 清理可能的多余空白字符
result = result.trimmed();
// 处理可能的换行符
result.replace(QRegularExpression(R"(\s+)"), " ");
return result;
}
QString ctai_math_convert::replace_tags_svg(const QString &text)
{
//QString result("4566545646\\[\n f'(a) = \\lim_{h \\to 0} \\frac{f(a + h) - f(a)}{h}\n \\]654564646");
QString result=text;
// 使用改进的正则表达式模式
QRegularExpression latexPattern(
QString(R"(\\[(](.*?)\\[)]|\\[{](.*?)\\[}]|\\[[](.*?)\\])"),
QRegularExpression::MultilineOption |
QRegularExpression::DotMatchesEverythingOption);
// 添加调试信息
qDebug() << "输入文本:" << result;
qDebug() << "正则表达式:" << latexPattern.pattern();
qDebug() << "正则表达式是否有效:" << latexPattern.isValid();
QRegularExpressionMatchIterator iterator = latexPattern.globalMatch(result);
//收集所有替换操作
QList<QPair<int, int>> replacements;
QStringList svgResults;
// 定义基础图片标签
const QString html_base = "<img src=\"data:image/png;base64,";
const QString markdown_base = "![math](data:image/png;base64,";
while (iterator.hasNext())
{
QRegularExpressionMatch match = iterator.next();
QString matchedText = match.captured(0);
// 调试输出
debug_latex_match(result, match);
// 清理和转换公式
//QString cleanFormula = clean_latex_for_mula(matchedText);
QString svg_markdown = markdown_base + math_convert_svg(matchedText) + ")";
QString svg_html = html_base + math_convert_svg(matchedText) + "/>";
// 保存替换信息
replacements.prepend({match.capturedStart(), match.capturedLength()});
svgResults.prepend(svg_markdown);
}
// 从后向前执行替换,避免位置改变影响
for (int i = 0; i < replacements.size(); ++i)
{
const auto &rep = replacements[i];
result.replace(rep.first, rep.second, svgResults[i]);
}
return result;
}

View File

@ -9,6 +9,11 @@
#include <QDir>
#include <QBuffer>
#include <QUuid>
#include <QRegularExpression>
#include <QRegularExpressionMatchIterator>
#include "ctai_base.h"
#include <regex>
#include <string>
class TexGuard {
public:
TexGuard() {
@ -26,23 +31,27 @@ class ctai_math_convert :public QObject
public:
explicit ctai_math_convert();
~ctai_math_convert();
// 追加内容并处理Markdown
void math_convert_svg(const QString& text);
QString replace_tags_svg(const QString &text);
void set_convert_opts();
void save_svg(QPixmap& img,const QString& svg);
QByteArray svg_to_base64(QPixmap& pix);
void save_svg(QPixmap& img);
private:
void debug_latex_match(const QString &text, const QRegularExpressionMatch &match);
QString clean_latex_for_mula(const QString &formula);
QString math_convert_svg(const QString& text);
QByteArray svg_to_base64(QPixmap& pix);
TexGuard texGuard;
//宽度
int m_width=600;
int m_width=300;
//文本大小
int m_text_size=20;
int m_text_size=14;
//行距
float m_lineSpace=20 / 3.f;
//颜色
tex::color m_color=0xff424242;
//处理后保存模式
bool save_mode=false;
};
#endif