diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs index d260a09d..aa4154ff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs @@ -43,4 +43,6 @@ public class PageActionArgs /// Wait time in seconds after page is opened /// public int WaitTime { get; set; } + + public bool ReadInnerHTMLAsBody { get; set; } = false; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs index 06a97174..9239b865 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs @@ -79,4 +79,7 @@ public abstract class ConversationHookBase : IConversationHook public virtual Task OnBreakpointUpdated(string conversationId, bool resetStates) => Task.CompletedTask; + + public virtual Task OnNotificationGenerated(RoleDialogModel message) + => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs new file mode 100644 index 00000000..c4e73d69 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Conversations.Enums; + +public static class MessageTypeName +{ + public const string Plain = "plain"; + public const string Notification = "notification"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs index 9ed47c61..34371ba5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Functions.Models; - namespace BotSharp.Abstraction.Conversations; public interface IConversationHook @@ -107,4 +105,11 @@ public interface IConversationHook /// /// Task OnBreakpointUpdated(string conversationId, bool resetStates); + + /// + /// Generate a notification + /// + /// + /// + Task OnNotificationGenerated(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index bddc5426..7e7e372e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -8,7 +8,7 @@ public interface IConversationService IConversationStateService States { get; } string ConversationId { get; } Task NewConversation(Conversation conversation); - void SetConversationId(string conversationId, List states); + void SetConversationId(string conversationId, List states, bool isReadOnly = false); Task GetConversation(string id); Task> GetConversations(ConversationFilter filter); Task UpdateConversationTitle(string id, string title); @@ -41,7 +41,7 @@ public interface IConversationService PostbackMessageModel? replyMessage, Func onResponseReceived); - List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true); + List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable? includeMessageTypes = null); Task CleanHistory(string agentId); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs index 5f7066d2..a586e217 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs @@ -2,7 +2,6 @@ namespace BotSharp.Abstraction.Conversations; public interface IConversationStorage { - void InitStorage(string conversationId); void Append(string conversationId, RoleDialogModel dialog); List GetDialogs(string conversationId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index ad7ffd04..890f3211 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -83,6 +83,9 @@ public class DialogMetaData [JsonPropertyName("message_id")] public string MessageId { get; set; } + [JsonPropertyName("message_type")] + public string MessageType { get; set; } + [JsonPropertyName("function_name")] public string? FunctionName { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 5b8f7017..679527a3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging.Models.RichContent; @@ -11,6 +12,11 @@ public class RoleDialogModel : ITrackableMessage /// public string MessageId { get; set; } + /// + /// The message type + /// + public string MessageType { get; set; } = MessageTypeName.Plain; + /// /// user, system, assistant, function /// @@ -101,6 +107,7 @@ public class RoleDialogModel : ITrackableMessage public List GeneratedImages { get; set; } = new List(); + private RoleDialogModel() { } @@ -110,6 +117,7 @@ public class RoleDialogModel : ITrackableMessage Role = role; Content = text; MessageId = Guid.NewGuid().ToString(); + MessageType = MessageTypeName.Plain; } public override string ToString() @@ -132,6 +140,7 @@ public class RoleDialogModel : ITrackableMessage { CurrentAgentId = source.CurrentAgentId, MessageId = source.MessageId, + MessageType = source.MessageType, FunctionArgs = source.FunctionArgs, FunctionName = source.FunctionName, ToolCallId = source.ToolCallId, diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs index 0b1c06b5..2313b2ba 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeService.cs @@ -40,7 +40,8 @@ public interface IKnowledgeService /// /// /// - Task ImportDocumentContentToKnowledge(string collectionName, string fileName, string fileSource, IEnumerable contents, DocMetaRefData? refData = null); + Task ImportDocumentContentToKnowledge(string collectionName, string fileName, string fileSource, IEnumerable contents, + DocMetaRefData? refData = null, Dictionary? payload = null); /// /// Delete one document and its related knowledge in the collection /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs index 45d74a51..78ab19ad 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Enums/ContentLogSource.cs @@ -7,4 +7,5 @@ public static class ContentLogSource public const string FunctionCall = "function call"; public const string AgentResponse = "agent response"; public const string HardRule = "hard rule"; + public const string Notification = "notification"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Processors/IBaseProcessor.cs b/src/Infrastructure/BotSharp.Abstraction/Processors/IBaseProcessor.cs new file mode 100644 index 00000000..f8899c51 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Processors/IBaseProcessor.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Processors.Models; + +namespace BotSharp.Abstraction.Processors; + +public interface IBaseProcessor where TInput : LlmBaseRequest where TOutput : class +{ + string Provider { get; } + string Name => string.Empty; + int Order { get; } + + Task Execute(TInput input); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Processors/Models/LlmBaseRequest.cs b/src/Infrastructure/BotSharp.Abstraction/Processors/Models/LlmBaseRequest.cs new file mode 100644 index 00000000..d14a234b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Processors/Models/LlmBaseRequest.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Abstraction.Processors.Models; + +public class LlmBaseRequest +{ + public string Provider { get; set; } + public string Model { get; set; } + public string? AgentId { get; set; } + public string? TemplateName { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs index 588dedfc..d3d6a453 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Translation/Models/TranslationRequestModel.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Infrastructures.Enums; - namespace BotSharp.OpenAPI.ViewModels.Translations; public class TranslationRequestModel @@ -7,3 +5,15 @@ public class TranslationRequestModel public string Text { get; set; } = null!; public string ToLang { get; set; } = LanguageType.CHINESE; } + +public class TranslationScriptTimestamp +{ + public string Text { set; get; } = null!; + public string Timestamp { get; set; } = null!; +} + +public class TranslationLongTextRequestModel +{ + public TranslationScriptTimestamp[] Texts { get; set; } = null!; + public string ToLang { get; set; } = LanguageType.CHINESE; +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs index 22a8eb95..cddabe10 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs @@ -33,4 +33,6 @@ public class UserRole /// AI Assistant /// public const string Assistant = "assistant"; + + public const string Root = "root"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 750858a7..5a3315b7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -13,7 +13,8 @@ public interface IUserService Task GetMyProfile(); Task VerifyUserNameExisting(string userName); Task VerifyEmailExisting(string email); - Task SendVerificationCodeResetPassword(User user); + Task SendVerificationCodeResetPasswordNoLogin(User user); + Task SendVerificationCodeResetPasswordLogin(); Task ResetUserPassword(User user); Task ModifyUserEmail(string email); Task ModifyUserPhone(string phone); diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 99f8b6ae..90ac40e8 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index 26a5ccb5..4bdfe0d7 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -8,6 +8,7 @@ using BotSharp.Abstraction.Messaging.JsonConverters; using BotSharp.Abstraction.Users.Settings; using BotSharp.Abstraction.Interpreters.Settings; using BotSharp.Abstraction.Infrastructures; +using BotSharp.Core.Processors; namespace BotSharp.Core; @@ -23,6 +24,7 @@ public static class BotSharpCoreExtensions services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 610c1ab4..59ce88c2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -46,6 +46,7 @@ public partial class ConversationService } // Before chat completion hook + hooks = ReOrderConversationHooks(hooks); foreach (var hook in hooks) { hook.SetAgent(agent) @@ -173,4 +174,18 @@ public partial class ConversationService // Add to dialog history _storage.Append(_conversationId, response); } + + private List ReOrderConversationHooks(List hooks) + { + var target = "ChatHubConversationHook"; + var chathub = hooks.FirstOrDefault(x => x.GetType().Name == target); + var otherHooks = hooks.Where(x => x.GetType().Name != target).ToList(); + + if (chathub != null) + { + var newHooks = new List { chathub }.Concat(otherHooks); + return newHooks.ToList(); + } + return hooks; + } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs index 0983e2ed..88d3a52d 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.Summary.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Templating; @@ -21,6 +22,7 @@ public partial class ConversationService if (dialogs.IsNullOrEmpty()) continue; + dialogs = dialogs.Where(x => x.MessageType != MessageTypeName.Notification).ToList(); var content = GetConversationContent(dialogs); if (string.IsNullOrWhiteSpace(content)) continue; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 6e0f97d1..8511b873 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -106,7 +106,7 @@ public partial class ConversationService : IConversationService throw new NotImplementedException(); } - public List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true) + public List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true, IEnumerable? includeMessageTypes = null) { if (string.IsNullOrEmpty(_conversationId)) { @@ -115,6 +115,15 @@ public partial class ConversationService : IConversationService var dialogs = _storage.GetDialogs(_conversationId); + if (!includeMessageTypes.IsNullOrEmpty()) + { + dialogs = dialogs.Where(x => string.IsNullOrEmpty(x.MessageType) || includeMessageTypes.Contains(x.MessageType)).ToList(); + } + else + { + dialogs = dialogs.Where(x => string.IsNullOrEmpty(x.MessageType) || x.MessageType.IsEqualTo(MessageTypeName.Plain)).ToList(); + } + if (fromBreakpoint) { var db = _services.GetRequiredService(); @@ -134,7 +143,7 @@ public partial class ConversationService : IConversationService .ToList(); } - public void SetConversationId(string conversationId, List states) + public void SetConversationId(string conversationId, List states, bool isReadOnly = false) { _conversationId = conversationId; _state.Load(_conversationId); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index 5abc4fc5..ea145ac0 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -41,6 +41,7 @@ public class ConversationStorage : IConversationStorage Role = dialog.Role, AgentId = agentId, MessageId = dialog.MessageId, + MessageType = dialog.MessageType, FunctionName = dialog.FunctionName, CreateTime = dialog.CreatedAt }; @@ -65,6 +66,7 @@ public class ConversationStorage : IConversationStorage Role = dialog.Role, AgentId = agentId, MessageId = dialog.MessageId, + MessageType = dialog.MessageType, SenderId = dialog.SenderId, FunctionName = dialog.FunctionName, CreateTime = dialog.CreatedAt @@ -108,6 +110,7 @@ public class ConversationStorage : IConversationStorage var role = meta.Role; var currentAgentId = meta.AgentId; var messageId = meta.MessageId; + var messageType = meta.MessageType; var function = meta.FunctionName; var senderId = role == AgentRole.Function ? currentAgentId : meta.SenderId; var createdAt = meta.CreateTime; @@ -120,6 +123,7 @@ public class ConversationStorage : IConversationStorage { CurrentAgentId = currentAgentId, MessageId = messageId, + MessageType = messageType, CreatedAt = createdAt, SenderId = senderId, FunctionName = function, @@ -143,23 +147,4 @@ public class ConversationStorage : IConversationStorage return results; } - - public void InitStorage(string conversationId) - { - var file = GetStorageFile(conversationId); - if (!File.Exists(file)) - { - File.WriteAllLines(file, new string[0]); - } - } - - private string GetStorageFile(string conversationId) - { - var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - return Path.Combine(dir, "dialogs.txt"); - } } diff --git a/src/Infrastructure/BotSharp.Core/Processors/ProcessorFactory.cs b/src/Infrastructure/BotSharp.Core/Processors/ProcessorFactory.cs new file mode 100644 index 00000000..f78bb559 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Processors/ProcessorFactory.cs @@ -0,0 +1,29 @@ +using BotSharp.Abstraction.Processors; +using BotSharp.Abstraction.Processors.Models; + +namespace BotSharp.Core.Processors; + +public class ProcessorFactory +{ + private readonly IServiceProvider _services; + + public ProcessorFactory(IServiceProvider services) + { + _services = services; + } + + public IEnumerable> Create(string provider) + where TInput : LlmBaseRequest where TOutput : class + { + var processors = _services.GetServices>(); + processors = processors.Where(x => x.Provider == provider); + return processors.OrderBy(x => x.Order); + } + + public IBaseProcessor? Create(string provider, string name) + where TInput : LlmBaseRequest where TOutput : class + { + var processors = _services.GetServices>(); + return processors.FirstOrDefault(x => x.Provider == provider && x.Name == name); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs index f9aa73e3..453b2277 100644 --- a/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs +++ b/src/Infrastructure/BotSharp.Core/Translation/TranslationService.cs @@ -101,12 +101,12 @@ public class TranslationService : ITranslationService { var translatedStringList = await InnerTranslate(texts, language, template); - int retry = 0; + /*int retry = 0; while (translatedStringList.Texts.Length != texts.Count && retry < 3) { translatedStringList = await InnerTranslate(texts, language, template); retry++; - } + }*/ // Override language if it's Unknown, it's used to output the corresponding language. var states = _services.GetRequiredService(); @@ -119,7 +119,7 @@ public class TranslationService : ITranslationService var translatedTexts = translatedStringList.Texts; var memoryInputs = new List(); - for (var i = 0; i < texts.Count; i++) + for (var i = 0; i < Math.Min(texts.Count, translatedTexts.Length); i++) { map[outOfMemoryList[i].OriginalText] = translatedTexts[i].Text; memoryInputs.Add(new TranslationMemoryInput @@ -375,6 +375,8 @@ public class TranslationService : ITranslationService var render = _services.GetRequiredService(); var prompt = render.Render(template, translator.TemplateDict); + _logger.LogInformation($"Translation prompt: {prompt}"); + var translationDialogs = new List { new RoleDialogModel(AgentRole.User, prompt) @@ -384,6 +386,8 @@ public class TranslationService : ITranslationService } }; var response = await _completion.GetChatCompletions(translator, translationDialogs); + + _logger.LogInformation(response.Content); return response.Content.JsonContent(); } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index dcfaa084..98adb35b 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -412,7 +412,48 @@ public class UserService : IUserService return false; } - public async Task SendVerificationCodeResetPassword(User user) + public async Task SendVerificationCodeResetPasswordNoLogin(User user) + { + var db = _services.GetRequiredService(); + + User? record = null; + + if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone)) + { + return false; + } + + if (!string.IsNullOrEmpty(user.Phone)) + { + record = db.GetUserByPhone(user.Phone); + } + + if (!string.IsNullOrEmpty(user.Email)) + { + record = db.GetUserByEmail(user.Email); + } + + if (record == null) + { + return false; + } + + record.VerificationCode = Nanoid.Generate(alphabet: "0123456789", size: 6); + + //update current verification code. + db.UpdateUserVerificationCode(record.Id, record.VerificationCode); + + //send code to user Email. + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.VerificationCodeResetPassword(record); + } + + return true; + } + + public async Task SendVerificationCodeResetPasswordLogin() { var db = _services.GetRequiredService(); @@ -422,23 +463,6 @@ public class UserService : IUserService { record = db.GetUserById(_user.Id); } - else - { - if (!string.IsNullOrEmpty(user.Email) && !string.IsNullOrEmpty(user.Phone)) - { - return false; - } - - if (!string.IsNullOrEmpty(user.Email)) - { - record = db.GetUserByEmail(user.Email); - } - - if (!string.IsNullOrEmpty(user.Phone)) - { - record = db.GetUserByPhone(user.Phone); - } - } if (record == null) { diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid index 6b3a8677..fd67a5fd 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/translation_prompt.liquid @@ -1,6 +1,19 @@ {{ text_list }} ===== +{% if language == "Chinese" %} +将以上所有句子翻译成中文。 + +要求: +* 以 JSON 格式输出翻译后的文本 {"input_lang":"原始文本语言", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}。 +* output_count 必须等于输出中texts数组的长度。 +{% else %} Translate all the above sentences into {{ language }}. -Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}. -The "output_count" must equal the length of the "texts" array in the output. + +Requirements: +* Output the translated text in JSON {"input_lang":"original text language", "output_count": {{ text_list_size }}, "output_lang":"{{ language }}", "texts":[{"id": 1, "text":""},{"id": 2, "text":""}]}. +* The "output_count" must equal the length of the "texts" array in the output. +{% endif %} + + + diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 5521b5e6..a020a490 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,8 +1,10 @@ +using Azure; using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Enums; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Enums; +using BotSharp.Core.Infrastructures; namespace BotSharp.OpenAPI.Controllers; @@ -251,6 +253,44 @@ public class ConversationController : ControllerBase return isSuccess ? newMessageId : string.Empty; } + #region Send notification + [HttpPost("/conversation/{conversationId}/notification")] + public async Task SendNotification([FromRoute] string conversationId, [FromBody] NewMessageModel input) + { + var conv = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + var userService = _services.GetRequiredService(); + + conv.SetConversationId(conversationId, new List(), isReadOnly: true); + + var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) + { + MessageId = Guid.NewGuid().ToString(), + CreatedAt = DateTime.UtcNow + }; + + var user = await userService.GetUser(_user.Id); + var response = new ChatResponseModel() + { + ConversationId = conversationId, + MessageId = inputMsg.MessageId, + Sender = new UserViewModel + { + Id = user?.Id ?? string.Empty, + FirstName = user?.FirstName ?? string.Empty, + LastName = user?.LastName ?? string.Empty + }, + CreatedAt = DateTime.UtcNow + }; + + await HookEmitter.Emit(_services, async hook => + await hook.OnNotificationGenerated(inputMsg) + ); + + return response; + } + #endregion + #region Send message [HttpPost("/conversation/{agentId}/{conversationId}")] public async Task SendMessage([FromRoute] string agentId, diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index bc02e846..61862781 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -29,7 +29,8 @@ public class InstructModeController : ControllerBase .SetState("model", input.Model, source: StateSource.External) .SetState("model_id", input.ModelId, source: StateSource.External) .SetState("instruction", input.Instruction, source: StateSource.External) - .SetState("input_text", input.Text,source: StateSource.External); + .SetState("input_text", input.Text, source: StateSource.External) + .SetState("template_name", input.Template, source: StateSource.External); var instructor = _services.GetRequiredService(); var result = await instructor.Execute(agentId, diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs index ac7686a1..7845e8cf 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/TranslationController.cs @@ -1,4 +1,4 @@ -using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Translation; using BotSharp.OpenAPI.ViewModels.Translations; @@ -9,10 +9,13 @@ namespace BotSharp.OpenAPI.Controllers; public class TranslationController : ControllerBase { private readonly IServiceProvider _services; + private readonly JsonSerializerOptions _jsonOptions; - public TranslationController(IServiceProvider services) + public TranslationController(IServiceProvider services, + BotSharpOptions options) { _services = services; + _jsonOptions = InitJsonOptions(options); } [HttpPost("/translate")] @@ -21,10 +24,79 @@ public class TranslationController : ControllerBase var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); var translator = _services.GetRequiredService(); - var text = await translator.Translate(agent, Guid.NewGuid().ToString(), model.Text, language: model.ToLang); + var states = _services.GetRequiredService(); + states.SetState("max_tokens", "8192"); + var text = await translator.Translate(agent, Guid.NewGuid().ToString(), model.Text.Split("\r\n"), language: model.ToLang); return new TranslationResponseModel { - Text = text + Text = string.Join("\r\n", text) }; } + + [HttpPost("/translate/long-text")] + public async Task SendMessageSse([FromBody] TranslationLongTextRequestModel model) + { + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(BuiltInAgentId.AIAssistant); + var translator = _services.GetRequiredService(); + + Response.StatusCode = 200; + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream"); + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache"); + Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive"); + + foreach (var script in model.Texts) + { + var translatedText = await translator.Translate(agent, Guid.NewGuid().ToString(), script.Text, language: model.ToLang); + + var json = JsonSerializer.Serialize(new TranslationScriptTimestamp + { + Text = translatedText, + Timestamp = script.Timestamp + }, _jsonOptions); + + await OnChunkReceived(Response, json); + } + + await OnEventCompleted(Response); + } + + private async Task OnChunkReceived(HttpResponse response, string text) + { + var buffer = Encoding.UTF8.GetBytes($"data:{text}\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + await Task.Delay(10); + + buffer = Encoding.UTF8.GetBytes("\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + } + + private async Task OnEventCompleted(HttpResponse response) + { + var buffer = Encoding.UTF8.GetBytes("data:[DONE]\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + + buffer = Encoding.UTF8.GetBytes("\n"); + await response.Body.WriteAsync(buffer, 0, buffer.Length); + } + + private JsonSerializerOptions InitJsonOptions(BotSharpOptions options) + { + var jsonOption = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + AllowTrailingCommas = true + }; + + if (options?.JsonSerializerOptions != null) + { + foreach (var option in options.JsonSerializerOptions.Converters) + { + jsonOption.Converters.Add(option); + } + } + + return jsonOption; + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 281de3e8..bf15a33b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -108,12 +108,20 @@ public class UserController : ControllerBase { return await _userService.VerifyEmailExisting(email); } + [AllowAnonymous] - [HttpPost("/user/verifycode")] + [HttpPost("/user/verifycode-out")] public async Task SendVerificationCodeResetPassword([FromBody] UserCreationModel user) { - return await _userService.SendVerificationCodeResetPassword(user.ToUser()); + return await _userService.SendVerificationCodeResetPasswordNoLogin(user.ToUser()); } + + [HttpPost("/user/verifycode-in")] + public async Task SendVerificationCodeResetPasswordLogined() + { + return await _userService.SendVerificationCodeResetPasswordLogin(); + } + [AllowAnonymous] [HttpPost("/user/resetpassword")] public async Task ResetUserPassword([FromBody] UserResetPasswordModel user) diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj index 5574afe5..c5bd88d0 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs index dc9df32a..047cc246 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/Providers/ChatCompletionProvider.cs @@ -179,8 +179,15 @@ public class ChatCompletionProvider : IChatCompletion Model = settings.Name, Stream = false, Temperature = temperature, - SystemMessage = instruction, - Tools = new List() { } + Tools = new List() + }; + + if (!string.IsNullOrEmpty(instruction)) + { + parameters.System = new List() + { + new SystemMessage(instruction) + }; }; JsonSerializerOptions jsonSerializationOptions = new() @@ -221,7 +228,7 @@ public class ChatCompletionProvider : IChatCompletion private string GetPrompt(MessageParameters parameters) { - var prompt = $"{parameters.SystemMessage}\r\n"; + var prompt = $"{string.Join("\r\n", parameters.System.Select(x => x.Text))}\r\n"; prompt += "\r\n[CONVERSATION]"; var verbose = string.Join("\r\n", parameters.Messages @@ -264,7 +271,7 @@ public class ChatCompletionProvider : IChatCompletion { var functions = string.Join("\r\n", parameters.Tools.Select(x => { - return $"\r\n{x.Name}: {x.Description}\r\n{JsonSerializer.Serialize(x.Parameters)}"; + return $"\r\n{x.Function.Name}: {x.Function.Description}\r\n{JsonSerializer.Serialize(x.Function.Parameters)}"; })); prompt += $"\r\n[FUNCTIONS]\r\n{functions}\r\n"; } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index bf2292ea..29c6df82 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -15,6 +15,7 @@ public class ChatHubConversationHook : ConversationHookBase private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; private const string GENERATE_SENDER_ACTION = "OnSenderActionGenerated"; private const string DELETE_MESSAGE = "OnMessageDeleted"; + private const string GENERATE_NOTIFICATION = "OnNotificationGenerated"; #endregion public ChatHubConversationHook( @@ -53,6 +54,7 @@ public class ChatHubConversationHook : ConversationHookBase { ConversationId = conv.ConversationId, MessageId = message.MessageId, + Payload = message.Payload, Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, Sender = UserViewModel.FromUser(sender) }; @@ -117,6 +119,31 @@ public class ChatHubConversationHook : ConversationHookBase await base.OnResponseGenerated(message); } + + public override async Task OnNotificationGenerated(RoleDialogModel message) + { + var conv = _services.GetRequiredService(); + var json = JsonSerializer.Serialize(new ChatResponseModel() + { + ConversationId = conv.ConversationId, + MessageId = message.MessageId, + Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, + Function = message.FunctionName, + RichContent = message.SecondaryRichContent ?? message.RichContent, + Data = message.Data, + Sender = new UserViewModel() + { + FirstName = "AI", + LastName = "Assistant", + Role = AgentRole.Assistant + } + }, _options.JsonSerializerOptions); + + await GenerateNotification(json); + await base.OnNotificationGenerated(message); + } + + public override async Task OnMessageDeleted(string conversationId, string messageId) { var model = new ChatResponseModel @@ -153,5 +180,10 @@ public class ChatHubConversationHook : ConversationHookBase { await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model); } + + private async Task GenerateNotification(string? json) + { + await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json); + } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj index 29c81ec7..42663445 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/BotSharp.Plugin.KnowledgeBase.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs index 74469177..dd7e0d1a 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Services/KnowledgeService.Document.cs @@ -60,7 +60,20 @@ public partial class KnowledgeService } // Save to vector db - var dataIds = await SaveToVectorDb(collectionName, fileId, file.FileName, contents, file.FileSource); + var payload = new Dictionary() + { + { KnowledgePayloadName.DataSource, VectorDataSource.File }, + { KnowledgePayloadName.FileId, fileId.ToString() }, + { KnowledgePayloadName.FileName, file.FileName }, + { KnowledgePayloadName.FileSource, file.FileSource } + }; + + if (!string.IsNullOrWhiteSpace(file.FileUrl)) + { + payload[KnowledgePayloadName.FileUrl] = file.FileUrl; + } + + var dataIds = await SaveToVectorDb(collectionName, contents, payload); if (!dataIds.IsNullOrEmpty()) { db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData @@ -99,7 +112,7 @@ public partial class KnowledgeService public async Task ImportDocumentContentToKnowledge(string collectionName, string fileName, string fileSource, - IEnumerable contents, DocMetaRefData? refData = null) + IEnumerable contents, DocMetaRefData? refData = null, Dictionary? payload = null) { if (string.IsNullOrWhiteSpace(collectionName) || string.IsNullOrWhiteSpace(fileName) @@ -119,7 +132,26 @@ public partial class KnowledgeService var fileId = Guid.NewGuid(); var contentType = FileUtility.GetFileContentType(fileName); - var dataIds = await SaveToVectorDb(collectionName, fileId, fileName, contents, fileSource, fileUrl: refData?.Url); + var innerPayload = new Dictionary(); + if (payload != null) + { + foreach (var item in payload) + { + innerPayload[item.Key] = item.Value; + } + } + + innerPayload[KnowledgePayloadName.DataSource] = VectorDataSource.File; + innerPayload[KnowledgePayloadName.FileId] = fileId.ToString(); + innerPayload[KnowledgePayloadName.FileName] = fileName; + innerPayload[KnowledgePayloadName.FileSource] = fileSource; + + if (!string.IsNullOrWhiteSpace(refData?.Url)) + { + innerPayload[KnowledgePayloadName.FileUrl] = refData.Url; + } + + var dataIds = await SaveToVectorDb(collectionName, contents, innerPayload); db.SaveKnolwedgeBaseFileMeta(new KnowledgeDocMetaData { Collection = collectionName, @@ -385,9 +417,7 @@ public partial class KnowledgeService return saved; } - private async Task> SaveToVectorDb( - string collectionName, Guid fileId, string fileName, IEnumerable contents, - string fileSource = KnowledgeDocSource.Api, string vectorDataSource = VectorDataSource.File, string? fileUrl = null) + private async Task> SaveToVectorDb(string collectionName, IEnumerable contents, Dictionary? payload = null) { if (contents.IsNullOrEmpty()) { @@ -398,25 +428,12 @@ public partial class KnowledgeService var vectorDb = GetVectorDb(); var textEmbedding = GetTextEmbedding(collectionName); - var payload = new Dictionary - { - { KnowledgePayloadName.DataSource, vectorDataSource }, - { KnowledgePayloadName.FileId, fileId.ToString() }, - { KnowledgePayloadName.FileName, fileName }, - { KnowledgePayloadName.FileSource, fileSource } - }; - - if (!string.IsNullOrWhiteSpace(fileUrl)) - { - payload[KnowledgePayloadName.FileUrl] = fileUrl; - } - for (int i = 0; i < contents.Count(); i++) { var content = contents.ElementAt(i); var vector = await textEmbedding.GetVectorAsync(content); var dataId = Guid.NewGuid(); - var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload); + var saved = await vectorDb.Upsert(collectionName, dataId, vector, content, payload ?? new Dictionary()); if (!saved) continue; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs index 5c2a1698..1179d3cd 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs @@ -48,6 +48,7 @@ public class DialogMetaDataMongoElement public string Role { get; set; } public string AgentId { get; set; } public string MessageId { get; set; } + public string MessageType { get; set; } public string? FunctionName { get; set; } public string? SenderId { get; set; } public DateTime CreateTime { get; set; } @@ -64,6 +65,7 @@ public class DialogMetaDataMongoElement Role = meta.Role, AgentId = meta.AgentId, MessageId = meta.MessageId, + MessageType = meta.MessageType, FunctionName = meta.FunctionName, SenderId = meta.SenderId, CreateTime = meta.CreateTime, @@ -77,6 +79,7 @@ public class DialogMetaDataMongoElement Role = meta.Role, AgentId = meta.AgentId, MessageId = meta.MessageId, + MessageType = meta.MessageType, FunctionName = meta.FunctionName, SenderId = meta.SenderId, CreateTime = meta.CreateTime, diff --git a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs index 9dde5b35..ef9faa93 100644 --- a/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs +++ b/src/Plugins/BotSharp.Plugin.Qdrant/QdrantDb.cs @@ -212,42 +212,45 @@ public class QdrantDb : IVectorDb { foreach (var item in payload) { - if (item.Value is string str) - { - point.Payload[item.Key] = str; - } - else if (item.Value is bool b) + var value = item.Value?.ToString(); + if (value == null) continue; + + if (bool.TryParse(value, out var b)) { point.Payload[item.Key] = b; } - else if (item.Value is byte int8) + else if (byte.TryParse(value, out var int8)) { point.Payload[item.Key] = int8; } - else if (item.Value is short int16) + else if (short.TryParse(value, out var int16)) { point.Payload[item.Key] = int16; } - else if (item.Value is int int32) + else if (int.TryParse(value, out var int32)) { point.Payload[item.Key] = int32; } - else if (item.Value is long int64) + else if (long.TryParse(value, out var int64)) { point.Payload[item.Key] = int64; } - else if (item.Value is float f32) + else if (float.TryParse(value, out var f32)) { point.Payload[item.Key] = f32; } - else if (item.Value is double f64) + else if (double.TryParse(value, out var f64)) { point.Payload[item.Key] = f64; } - else if (item.Value is DateTime dt) + else if (DateTime.TryParse(value, out var dt)) { point.Payload[item.Key] = dt.ToUniversalTime().ToString("o"); } + else + { + point.Payload[item.Key] = value; + } } } diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json index 40a86c13..66abe705 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/sql_dictionary_lookup.json @@ -19,7 +19,8 @@ "type": "string", "description": "table name" } - }, + } + }, "required": [ "sql_statement", "reason", "tables" ] } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs index cc1a0009..108ff433 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.GoToPage.cs @@ -67,9 +67,13 @@ public partial class PlaywrightWebDriver result.ResponseStatusCode = response.Status; if (response.Status == 200) { - // Disable this due to performance issue, some page is too large - // result.Body = await page.InnerHTMLAsync("body"); result.IsSuccess = true; + + // Be careful if page is too large, it will cause performance issue + if (args.ReadInnerHTMLAsBody) + { + result.Body = await page.InnerHTMLAsync("body"); + } } else {