diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..ed4cf565 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,44 @@ +name: build + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v1 + - name: Setup .NET Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0.x' + - name: Set env + run: | + echo "DOTNET_CLI_TELEMETRY_OPTOUT=1" >> $GITHUB_ENV + echo "DOTNET_hostBuilder:reloadConfigOnChange=false" >> $GITHUB_ENV + - name: Install required workloads + run: | + dotnet workload install aspire --source https://aka.ms/dotnet8/nuget/index.json --source https://api.nuget.org/v3/index.json + - name: Clean + run: | + dotnet clean ./BotSharp.sln --configuration Release + dotnet nuget locals all --clear + - name: Build + run: dotnet build ./BotSharp.sln -c Release + - name: Test + run: | + cd ./tests/UnitTest + dotnet test --logger "console;verbosity=detailed" + cd ../BotSharp.Plugin.SemanticKernel.UnitTests + dotnet test --logger "console;verbosity=detailed" \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 01aba146..6d84103e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -71,5 +71,5 @@ public interface IConversationService /// conversation limit /// if pre-loading, then keys are not filter by the search query /// - Task> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false); + Task> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs index a485a9c4..eb5adacd 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Conversation.cs @@ -58,13 +58,17 @@ public class DialogElement [JsonPropertyName("payload")] public string? Payload { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("data")] + public object? Data { get; set; } + public DialogElement() { } public DialogElement(DialogMetaData meta, string content, string? richContent = null, - string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null) + string? secondaryContent = null, string? secondaryRichContent = null, string? payload = null, object? data = null) { MetaData = meta; Content = content; @@ -72,6 +76,7 @@ public class DialogElement SecondaryContent = secondaryContent; SecondaryRichContent = secondaryRichContent; Payload = payload; + Data = data; } public override string ToString() diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index a58600e5..06582d45 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -148,7 +148,7 @@ public interface IBotSharpRepository : IHaveServiceProvider => throw new NotImplementedException(); List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) => throw new NotImplementedException(); - List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) + List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) => throw new NotImplementedException(); #endregion diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 63a257e5..0cec37d8 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -222,17 +222,17 @@ public partial class ConversationService : IConversationService _state.Save(); } - public async Task> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false) + public async Task> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false) { var keys = new List(); - if (!preLoad && string.IsNullOrWhiteSpace(query)) + if (!preload && string.IsNullOrWhiteSpace(query)) { return keys; } var db = _services.GetRequiredService(); - keys = db.GetConversationStateSearchKeys(convUpperlimit: convlimit); - keys = preLoad ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList(); + keys = db.GetConversationStateSearchKeys(convUpperLimit: convLimit); + keys = preload ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList(); return keys.OrderBy(x => x).Take(keyLimit).ToList(); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 52dcd032..7cdd40bd 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -15,6 +15,7 @@ ******************************************************************************/ using BotSharp.Abstraction.Conversations.Enums; +using BotSharp.Abstraction.SideCar; namespace BotSharp.Core.Conversations.Services; @@ -26,6 +27,7 @@ public class ConversationStateService : IConversationStateService, IDisposable private readonly ILogger _logger; private readonly IServiceProvider _services; private readonly IBotSharpRepository _db; + private readonly IConversationSideCar _sidecar; private string _conversationId; /// /// States in the current round of conversation @@ -39,10 +41,12 @@ public class ConversationStateService : IConversationStateService, IDisposable public ConversationStateService( IServiceProvider services, IBotSharpRepository db, + IConversationSideCar sidecar, ILogger logger) { _services = services; _db = db; + _sidecar = sidecar; _logger = logger; _curStates = new ConversationState(); _historyStates = new ConversationState(); @@ -138,15 +142,20 @@ public class ConversationStateService : IConversationStateService, IDisposable _conversationId = !isReadOnly ? conversationId : null; Reset(); - var routingCtx = _services.GetRequiredService(); - var curMsgId = routingCtx.MessageId; + var endNodes = new Dictionary(); + if (_sidecar?.IsEnabled() == true) + { + return endNodes; + } _historyStates = _db.GetConversationStates(conversationId); + if (_historyStates.IsNullOrEmpty()) + { + return endNodes; + } - var endNodes = new Dictionary(); - - if (_historyStates.IsNullOrEmpty()) return endNodes; - + var routingCtx = _services.GetRequiredService(); + var curMsgId = routingCtx.MessageId; var dialogs = _db.GetConversationDialogs(conversationId); var userDialogs = dialogs.Where(x => x.MetaData?.Role == AgentRole.User) .GroupBy(x => x.MetaData?.MessageId) @@ -210,7 +219,7 @@ public class ConversationStateService : IConversationStateService, IDisposable public void Save() { - if (_conversationId == null) + if (_conversationId == null || _sidecar?.IsEnabled() == true) { Reset(); return; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index f9ba5b12..1836ef23 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -52,7 +52,8 @@ public class ConversationStorage : IConversationStorage MetaData = meta, Content = dialog.Content, SecondaryContent = dialog.SecondaryContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }); } else @@ -83,7 +84,8 @@ public class ConversationStorage : IConversationStorage SecondaryContent = dialog.SecondaryContent, RichContent = richContent, SecondaryRichContent = secondaryRichContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }); } @@ -121,7 +123,8 @@ public class ConversationStorage : IConversationStorage MetaData = meta, Content = dialog.Content, SecondaryContent = dialog.SecondaryContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }); } else @@ -152,7 +155,8 @@ public class ConversationStorage : IConversationStorage SecondaryContent = dialog.SecondaryContent, RichContent = richContent, SecondaryRichContent = secondaryRichContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }); } } @@ -196,7 +200,8 @@ public class ConversationStorage : IConversationStorage RichContent = richContent, SecondaryContent = secondaryContent, SecondaryRichContent = secondaryRichContent, - Payload = payload + Payload = payload, + Data = dialog.Data }; results.Add(record); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 25682662..932bb9ba 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -605,7 +605,7 @@ public partial class FileRepository #if !DEBUG [SharpCache(10)] #endif - public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) + public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) { var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); if (!Directory.Exists(dir)) return []; @@ -635,7 +635,7 @@ public partial class FileRepository keys.AddRange(stateKeys); count++; - if (count >= convUpperlimit) + if (count >= convUpperLimit) { break; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 7461518d..f41c69fe 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -79,9 +79,8 @@ public class ConversationController : ControllerBase [HttpGet("/conversation/{conversationId}/dialogs")] public async Task> GetDialogs([FromRoute] string conversationId) { - var conv = _services.GetRequiredService(); - conv.SetConversationId(conversationId, new List(), isReadOnly: true); - var history = conv.GetDialogHistory(fromBreakpoint: false); + var storage = _services.GetRequiredService(); + var history = storage.GetDialogs(conversationId); var userService = _services.GetRequiredService(); var agentService = _services.GetRequiredService(); @@ -555,10 +554,10 @@ public class ConversationController : ControllerBase #region Search state keys [HttpGet("/conversation/state/keys")] - public async Task> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] bool preLoad = false) + public async Task> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] int convLimit = 100, [FromQuery] bool preload = false) { var convService = _services.GetRequiredService(); - var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad); + var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, convLimit: convLimit, preload: preload); return keys; } #endregion diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/BotSharp.Plugin.ChatHub.csproj b/src/Plugins/BotSharp.Plugin.ChatHub/BotSharp.Plugin.ChatHub.csproj index 4268cf38..bf8d28a6 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/BotSharp.Plugin.ChatHub.csproj +++ b/src/Plugins/BotSharp.Plugin.ChatHub/BotSharp.Plugin.ChatHub.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs index e7cf082f..41d1851b 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatHubPlugin.cs @@ -1,5 +1,4 @@ -using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.Routing; +using BotSharp.Abstraction.Interpreters.Settings; using BotSharp.Core.Crontab.Abstraction; using BotSharp.Plugin.ChatHub.Hooks; using Microsoft.Extensions.Configuration; @@ -18,6 +17,10 @@ public class ChatHubPlugin : IBotSharpPlugin public void RegisterDI(IServiceCollection services, IConfiguration config) { + var settings = new ChatHubSettings(); + config.Bind("ChatHub", settings); + services.AddSingleton(x => settings); + // Register hooks services.AddScoped(); services.AddScoped(); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Enums/EventDispatchType.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Enums/EventDispatchType.cs new file mode 100644 index 00000000..14115808 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Enums/EventDispatchType.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Plugin.ChatHub.Enums; + +public static class EventDispatchType +{ + public const string Group = "group"; + public const string User = "user"; +} diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 83cb2746..9f2c1008 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -9,8 +9,9 @@ public class ChatHubConversationHook : ConversationHookBase private readonly IHubContext _chatHub; private readonly IUserIdentity _user; private readonly BotSharpOptions _options; + private readonly ChatHubSettings _settings; - #region Event + #region Events private const string INIT_CLIENT_CONVERSATION = "OnConversationInitFromClient"; private const string RECEIVE_CLIENT_MESSAGE = "OnMessageReceivedFromClient"; private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; @@ -23,12 +24,14 @@ public class ChatHubConversationHook : ConversationHookBase IServiceProvider services, IHubContext chatHub, BotSharpOptions options, + ChatHubSettings settings, IUserIdentity user) { _services = services; _chatHub = chatHub; _user = user; _options = options; + _settings = settings; Priority = -1; // Make sure this hook is the top one. } @@ -42,7 +45,7 @@ public class ChatHubConversationHook : ConversationHookBase var user = await userService.GetUser(conv.User.Id); conv.User = UserViewModel.FromUser(user); - await InitClientConversation(conv); + await InitClientConversation(conv.Id, conv); await base.OnConversationInitialized(conversation); } @@ -63,7 +66,7 @@ public class ChatHubConversationHook : ConversationHookBase Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content, Sender = UserViewModel.FromUser(sender) }; - await ReceiveClientMessage(model); + await ReceiveClientMessage(conv.ConversationId, model); // Send typing-on to client var action = new ConversationSenderActionModel @@ -71,7 +74,8 @@ public class ChatHubConversationHook : ConversationHookBase ConversationId = conv.ConversationId, SenderAction = SenderActionEnum.TypingOn }; - await GenerateSenderAction(action); + + await GenerateSenderAction(conv.ConversationId, action); await base.OnMessageReceived(message); } @@ -84,7 +88,8 @@ public class ChatHubConversationHook : ConversationHookBase SenderAction = SenderActionEnum.TypingOn, Indication = message.Indication }; - await GenerateSenderAction(action); + + await GenerateSenderAction(conv.ConversationId, action); await base.OnFunctionExecuting(message); } @@ -121,8 +126,8 @@ public class ChatHubConversationHook : ConversationHookBase SenderAction = SenderActionEnum.TypingOff }; - await GenerateSenderAction(action); - await ReceiveAssistantMessage(json); + await GenerateSenderAction(conv.ConversationId, action); + await ReceiveAssistantMessage(conv.ConversationId, json); await base.OnResponseGenerated(message); } @@ -146,7 +151,7 @@ public class ChatHubConversationHook : ConversationHookBase } }, _options.JsonSerializerOptions); - await GenerateNotification(json); + await GenerateNotification(conv.ConversationId, json); await base.OnNotificationGenerated(message); } @@ -158,7 +163,8 @@ public class ChatHubConversationHook : ConversationHookBase ConversationId = conversationId, MessageId = messageId }; - await DeleteMessage(model); + + await DeleteMessage(conversationId, model); await base.OnMessageDeleted(conversationId, messageId); } @@ -169,34 +175,77 @@ public class ChatHubConversationHook : ConversationHookBase return sidecar == null || !sidecar.IsEnabled(); } - private async Task InitClientConversation(ConversationViewModel conversation) + private async Task InitClientConversation(string conversationId, ConversationViewModel conversation) { - await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(INIT_CLIENT_CONVERSATION, conversation); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation); + } } - private async Task ReceiveClientMessage(ChatResponseModel model) + private async Task ReceiveClientMessage(string conversationId, ChatResponseModel model) { - await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_CLIENT_MESSAGE, model); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_CLIENT_MESSAGE, model); + } } - private async Task ReceiveAssistantMessage(string? json) + private async Task ReceiveAssistantMessage(string conversationId, string? json) { - await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); + } + } - private async Task GenerateSenderAction(ConversationSenderActionModel action) + private async Task GenerateSenderAction(string conversationId, ConversationSenderActionModel action) { - await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_SENDER_ACTION, action); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_SENDER_ACTION, action); + } } - private async Task DeleteMessage(ChatResponseModel model) + private async Task DeleteMessage(string conversationId, ChatResponseModel model) { - await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(DELETE_MESSAGE, model); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(DELETE_MESSAGE, model); + } } - private async Task GenerateNotification(string? json) + private async Task GenerateNotification(string conversationId, string? json) { - await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(GENERATE_NOTIFICATION, json); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(GENERATE_NOTIFICATION, json); + } } #endregion } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs index 4f7ef3d2..a00dae90 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubCrontabHook.cs @@ -11,18 +11,25 @@ public class ChatHubCrontabHook : ICrontabHook private readonly IUserIdentity _user; private readonly IConversationStorage _storage; private readonly BotSharpOptions _options; + private readonly ChatHubSettings _settings; + + #region Events + private const string GENERATE_NOTIFICATION = "OnNotificationGenerated"; + #endregion public ChatHubCrontabHook(IServiceProvider services, IHubContext chatHub, IUserIdentity user, IConversationStorage storage, - BotSharpOptions options) + BotSharpOptions options, + ChatHubSettings settings) { _services = services; _chatHub = chatHub; _user = user; _storage = storage; _options = options; + _settings = settings; } public async Task OnCronTriggered(CrontabItem item) @@ -41,6 +48,13 @@ public class ChatHubCrontabHook : ICrontabHook } }, _options.JsonSerializerOptions); - await _chatHub.Clients.User(item.UserId).SendAsync("OnNotificationGenerated", json); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(item.ConversationId).SendAsync(GENERATE_NOTIFICATION, json); + } + else + { + await _chatHub.Clients.User(item.UserId).SendAsync(GENERATE_NOTIFICATION, json); + } } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 410589e7..5d28ee2e 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -9,6 +9,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR private readonly ConversationSetting _convSettings; private readonly BotSharpOptions _options; private readonly JsonSerializerOptions _localJsonOptions; + private readonly ChatHubSettings _settings; private readonly IServiceProvider _services; private readonly IHubContext _chatHub; private readonly IConversationStateService _state; @@ -16,7 +17,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR private readonly IAgentService _agentService; private readonly IRoutingContext _routingCtx; - #region Event + #region Events private const string CONTENT_LOG_GENERATED = "OnConversationContentLogGenerated"; private const string STATE_LOG_GENERATED = "OnConversateStateLogGenerated"; private const string AGENT_QUEUE_CHANGED = "OnAgentQueueChanged"; @@ -26,6 +27,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR public StreamingLogHook( ConversationSetting convSettings, BotSharpOptions options, + ChatHubSettings settings, IServiceProvider serivces, IHubContext chatHub, IConversationStateService state, @@ -35,6 +37,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR { _convSettings = convSettings; _options = options; + _settings = settings; _services = serivces; _chatHub = chatHub; _state = state; @@ -58,7 +61,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) @@ -76,7 +79,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.UserInput, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnRenderingTemplate(Agent agent, string name, string content) @@ -98,7 +101,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task BeforeGenerating(Agent agent, List conversations) @@ -126,7 +129,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public override async Task OnFunctionExecuted(RoleDialogModel message) @@ -147,7 +150,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } /// @@ -174,7 +177,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.Prompt, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } /// @@ -208,7 +211,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } } @@ -226,7 +229,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public override async Task OnConversationEnding(RoleDialogModel message) @@ -243,7 +246,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.FunctionCall, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public override async Task OnBreakpointUpdated(string conversationId, bool resetStates) @@ -271,7 +274,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR }, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public override async Task OnStateChanged(StateChangeModel stateChange) @@ -281,7 +284,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR if (stateChange == null) return; - await SendStateChange(stateChange); + await SendStateChange(conversationId, stateChange); } #endregion @@ -310,7 +313,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnAgentDequeued(string agentId, string currentAgentId, string? reason = null) @@ -338,7 +341,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnAgentReplaced(string fromAgentId, string toAgentId, string? reason = null) @@ -366,7 +369,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnAgentQueueEmptied(string agentId, string? reason = null) @@ -391,7 +394,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnRoutingInstructionReceived(FunctionCallFromLlm instruct, RoleDialogModel message) @@ -410,7 +413,7 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.AgentResponse, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } public async Task OnRoutingInstructionRevised(FunctionCallFromLlm instruct, RoleDialogModel message) @@ -428,32 +431,62 @@ public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook, IR Source = ContentLogSource.HardRule, Log = log }; - await SendContentLog(input); + await SendContentLog(conversationId, input); } #endregion #region Private methods - private async Task SendContentLog(ContentLogInputModel input) + private async Task SendContentLog(string conversationId, ContentLogInputModel input) { - await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input)); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input)); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(CONTENT_LOG_GENERATED, BuildContentLog(input)); + } } private async Task SendStateLog(string conversationId, string agentId, Dictionary states, RoleDialogModel message) { - await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message)); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message)); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(STATE_LOG_GENERATED, BuildStateLog(conversationId, agentId, states, message)); + } } private async Task SendAgentQueueLog(string conversationId, string log) { - await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log)); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log)); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(AGENT_QUEUE_CHANGED, BuildAgentQueueChangedLog(conversationId, log)); + } } - private async Task SendStateChange(StateChangeModel stateChange) + private async Task SendStateChange(string conversationId, StateChangeModel stateChange) { - await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange)); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversationId).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange)); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(STATE_CHANGED, BuildStateChangeLog(stateChange)); + } } + + private string BuildContentLog(ContentLogInputModel input) { var output = new ContentLogOutputModel diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs index 43bc839a..c7aedd83 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/WelcomeHook.cs @@ -9,18 +9,25 @@ public class WelcomeHook : ConversationHookBase private readonly IUserIdentity _user; private readonly IConversationStorage _storage; private readonly BotSharpOptions _options; + private readonly ChatHubSettings _settings; + + #region Events + private const string RECEIVE_ASSISTANT_MESSAGE = "OnMessageReceivedFromAssistant"; + #endregion public WelcomeHook(IServiceProvider services, IHubContext chatHub, IUserIdentity user, IConversationStorage storage, - BotSharpOptions options) + BotSharpOptions options, + ChatHubSettings settings) { _services = services; _chatHub = chatHub; _user = user; _storage = storage; _options = options; + _settings = settings; } public override async Task OnUserAgentConnectedInitially(Conversation conversation) @@ -71,7 +78,14 @@ public class WelcomeHook : ConversationHookBase _storage.Append(conversation.Id, dialog); - await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json); + if (_settings.EventDispatchBy == EventDispatchType.Group) + { + await _chatHub.Clients.Group(conversation.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); + } + else + { + await _chatHub.Clients.User(_user.Id).SendAsync(RECEIVE_ASSISTANT_MESSAGE, json); + } } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Settings/ChatHubSettings.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Settings/ChatHubSettings.cs new file mode 100644 index 00000000..70eaa6a5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Settings/ChatHubSettings.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Plugin.ChatHub.Settings; + +public class ChatHubSettings +{ + public string EventDispatchBy { get; set; } = EventDispatchType.Group; +} diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs b/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs index 0593b94f..240b5993 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/SignalRHub.cs @@ -33,6 +33,13 @@ public class SignalRHub : Hub if (!string.IsNullOrEmpty(conversationId)) { _logger.LogInformation($"Connection {Context.ConnectionId} is with conversation {conversationId}"); + + var settings = _services.GetRequiredService(); + if (settings.EventDispatchBy == EventDispatchType.Group) + { + await Groups.AddToGroupAsync(Context.ConnectionId, conversationId); + } + var conv = await convService.GetConversation(conversationId); if (conv != null) { diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs index 7cc89c9d..bc50d257 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Using.cs @@ -31,4 +31,6 @@ global using BotSharp.Abstraction.Routing; global using BotSharp.Abstraction.Messaging; global using BotSharp.Abstraction.Messaging.Enums; global using BotSharp.Abstraction.Messaging.Models.RichContent; -global using BotSharp.Abstraction.Templating; \ No newline at end of file +global using BotSharp.Abstraction.Templating; +global using BotSharp.Plugin.ChatHub.Settings; +global using BotSharp.Plugin.ChatHub.Enums; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs index 126dfee8..643806a5 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Models/DialogMongoElement.cs @@ -11,6 +11,7 @@ public class DialogMongoElement public string? RichContent { get; set; } public string? SecondaryRichContent { get; set; } public string? Payload { get; set; } + public object? Data { get; set; } public DialogMongoElement() { @@ -26,7 +27,8 @@ public class DialogMongoElement SecondaryContent = dialog.SecondaryContent, RichContent = dialog.RichContent, SecondaryRichContent = dialog.SecondaryRichContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }; } @@ -39,7 +41,8 @@ public class DialogMongoElement SecondaryContent = dialog.SecondaryContent, RichContent = dialog.RichContent, SecondaryRichContent = dialog.SecondaryRichContent, - Payload = dialog.Payload + Payload = dialog.Payload, + Data = dialog.Data }; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index cabaecd2..64c8bb53 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -614,20 +614,20 @@ public partial class MongoRepository #if !DEBUG [SharpCache(10)] #endif - public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) + public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) { - var convFilter = Builders.Filter.Gte(x => x.DialogCount, messageLowerLimit); - var conversations = _dc.Conversations.Find(convFilter) - .SortByDescending(x => x.UpdatedTime) - .Limit(convUpperlimit) - .ToList(); + var stateBuilder = Builders.Filter; + var sortDef = Builders.Sort.Descending(x => x.UpdatedTime); + var stateFilters = new List>() + { + stateBuilder.Exists(x => x.States), + stateBuilder.Ne(x => x.States, []) + }; - if (conversations.IsNullOrEmpty()) return []; - - var convIds = conversations.Select(x => x.Id).ToList(); - var stateFilter = Builders.Filter.In(x => x.ConversationId, convIds); - - var states = _dc.ConversationStates.Find(stateFilter).ToList(); + var states = _dc.ConversationStates.Find(stateBuilder.And(stateFilters)) + .Sort(sortDef) + .Limit(convUpperLimit) + .ToList(); var keys = states.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList(); return keys; } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 655fa954..a0f02893 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -222,6 +222,10 @@ "Enabled": false }, + "ChatHub": { + "EventDispatchBy": "group" + }, + "SharpCache": { "Enabled": true, "CacheType": 1,