Skip to content

Commit 7ea7be0

Browse files
committed
Replace MermaidContainerRenderer with mermaid fences + MermaidBlockRenderer
1 parent 1c4d8a0 commit 7ea7be0

File tree

5 files changed

+115
-91
lines changed

5 files changed

+115
-91
lines changed

MyApp/MarkdownPagesBase.cs

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Markdig.Extensions.CustomContainers;
1212
using ServiceStack.IO;
1313
using ServiceStack.Text;
14+
using Markdig.Extensions.Diagrams;
1415

1516
namespace MyApp;
1617

@@ -112,6 +113,7 @@ public virtual MarkdownPipeline CreatePipeline()
112113
.UseAdvancedExtensions()
113114
.UseAutoLinkHeadings()
114115
.UseHeadingsMap()
116+
.UseCustomDiagramExtension()
115117
.UseCustomContainers(MarkdigConfig.Instance.ConfigureContainers);
116118
MarkdigConfig.Instance.ConfigurePipeline?.Invoke(builder);
117119

@@ -374,6 +376,45 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
374376
}
375377
}
376378

379+
public class MermaidBlockRenderer(CodeBlockRenderer? underlyingRenderer = null) : HtmlObjectRenderer<CodeBlock>
380+
{
381+
private readonly CodeBlockRenderer underlyingRenderer = underlyingRenderer ?? new CodeBlockRenderer();
382+
protected override void Write(HtmlRenderer renderer, CodeBlock obj)
383+
{
384+
if (obj is not FencedCodeBlock fencedCodeBlock || obj.Parser is not FencedCodeBlockParser parser)
385+
{
386+
underlyingRenderer.Write(renderer, obj);
387+
return;
388+
}
389+
390+
var attributes = obj.TryGetAttributes() ?? new HtmlAttributes();
391+
attributes.AddClass("mermaid not-prose");
392+
var txt = GetContent(obj);
393+
renderer
394+
.Write("<pre")
395+
.WriteAttributes(attributes)
396+
.Write(">")
397+
.Write(txt)
398+
.WriteLine("</pre>");
399+
}
400+
private static string GetContent(LeafBlock obj)
401+
{
402+
var code = new StringBuilder();
403+
foreach (var line in obj.Lines.Lines)
404+
{
405+
var slice = line.Slice;
406+
if (slice.Text == null)
407+
continue;
408+
409+
var lineText = slice.Text.Substring(slice.Start, slice.Length);
410+
code.AppendLine();
411+
code.Append(lineText);
412+
}
413+
414+
return code.ToString();
415+
}
416+
}
417+
377418
public class FilesCodeBlockRenderer(CodeBlockRenderer? underlyingRenderer = null) : HtmlObjectRenderer<CodeBlock>
378419
{
379420
private readonly CodeBlockRenderer underlyingRenderer = underlyingRenderer ?? new CodeBlockRenderer();
@@ -614,55 +655,6 @@ protected override void Write(HtmlRenderer renderer, CustomContainer obj)
614655
}
615656
}
616657

