From dc8bfce62e03a884b318655d2fe4d21d95a4369d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:36:40 +0200 Subject: [PATCH] ai: fix latex rendering --- .../quickshell/modules/common/Directories.qml | 2 +- .../quickshell/modules/sidebarLeft/AiChat.qml | 6 ++--- .../modules/sidebarLeft/aiChat/AiMessage.qml | 22 ++++++++++------- .../sidebarLeft/aiChat/MessageTextBlock.qml | 19 +++++++++++---- .config/quickshell/services/LatexRenderer.qml | 24 ++++++++++++------- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 9e3e3000f..b256666fc 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -21,7 +21,7 @@ Singleton { property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`) property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️") - property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`) + property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`) property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`) property string shellConfigName: "config.json" property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}` diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index b78ff89ce..ecb8628be 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -125,9 +125,9 @@ int main(int argc, char* argv[]) { ### LaTeX -- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$ -- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ -- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\] +- Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$ +- Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ +- Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\] `, Ai.interfaceRole); } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index e115338ad..775d12082 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -28,6 +28,8 @@ Rectangle { property bool renderMarkdown: true property bool editing: false + property list messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content) + anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 @@ -246,23 +248,25 @@ Rectangle { spacing: 0 Repeater { model: ScriptModel { - values: StringUtils.splitMarkdownBlocks(root.messageData?.content) + values: root.messageBlocks.map((block, index) => index) } delegate: Loader { + required property int index + property var thisBlock: root.messageBlocks[index] Layout.fillWidth: true - // property var segment: modelData - property var segmentContent: modelData.content - property var segmentLang: modelData.lang + // property var segment: thisBlock + property var segmentContent: thisBlock.content + property var segmentLang: thisBlock.lang property var messageData: root.messageData property var editing: root.editing property var renderMarkdown: root.renderMarkdown property var enableMouseSelection: root.enableMouseSelection property bool thinking: root.messageData?.thinking ?? true property bool done: root.messageData?.done ?? false - property bool completed: modelData.completed ?? false + property bool completed: thisBlock.completed ?? false - source: modelData.type === "code" ? "MessageCodeBlock.qml" : - modelData.type === "think" ? "MessageThinkBlock.qml" : + source: thisBlock.type === "code" ? "MessageCodeBlock.qml" : + thisBlock.type === "think" ? "MessageThinkBlock.qml" : "MessageTextBlock.qml" } @@ -277,7 +281,9 @@ Rectangle { Layout.alignment: Qt.AlignLeft Repeater { - model: root.messageData?.annotationSources + model: ScriptModel { + values: root.messageData?.annotationSources || [] + } delegate: AnnotationSourceButton { id: annotationButton displayText: modelData.text diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 87087c6b9..bc7f93df1 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -30,6 +30,18 @@ ColumnLayout { Layout.fillWidth: true + Timer { + id: renderTimer + interval: 1000 + repeat: true + onTriggered: { + renderLatex() + for (const hash of renderedLatexHashes) { + handleRenderedLatex(hash, true); + } + } + } + function renderLatex() { // Regex for $...$, $$...$$, \[...\] // Note: This is a simple approach and may need refinement for edge cases @@ -53,16 +65,13 @@ ColumnLayout { const imagePath = LatexRenderer.renderedImagePaths[hash]; const markdownImage = `![latex](${imagePath})`; - const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]); + const expression = LatexRenderer.processedExpressions[hash]; renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage); } } onDoneChanged: { - renderLatex() - for (const hash of renderedLatexHashes) { - handleRenderedLatex(hash, true); - } + renderTimer.restart(); } onEditingChanged: { if (!editing) { diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index d3b9ade1f..e7066fa4c 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -25,7 +25,8 @@ Singleton { property list processedHashes: [] property var processedExpressions: ({}) property var renderedImagePaths: ({}) - property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX") + property string microtexBinaryDir: "/opt/MicroTeX" + property string microtexBinaryName: "LaTeX" property string latexOutputPath: Directories.latexOutput signal renderFinished(string hash, string imagePath) @@ -51,23 +52,28 @@ Singleton { } // 3. If not, render it with MicroTeX and mark as processed + // console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`) + // console.log(` to file: ${imagePath}`) + // console.log(` with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`) const processQml = ` import Quickshell.Io Process { id: microtexProcess${hash} running: true - command: [ "${microtexBinaryPath}", "-headless", - "-input=${StringUtils.escapeBackslashes(expression)}", - "-output=${imagePath}", - "-textsize=${Appearance.font.pixelSize.normal}", - "-padding=${renderPadding}", - "-background=${Appearance.m3colors.m3tertiary}", - "-foreground=${Appearance.m3colors.m3onTertiary}", - "-maxwidth=0.85" ] + command: [ "bash", "-c", + "cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' " + + "'-output=${imagePath}' " + + "'-textsize=${Appearance.font.pixelSize.normal}' " + + "'-padding=${renderPadding}' " + // + "'-background=${Appearance.m3colors.m3tertiary}' " + + "'-foreground=${Appearance.colors.colOnLayer1}' " + + "-maxwidth=0.85 " + ] // stdout: SplitParser { // onRead: data => { console.log("MicroTeX: " + data) } // } onExited: (exitCode, exitStatus) => { + // console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus) renderedImagePaths["${hash}"] = "${imagePath}" root.renderFinished("${hash}", "${imagePath}") microtexProcess${hash}.destroy()