From 1f05bc31971a0e85db9ec517a73cfad88847a9c6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 21 Mar 2026 18:50:28 +0100 Subject: [PATCH] Added init math rendering --- .../Chat/ContentBlockComponent.razor | 13 ++- .../Chat/ContentBlockComponent.razor.cs | 100 ++++++++++++++++++ .../Chat/MathJaxBlock.razor | 5 + .../Chat/MathJaxBlock.razor.cs | 30 ++++++ app/MindWork AI Studio/wwwroot/app.css | 17 ++- 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 app/MindWork AI Studio/Chat/MathJaxBlock.razor create mode 100644 app/MindWork AI Studio/Chat/MathJaxBlock.razor.cs diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor index 579e8bf2..17158e45 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor @@ -96,7 +96,18 @@ } else { - + var renderSegments = GetMarkdownRenderSegments(textContent.Text); + foreach (var segment in renderSegments) + { + if (segment.Type is MarkdownRenderSegmentType.MARKDOWN) + { + + } + else + { + + } + } @if (textContent.Sources.Count > 0) { diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 29e70487..f2036774 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -2,6 +2,7 @@ using AIStudio.Components; using AIStudio.Dialogs; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; +using System.Text; namespace AIStudio.Chat; @@ -194,6 +195,72 @@ public partial class ContentBlockComponent : MSGComponentBase CodeBlock = { Theme = this.CodeColorPalette }, }; + private static IReadOnlyList GetMarkdownRenderSegments(string text) + { + var normalized = NormalizeMarkdownForRendering(text); + if (string.IsNullOrWhiteSpace(normalized)) + return []; + + var normalizedWithUnixLineEndings = normalized.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n'); + var lines = normalizedWithUnixLineEndings.Split('\n'); + var markdownBuilder = new StringBuilder(); + var mathBuilder = new StringBuilder(); + var segments = new List(); + string? activeCodeFenceMarker = null; + var inMathBlock = false; + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + + if (!inMathBlock && TryUpdateCodeFenceState(trimmedLine, ref activeCodeFenceMarker)) + { + AppendLine(markdownBuilder, line); + continue; + } + + if (activeCodeFenceMarker is not null) + { + AppendLine(markdownBuilder, line); + continue; + } + + if (trimmedLine == "$$") + { + if (inMathBlock) + { + segments.Add(new(MarkdownRenderSegmentType.MATH_BLOCK, mathBuilder.ToString().Trim('\r', '\n'))); + mathBuilder.Clear(); + inMathBlock = false; + } + else + { + FlushMarkdownSegment(); + inMathBlock = true; + } + + continue; + } + + AppendLine(inMathBlock ? mathBuilder : markdownBuilder, line); + } + + if (inMathBlock) + return [new(MarkdownRenderSegmentType.MARKDOWN, normalized)]; + + FlushMarkdownSegment(); + return segments.Count > 0 ? segments : [new(MarkdownRenderSegmentType.MARKDOWN, normalized)]; + + void FlushMarkdownSegment() + { + if (markdownBuilder.Length == 0) + return; + + segments.Add(new(MarkdownRenderSegmentType.MARKDOWN, markdownBuilder.ToString())); + markdownBuilder.Clear(); + } + } + private static string NormalizeMarkdownForRendering(string text) { var cleaned = text.RemoveThinkTags().Trim(); @@ -221,6 +288,39 @@ public partial class ContentBlockComponent : MSGComponentBase return content.Contains("", StringComparison.Ordinal); } + + private static bool TryUpdateCodeFenceState(string trimmedLine, ref string? activeCodeFenceMarker) + { + string? fenceMarker = null; + if (trimmedLine.StartsWith("```", StringComparison.Ordinal)) + fenceMarker = "```"; + else if (trimmedLine.StartsWith("~~~", StringComparison.Ordinal)) + fenceMarker = "~~~"; + + if (fenceMarker is null) + return false; + + activeCodeFenceMarker = activeCodeFenceMarker is null + ? fenceMarker + : activeCodeFenceMarker == fenceMarker + ? null + : activeCodeFenceMarker; + + return true; + } + + private static void AppendLine(StringBuilder builder, string line) + { + builder.AppendLine(line); + } + + private enum MarkdownRenderSegmentType + { + MARKDOWN, + MATH_BLOCK, + } + + private readonly record struct MarkdownRenderSegment(MarkdownRenderSegmentType Type, string Content); private async Task RemoveBlock() { diff --git a/app/MindWork AI Studio/Chat/MathJaxBlock.razor b/app/MindWork AI Studio/Chat/MathJaxBlock.razor new file mode 100644 index 00000000..4e6c0ba2 --- /dev/null +++ b/app/MindWork AI Studio/Chat/MathJaxBlock.razor @@ -0,0 +1,5 @@ +@namespace AIStudio.Chat + +
+ @this.MathText +
\ No newline at end of file diff --git a/app/MindWork AI Studio/Chat/MathJaxBlock.razor.cs b/app/MindWork AI Studio/Chat/MathJaxBlock.razor.cs new file mode 100644 index 00000000..7f59e874 --- /dev/null +++ b/app/MindWork AI Studio/Chat/MathJaxBlock.razor.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Chat; + +public partial class MathJaxBlock +{ + private const string MATH_JAX_SCRIPT_ID = "mudblazor-markdown-mathjax"; + + [Parameter] + public string Value { get; init; } = string.Empty; + + [Parameter] + public string Class { get; init; } = string.Empty; + + [Inject] + private IJSRuntime JsRuntime { get; init; } = null!; + + private string RootClass => string.IsNullOrWhiteSpace(this.Class) + ? "chat-mathjax-block" + : $"chat-mathjax-block {this.Class}"; + + private string MathText => $"$${Environment.NewLine}{this.Value}{Environment.NewLine}$$"; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await this.JsRuntime.InvokeVoidAsync("appendMathJaxScript", MATH_JAX_SCRIPT_ID); + await this.JsRuntime.InvokeVoidAsync("refreshMathJaxScript"); + await base.OnAfterRenderAsync(firstRender); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/app.css b/app/MindWork AI Studio/wwwroot/app.css index cd80c5a9..909d350d 100644 --- a/app/MindWork AI Studio/wwwroot/app.css +++ b/app/MindWork AI Studio/wwwroot/app.css @@ -150,4 +150,19 @@ .sources-card-header { top: 0em !important; left: 2.2em !important; -} \ No newline at end of file +} + +.chat-mathjax-block { + text-align: left; +} + +.chat-mathjax-block mjx-container[display="true"] { + text-align: left !important; + margin-left: 0 !important; + margin-right: 0 !important; +} + +.chat-mathjax-block mjx-container[display="true"] mjx-math { + margin-left: 0 !important; + margin-right: 0 !important; +}