617-
public class MermaidContainerRenderer : HtmlObjectRenderer<CustomContainer>
618-
{
619-
protected override void Write(HtmlRenderer renderer, CustomContainer obj)
620-
{
621-
renderer.EnsureLine();
622-
if (renderer.EnableHtmlForBlock)
623-
{
624-
renderer.Write("<div class=\"mermaid-diagram\">");
625-
renderer.WriteLine("<pre class=\"mermaid\">");
626-
}
627-
628-
// Write the Mermaid diagram content
629-
if (obj.FirstOrDefault() is LeafBlock leafBlock)
630-
{
631-
// There has to be an official API to resolve the original text from a renderer?
632-
string? FindOriginalText(ContainerBlock? block)
633-
{
634-
if (block != null)
635-
{
636-
if (block.FirstOrDefault(x => x is LeafBlock { Lines.Count: > 0 }) is LeafBlock first)
637-
return first.Lines.Lines[0].Slice.Text;
638-
return FindOriginalText(block.Parent);
639-
}
640-
641-
return null;
642-
}
643-
644-
var originalSource = leafBlock.Lines.Count > 0
645-
? leafBlock.Lines.Lines[0].Slice.Text
646-
: FindOriginalText(obj.Parent);
647-
if (originalSource == null)
648-
{
649-
HostContext.Resolve<ILogger<PreContainerRenderer>>().LogError("Could not find original Text");
650-
renderer.WriteLine($"Could not find original Text");
651-
}
652-
else
653-
{
654-
renderer.WriteEscape(originalSource.AsSpan().Slice(leafBlock.Span.Start, leafBlock.Span.Length));
655-
}
656-
}
657-
658-
if (renderer.EnableHtmlForBlock)
659-
{
660-
renderer.WriteLine("</pre>");
661-
renderer.WriteLine("</div>");
662-
}
663-
}
664-
}
665-
666658
public class IncludeContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
667659
{
668660
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
@@ -812,7 +804,8 @@ public void AddBuiltInContainers(string[]? exclude = null)
812804
{
813805
CodeBlocks = new()
814806
{
815-
["files"] = origRenderer => new FilesCodeBlockRenderer(origRenderer)
807+
["files"] = origRenderer => new FilesCodeBlockRenderer(origRenderer),
808+
["mermaid"] = origRenderer => new MermaidBlockRenderer(origRenderer),
816809
};
817810
BlockContainers = new()
818811
{
@@ -846,7 +839,6 @@ public void AddBuiltInContainers(string[]? exclude = null)
846839
},
847840
["pre"] = new PreContainerRenderer(),
848841
["youtube"] = new YouTubeContainerRenderer(),
849-
["mermaid"] = new MermaidContainerRenderer(),
850842
};
851843
InlineContainers = new()
852844
{
@@ -915,6 +907,24 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
915907
}
916908
}
917909

910+
public class CustomDiagramExtension : IMarkdownExtension
911+
{
912+
public void Setup(MarkdownPipelineBuilder pipeline)
913+
{
914+
}
915+
916+
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
917+
{
918+
if (renderer is HtmlRenderer htmlRenderer)
919+
{
920+
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>()!;
921+
// TODO: Add other well known diagram languages
922+
//codeRenderer.BlocksAsDiv.Add("mermaid");
923+
codeRenderer.BlocksAsDiv.Add("nomnoml");
924+
}
925+
}
926+
}
927+
918928
public class HeadingsMapExtension : IMarkdownExtension
919929
{
920930
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -1004,6 +1014,12 @@ public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineB
10041014
pipeline.Extensions.AddIfNotAlready(ext);
10051015
return pipeline;
10061016
}
1017+
1018+
public static MarkdownPipelineBuilder UseCustomDiagramExtension(this MarkdownPipelineBuilder pipeline)
1019+
{
1020+
pipeline.Extensions.ReplaceOrAdd<DiagramExtension>(new CustomDiagramExtension());
1021+
return pipeline;
1022+
}
10071023
}
10081024

10091025
public class DocumentMap

MyApp/MyApp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</Target>
2222

