Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
<PackageReference Include="BootstrapBlazor.Middleware" Version="9.0.0" />
<PackageReference Include="Longbow.Logging" Version="9.0.0" />
<PackageReference Include="Longbow.Tasks" Version="9.0.0" />
<PackageReference Include="Markdig" Version="0.40.0" />
<PackageReference Include="Markdown.ColorCode" Version="3.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
167 changes: 167 additions & 0 deletions src/BootstrapBlazor.Server/Components/Components/MarkdownContent.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
@using Markdig
@using Markdown.ColorCode
@using ColorCode.Styling
@using System.Text.RegularExpressions
@using ColorCode
@using ColorCode.Compilation.Languages
@using Markdown = Markdig.Markdown
@inherits ComponentBase

<div id="@(Id ?? string.Empty)">
@((MarkupString)(AsMarkdown ?? string.Empty))
</div>

<style>
.think {
margin-bottom: 1rem;
font-size: 0.75rem;
font-weight: 400;
color: #A0A0A0;
border-left: 3px solid #dfdbdb;
padding-left: 10px;
}
</style>

@code {
[Parameter]
public required string Content { get; set; }

[Parameter]
public string? Id { get; set; }

public string? AsMarkdown;

string? Embedding { get; set; }

protected override void OnInitialized()
{
markdownPipeline = new MarkdownPipelineBuilder()
.UsePipeTables()
.UseAdvancedExtensions()
.UseColorCode(styleDictionary: StyleDictionary.DefaultLight,
additionalLanguages: new List<ILanguage>()
{
new Json(),
new CSharp(),
new Cpp(),
new Css(),
new Html(),
new JavaScript(),
new Php(),
})
.UseAutoLinks()
.UseEmojiAndSmiley()
.UseMediaLinks()
.UseCitations()
.UseMathematics()
.UseAutoLinks()
.UseDiagrams()
.Build();
}

protected override void OnParametersSet()
{
var embeddingsRemoved = RemoveEmbeddingsElement(Content);
AsMarkdown = GetMarkdown(embeddingsRemoved);
StateHasChanged();
}

private MarkdownPipeline? markdownPipeline;

private string GetMarkdown(string toHtml)
{
try
{
if (string.IsNullOrEmpty(toHtml))
return string.Empty;

// 处理未封闭的 think 标签
toHtml = HandleUnclosedThinkTags(toHtml);

// 处理正常的 think 标签
var thinkPattern = @"<\s*think\b[^>]*>(.*?)<\s*/\s*think\s*>";
toHtml = Regex.Replace(toHtml, thinkPattern, @"<div class=""think"">$1</div>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
toHtml = RemoveEmbeddingsElement(toHtml);

var html = Markdown.ToHtml(toHtml, markdownPipeline);

var pattern = "(<div style=\"color:#DADADA;background-color:#1E1E1E;\"><pre>(.*?)</pre></div>)";
var matches = Regex.Matches(html, pattern, RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase);

for (var i = matches.Count - 1; i >= 0; i--)
{
var match = matches[i].ToString();
var id = "copy" + i;
var replacement = $"<button data-clipboard-target=\"#{id}\" class=\"float-end copyBtn mt-0\">Copy</button>" + match;
html = html.Remove(matches[i].Index, matches[i].Length).Insert(matches[i].Index, replacement);
}

return html;
}
catch (Exception)
{
return "error markdowncontent.razor";
}
}

private string HandleUnclosedThinkTags(string content)
{
if (string.IsNullOrEmpty(content))
return content;

// 匹配开始标签
var openTagPattern = @"<\s*think\b[^>]*>";
var closeTagPattern = @"<\s*/\s*think\s*>";

var openTags = Regex.Matches(content, openTagPattern);
var closeTags = Regex.Matches(content, closeTagPattern);

// 如果开始标签数量等于结束标签数量,说明标签都是配对的
if (openTags.Count == closeTags.Count)
return content;

// 处理未封闭的标签
var parts = Regex.Split(content, openTagPattern);
if (parts.Length <= 1)
return content;

var result = parts[0]; // 保留第一部分的内容
for (int i = 1; i < parts.Length; i++)
{
var part = parts[i];
// 检查这部分是否已经包含结束标签
if (!part.Contains("</think>", StringComparison.OrdinalIgnoreCase))
{
// 没有结束标签,添加一个完整的 think 标签包装
result += $"<think>{part}</think>";
}
else
{
// 已经有结束标签,保持原样
result += $"<think>{part}";
}
}

return result;
}

private string RemoveEmbeddingsElement(string data)
{
if (string.IsNullOrEmpty(data))
{
return "";
}
string pattern = @"\[EMBEDDINGS\](.*?)\[/EMBEDDINGS\]";
var matches = Regex.Matches(data, pattern, RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);

if (matches.Count == 0)
{
return data;
}

data = Regex.Replace(data, pattern, "", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);

Embedding = $"{matches[0].Groups[1].Value}";
return data;
}
}
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/Components/Pages/Chats.razor
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
</div>
<div class="msg-body">
<div class="msg-time">@message.Time.ToString("HH:mm:ss")</div>
<div>@message.Content</div>
@* <div>@message.Content</div> *@
<MarkdownContent Content="@message.Content" Id="mdcontent" />
@if (message.Role == ChatRole.Assistant)
{
<div class="msg-desc">AI-generated content may be incorrect</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public partial class Markdowns
private string? AsyncValue { get; set; }

[NotNull]
private Markdown? MarkdownSetValue { get; set; }
private BootstrapBlazor.Components.Markdown? MarkdownSetValue { get; set; }

[NotNull]
private Markdown? Markdown { get; set; }
private BootstrapBlazor.Components.Markdown? Markdown { get; set; }

private string JsString { get; set; } = @"```js
console.log('test');
Expand Down