Skip to content

Commit 1dad527

Browse files
committed
Still WIP but added:
- Mermaid support for diagrams on doc ai-server doc pages. - Lots of includes for ai-server. - Fixed AI server menu - Still more usage examples to do.
1 parent 807c117 commit 1dad527

32 files changed

+762
-432
lines changed

MyApp/MarkdownPagesBase.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,55 @@ protected override void Write(HtmlRenderer renderer, CustomContainer obj)
614614
}
615615
}
616616

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+
617666
public class IncludeContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
618667
{
619668
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
@@ -797,6 +846,7 @@ public void AddBuiltInContainers(string[]? exclude = null)
797846
},
798847
["pre"] = new PreContainerRenderer(),
799848
["youtube"] = new YouTubeContainerRenderer(),
849+
["mermaid"] = new MermaidContainerRenderer(),
800850
};
801851
InlineContainers = new()
802852
{

MyApp/Pages/AiServer.cshtml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
ctx.Resolve<MarkdownPages>().GetVisiblePages("ai-server", allDirectories:true).Map(page => new Page { Slug = page.Slug.RightPart('/') });
99
}
1010

11+
@await Html.PartialAsync("MermaidIncludes")
12+
1113
@await Html.PartialAsync("DocsPage", new Shared.DocsPage {
1214
Brand = "AI Server",
1315
Slug = Model.Slug,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script type="module">
2+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
3+
mermaid.initialize({ startOnLoad: true });
4+
</script>
5+
<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>

MyApp/_includes/ai-server/cs/ai-server-compatible-1.cs.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
var apiClient = GetLocalApiClient("https://localhost:5005");
33
apiClient.BearerToken = Environment.GetEnvironmentVariable("AI_SERVER_API_KEY");
44

5-
var request = new CreateOpenAiChatCompletion {
5+
var request = new OpenAiChatCompletion() {
66
Model = "llama3:8b",
77
Messages = new List<OpenAiMessage>
88
{
9-
new OpenAiMessage { Role = "system", Content = "You are a helpful AI assistant." },
10-
new OpenAiMessage { Role = "user", Content = "How do LLMs work?" }
9+
new() { Role = "system", Content = "You are a helpful AI assistant." },
10+
new() { Role = "user", Content = "How do LLMs work?" }
1111
},
1212
MaxTokens = 50
1313
};
1414

15-
var response = await apiClient.PostAsync(request);
16-
var openAiResponse = response; // The same
17-
Console.WriteLine(openAiResponse.Choices[0].Message.Content);
15+
var response = await apiClient.ApiAsync(request);
16+
response.ThrowIfError();
17+
var openAiResponse = response; // compatible with openai response schema
18+
Console.WriteLine(openAiResponse.Response.Choices[0].Message.Content);
1819
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
```csharp
2+
// Using OpenAI API directly via JsonApiClient
3+
var client = new JsonApiClient("https://api.openai.com/v1");
4+
client.AddHeader("Authorization", "Bearer " + Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
5+
6+
// Using AI Server DTOs with OpenAI API
7+
var request = new OpenAiChat {
8+
Model = "gpt-4-turbo",
9+
Messages = new List<OpenAiMessage> {
10+
new OpenAiMessage { Role = "system", Content = "You are a helpful AI assistant." },
11+
new OpenAiMessage { Role = "user", Content = "How do LLMs work?" }
12+
},
13+
MaxTokens = 50
14+
};
15+
16+
// Typed response DTO
17+
var response = await client.PostAsync<OpenAiChatResponse>("/chat/completions", request);
18+
```
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
```csharp
2+
var client = GetLocalApiClient("https://localhost:5005");
3+
client.BearerToken = Environment.GetEnvironmentVariable("AI_SERVER_API_KEY");
4+
5+
var request = new QueueOpenAiChatCompletion
6+
{
7+
Request = new()
8+
{
9+
Model = "gpt-4-turbo",
10+
Messages = new List<OpenAiMessage>
11+
{
12+
new() { Role = "system", Content = "You are a helpful AI assistant." },
13+
new() { Role = "user", Content = "How do LLMs work?" }
14+
},
15+
MaxTokens = 50
16+
}
17+
};
18+
19+
var response = await client.ApiAsync(request);
20+
response.ThrowIfError();
21+
// Response only returns the related job information
22+
Console.WriteLine($"RefId: {response.Response.RefId}, JobId: {response.Response.Id}");
23+
```

MyApp/_includes/ai-server/cs/ai-server-raw-1.cs.md

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
```csharp
2+
var request = new ConvertVideo()
3+
{
4+
Format = "mp4"
5+
};
6+
7+
var response = client.PostFilesWithRequest<MediaTransformResponse>(
8+
request,
9+
[new UploadFile("video", File.OpenRead("video.avi"), "video.avi")]
10+
);
11+
12+
var videoUrl = response.Outputs[0].Url;
13+
videoUrl.DownloadFileTo("converted-video.mp4");
14+
```
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
```csharp
2-
var request = new CropImage()
3-
{
4-
X = 120,
5-
Y = 120,
6-
Width = 720,
7-
Height = 720
8-
};
2+
var client = CreateClient(); // JsonApiClient
93
10-
// Returns the cropped image directly as a Stream
11-
var response = client.PostFilesWithRequest<Stream>(
12-
request,
13-
[new UploadFile("image", File.OpenRead("image.jpg"), "image.jpg")]
14-
);
4+
using var imageStream = File.OpenRead("files/comfyui_upload_test.png");
5+
var response = client.PostFilesWithRequest<MediaTransformResponse>(new CropImage
6+
{
7+
X = 10,
8+
Y = 10,
9+
Width = 100,
10+
Height = 100
11+
}, [
12+
new UploadFile("image.png", imageStream) { FieldName = "image" }
13+
]);
1514

16-
File.WriteAllBytes("result.jpg",response.ReadFully());
15+
var croppedImageUrl = response.Outputs[0].Url;
16+
croppedImageUrl.DownloadFileTo("files/comfyui_upload_test_cropped.png");
1717
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
```csharp
2+
var request = new CropVideo()
3+
{
4+
X = 100,
5+
Y = 100,
6+
Width = 500,
7+
Height = 300
8+
};
9+
10+
var response = client.PostFilesWithRequest<MediaTransformResponse>(
11+
request,
12+
[new UploadFile("test_video.mp4", File.OpenRead("files/test_video.mp4"), "video")]
13+
);
14+
15+
var videoUrl = response.Outputs[0].Url;
16+
videoUrl.DownloadFileTo("cropped-video.mp4");
17+
```

MyApp/_includes/ai-server/cs/open-ai-requests-1.cs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
```csharp
22
var client = new JsonApiClient("https://api.openai.com/v1");
3-
client.AddHeader("Authorization", "Bearer " + Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
3+
client.AddHeader("Authorization", "Bearer " +
4+
Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
45

56
// Using AI Server DTOs with OpenAI API
6-
var request = new OpenAiChat {
7+
var request = new OpenAiChatCompletion {
78
Model = "gpt-4-turbo",
89
Messages = new List<OpenAiMessage> {
910
new OpenAiMessage { Role = "system", Content = "You are a helpful AI assistant." },
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
```csharp
2+
var request = new QueueConvertVideo()
3+
{
4+
Format = "mp4",
5+
ReplyTo = "https://example.com/my/reply/endpoint"
6+
};
7+
8+
var response = client.PostFilesWithRequest<QueueMediaTransformResponse>(
9+
request,
10+
[new UploadFile("video", File.OpenRead("video.avi"), "video.avi")]
11+
);
12+
13+
var status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
14+
var timeout = DateTime.UtcNow.AddMinutes(5);
15+
while (status.JobState is not BackgroundJobState.Completed &&
16+
DateTime.UtcNow < timeout)
17+
{
18+
await Task.Delay(1000);
19+
status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
20+
}
21+
22+
// Download the converted video
23+
var videoUrl = status.Outputs[0].Url;
24+
videoUrl.DownloadFileTo("converted-video.mp4");
25+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
```csharp
2+
var request = new QueueCropVideo()
3+
{
4+
X = 100,
5+
Y = 100,
6+
Width = 500,
7+
Height = 300
8+
};
9+
10+
var response = client.PostFilesWithRequest<QueueMediaTransformResponse>(
11+
request,
12+
[new UploadFile("test_video.mp4", File.OpenRead("files/test_video.mp4"), "video")]
13+
);
14+
15+
var status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
16+
while (status.JobState is BackgroundJobState.Started or BackgroundJobState.Queued)
17+
{
18+
await Task.Delay(1000);
19+
status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
20+
}
21+
22+
// Download the cropped video
23+
var videoUrl = status.Outputs[0].Url;
24+
videoUrl.DownloadFileTo($"cropped-video-{status.RefId}.mp4");
25+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
```csharp
2+
var request = new QueueScaleVideo()
3+
{
4+
Width = 1280,
5+
Height = 720,
6+
ReplyTo = "https://example.com/my/reply/endpoint" // optional
7+
};
8+
9+
var response = client.PostFilesWithRequest<QueueMediaTransformResponse>(
10+
request,
11+
[new UploadFile("test_video.mp4", File.OpenRead("files/test_video.mp4"), "video")]
12+
);
13+
14+
var status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
15+
while (status.JobState is BackgroundJobState.Started or BackgroundJobState.Queued)
16+
{
17+
await Task.Delay(1000);
18+
status = await client.GetAsync(new GetJobStatus { RefId = response.RefId });
19+
}
20+
21+
// Download the scaled video
22+
var videoUrl = status.Outputs[0].Url;
23+
videoUrl.DownloadFileTo($"scaled-video-{status.RefId}.mp4");
24+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
```csharp
2+
var request = new ScaleVideo()
3+
{
4+
Width = 1280,
5+
Height = 720,
6+
};
7+
8+
var response = client.PostFilesWithRequest<MediaTransformResponse>(
9+
request,
10+
[new UploadFile("test_video.mp4", File.OpenRead("files/test_video.mp4"), "video")]
11+
);
12+
13+
var videoUrl = response.Outputs[0].Url;
14+
videoUrl.DownloadFileTo($"scaled-video.mp4");
15+
```

0 commit comments

Comments
 (0)