2323
<ItemGroup>
24-
<PackageReference Include="Markdig" Version="0.33.*" />
24+
<PackageReference Include="Markdig" Version="0.37.*" />
2525
<PackageReference Include="ServiceStack" Version="8.*" />
2626
<PackageReference Include="ServiceStack.Mvc" Version="8.*" />
2727

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,35 @@
11
<script type="module">
22
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
3-
mermaid.initialize({ startOnLoad: true });
3+
mermaid.initialize({
4+
startOnLoad: true,
5+
theme: 'base',
6+
'themeVariables': {
7+
'primaryColor': '#fff',
8+
'lineColor': '#333',
9+
'primaryBorderColor': 'rgb(67 56 202)', // text-indigo-700
10+
'secondaryColor': '#fff',
11+
'primaryTextColor': '#333',
12+
'tertiaryColor': '#333'
13+
}
14+
});
415
</script>
516
<style>
6-
.mermaid-diagram {
7-
margin-top: 1rem;
8-
margin-bottom: 1rem;
9-
padding: 1rem;
10-
background-color: #f3f4f6;
11-
border-radius: 0.5rem;
12-
overflow-x: auto;
13-
}
14-
.mermaid-diagram pre.mermaid {
15-
font-size: 0.875rem;
16-
background-color: #f3f4f6 !important;
17-
}
18-
/* Ensure text color remains visible in both light and dark modes */
19-
.mermaid-diagram pre.mermaid,
20-
.mermaid-diagram pre.mermaid * {
21-
color: #333 !important;
22-
}
23-
/* Specifically target SVG text elements */
24-
.mermaid-diagram pre.mermaid svg text {
25-
fill: #333 !important;
26-
}
27-
/* Ensure edge labels remain visible */
28-
.mermaid-diagram pre.mermaid .edgeLabel {
29-
background-color: #f3f4f6;
30-
color: #333;
31-
}
32-
/* Override any Tailwind dark mode styles */
33-
@@media (prefers-color-scheme: dark) {
34-
.mermaid-diagram pre.mermaid,
35-
.mermaid-diagram pre.mermaid *,
36-
.mermaid-diagram pre.mermaid svg text,
37-
.mermaid-diagram pre.mermaid .edgeLabel {
38-
color: #333 !important;
39-
}
40-
}
41-
</style>
17+
pre.mermaid {
18+
background-color: #fff !important;
19+
}
20+
.dark pre.mermaid {
21+
background-color: #000 !important; /*bg-gray-800*/
22+
}
23+
.dark pre.mermaid .edgeLabel, .dark pre.mermaid .edgeLabel p, .dark pre.mermad .labelBkg {
24+
background-color: #000 !important;
25+
}
26+
.dark pre.mermaid .edgeLabel, .dark pre.mermaid .edgeLabel p {
27+
color: rgb(209 213 219) !important;
28+
}
29+
.dark pre.mermaid .flowchart-link {
30+
stroke: rgb(209 213 219) !important;
31+
}
32+
.dark pre.mermaid .node rect {
33+
stroke: #fff !important;
34+
}
35+
</style>

MyApp/_pages/ai-server/index.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ description: Introduction to AI Server and its key features
55

66
AI Server is a way to orchestrate your AI requests through a single self-hosted application to control what AI Providers you use without impacting your client integrations. It serves as a private gateway to process LLM, AI, and image transformation requests, dynamically delegating tasks across multiple providers including Ollama, OpenRouter, Replicate, Comfy UI, utilizing models like Whisper, SDXL, Flux, and tools like ffmpeg.
77

8+
```mermaid
9+
flowchart TB
10+
A[AI Server]
11+
A --> D{LLM APIs}
12+
A --> C{Ollama}
13+
A --> E{Media APIs}
14+
A --> F{Comfy UI
15+
+
16+
FFmpeg}
17+
D --> D1[OpenRouter / OpenAI / Mistral / Anthropic / Google Cloud / Groq]
18+
E --> E1[Replicate / dall-e-3 / Text to speech]
19+
F --> F1[Diffusion / Whisper / TTS]
20+
```
21+
822
## Key Features
923

1024
- **Unified AI Gateway**: Centralize all your AI requests through a single self-hosted service.

MyApp/_pages/ai-server/usage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Media endpoints are used for processing images, and videos. Videos are processed
108108
The AI Server is designed to be a lite-weight router for AI services, providing a common interface for AI services to be accessed via APIs with typed client support in many languages.
109109
As such, heavy processing tasks are offloaded to other services, including self-hosted ones like the ComfyUI Agent.
110110

111-
:::mermaid
111+
```mermaid
112112
graph TD
113113
A[API Client] -->|API Request| B(<img class="w-24 h-24" src="/img/logo.svg"/>)
114114
B -->|API Request| C[Replicate API]
@@ -122,4 +122,4 @@ graph TD
122122
E -->|AI Processing| L[Flux.1.Schnell]
123123
B -->|Image Processing| H[AI Server]
124124
B -->|AI Processing| E[ComfyUI Agent]
125-
:::
125+
```

0 commit comments

Comments
 (0)