diff --git a/BootstrapBlazor.Extensions.sln b/BootstrapBlazor.Extensions.sln
index a9ddbde4..4948adcd 100644
--- a/BootstrapBlazor.Extensions.sln
+++ b/BootstrapBlazor.Extensions.sln
@@ -180,6 +180,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverSheet
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.UniverIcon", "src\components\BootstrapBlazor.UniverIcon\BootstrapBlazor.UniverIcon.csproj", "{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootstrapBlazor.ChatBot", "src\components\BootstrapBlazor.ChatBot\BootstrapBlazor.ChatBot.csproj", "{2F37FBF4-5C1C-4493-B614-0E8361432621}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -486,6 +488,10 @@ Global
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F37FBF4-5C1C-4493-B614-0E8361432621}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F37FBF4-5C1C-4493-B614-0E8361432621}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -570,6 +576,7 @@ Global
{F184D96E-7855-4E3B-B447-D09DBC1C91C6} = {FF1089BE-C704-4374-B629-C57C08E1798F}
{E30AAB64-BF28-4960-89C1-1F521025F531} = {FF1089BE-C704-4374-B629-C57C08E1798F}
{A657E04C-1495-439E-BC2E-1EEAB2D1B4DA} = {FF1089BE-C704-4374-B629-C57C08E1798F}
+ {2F37FBF4-5C1C-4493-B614-0E8361432621} = {FF1089BE-C704-4374-B629-C57C08E1798F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D5EB1960-6F30-4CE1-B375-EAE1F787D6FF}
diff --git a/src/components/BootstrapBlazor.ChatBot/BootstrapBlazor.ChatBot.csproj b/src/components/BootstrapBlazor.ChatBot/BootstrapBlazor.ChatBot.csproj
new file mode 100644
index 00000000..ee56aa54
--- /dev/null
+++ b/src/components/BootstrapBlazor.ChatBot/BootstrapBlazor.ChatBot.csproj
@@ -0,0 +1,26 @@
+
+
+
+ 9.0.0
+
+
+
+ Bootstrap Blazor WebAssembly wasm UI Components AI Chat bot
+ Bootstrap UI components extensions of ChatBot
+ BootstrapBlazor.Components
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor b/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor
new file mode 100644
index 00000000..91175e46
--- /dev/null
+++ b/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor
@@ -0,0 +1,17 @@
+@namespace BootstrapBlazor.Components
+@inherits IdComponentBase
+
+
+ @((MarkupString)(_markdown ?? string.Empty))
+
+
+
diff --git a/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor.cs b/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor.cs
new file mode 100644
index 00000000..41b59c8f
--- /dev/null
+++ b/src/components/BootstrapBlazor.ChatBot/MarkdownContent.razor.cs
@@ -0,0 +1,169 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using ColorCode;
+using ColorCode.Compilation.Languages;
+using ColorCode.Styling;
+using Markdig;
+using Markdown.ColorCode;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.Logging;
+using System.Text.RegularExpressions;
+
+namespace BootstrapBlazor.Components;
+
+///
+/// MarkdownContent component
+///
+public partial class MarkdownContent
+{
+ ///
+ /// Gets or sets the content. Default value is null.
+ ///
+ [Parameter]
+ [EditorRequired]
+ public string? Content { get; set; }
+
+ [Inject, NotNull]
+ private ILogger? Logger { get; set; }
+
+ private string? _markdown;
+ private MarkdownPipeline? _markdownPipeline;
+
+ ///
+ ///
+ ///
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+
+ _markdownPipeline = new MarkdownPipelineBuilder()
+ .UsePipeTables()
+ .UseAdvancedExtensions()
+ .UseColorCode(styleDictionary: StyleDictionary.DefaultLight, additionalLanguages: new List()
+ {
+ new Json(),
+ new CSharp(),
+ new Cpp(),
+ new Css(),
+ new Html(),
+ new JavaScript(),
+ new Php(),
+ })
+ .UseAutoLinks()
+ .UseEmojiAndSmiley()
+ .UseMediaLinks()
+ .UseCitations()
+ .UseMathematics()
+ .UseDiagrams()
+ .Build();
+ }
+
+ ///
+ ///
+ ///
+ protected override void OnParametersSet()
+ {
+ base.OnParametersSet();
+
+ _markdown = GetMarkdown(Content);
+ }
+
+ private string GetMarkdown(string? toHtml)
+ {
+ var html = "";
+
+ if (string.IsNullOrEmpty(toHtml))
+ {
+ return html;
+ }
+
+ try
+ {
+ // 处理未封闭的 think 标签
+ toHtml = HandleUnclosedThinkTags(toHtml);
+
+ // 处理正常的 think 标签
+ var thinkPattern = @"<\s*think\b[^>]*>(.*?)<\s*/\s*think\s*>";
+ toHtml = Regex.Replace(toHtml, thinkPattern, @"$1
", RegexOptions.Singleline | RegexOptions.IgnoreCase);
+ toHtml = RemoveEmbeddingsElement(toHtml);
+
+ html = Markdig.Markdown.ToHtml(toHtml, _markdownPipeline);
+ var pattern = "()";
+ 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 = $"" + match;
+ html = html.Remove(matches[i].Index, matches[i].Length).Insert(matches[i].Index, replacement);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "{GetMakrDown} method throw exception", nameof(GetMarkdown));
+ }
+
+ return html;
+ }
+
+ private static 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("", StringComparison.OrdinalIgnoreCase))
+ {
+ // 没有结束标签,添加一个完整的 think 标签包装
+ result += $"{part}";
+ }
+ else
+ {
+ // 已经有结束标签,保持原样
+ result += $"{part}";
+ }
+ }
+
+ return result;
+ }
+
+ private static 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;
+ }
+
+ return Regex.Replace(data, pattern, "", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase);
+ }
+}
diff --git a/src/components/BootstrapBlazor.ChatBot/_Imports.razor b/src/components/BootstrapBlazor.ChatBot/_Imports.razor
new file mode 100644
index 00000000..77285129
--- /dev/null
+++ b/src/components/BootstrapBlazor.ChatBot/_Imports.razor
@@ -0,0 +1 @@
+@using Microsoft.AspNetCore.Components.Web