Skip to content

Commit 02a9b29

Browse files
authored
feat(ChatBot): add MarkdownContent component (#399)
1 parent 1e3c0b4 commit 02a9b29

5 files changed

Lines changed: 220 additions & 0 deletions

File tree

BootstrapBlazor.Extensions.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverSheet
180180
EndProject
181181
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverIcon", "src\components\BootstrapBlazor.UniverIcon\BootstrapBlazor.UniverIcon.csproj", "{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}"
182182
EndProject
183+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.ChatBot", "src\components\BootstrapBlazor.ChatBot\BootstrapBlazor.ChatBot.csproj", "{2F37FBF4-5C1C-4493-B614-0E8361432621}"
184+
EndProject
183185
Global
184186
GlobalSection(SolutionConfigurationPlatforms) = preSolution
185187
Debug|Any CPU = Debug|Any CPU
@@ -486,6 +488,10 @@ Global
486488
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
487489
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
488490
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.Build.0 = Release|Any CPU
491+
{2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
492+
{2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.Build.0 = Debug|Any CPU
493+
{2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.ActiveCfg = Release|Any CPU
494+
{2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.Build.0 = Release|Any CPU
489495
EndGlobalSection
490496
GlobalSection(SolutionProperties) = preSolution
491497
HideSolutionNode = FALSE
@@ -570,6 +576,7 @@ Global
570576
{F184D96E-7855-4E3B-B447-D09DBC1C91C6} = {FF1089BE-C704-4374-B629-C57C08E1798F}
571577
{E30AAB64-BF28-4960-89C1-1F521025F531} = {FF1089BE-C704-4374-B629-C57C08E1798F}
572578
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA} = {FF1089BE-C704-4374-B629-C57C08E1798F}
579+
{2F37FBF4-5C1C-4493-B614-0E8361432621} = {FF1089BE-C704-4374-B629-C57C08E1798F}
573580
EndGlobalSection
574581
GlobalSection(ExtensibilityGlobals) = postSolution
575582
SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<Version>9.0.0</Version>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components AI Chat bot</PackageTags>
9+
<Description>Bootstrap UI components extensions of ChatBot</Description>
10+
<RootNamespace>BootstrapBlazor.Components</RootNamespace>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<SupportedPlatform Include="browser" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<PackageReference Include="Markdig" Version="0.40.0" />
19+
<PackageReference Include="Markdown.ColorCode" Version="3.0.0" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="BootstrapBlazor" Version="$(BBVersion)" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@namespace BootstrapBlazor.Components
2+
@inherits IdComponentBase
3+
4+
<div id="@Id">
5+
@((MarkupString)(_markdown ?? string.Empty))
6+
</div>
7+
8+
<style>
9+
.think {
10+
margin-bottom: 1rem;
11+
font-size: 0.75rem;
12+
font-weight: 400;
13+
color: #A0A0A0;
14+
border-left: 3px solid #dfdbdb;
15+
padding-left: 10px;
16+
}
17+
</style>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
using ColorCode;
6+
using ColorCode.Compilation.Languages;
7+
using ColorCode.Styling;
8+
using Markdig;
9+
using Markdown.ColorCode;
10+
using Microsoft.AspNetCore.Components;
11+
using Microsoft.Extensions.Logging;
12+
using System.Text.RegularExpressions;
13+
14+
namespace BootstrapBlazor.Components;
15+
16+
/// <summary>
17+
/// MarkdownContent component
18+
/// </summary>
19+
public partial class MarkdownContent
20+
{
21+
/// <summary>
22+
/// Gets or sets the content. Default value is null.
23+
/// </summary>
24+
[Parameter]
25+
[EditorRequired]
26+
public string? Content { get; set; }
27+
28+
[Inject, NotNull]
29+
private ILogger<MarkdownContent>? Logger { get; set; }
30+
31+
private string? _markdown;
32+
private MarkdownPipeline? _markdownPipeline;
33+
34+
/// <summary>
35+
/// <inheritdoc/>
36+
/// </summary>
37+
protected override void OnInitialized()
38+
{
39+
base.OnInitialized();
40+
41+
_markdownPipeline = new MarkdownPipelineBuilder()
42+
.UsePipeTables()
43+
.UseAdvancedExtensions()
44+
.UseColorCode(styleDictionary: StyleDictionary.DefaultLight, additionalLanguages: new List<ILanguage>()
45+
{
46+
new Json(),
47+
new CSharp(),
48+
new Cpp(),
49+
new Css(),
50+
new Html(),
51+
new JavaScript(),
52+
new Php(),
53+
})
54+
.UseAutoLinks()
55+
.UseEmojiAndSmiley()
56+
.UseMediaLinks()
57+
.UseCitations()
58+
.UseMathematics()
59+
.UseDiagrams()
60+
.Build();
61+
}
62+
63+
/// <summary>
64+
/// <inheritdoc/>
65+
/// </summary>
66+
protected override void OnParametersSet()
67+
{
68+
base.OnParametersSet();
69+
70+
_markdown = GetMarkdown(Content);
71+
}
72+
73+
private string GetMarkdown(string? toHtml)
74+
{
75+
var html = "";
76+
77+
if (string.IsNullOrEmpty(toHtml))
78+
{
79+
return html;
80+
}
81+
82+
try
83+
{
84+
// 处理未封闭的 think 标签
85+
toHtml = HandleUnclosedThinkTags(toHtml);
86+
87+
// 处理正常的 think 标签
88+
var thinkPattern = @"<\s*think\b[^>]*>(.*?)<\s*/\s*think\s*>";
89+
toHtml = Regex.Replace(toHtml, thinkPattern, @"<div class=""think"">$1</div>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
90+
toHtml = RemoveEmbeddingsElement(toHtml);
91+
92+
html = Markdig.Markdown.ToHtml(toHtml, _markdownPipeline);
93+
var pattern = "(<div style=\"color:#DADADA;background-color:#1E1E1E;\"><pre>(.*?)</pre></div>)";
94+
var matches = Regex.Matches(html, pattern, RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase);
95+
96+
for (var i = matches.Count - 1; i >= 0; i--)
97+
{
98+
var match = matches[i].ToString();
99+
var id = "copy" + i;
100+
var replacement = $"<button data-clipboard-target=\"#{id}\" class=\"float-end copyBtn mt-0\">Copy</button>" + match;
101+
html = html.Remove(matches[i].Index, matches[i].Length).Insert(matches[i].Index, replacement);
102+
}
103+
}
104+
catch (Exception ex)
105+
{
106+
Logger.LogError(ex, "{GetMakrDown} method throw exception", nameof(GetMarkdown));
107+
}
108+
109+
return html;
110+
}
111+
112+
private static string HandleUnclosedThinkTags(string content)
113+
{
114+
if (string.IsNullOrEmpty(content))
115+
return content;
116+
117+
// 匹配开始标签
118+
var openTagPattern = @"<\s*think\b[^>]*>";
119+
var closeTagPattern = @"<\s*/\s*think\s*>";
120+
121+
var openTags = Regex.Matches(content, openTagPattern);
122+
var closeTags = Regex.Matches(content, closeTagPattern);
123+
124+
// 如果开始标签数量等于结束标签数量,说明标签都是配对的
125+
if (openTags.Count == closeTags.Count)
126+
return content;
127+
128+
// 处理未封闭的标签
129+
var parts = Regex.Split(content, openTagPattern);
130+
if (parts.Length <= 1)
131+
return content;
132+
133+
var result = parts[0]; // 保留第一部分的内容
134+
for (int i = 1; i < parts.Length; i++)
135+
{
136+
var part = parts[i];
137+
// 检查这部分是否已经包含结束标签
138+
if (!part.Contains("</think>", StringComparison.OrdinalIgnoreCase))
139+
{
140+
// 没有结束标签,添加一个完整的 think 标签包装
141+
result += $"<think>{part}</think>";
142+
}
143+
else
144+
{
145+
// 已经有结束标签,保持原样
146+
result += $"<think>{part}";
147+
}
148+
}
149+
150+
return result;
151+
}
152+
153+
private static string RemoveEmbeddingsElement(string data)
154+
{
155+
if (string.IsNullOrEmpty(data))
156+
{
157+
return "";
158+
}
159+
string pattern = @"\[EMBEDDINGS\](.*?)\[/EMBEDDINGS\]";
160+
var matches = Regex.Matches(data, pattern, RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);
161+
162+
if (matches.Count == 0)
163+
{
164+
return data;
165+
}
166+
167+
return Regex.Replace(data, pattern, "", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);
168+
}
169+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@using Microsoft.AspNetCore.Components.Web

0 commit comments

Comments
 (0)