From 805dc63ea8642a549f6370197434410c8741a402 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 29 Oct 2024 14:59:46 -0500 Subject: [PATCH 01/13] init conv side car --- .../Conversations/IConversationSideCar.cs | 11 ++ .../IConversationStateService.cs | 4 + .../Models/ConversationContext.cs | 10 ++ .../Routing/IRoutingContext.cs | 11 ++ .../Routing/IRoutingService.cs | 6 +- .../Conversations/ConversationPlugin.cs | 1 + .../ConversationService.SendMessage.cs | 3 +- .../ConversationService.UpdateBreakpoint.cs | 10 +- .../Services/ConversationService.cs | 13 +- .../Services/ConversationSideCar.cs | 154 ++++++++++++++++++ .../Services/ConversationStateService.cs | 17 ++ .../Services/ConversationStorage.cs | 14 +- .../FileRepository/FileRepository.Agent.cs | 4 +- .../Routing/Planning/SequentialPlanner.cs | 2 +- .../BotSharp.Core/Routing/RoutingContext.cs | 41 ++++- .../Routing/RoutingService.InvokeAgent.cs | 14 +- .../BotSharp.Core/Routing/RoutingService.cs | 21 ++- .../Hooks/ChatHubConversationHook.cs | 12 ++ .../TwoStaging/TwoStageTaskPlanner.cs | 2 +- 19 files changed, 318 insertions(+), 32 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationContext.cs create mode 100644 src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs new file mode 100644 index 00000000..569555c2 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Abstraction.Conversations; + +public interface IConversationSideCar +{ + bool IsEnabled(); + void AppendConversationDialogs(string conversationId, List messages); + List GetConversationDialogs(string conversationId); + void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); + ConversationBreakpoint? GetConversationBreakpoint(string conversationId); + Task Execute(string conversationId, string agentId, string text, PostbackMessageModel? postback = null, List? states = null); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs index de26ef5b..df123922 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs @@ -19,4 +19,8 @@ public interface IConversationStateService bool RemoveState(string name); void CleanStates(params string[] excludedStates); void Save(); + + ConversationState GetCurrentState(); + void SetCurrentState(ConversationState state); + void ResetCurrentState(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationContext.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationContext.cs new file mode 100644 index 00000000..0b0b0846 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationContext.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Conversations.Models; + +public class ConversationContext +{ + public ConversationState State { get; set; } + public List Dialogs { get; set; } = new(); + public List Breakpoints { get; set; } = new(); + public int RecursiveCounter { get; set; } + public Stack RoutingStack { get; set; } = new(); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs index 3c93976b..854832c6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs @@ -17,4 +17,15 @@ public interface IRoutingContext void PopTo(string agentId, string reason); void Replace(string agentId, string? reason = null); void Empty(string? reason = null); + + + int CurrentRecursionDepth { get; } + int GetRecursiveCounter(); + int IncreaseRecursiveCounter(); + void SetRecursiveCounter(int counter); + void ResetRecursiveCounter(); + + Stack GetAgentStack(); + void SetAgentStack(Stack stack); + void ResetAgentStack(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 3b221f4d..c0f7c81c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -27,7 +27,11 @@ public interface IRoutingService RoutingRule[] GetRulesByAgentId(string id); List GetHandlers(Agent router); - void ResetRecursiveCounter(); + + //void ResetRecursiveCounter(); + //int GetRecursiveCounter(); + //void SetRecursiveCounter(int counter); + Task InvokeAgent(string agentId, List dialogs); Task InvokeFunction(string name, RoleDialogModel messages); Task InstructLoop(RoleDialogModel message, List dialogs); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index a9be6fd2..7db04623 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -43,6 +43,7 @@ public class ConversationPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); // Rich content messaging diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 59ce88c2..375cf3e6 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,7 +1,6 @@ using BotSharp.Abstraction.Messaging; using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Routing.Settings; -using BotSharp.Core.Routing.Planning; namespace BotSharp.Core.Conversations.Services; @@ -90,7 +89,7 @@ public partial class ConversationService response = await routing.InstructDirect(agent, message); } - routing.ResetRecursiveCounter(); + routing.Context.ResetRecursiveCounter(); } await HandleAssistantMessage(response, onMessageReceived); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs index 8f88f44f..618095e4 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs @@ -10,7 +10,15 @@ public partial class ConversationService : IConversationService var routingCtx = _services.GetRequiredService(); var messageId = routingCtx.MessageId; - db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint + //db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint + //{ + // MessageId = messageId, + // Breakpoint = DateTime.UtcNow, + // Reason = reason + //}); + + var sidecar = _services.GetRequiredService(); + sidecar.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint { MessageId = messageId, Breakpoint = DateTime.UtcNow, diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 070f4c59..302107ab 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -98,6 +98,7 @@ public partial class ConversationService : IConversationService var record = sess; record.Id = sess.Id.IfNullOrEmptyAs(Guid.NewGuid().ToString()); record.UserId = sess.UserId.IfNullOrEmptyAs(foundUserId); + record.Tags = sess.Tags; record.Title = "New Conversation"; db.CreateNewConversation(record); @@ -139,8 +140,12 @@ public partial class ConversationService : IConversationService if (fromBreakpoint) { - var db = _services.GetRequiredService(); - var breakpoint = db.GetConversationBreakpoint(_conversationId); + //var db = _services.GetRequiredService(); + //var breakpoint = db.GetConversationBreakpoint(_conversationId); + + var sidecar = _services.GetRequiredService(); + var breakpoint = sidecar.GetConversationBreakpoint(_conversationId); + if (breakpoint != null) { dialogs = dialogs.Where(x => x.CreatedAt >= breakpoint.Breakpoint).ToList(); @@ -151,9 +156,7 @@ public partial class ConversationService : IConversationService } } - return dialogs - .TakeLast(lastCount) - .ToList(); + return dialogs.TakeLast(lastCount).ToList(); } public void SetConversationId(string conversationId, List states, bool isReadOnly = false) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs new file mode 100644 index 00000000..903b8ee0 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs @@ -0,0 +1,154 @@ +using BotSharp.Abstraction.Conversations.Enums; +using BotSharp.Abstraction.Models; + +namespace BotSharp.Core.Conversations.Services; + +public class ConversationSideCar : IConversationSideCar +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + private Stack contextStack = new(); + + private bool enabled = false; + + public ConversationSideCar( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public bool IsEnabled() + { + return enabled; + } + + public void AppendConversationDialogs(string conversationId, List messages) + { + if (enabled) + { + var top = contextStack.Peek(); + top.Dialogs.AddRange(messages); + } + else + { + var db = _services.GetRequiredService(); + db.AppendConversationDialogs(conversationId, messages); + } + } + + public List GetConversationDialogs(string conversationId) + { + if (enabled) + { + return contextStack.Peek().Dialogs; + } + else + { + var db = _services.GetRequiredService(); + return db.GetConversationDialogs(conversationId); + } + } + + public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) + { + if (enabled) + { + var top = contextStack.Peek().Breakpoints; + top.Add(breakpoint); + } + else + { + var db = _services.GetRequiredService(); + db.UpdateConversationBreakpoint(conversationId, breakpoint); + } + } + + public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) + { + if (enabled) + { + var top = contextStack.Peek().Breakpoints; + return top.LastOrDefault(); + } + else + { + var db = _services.GetRequiredService(); + return db.GetConversationBreakpoint(conversationId); + } + } + + public async Task Execute(string conversationId, string agentId, string text, + PostbackMessageModel? postback = null, List? states = null) + { + BeforeExecute(); + var response = await InnerExecute(agentId, text, postback, states); + AfterExecute(); + return response; + } + + private async Task InnerExecute(string agentId, string text, + PostbackMessageModel? postback = null, List? states = null) + { + var conv = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + + var inputMsg = new RoleDialogModel(AgentRole.User, text); + routing.Context.SetMessageId(conv.ConversationId, inputMsg.MessageId); + states?.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); + + var response = new RoleDialogModel(AgentRole.Assistant, string.Empty); + await conv.SendMessage(agentId, inputMsg, + replyMessage: postback, + async msg => + { + response.Content = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content; + response.FunctionName = msg.FunctionName; + response.RichContent = msg.SecondaryRichContent ?? msg.RichContent; + response.Instruction = msg.Instruction; + response.Data = msg.Data; + }); + + return response; + } + + private void BeforeExecute() + { + enabled = true; + var state = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + + var node = new ConversationContext + { + State = state.GetCurrentState(), + Dialogs = new(), + Breakpoints = new(), + RecursiveCounter = routing.Context.GetRecursiveCounter(), + RoutingStack = routing.Context.GetAgentStack() + }; + contextStack.Push(node); + + // Reset + state.ResetCurrentState(); + routing.Context.ResetRecursiveCounter(); + routing.Context.ResetAgentStack(); + + } + + private void AfterExecute() + { + var state = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + + var node = contextStack.Pop(); + + // Recover + state.SetCurrentState(node.State); + routing.Context.SetRecursiveCounter(node.RecursiveCounter); + routing.Context.SetAgentStack(node.RoutingStack); + enabled = false; + } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 7a3d1d5d..f6c146a8 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -384,4 +384,21 @@ public class ConversationStateService : IConversationStateService, IDisposable } return true; } + + public ConversationState GetCurrentState() + { + var values = _curStates.Values.ToList(); + var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); + return new ConversationState(copy ?? new()); + } + + public void SetCurrentState(ConversationState state) + { + _curStates = state; + } + + public void ResetCurrentState() + { + _curStates.Clear(); + } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index ea145ac0..1ce112ca 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -91,13 +91,21 @@ public class ConversationStorage : IConversationStorage }); } - db.AppendConversationDialogs(conversationId, dialogElements); + //db.AppendConversationDialogs(conversationId, dialogElements); + + var sidecar = _services.GetRequiredService(); + sidecar.AppendConversationDialogs(conversationId, dialogElements); + } public List GetDialogs(string conversationId) { - var db = _services.GetRequiredService(); - var dialogs = db.GetConversationDialogs(conversationId); + //var db = _services.GetRequiredService(); + //var dialogs = db.GetConversationDialogs(conversationId); + + var sidecar = _services.GetRequiredService(); + var dialogs = sidecar.GetConversationDialogs(conversationId); + var hooks = _services.GetServices(); var results = new List(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index bb137279..7f9a7b81 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -188,7 +188,7 @@ namespace BotSharp.Core.Repository // Save default instructions var instructionFile = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}"); File.WriteAllText(instructionFile, instruction ?? string.Empty); - Thread.Sleep(100); + Thread.Sleep(50); // Save channel instructions foreach (var ci in channelInstructions) @@ -197,7 +197,7 @@ namespace BotSharp.Core.Repository var file = Path.Combine(instructionDir, $"{AGENT_INSTRUCTION_FILE}.{ci.Channel}.{_agentSettings.TemplateFormat}"); File.WriteAllText(file, ci.Instruction ?? string.Empty); - Thread.Sleep(100); + Thread.Sleep(50); } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs index 93fa2f3b..f6b05375 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Planning/SequentialPlanner.cs @@ -147,7 +147,7 @@ public class SequentialPlanner : IRoutingPlaner context.Pop(); var routing = _services.GetRequiredService(); - routing.ResetRecursiveCounter(); + routing.Context.ResetRecursiveCounter(); return true; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index a77397d0..3450d41e 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Routing.Settings; -using BotSharp.Abstraction.Utilities; namespace BotSharp.Core.Routing; @@ -10,6 +9,7 @@ public class RoutingContext : IRoutingContext private string[] _routerAgentIds; private string _conversationId; private string _messageId; + private int _currentRecursionDepth = 0; public RoutingContext(IServiceProvider services, RoutingSettings setting) { @@ -20,9 +20,9 @@ public class RoutingContext : IRoutingContext public int AgentCount => _stack.Count; public string ConversationId => _conversationId; public string MessageId => _messageId; + public int CurrentRecursionDepth => _currentRecursionDepth; - private Stack _stack { get; set; } - = new Stack(); + private Stack _stack { get; set; } = new(); /// /// Intent name @@ -208,4 +208,39 @@ public class RoutingContext : IRoutingContext _conversationId = conversationId; _messageId = messageId; } + + public int GetRecursiveCounter() + { + return _currentRecursionDepth; + } + + public int IncreaseRecursiveCounter() + { + return _currentRecursionDepth; + } + + public void SetRecursiveCounter(int counter) + { + _currentRecursionDepth = counter; + } + + public void ResetRecursiveCounter() + { + _currentRecursionDepth = 0; + } + + public Stack GetAgentStack() + { + return new Stack(_stack); + } + + public void SetAgentStack(Stack stack) + { + _stack = new Stack(stack); + } + + public void ResetAgentStack() + { + _stack.Clear(); + } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index ccd708c3..25ab3552 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -4,14 +4,15 @@ namespace BotSharp.Core.Routing; public partial class RoutingService { - private int _currentRecursionDepth = 0; + //private int _currentRecursionDepth = 0; public async Task InvokeAgent(string agentId, List dialogs) { var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(agentId); - _currentRecursionDepth++; - if (_currentRecursionDepth > agent.LlmConfig.MaxRecursionDepth) + //_currentRecursionDepth++; + Context.IncreaseRecursiveCounter(); + if (Context.CurrentRecursionDepth > agent.LlmConfig.MaxRecursionDepth) { _logger.LogWarning($"Current recursive call depth greater than {agent.LlmConfig.MaxRecursionDepth}, which will cause unexpected result."); return false; @@ -36,8 +37,7 @@ public partial class RoutingService if (response.Role == AgentRole.Function) { - message = RoleDialogModel.From(message, - role: AgentRole.Function); + message = RoleDialogModel.From(message, role: AgentRole.Function); if (response.FunctionName != null && response.FunctionName.Contains("/")) { response.FunctionName = response.FunctionName.Split("/").Last(); @@ -57,9 +57,7 @@ public partial class RoutingService response.Content = "Apologies, I'm not quite sure I understand. Could you please provide additional clarification or context?"; } - message = RoleDialogModel.From(message, - role: AgentRole.Assistant, - content: response.Content); + message = RoleDialogModel.From(message, role: AgentRole.Assistant, content: response.Content); message.CurrentAgentId = agent.Id; dialogs.Add(message); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index b7c489e1..770acaf9 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -16,12 +16,23 @@ public partial class RoutingService : IRoutingService public IRoutingContext Context => _context; public Agent Router => _router; - public void ResetRecursiveCounter() - { - _currentRecursionDepth = 0; - } + //public int GetRecursiveCounter() + //{ + // return _currentRecursionDepth; + //} - public RoutingService(IServiceProvider services, + //public void SetRecursiveCounter(int counter) + //{ + // _currentRecursionDepth = counter; + //} + + //public void ResetRecursiveCounter() + //{ + // _currentRecursionDepth = 0; + //} + + public RoutingService( + IServiceProvider services, RoutingSettings settings, IRoutingContext context, ILogger logger) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 29c6df82..2d693603 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -32,6 +32,8 @@ public class ChatHubConversationHook : ConversationHookBase public override async Task OnConversationInitialized(Conversation conversation) { + if (!AllowSendingMessage()) return; + var userService = _services.GetRequiredService(); var conv = ConversationViewModel.FromSession(conversation); @@ -44,6 +46,8 @@ public class ChatHubConversationHook : ConversationHookBase public override async Task OnMessageReceived(RoleDialogModel message) { + if (!AllowSendingMessage()) return; + var conv = _services.GetRequiredService(); var userService = _services.GetRequiredService(); var sender = await userService.GetMyProfile(); @@ -90,6 +94,8 @@ public class ChatHubConversationHook : ConversationHookBase public override async Task OnResponseGenerated(RoleDialogModel message) { + if (!AllowSendingMessage()) return; + var conv = _services.GetRequiredService(); var json = JsonSerializer.Serialize(new ChatResponseModel() { @@ -156,6 +162,12 @@ public class ChatHubConversationHook : ConversationHookBase } #region Private methods + private bool AllowSendingMessage() + { + var sidecar = _services.GetRequiredService(); + return !sidecar.IsEnabled(); + } + private async Task InitClientConversation(ConversationViewModel conversation) { await _chatHub.Clients.User(_user.Id).SendAsync(INIT_CLIENT_CONVERSATION, conversation); diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs index 2ecd9ac0..565115a5 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/TwoStageTaskPlanner.cs @@ -92,7 +92,7 @@ public partial class TwoStageTaskPlanner : IRoutingPlaner } var routing = _services.GetRequiredService(); - routing.ResetRecursiveCounter(); + routing.Context.ResetRecursiveCounter(); return true; } From 170d9ffc1e050254b1073a591c5bf7e636ebf45c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Oct 2024 14:36:26 -0500 Subject: [PATCH 02/13] temp save --- .../Agents/IAgentService.cs | 2 +- .../Agents/Models/UserAgent.cs | 20 ++- .../Repositories/IBotSharpRepository.cs | 6 +- .../Users/Enums/UserAction.cs | 7 + .../Users/Enums/UserConstant.cs | 10 ++ .../Users/Enums/UserPermission.cs | 6 + .../Users/Enums/UserRole.cs | 2 +- .../Users/IUserService.cs | 1 + .../BotSharp.Abstraction/Users/Models/User.cs | 4 + .../Users/Models/UserAgentAction.cs | 16 ++ .../Users/Models/UserFilter.cs | 19 +++ .../BotSharp.Core/Agents/AgentPlugin.cs | 2 +- .../Services/AgentService.CreateAgent.cs | 39 +---- .../Services/AgentService.DeleteAgent.cs | 5 +- .../Services/AgentService.RefreshAgents.cs | 2 - .../Services/AgentService.UpdateAgent.cs | 13 +- .../Agents/Services/AgentService.cs | 8 +- .../Repository/BotSharpDbContext.cs | 61 +------ .../FileRepository/FileRepository.Agent.cs | 32 ++-- .../FileRepository.Transaction.cs | 82 --------- .../FileRepository/FileRepository.User.cs | 62 +++++++ .../BotSharp.Core/Tasks/TaskPlugin.cs | 2 +- .../Users/Services/UserService.cs | 7 + .../Controllers/AgentController.cs | 34 +++- .../Controllers/ConversationController.cs | 8 +- .../Controllers/PluginController.cs | 12 +- .../Controllers/UserController.cs | 33 +++- .../ViewModels/Agents/AgentViewModel.cs | 1 + .../Users/UserAgentActionViewModel.cs | 30 ++++ .../ViewModels/Users/UserViewModel.cs | 11 ++ .../Collections/UserAgentDocument.cs | 3 +- .../Collections/UserDocument.cs | 2 + .../MongoStoragePlugin.cs | 2 +- .../Repository/MongoRepository.Agent.cs | 45 +++-- .../Repository/MongoRepository.Transaction.cs | 157 ------------------ .../Repository/MongoRepository.User.cs | 85 ++++++++++ .../Repository/MongoRepository.cs | 9 - 37 files changed, 435 insertions(+), 405 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs delete mode 100644 src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 1fd7a38c..1461cc22 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -55,7 +55,7 @@ public interface IAgentService string GetDataDir(); string GetAgentDataDir(string agentId); - List GetAgentsByUser(string userId); + Task> GetUserAgents(string userId); PluginDef GetPlugin(string agentId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs index b6eab94d..a130f9b2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs @@ -1,11 +1,27 @@ +using BotSharp.Abstraction.Users.Models; + namespace BotSharp.Abstraction.Agents.Models; public class UserAgent { + [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_id")] public string UserId { get; set; } = string.Empty; - public string AgentId { get; set; } = string.Empty; - public bool Editable { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("updated_time")] public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName("created_time")] public DateTime CreatedTime { get; set; } = DateTime.UtcNow; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 1471093b..a2175cc1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -10,9 +10,6 @@ namespace BotSharp.Abstraction.Repositories; public interface IBotSharpRepository { - int Transaction(Action action); - void Add(object entity); - #region Plugin PluginConfig GetPluginConfig(); void SavePluginConfig(PluginConfig config); @@ -35,13 +32,14 @@ public interface IBotSharpRepository void UpdateUserPhone(string userId, string Iphone) => throw new NotImplementedException(); void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); + PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); #endregion #region Agent void UpdateAgent(Agent agent, AgentField field); Agent? GetAgent(string agentId); List GetAgents(AgentFilter filter); - List GetAgentsByUser(string userId); + List GetUserAgents(string userId); void BulkInsertAgents(List agents); void BulkInsertUserAgents(List userAgents); bool DeleteAgents(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs new file mode 100644 index 00000000..4838e757 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserAction +{ + public const string Edit = "edit"; + public const string Chat = "chat"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs new file mode 100644 index 00000000..9fde5376 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserConstant +{ + public static IEnumerable AdminRoles = new List + { + UserRole.Admin, + UserRole.Root + }; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs new file mode 100644 index 00000000..58dfc186 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserPermission +{ + public const string CreateAgent = "create-agent"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs index cddabe10..0bde3b08 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs @@ -35,4 +35,4 @@ public class UserRole public const string Assistant = "assistant"; public const string Root = "root"; -} +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 421e217b..94f30aad 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -6,6 +6,7 @@ namespace BotSharp.Abstraction.Users; public interface IUserService { Task GetUser(string id); + Task> GetUsers(UserFilter filter); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); Task GetAffiliateToken(string authorization); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs index cb48701f..4b309733 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs @@ -23,6 +23,10 @@ public class User public bool Verified { get; set; } public string? AffiliateId { get; set; } public bool IsDisabled { get; set; } + public IEnumerable Permissions { get; set; } = []; + + [JsonIgnore] + public IEnumerable AgentActions { get; set; } = []; public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs new file mode 100644 index 00000000..1ac759a1 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class UserAgentAction +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs new file mode 100644 index 00000000..73e1794d --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs @@ -0,0 +1,19 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class UserFilter : Pagination +{ + [JsonPropertyName("user_ids")] + public IEnumerable? UserIds { get; set; } + + [JsonPropertyName("user_names")] + public IEnumerable? UserNames { get; set; } + + [JsonPropertyName("external_ids")] + public IEnumerable? ExternalIds { get; set; } + + [JsonPropertyName("roles")] + public IEnumerable? Roles { get; set; } + + [JsonPropertyName("sources")] + public IEnumerable? Sources { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs index 43bd9c7f..2f51f1dd 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs @@ -45,7 +45,7 @@ public class AgentPlugin : IBotSharpPlugin SubMenu = new List { new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin" - new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-task" + new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Root, UserRole.Admin } }, // icon: "bx bx-task" new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot" } }); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index 9e2e1d5f..3c9e6bf2 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -8,14 +8,14 @@ public partial class AgentService { public async Task CreateAgent(Agent agent) { - var agentRecord = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Name.IsEqualTo(agent.Name)); - - if (agentRecord != null) + var userAgents = _db.GetUserAgents(_user.Id); + var found = userAgents?.FirstOrDefault(x => x.Agent != null && x.Agent.Name.IsEqualTo(agent.Name)); + if (found != null) { - return agentRecord; + return found.Agent; } - agentRecord = Agent.Clone(agent); + var agentRecord = Agent.Clone(agent); agentRecord.Id = Guid.NewGuid().ToString(); agentRecord.CreatedDateTime = DateTime.UtcNow; agentRecord.UpdatedDateTime = DateTime.UtcNow; @@ -24,21 +24,7 @@ public partial class AgentService var agentSettings = _services.GetRequiredService(); var user = _db.GetUserById(_user.Id); - var userAgentRecord = new UserAgent - { - Id = Guid.NewGuid().ToString(), - UserId = user.Id, - AgentId = agentRecord.Id, - Editable = false, - CreatedTime = DateTime.UtcNow, - UpdatedTime = DateTime.UtcNow - }; - - _db.Transaction(delegate - { - _db.Add(agentRecord); - _db.Add(userAgentRecord); - }); + _db.BulkInsertAgents(new List { agentRecord }); Utilities.ClearCache(); return await Task.FromResult(agentRecord); @@ -213,17 +199,4 @@ public partial class AgentService task.Content = content.Substring(suffix.Length).Trim(); return task; } - - private UserAgent BuildUserAgent(string agentId, string userId, bool editable = false) - { - return new UserAgent - { - Id = Guid.NewGuid().ToString(), - UserId = userId, - AgentId = agentId, - Editable = editable, - CreatedTime = DateTime.UtcNow, - UpdatedTime = DateTime.UtcNow - }; - } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs index 1fe5c6e1..8a05542e 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs @@ -7,9 +7,10 @@ public partial class AgentService public async Task DeleteAgent(string id) { var user = _db.GetUserById(_user.Id); - var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id)); + var userAgents = await GetUserAgents(user?.Id); + var found = userAgents?.FirstOrDefault(x => x.AgentId == id); - if (user?.Role != UserRole.Admin && agent == null) + if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit))) { return false; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index 10598d4e..e4a311d2 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -54,7 +54,6 @@ public partial class AgentService .SetResponses(responses) .SetSamples(samples); - var userAgent = BuildUserAgent(agent.Id, user.Id); var tasks = GetTasksFromFile(dir); var isAgentDeleted = _db.DeleteAgent(agent.Id); @@ -62,7 +61,6 @@ public partial class AgentService { await Task.Delay(100); _db.BulkInsertAgents(new List { agent }); - _db.BulkInsertUserAgents(new List { userAgent }); _db.BulkInsertAgentTasks(tasks); refreshedAgents.Add(agent.Name); _logger.LogInformation($"Agent {agent.Name} has been migrated."); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index fe532e28..17aa4aa7 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -9,13 +9,18 @@ public partial class AgentService { public async Task UpdateAgent(Agent agent, AgentField updateField) { + if (agent == null || string.IsNullOrEmpty(agent.Id)) return; + var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - var userAgents = GetAgentsByUser(user?.Id); - var editable = userAgents?.Select(x => x.Id)?.Contains(agent.Id) ?? false; - if (user?.Role != UserRole.Admin && !editable) return; - if (agent == null || string.IsNullOrEmpty(agent.Id)) return; + var userAgents = await GetUserAgents(user.Id); + var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id); + + if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit))) + { + return; + } var record = _db.GetAgent(agent.Id); if (record == null) return; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index b69f7529..a48201d8 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -49,10 +49,12 @@ public partial class AgentService : IAgentService return dir; } - public List GetAgentsByUser(string userId) + public async Task> GetUserAgents(string userId) { - var agents = _db.GetAgentsByUser(userId); - return agents; + if (string.IsNullOrEmpty(userId)) return []; + + var userAgents = _db.GetUserAgents(userId); + return userAgents; } public IEnumerable GetAgentUtilities() diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index a4131e64..80c37f7a 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -2,71 +2,12 @@ using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Translation.Models; -using BotSharp.Abstraction.Users.Models; using BotSharp.Abstraction.VectorStorage.Models; -using Microsoft.EntityFrameworkCore.Infrastructure; namespace BotSharp.Core.Repository; public class BotSharpDbContext : Database, IBotSharpRepository { - public IQueryable Users => throw new NotImplementedException(); - - public IQueryable Agents => throw new NotImplementedException(); - - public IQueryable UserAgents => throw new NotImplementedException(); - - public IQueryable Conversations => throw new NotImplementedException(); - - public int Transaction(Action action) - { - DatabaseFacade database = base.GetMaster(typeof(TTableInterface)).Database; - int num = 0; - if (database.CurrentTransaction == null) - { - using (Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction dbContextTransaction = database.BeginTransaction()) - { - try - { - action(); - num = base.SaveChanges(); - dbContextTransaction.Commit(); - return num; - } - catch (Exception ex) - { - dbContextTransaction.Rollback(); - if (ex.Message.Contains("See the inner exception for details")) - { - throw ex.InnerException; - } - - throw ex; - } - } - } - - try - { - action(); - return base.SaveChanges(); - } - catch (Exception ex2) - { - if (database.CurrentTransaction != null) - { - database.CurrentTransaction.Rollback(); - } - - if (ex2.Message.Contains("See the inner exception for details")) - { - throw ex2.InnerException; - } - - throw ex2; - } - } - #region Plugin public PluginConfig GetPluginConfig() => throw new NotImplementedException(); public void SavePluginConfig(PluginConfig config) => throw new NotImplementedException(); @@ -79,7 +20,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository public List GetAgents(AgentFilter filter) => throw new NotImplementedException(); - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) => throw new NotImplementedException(); public void UpdateAgent(Agent agent, AgentField field) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index bb137279..71e9ea13 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,5 +1,5 @@ -using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Routing.Models; +using BotSharp.Abstraction.Users.Models; using System.IO; namespace BotSharp.Core.Repository @@ -386,19 +386,26 @@ namespace BotSharp.Core.Repository return query.ToList(); } - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) { - var agentIds = (from ua in UserAgents - join u in Users on ua.UserId equals u.Id - where ua.UserId == userId || u.ExternalId == userId - select ua.AgentId).ToList(); + var found = (from ua in UserAgents + join u in Users on ua.UserId equals u.Id + where ua.UserId == userId || u.ExternalId == userId + select ua).ToList(); - var filter = new AgentFilter + if (found.IsNullOrEmpty()) return []; + + var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in found) { - AgentIds = agentIds - }; - var agents = GetAgents(filter); - return agents; + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + return found; } @@ -473,14 +480,13 @@ namespace BotSharp.Core.Repository var userAgents = JsonSerializer.Deserialize>(text, _options); if (userAgents.IsNullOrEmpty()) continue; - userAgents = userAgents.Where(x => x.AgentId != agentId).ToList(); + userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); } } // Delete agent folder Directory.Delete(agentDir, true); - return true; } catch diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs deleted file mode 100644 index 13152f93..00000000 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs +++ /dev/null @@ -1,82 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Users.Models; -using System.IO; - -namespace BotSharp.Core.Repository; - -public partial class FileRepository -{ - public void Add(object entity) - { - if (entity is Agent agent) - { - _agents.Add(agent); - _changedTableNames.Add(nameof(Agent)); - } - else if (entity is User user) - { - _users.Add(user); - _changedTableNames.Add(nameof(User)); - } - else if (entity is UserAgent userAgent) - { - _userAgents.Add(userAgent); - _changedTableNames.Add(nameof(UserAgent)); - } - } - - private readonly List _changedTableNames = new List(); - public int Transaction(Action action) - { - _changedTableNames.Clear(); - action(); - - // Persist to disk - foreach (var table in _changedTableNames) - { - if (table == nameof(Agent)) - { - foreach (var agent in _agents) - { - var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agent.Id); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - var path = Path.Combine(dir, AGENT_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(agent, _options)); - } - } - else if (table == nameof(User)) - { - foreach (var user in _users) - { - var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - var path = Path.Combine(dir, USER_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(user, _options)); - } - } - else if (table == nameof(UserAgent)) - { - _userAgents.GroupBy(x => x.UserId) - .Select(x => x.Key).ToList() - .ForEach(uid => - { - var agents = _userAgents.Where(x => x.UserId == uid).ToList(); - if (agents.Any()) - { - var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, uid); - var path = Path.Combine(dir, USER_AGENT_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(agents, _options)); - } - }); - } - } - - return _changedTableNames.Count; - } -} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 465ae961..0719509e 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; +using System; using System.IO; namespace BotSharp.Core.Repository; @@ -68,4 +69,65 @@ public partial class FileRepository var path = Path.Combine(dir, USER_FILE); File.WriteAllText(path, JsonSerializer.Serialize(user, _options)); } + + public PagedItems GetUsers(UserFilter filter) + { + var users = Users; + + // Apply filters + if (!filter.UserIds.IsNullOrEmpty()) + { + users = users.Where(x => filter.UserIds.Contains(x.Id)); + } + if (!filter.UserNames.IsNullOrEmpty()) + { + users = users.Where(x => filter.UserNames.Contains(x.UserName)); + } + if (!filter.ExternalIds.IsNullOrEmpty()) + { + users = users.Where(x => filter.ExternalIds.Contains(x.ExternalId)); + } + if (!filter.Roles.IsNullOrEmpty()) + { + users = users.Where(x => filter.Roles.Contains(x.Role)); + } + if (!filter.Sources.IsNullOrEmpty()) + { + users = users.Where(x => filter.Sources.Contains(x.Source)); + } + + // Get user agents + var userIds = users.Select(x => x.Id).ToList(); + var userAgents = UserAgents.Where(x => userIds.Contains(x.UserId)).ToList(); + var agentIds = userAgents?.Select(x => x.AgentId)?.Distinct()?.ToList() ?? []; + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in userAgents) + { + item.Agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + } + + foreach (var user in users) + { + var found = userAgents.Where(x => x.UserId == user.Id).ToList(); + if (found.IsNullOrEmpty()) continue; + + user.AgentActions = found.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Agent = x.Agent, + Actions = x.Actions + }); + } + } + + return new PagedItems + { + Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size), + Count = users.Count() + }; + } } diff --git a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs index 27c55ab5..7f907fd1 100644 --- a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs @@ -22,7 +22,7 @@ public class TaskPlugin : IBotSharpPlugin var section = menu.First(x => x.Label == "Apps"); menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }); return true; diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 10dbe8de..8b228dcd 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -377,6 +377,13 @@ public class UserService : IUserService return user; } + public async Task> GetUsers(UserFilter filter) + { + var db = _services.GetRequiredService(); + var users = db.GetUsers(filter); + return users; + } + public async Task ActiveUser(UserActivationModel model) { var id = model.UserName; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index f033e31c..6a24c584 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; namespace BotSharp.OpenAPI.Controllers; @@ -58,15 +59,19 @@ public class AgentController : ControllerBase } var editable = true; + var chatable = true; var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - if (user?.Role != UserRole.Admin) + if (!UserConstant.AdminRoles.Contains(user?.Role)) { - var userAgents = _agentService.GetAgentsByUser(user?.Id); - editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false; + var userAgents = await _agentService.GetUserAgents(user?.Id); + var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? []; + editable = actions.Contains(UserAction.Edit); + chatable = actions.Contains(UserAction.Chat); } targetAgent.Editable = editable; + targetAgent.Chatable = chatable; return targetAgent; } @@ -74,8 +79,29 @@ public class AgentController : ControllerBase public async Task> GetAgents([FromQuery] AgentFilter filter) { var agentSetting = _services.GetRequiredService(); + var userService = _services.GetRequiredService(); + var pagedAgents = await _agentService.GetAgents(filter); - var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List(); + var userAgents = new List(); + var user = await userService.GetUser(_user.Id); + if (!UserConstant.AdminRoles.Contains(user.Role)) + { + userAgents = await _agentService.GetUserAgents(user.Id); + } + + var agents = pagedAgents?.Items?.Select(x => + { + var chatable = true; + if (!UserConstant.AdminRoles.Contains(user.Role)) + { + var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? []; + chatable = actions.Contains(UserAction.Chat); + } + + var model = AgentViewModel.FromAgent(x); + model.Chatable = chatable; + return model; + })?.ToList() ?? []; return new PagedItems { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 91617a0f..19f6241f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -55,7 +55,7 @@ public class ConversationController : ControllerBase return new PagedItems(); } - filter.UserId = user.Role != UserRole.Admin ? user.Id : filter.UserId; + filter.UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : filter.UserId; var conversations = await convService.GetConversations(filter); var agentService = _services.GetRequiredService(); var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList(); @@ -146,7 +146,7 @@ public class ConversationController : ControllerBase var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await service.GetConversations(filter); if (conversations.Items.IsNullOrEmpty()) @@ -209,7 +209,7 @@ public class ConversationController : ControllerBase var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await conv.GetConversations(filter); @@ -262,7 +262,7 @@ public class ConversationController : ControllerBase var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await conversationService.GetConversations(filter); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index 342f39fb..e4ec4aa8 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -24,7 +24,7 @@ public class PluginController : ControllerBase { var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - if (user?.Role != UserRole.Admin) + if (!UserConstant.AdminRoles.Contains(user?.Role)) { return new PagedItems(); } @@ -45,15 +45,19 @@ public class PluginController : ControllerBase new PluginMenuDef("System", weight: 30) { IsHeader = true, - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }, new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }, new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } + }, + new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33) + { + Roles = new List { UserRole.Root, UserRole.Admin } } }; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 67c0f4e3..d0610dd3 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Users.Enums; +using EntityFrameworkCore.BootKit; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.ComponentModel.DataAnnotations; @@ -10,10 +12,16 @@ public class UserController : ControllerBase { private readonly IServiceProvider _services; private readonly IUserService _userService; - public UserController(IUserService userService, IServiceProvider services) + private readonly IUserIdentity _user; + + public UserController( + IUserService userService, + IServiceProvider services, + IUserIdentity user) { _services = services; _userService = userService; + _user = user; } [AllowAnonymous] @@ -164,6 +172,29 @@ public class UserController : ControllerBase return await _userService.UpdateUsersIsDisable(userIds, isDisable); } + #region User management + [HttpPost("/users")] + public async Task> GetUsers([FromBody] UserFilter filter) + { + var userService = _services.GetRequiredService(); + var user = await userService.GetUser(_user.Id); + if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + { + return new PagedItems(); + } + + var users = await userService.GetUsers(filter); + var views = users.Items.Select(x => UserViewModel.FromUser(x)).ToList(); + + return new PagedItems + { + Count = users.Count, + Items = views + }; + } + #endregion + + #region Avatar [HttpPost("/user/avatar")] public bool UploadUserAvatar([FromBody] UserAvatarModel input) diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index 5c23c4d4..b368cde0 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -47,6 +47,7 @@ public class AgentViewModel public PluginDef Plugin { get; set; } public bool Editable { get; set; } + public bool Chatable { get; set; } [JsonPropertyName("created_datetime")] public DateTime CreatedDateTime { get; set; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs new file mode 100644 index 00000000..49c1d6b4 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs @@ -0,0 +1,30 @@ +using BotSharp.Abstraction.Agents.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Users; + +public class UserAgentActionViewModel +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("agent")] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + public static UserAgentActionViewModel ToViewModel(UserAgentAction action) + { + return new UserAgentActionViewModel + { + Id = action.Id, + AgentId = action.AgentId, + Agent = action.Agent, + Actions = action.Actions + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs index 393bbb86..78100b18 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs @@ -16,14 +16,23 @@ public class UserViewModel public string? Phone { get; set; } public string Type { get; set; } = UserType.Client; public string Role { get; set; } = UserRole.User; + [JsonPropertyName("full_name")] public string FullName => $"{FirstName} {LastName}".Trim(); public string? Source { get; set; } + [JsonPropertyName("external_id")] public string? ExternalId { get; set; } public string Avatar { get; set; } = "/user/avatar"; + + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + [JsonPropertyName("create_date")] public DateTime CreateDate { get; set; } + [JsonPropertyName("update_date")] public DateTime UpdateDate { get; set; } @@ -52,6 +61,8 @@ public class UserViewModel Role = user.Role, Source = user.Source, ExternalId = user.ExternalId, + Permissions = user.Permissions, + AgentActions = user.AgentActions?.Select(x => UserAgentActionViewModel.ToViewModel(x)) ?? [], CreateDate = user.CreatedTime, UpdateDate = user.UpdatedTime, Avatar = "/user/avatar" diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs index bb4af1ae..9de5345f 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs @@ -4,8 +4,7 @@ public class UserAgentDocument : MongoBase { public string UserId { get; set; } public string AgentId { get; set; } - public bool Editable { get; set; } - + public IEnumerable Actions { get; set; } = []; public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs index ffcd71a2..5d404aff 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs @@ -20,6 +20,7 @@ public class UserDocument : MongoBase public bool Verified { get; set; } public string? AffiliateId { get; set; } public bool IsDisabled { get; set; } + public IEnumerable Permissions { get; set; } = []; public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } @@ -43,6 +44,7 @@ public class UserDocument : MongoBase IsDisabled = IsDisabled, VerificationCode = VerificationCode, Verified = Verified, + Permissions = Permissions, }; } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs index 7d47a98c..755958c7 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs @@ -37,7 +37,7 @@ public class MongoStoragePlugin : IBotSharpPlugin var section = menu.First(x => x.Label == "Apps"); menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "page/mongodb", weight: section.Weight + 10) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 49c8616e..d4a02e93 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -318,19 +318,36 @@ public partial class MongoRepository return agentDocs.Select(x => TransformAgentDocument(x)).ToList(); } - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) { - var agentIds = (from ua in _dc.UserAgents.AsQueryable() - join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id - where ua.UserId == userId || u.ExternalId == userId - select ua.AgentId).ToList(); + var found = (from ua in _dc.UserAgents.AsQueryable() + join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id + where ua.UserId == userId || u.ExternalId == userId + select ua).ToList(); - var filter = new AgentFilter + if (found.IsNullOrEmpty()) return []; + + var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + var res = found.Select(x => new UserAgent { - AgentIds = agentIds - }; - var agents = GetAgents(filter); - return agents; + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); + + foreach (var item in res) + { + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + return res; } public List GetAgentResponses(string agentId, string prefix, string intent) @@ -415,9 +432,9 @@ public partial class MongoRepository var userAgentDocs = userAgents.Select(x => new UserAgentDocument { Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - AgentId = x.AgentId, UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty, - Editable = x.Editable, + AgentId = x.AgentId, + Actions = x.Actions, CreatedTime = x.CreatedTime, UpdatedTime = x.UpdatedTime }).ToList(); @@ -446,11 +463,11 @@ public partial class MongoRepository if (string.IsNullOrEmpty(agentId)) return false; var agentFilter = Builders.Filter.Eq(x => x.Id, agentId); - var agentUserFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId); _dc.Agents.DeleteOne(agentFilter); - _dc.UserAgents.DeleteMany(agentUserFilter); + _dc.UserAgents.DeleteMany(userAgentFilter); _dc.AgentTasks.DeleteMany(agentTaskFilter); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs deleted file mode 100644 index e2ffbb0e..00000000 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs +++ /dev/null @@ -1,157 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Users.Models; - -namespace BotSharp.Plugin.MongoStorage.Repository; - -public partial class MongoRepository -{ - public void Add(object entity) - { - if (entity is Agent agent) - { - _agents.Add(agent); - _changedTableNames.Add(nameof(Agent)); - } - else if (entity is User user) - { - _users.Add(user); - _changedTableNames.Add(nameof(User)); - } - else if (entity is UserAgent userAgent) - { - _userAgents.Add(userAgent); - _changedTableNames.Add(nameof(UserAgent)); - } - } - - public int Transaction(Action action) - { - _changedTableNames.Clear(); - action(); - - foreach (var table in _changedTableNames) - { - if (table == nameof(Agent)) - { - var agents = _agents.Select(x => new AgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - Name = x.Name, - IconUrl = x.IconUrl, - Description = x.Description, - Instruction = x.Instruction, - ChannelInstructions = x.ChannelInstructions? - .Select(i => ChannelInstructionMongoElement.ToMongoElement(i))? - .ToList() ?? new List(), - Templates = x.Templates? - .Select(t => AgentTemplateMongoElement.ToMongoElement(t))? - .ToList() ?? new List(), - Functions = x.Functions? - .Select(f => FunctionDefMongoElement.ToMongoElement(f))? - .ToList() ?? new List(), - Responses = x.Responses? - .Select(r => AgentResponseMongoElement.ToMongoElement(r))? - .ToList() ?? new List(), - Samples = x.Samples ?? new List(), - Utilities = x.Utilities ?? new List(), - IsPublic = x.IsPublic, - Type = x.Type, - InheritAgentId = x.InheritAgentId, - Disabled = x.Disabled, - Profiles = x.Profiles, - RoutingRules = x.RoutingRules? - .Select(r => RoutingRuleMongoElement.ToMongoElement(r))? - .ToList() ?? new List(), - LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig), - CreatedTime = x.CreatedDateTime, - UpdatedTime = x.UpdatedDateTime - }).ToList(); - - foreach (var agent in agents) - { - var filter = Builders.Filter.Eq(x => x.Id, agent.Id); - var update = Builders.Update - .Set(x => x.Name, agent.Name) - .Set(x => x.Description, agent.Description) - .Set(x => x.Instruction, agent.Instruction) - .Set(x => x.ChannelInstructions, agent.ChannelInstructions) - .Set(x => x.Templates, agent.Templates) - .Set(x => x.Functions, agent.Functions) - .Set(x => x.Responses, agent.Responses) - .Set(x => x.Samples, agent.Samples) - .Set(x => x.Utilities, agent.Utilities) - .Set(x => x.IsPublic, agent.IsPublic) - .Set(x => x.Type, agent.Type) - .Set(x => x.InheritAgentId, agent.InheritAgentId) - .Set(x => x.Disabled, agent.Disabled) - .Set(x => x.Profiles, agent.Profiles) - .Set(x => x.RoutingRules, agent.RoutingRules) - .Set(x => x.LlmConfig, agent.LlmConfig) - .Set(x => x.CreatedTime, agent.CreatedTime) - .Set(x => x.UpdatedTime, agent.UpdatedTime); - _dc.Agents.UpdateOne(filter, update, _options); - } - } - else if (table == nameof(User)) - { - var users = _users.Select(x => new UserDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - UserName = x.UserName, - FirstName = x.FirstName, - LastName = x.LastName, - Salt = x.Salt, - Password = x.Password, - Email = x.Email, - ExternalId = x.ExternalId, - Role = x.Role, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); - - foreach (var user in users) - { - var filter = Builders.Filter.Eq(x => x.Id, user.Id); - var update = Builders.Update - .Set(x => x.UserName, user.UserName) - .Set(x => x.FirstName, user.FirstName) - .Set(x => x.LastName, user.LastName) - .Set(x => x.Email, user.Email) - .Set(x => x.Salt, user.Salt) - .Set(x => x.Password, user.Password) - .Set(x => x.ExternalId, user.ExternalId) - .Set(x => x.Role, user.Role) - .Set(x => x.CreatedTime, user.CreatedTime) - .Set(x => x.UpdatedTime, user.UpdatedTime); - _dc.Users.UpdateOne(filter, update, _options); - } - } - else if (table == nameof(UserAgent)) - { - var userAgents = _userAgents.Select(x => new UserAgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - AgentId = x.AgentId, - UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty, - Editable = x.Editable, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); - - foreach (var userAgent in userAgents) - { - var filter = Builders.Filter.Eq(x => x.Id, userAgent.Id); - var update = Builders.Update - .Set(x => x.AgentId, userAgent.AgentId) - .Set(x => x.UserId, userAgent.UserId) - .Set(x => x.Editable, userAgent.Editable) - .Set(x => x.CreatedTime, userAgent.CreatedTime) - .Set(x => x.UpdatedTime, userAgent.UpdatedTime); - _dc.UserAgents.UpdateOne(filter, update, _options); - } - } - } - - return _changedTableNames.Count; - } -} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 55c454bd..540b6508 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -1,5 +1,9 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; +using System.Globalization; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -146,4 +150,85 @@ public partial class MongoRepository UpdateUserIsDisable(userId, isDisable); } } + + public PagedItems GetUsers(UserFilter filter) + { + var userBuilder = Builders.Filter; + var userFilters = new List>() { userBuilder.Empty }; + + // Apply filters + if (!filter.UserIds.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Id, filter.UserIds)); + } + if (!filter.UserNames.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.UserName, filter.UserNames)); + } + if (!filter.ExternalIds.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.ExternalId, filter.ExternalIds)); + } + if (!filter.Roles.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Role, filter.Roles)); + } + if (!filter.Sources.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Source, filter.Sources)); + } + + // Filter def and sort + var filterDef = userBuilder.And(userFilters); + var sortDef = Builders.Sort.Descending(x => x.CreatedTime); + + // Search + var userDocs = _dc.Users.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); + var count = _dc.Users.CountDocuments(filterDef); + + var users = userDocs.Select(x => x.ToUser()).ToList(); + var userIds = users.Select(x => x.Id).ToList(); + var userAgents = _dc.UserAgents.AsQueryable().Where(x => userIds.Contains(x.UserId)).Select(x => new UserAgent + { + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions ?? Enumerable.Empty(), + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); + var agentIds = userAgents.Select(x => x.AgentId).Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in userAgents) + { + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + foreach (var user in users) + { + var found = userAgents.Where(x => x.UserId == user.Id).ToList(); + if (found.IsNullOrEmpty()) continue; + + user.AgentActions = found.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Agent = x.Agent, + Actions = x.Actions + }); + } + } + + return new PagedItems + { + Items = users, + Count = (int)count + }; + } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs index 133edba1..689c06be 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs @@ -1,6 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Users.Models; using Microsoft.Extensions.Logging; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -25,10 +22,4 @@ public partial class MongoRepository : IBotSharpRepository IsUpsert = true, }; } - - private List _agents = new List(); - private List _users = new List(); - private List _userAgents = new List(); - private List _conversations = new List(); - List _changedTableNames = new List(); } From 07ee8fcf2f7380694ca4664dc771209e6dca766c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Oct 2024 15:32:58 -0500 Subject: [PATCH 03/13] refine code --- .../Conversations/IConversationSideCar.cs | 2 +- .../BotSharp.Abstraction/Routing/IRoutingContext.cs | 2 +- .../Conversations/Services/ConversationSideCar.cs | 2 +- .../Conversations/Services/ConversationStateService.cs | 4 +++- src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs index 569555c2..8dab52e1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs @@ -7,5 +7,5 @@ public interface IConversationSideCar List GetConversationDialogs(string conversationId); void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); ConversationBreakpoint? GetConversationBreakpoint(string conversationId); - Task Execute(string conversationId, string agentId, string text, PostbackMessageModel? postback = null, List? states = null); + Task Execute(string agentId, string text, PostbackMessageModel? postback = null, List? states = null); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs index 854832c6..f75e8e0e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingContext.cs @@ -21,7 +21,7 @@ public interface IRoutingContext int CurrentRecursionDepth { get; } int GetRecursiveCounter(); - int IncreaseRecursiveCounter(); + void IncreaseRecursiveCounter(); void SetRecursiveCounter(int counter); void ResetRecursiveCounter(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs index 903b8ee0..ae72fbdb 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs @@ -80,7 +80,7 @@ public class ConversationSideCar : IConversationSideCar } } - public async Task Execute(string conversationId, string agentId, string text, + public async Task Execute(string agentId, string text, PostbackMessageModel? postback = null, List? states = null) { BeforeExecute(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index f6c146a8..8ee4c662 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -394,7 +394,9 @@ public class ConversationStateService : IConversationStateService, IDisposable public void SetCurrentState(ConversationState state) { - _curStates = state; + var values = _curStates.Values.ToList(); + var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); + _curStates = new ConversationState(copy ?? new()); } public void ResetCurrentState() diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 3450d41e..5c1f1903 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -214,9 +214,9 @@ public class RoutingContext : IRoutingContext return _currentRecursionDepth; } - public int IncreaseRecursiveCounter() + public void IncreaseRecursiveCounter() { - return _currentRecursionDepth; + _currentRecursionDepth++; } public void SetRecursiveCounter(int counter) From d0b7e08cfe8416fbde553ce342333c865ae15c9c Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 31 Oct 2024 17:32:39 -0500 Subject: [PATCH 04/13] add update user --- .../Repositories/IBotSharpRepository.cs | 1 + .../Users/IUserService.cs | 1 + .../FileRepository/FileRepository.Agent.cs | 12 ++++- .../FileRepository/FileRepository.User.cs | 36 ++++++++++++++ .../Users/Services/UserService.cs | 6 +++ .../Controllers/UserController.cs | 18 ++++++- .../Users/UserAgentActionViewModel.cs | 10 ++++ .../ViewModels/Users/UserUpdateModel.cs | 49 +++++++++++++++++++ .../Repository/MongoRepository.User.cs | 44 ++++++++++++++++- 9 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index a2175cc1..f21850e3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -33,6 +33,7 @@ public interface IBotSharpRepository void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); + bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException(); #endregion #region Agent diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 94f30aad..734884ef 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -7,6 +7,7 @@ public interface IUserService { Task GetUser(string id); Task> GetUsers(UserFilter filter); + Task UpdateUser(User model, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); Task GetAffiliateToken(string authorization); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 71e9ea13..90f05ceb 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -449,9 +449,15 @@ namespace BotSharp.Core.Repository return true; } - public void BulkInsertAgents(List agents) { } + public void BulkInsertAgents(List agents) + { + _agents = []; + } - public void BulkInsertUserAgents(List userAgents) { } + public void BulkInsertUserAgents(List userAgents) + { + _userAgents = []; + } public bool DeleteAgents() { @@ -487,6 +493,8 @@ namespace BotSharp.Core.Repository // Delete agent folder Directory.Delete(agentDir, true); + _agents = []; + _userAgents = []; return true; } catch diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 0719509e..9ee6bdb4 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; using System; @@ -130,4 +131,39 @@ public partial class FileRepository Count = users.Count() }; } + + public bool UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (string.IsNullOrEmpty(user?.Id)) return false; + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + var userFile = Path.Combine(dir, USER_FILE); + user.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(_dbSettings.FileRepository, JsonSerializer.Serialize(user, _options)); + + if (isUpdateUserAgents) + { + var userAgents = user.AgentActions?.Select(x => new UserAgent + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = user.Id, + AgentId = x.AgentId, + Actions = x.Actions ?? [], + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var userAgentFile = Path.Combine(dir, USER_AGENT_FILE); + File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + } + + _users = []; + _userAgents = []; + return true; + } } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 8b228dcd..2098d71d 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -384,6 +384,12 @@ public class UserService : IUserService return users; } + public async Task UpdateUser(User model, bool isUpdateUserAgents = false) + { + var db = _services.GetRequiredService(); + return db.UpdateUser(model, isUpdateUserAgents); + } + public async Task ActiveUser(UserActivationModel model) { var id = model.UserName; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index d0610dd3..dbb312b0 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Users.Enums; -using EntityFrameworkCore.BootKit; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.ComponentModel.DataAnnotations; @@ -192,6 +191,23 @@ public class UserController : ControllerBase Items = views }; } + + + [HttpPut("/user")] + public async Task UpdateUser([FromBody] UserUpdateModel model) + { + if (model == null) return false; + + var userService = _services.GetRequiredService(); + var user = await userService.GetUser(_user.Id); + if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + { + return false; + } + + var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true); + return updated; + } #endregion diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs index 49c1d6b4..adf3d45b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs @@ -27,4 +27,14 @@ public class UserAgentActionViewModel Actions = action.Actions }; } + + public static UserAgentAction ToDomainModel(UserAgentActionViewModel action) + { + return new UserAgentAction + { + Id = action.Id, + AgentId = action.AgentId, + Actions = action.Actions + }; + } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs new file mode 100644 index 00000000..76b8e347 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Users; + +public class UserUpdateModel +{ + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_name")] + public string UserName { get; set; } = string.Empty; + + [JsonPropertyName("first_name")] + public string FirstName { get; set; } = string.Empty; + + [JsonPropertyName("last_name")] + public string? LastName { get; set; } + public string? Email { get; set; } + public string? Phone { get; set; } + public string? Type { get; set; } + public string? Role { get; set; } + public string? Source { get; set; } + + [JsonPropertyName("external_id")] + public string? ExternalId { get; set; } + + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + + public static User ToUser(UserUpdateModel model) + { + return new User + { + Id = model.Id, + UserName = model.UserName, + FirstName = model.FirstName, + LastName = model.LastName, + Email = model.Email, + Phone = model.Phone, + Type = model.Type, + Role = model.Role, + Source = model.Source, + ExternalId = model.ExternalId, + Permissions = model.Permissions, + AgentActions = model.AgentActions?.Select(x => UserAgentActionViewModel.ToDomainModel(x)) ?? [] + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 540b6508..4bd31fa6 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -1,9 +1,7 @@ using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; -using System.Globalization; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -231,4 +229,46 @@ public partial class MongoRepository Count = (int)count }; } + + + public bool UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (string.IsNullOrEmpty(user?.Id)) return false; + + var userFilter = Builders.Filter.Eq(x => x.Id, user.Id); + var userUpdate = Builders.Update + .Set(x => x.Role, user.Role) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Users.UpdateOne(userFilter, userUpdate); + + if (isUpdateUserAgents) + { + var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = user.Id, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + _dc.UserAgents.DeleteMany(Builders.Filter.Nin(x => x.Id, userAgentDocs.Select(x => x.Id))); + foreach (var doc in userAgentDocs) + { + var userAgentFilter = Builders.Filter.Eq(x => x.Id, doc.Id); + var userAgentUpdate = Builders.Update + .Set(x => x.Id, doc.Id) + .Set(x => x.UserId, user.Id) + .Set(x => x.AgentId, doc.AgentId) + .Set(x => x.Actions, doc.Actions) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.UserAgents.UpdateOne(userAgentFilter, userAgentUpdate, _options); + } + } + + return true; + } } From 39bd401c4fa00ce8c53ac29f7b2f7cf9542b3be2 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Nov 2024 13:17:25 -0500 Subject: [PATCH 05/13] refine code --- .../FileRepository/FileRepository.User.cs | 2 +- .../Controllers/AgentController.cs | 19 +++++++++++++++++-- .../Users/UserAgentActionViewModel.cs | 2 +- .../Repository/MongoRepository.User.cs | 9 ++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 9ee6bdb4..650cdeeb 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -144,7 +144,7 @@ public partial class FileRepository var userFile = Path.Combine(dir, USER_FILE); user.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(_dbSettings.FileRepository, JsonSerializer.Serialize(user, _options)); + File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options)); if (isUpdateUserAgents) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index 6a24c584..54c9b75e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -76,12 +76,24 @@ public class AgentController : ControllerBase } [HttpGet("/agents")] - public async Task> GetAgents([FromQuery] AgentFilter filter) + public async Task> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool checkAuth = false) { var agentSetting = _services.GetRequiredService(); var userService = _services.GetRequiredService(); + List agents; var pagedAgents = await _agentService.GetAgents(filter); + + if (!checkAuth) + { + agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? []; + return new PagedItems + { + Items = agents, + Count = pagedAgents?.Count ?? 0 + }; + } + var userAgents = new List(); var user = await userService.GetUser(_user.Id); if (!UserConstant.AdminRoles.Contains(user.Role)) @@ -89,16 +101,19 @@ public class AgentController : ControllerBase userAgents = await _agentService.GetUserAgents(user.Id); } - var agents = pagedAgents?.Items?.Select(x => + agents = pagedAgents?.Items?.Select(x => { var chatable = true; + var editable = true; if (!UserConstant.AdminRoles.Contains(user.Role)) { var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? []; chatable = actions.Contains(UserAction.Chat); + editable = actions.Contains(UserAction.Edit); } var model = AgentViewModel.FromAgent(x); + model.Editable = editable; model.Chatable = chatable; return model; })?.ToList() ?? []; diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs index adf3d45b..43c9fc4d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs @@ -6,7 +6,7 @@ namespace BotSharp.OpenAPI.ViewModels.Users; public class UserAgentActionViewModel { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("agent_id")] public string AgentId { get; set; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 4bd31fa6..a9894638 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -237,7 +237,9 @@ public partial class MongoRepository var userFilter = Builders.Filter.Eq(x => x.Id, user.Id); var userUpdate = Builders.Update + .Set(x => x.Type, user.Type) .Set(x => x.Role, user.Role) + .Set(x => x.Permissions, user.Permissions) .Set(x => x.UpdatedTime, DateTime.UtcNow); _dc.Users.UpdateOne(userFilter, userUpdate); @@ -254,7 +256,12 @@ public partial class MongoRepository UpdatedTime = DateTime.UtcNow })?.ToList() ?? []; - _dc.UserAgents.DeleteMany(Builders.Filter.Nin(x => x.Id, userAgentDocs.Select(x => x.Id))); + var toDelete = _dc.UserAgents.Find(Builders.Filter.And( + Builders.Filter.Eq(x => x.UserId, user.Id), + Builders.Filter.Nin(x => x.Id, userAgentDocs.Select(x => x.Id)) + )).ToList(); + + _dc.UserAgents.DeleteMany(Builders.Filter.In(x => x.Id, toDelete.Select(x => x.Id))); foreach (var doc in userAgentDocs) { var userAgentFilter = Builders.Filter.Eq(x => x.Id, doc.Id); From a9f3bca8d03b5fa5a0870340d93dda46f7666b49 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 1 Nov 2024 13:21:30 -0500 Subject: [PATCH 06/13] resolve conflict --- .../BotSharp.OpenAPI/Controllers/UserController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 2521fff6..90bf20f1 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -13,13 +13,13 @@ public class UserController : ControllerBase private readonly IServiceProvider _services; private readonly IUserService _userService; private readonly IUserIdentity _user; + private readonly AccountSetting _setting; public UserController( IUserService userService, IServiceProvider services, - IUserIdentity user) - private readonly AccountSetting _setting; - public UserController(IUserService userService, IServiceProvider services, AccountSetting setting) + IUserIdentity user, + AccountSetting setting) { _services = services; _userService = userService; From 7c34dbc0a73124754cb1a78bbefd9bea431fd1c5 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Mon, 4 Nov 2024 00:00:32 -0600 Subject: [PATCH 07/13] temp save --- .../Services/AgentService.CreateAgent.cs | 15 +++++++++++++++ .../Repository/MongoRepository.Agent.cs | 19 ++++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index 3c9e6bf2..c4fce4c5 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Tasks.Models; +using BotSharp.Abstraction.Users.Enums; using System.IO; using System.Text.RegularExpressions; @@ -25,6 +26,20 @@ public partial class AgentService var user = _db.GetUserById(_user.Id); _db.BulkInsertAgents(new List { agentRecord }); + if (!UserConstant.AdminRoles.Contains(user.Role)) + { + _db.BulkInsertUserAgents(new List + { + new UserAgent + { + UserId = user.Id, + AgentId = agent.Id, + Actions = new List { UserAction.Edit }, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + } + }); + } Utilities.ClearCache(); return await Task.FromResult(agentRecord); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index d4a02e93..bf3f811f 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -429,15 +429,16 @@ public partial class MongoRepository { if (userAgents.IsNullOrEmpty()) return; - var userAgentDocs = userAgents.Select(x => new UserAgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty, - AgentId = x.AgentId, - Actions = x.Actions, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); + var userAgentDocs = userAgents.Where(x => !string.IsNullOrEmpty(x.UserId)) + .Select(x => new UserAgentDocument + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); _dc.UserAgents.InsertMany(userAgentDocs); } From 0657b16e914b0c924b79347b1e69e9776775679d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Nov 2024 11:22:02 -0600 Subject: [PATCH 08/13] fix create agent issue --- .../Services/AgentService.CreateAgent.cs | 2 +- .../FileRepository/FileRepository.Agent.cs | 66 +++++++++++++++++-- .../Repository/MongoRepository.Agent.cs | 22 ++++--- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index c4fce4c5..4f020a78 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -33,7 +33,7 @@ public partial class AgentService new UserAgent { UserId = user.Id, - AgentId = agent.Id, + AgentId = agentRecord.Id, Actions = new List { UserAction.Edit }, CreatedTime = DateTime.UtcNow, UpdatedTime = DateTime.UtcNow diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 90f05ceb..8634eee3 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,5 +1,7 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Users.Models; +using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Repository @@ -451,12 +453,63 @@ namespace BotSharp.Core.Repository public void BulkInsertAgents(List agents) { - _agents = []; + if (agents.IsNullOrEmpty()) return; + + var baseDir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir); + foreach (var agent in agents) + { + var dir = Path.Combine(baseDir, agent.Id); + if (Directory.Exists(dir)) continue; + + Directory.CreateDirectory(dir); + Thread.Sleep(50); + + var agentFile = Path.Combine(dir, AGENT_FILE); + var json = JsonSerializer.Serialize(agent, _options); + File.WriteAllText(agentFile, json); + + if (!string.IsNullOrWhiteSpace(agent.Instruction)) + { + var instDir = Path.Combine(dir, AGENT_INSTRUCTIONS_FOLDER); + Directory.CreateDirectory(instDir); + var instFile = Path.Combine(instDir, $"{AGENT_INSTRUCTION_FILE}.{_agentSettings.TemplateFormat}"); + File.WriteAllText(instFile, agent.Instruction); + } + } + Reset(); } public void BulkInsertUserAgents(List userAgents) { - _userAgents = []; + if (userAgents.IsNullOrEmpty()) return; + + var groups = userAgents.GroupBy(x => x.UserId); + var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER); + + foreach (var group in groups) + { + var filtered = group.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList(); + if (filtered.IsNullOrEmpty()) continue; + + filtered.ForEach(x => x.Id = Guid.NewGuid().ToString()); + var userId = filtered.First().UserId; + var userDir = Path.Combine(usersDir, userId); + if (!Directory.Exists(userDir)) continue; + + var userAgentFile = Path.Combine(userDir, USER_AGENT_FILE); + var list = new List(); + if (File.Exists(userAgentFile)) + { + var str = File.ReadAllText(userAgentFile); + list = JsonSerializer.Deserialize>(str, _options); + } + + list.AddRange(filtered); + File.WriteAllText(userAgentFile, JsonSerializer.Serialize(list, _options)); + Thread.Sleep(50); + } + + Reset(); } public bool DeleteAgents() @@ -493,8 +546,7 @@ namespace BotSharp.Core.Repository // Delete agent folder Directory.Delete(agentDir, true); - _agents = []; - _userAgents = []; + Reset(); return true; } catch @@ -502,5 +554,11 @@ namespace BotSharp.Core.Repository return false; } } + + private void Reset() + { + _agents = []; + _userAgents = []; + } } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index bf3f811f..44a4e2bc 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -429,16 +429,18 @@ public partial class MongoRepository { if (userAgents.IsNullOrEmpty()) return; - var userAgentDocs = userAgents.Where(x => !string.IsNullOrEmpty(x.UserId)) - .Select(x => new UserAgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - UserId = x.UserId, - AgentId = x.AgentId, - Actions = x.Actions, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); + var filtered = userAgents.Where(x => !string.IsNullOrEmpty(x.UserId) && !string.IsNullOrEmpty(x.AgentId)).ToList(); + if (filtered.IsNullOrEmpty()) return; + + var userAgentDocs = filtered.Select(x => new UserAgentDocument + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); _dc.UserAgents.InsertMany(userAgentDocs); } From 51bca39dd63ce5e9b43824de3ef2a9268ae86557 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Nov 2024 12:12:40 -0600 Subject: [PATCH 09/13] add chat file download --- .../Files/Models/FileInformation.cs | 9 +++++++- .../Services/AgentService.CreateAgent.cs | 2 +- .../LocalFileStorageService.Conversation.cs | 1 + .../Controllers/ConversationController.cs | 21 +++++++++++++++++++ .../ViewModels/Files/MessageFileViewModel.cs | 7 ++++++- .../WebSocketsMiddleware.cs | 1 + .../TencentCosService.Conversation.cs | 1 + 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileInformation.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileInformation.cs index fe767e16..f8dd9449 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileInformation.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/FileInformation.cs @@ -3,7 +3,7 @@ namespace BotSharp.Abstraction.Files.Models; public class FileInformation { /// - /// External file url + /// External file url for display /// [JsonPropertyName("file_url")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -35,4 +35,11 @@ public class FileInformation [JsonPropertyName("file_extension")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? FileExtension { get; set; } = string.Empty; + + /// + /// External file url for download + /// + [JsonPropertyName("file_download_url")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? FileDownloadUrl { get; set; } = string.Empty; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index 4f020a78..df10ac4b 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -34,7 +34,7 @@ public partial class AgentService { UserId = user.Id, AgentId = agentRecord.Id, - Actions = new List { UserAction.Edit }, + Actions = new List { UserAction.Edit, UserAction.Chat }, CreatedTime = DateTime.UtcNow, UpdatedTime = DateTime.UtcNow } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs index 44bfb9f1..d296e0b3 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Conversation.cs @@ -69,6 +69,7 @@ public partial class LocalFileStorageService { MessageId = messageId, FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}", + FileDownloadUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download", FileStorageUrl = file, FileName = fileName, FileExtension = fileExtension, diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 19f6241f..b7391be7 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,6 +1,7 @@ using Azure; using BotSharp.Abstraction.Files.Constants; using BotSharp.Abstraction.Files.Enums; +using BotSharp.Abstraction.Files.Utilities; using BotSharp.Abstraction.Options; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Users.Enums; @@ -488,6 +489,26 @@ public class ConversationController : ControllerBase } return BuildFileResult(file); } + + [HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download")] + public IActionResult DownloadMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName) + { + var fileStorage = _services.GetRequiredService(); + var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName); + if (string.IsNullOrEmpty(file)) + { + return NotFound(); + } + + var fName = file.Split(Path.DirectorySeparatorChar).Last(); + var contentType = FileUtility.GetFileContentType(fName); + var stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read); + var bytes = new byte[stream.Length]; + stream.Read(bytes, 0, (int)stream.Length); + stream.Position = 0; + + return new FileStreamResult(stream, contentType) { FileDownloadName = fName }; + } #endregion #region Private methods diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Files/MessageFileViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Files/MessageFileViewModel.cs index 787ab147..fa8ebab6 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Files/MessageFileViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Files/MessageFileViewModel.cs @@ -19,6 +19,10 @@ public class MessageFileViewModel [JsonPropertyName("file_source")] public string FileSource { get; set; } + [JsonPropertyName("file_download_url")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? FileDownloadUrl { get; set; } + public MessageFileViewModel() { @@ -32,7 +36,8 @@ public class MessageFileViewModel FileName = model.FileName, FileExtension = model.FileExtension, ContentType = model.ContentType, - FileSource = model.FileSource + FileSource = model.FileSource, + FileDownloadUrl = model.FileDownloadUrl }; } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs b/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs index ff986be6..47c646b7 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs @@ -37,6 +37,7 @@ public class WebSocketsMiddleware var regexes = new List { new Regex(@"/conversation/(.*?)/message/(.*?)/(.*?)/file/(.*?)/(.*?)", RegexOptions.IgnoreCase), + new Regex(@"/conversation/(.*?)/message/(.*?)/(.*?)/file/(.*?)/(.*?)/download", RegexOptions.IgnoreCase), new Regex(@"/user/avatar", RegexOptions.IgnoreCase), new Regex(@"/knowledge/document/(.*?)/file/(.*?)", RegexOptions.IgnoreCase) }; diff --git a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs b/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs index 844afb65..69cda8ca 100644 --- a/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.TencentCos/Services/TencentCosService.Conversation.cs @@ -66,6 +66,7 @@ public partial class TencentCosService { MessageId = messageId, FileUrl = BuilFileUrl(file), + FileDownloadUrl = BuilFileUrl(file), FileStorageUrl = file, FileName = fileName, FileExtension = fileExtension, From aeaaa3cf755630902e7fdc7b3b34bd0e2375628d Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 4 Nov 2024 16:06:34 -0600 Subject: [PATCH 10/13] Optimize background services. --- .../BotSharp.Core/Infrastructures/DistributedLocker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index c2aac231..a5373cd1 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -31,7 +31,7 @@ public class DistributedLocker } } - public async Task Lock(string resource, Func action, int timeoutInSeconds = 30) + public async Task Lock(string resource, Action action, int timeoutInSeconds = 30) { await ConnectToRedis(); @@ -45,7 +45,7 @@ public class DistributedLocker Serilog.Log.Logger.Error($"Acquire lock for {resource} failed due to after {timeout}s timeout."); } - return action(); + action(); } } From 43882b52bc1f4b6240c16580d7e50d15ae12d182 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Nov 2024 16:44:41 -0600 Subject: [PATCH 11/13] refine side car --- BotSharp.sln | 11 ++ .../BotSharp.Abstraction.csproj | 3 +- .../Repositories/IBotSharpRepository.cs | 3 +- .../Shared/IHaveServiceProvider.cs | 6 + .../SideCar/Attributes/SideCarAspect.cs | 168 ++++++++++++++++++ .../SideCar/Attributes/SideCarAttribute.cs | 13 ++ .../IConversationSideCar.cs | 6 +- .../BotSharp.Core.SideCar.csproj | 16 ++ .../BotSharpSideCarPlugin.cs | 28 +++ .../Services/BotSharpConversationSideCar.cs} | 69 +++---- .../Settings/SideCarSettings.cs | 11 ++ .../BotSharp.Core.SideCar/Using.cs | 20 +++ .../Conversations/ConversationPlugin.cs | 1 - .../ConversationService.UpdateBreakpoint.cs | 10 +- .../Services/ConversationService.cs | 7 +- .../Services/ConversationStorage.cs | 14 +- .../Repository/BotSharpDbContext.cs | 6 + .../FileRepository.Conversation.cs | 4 + .../FileRepository/FileRepository.cs | 2 + src/Infrastructure/BotSharp.Core/Using.cs | 1 + .../Hooks/ChatHubConversationHook.cs | 1 + .../MongoRepository.Conversation.cs | 4 + .../Repository/MongoRepository.cs | 2 + .../BotSharp.Plugin.MongoStorage/Using.cs | 1 + src/WebStarter/WebStarter.csproj | 1 + src/WebStarter/appsettings.json | 7 + 26 files changed, 341 insertions(+), 74 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Shared/IHaveServiceProvider.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAspect.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs rename src/Infrastructure/BotSharp.Abstraction/{Conversations => SideCar}/IConversationSideCar.cs (65%) create mode 100644 src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj create mode 100644 src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs rename src/Infrastructure/{BotSharp.Core/Conversations/Services/ConversationSideCar.cs => BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs} (69%) create mode 100644 src/Infrastructure/BotSharp.Core.SideCar/Settings/SideCarSettings.cs create mode 100644 src/Infrastructure/BotSharp.Core.SideCar/Using.cs diff --git a/BotSharp.sln b/BotSharp.sln index a1c1ddb9..93f289ba 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -117,6 +117,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Graph", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.AudioHandler", "src\Plugins\BotSharp.Plugin.AudioHandler\BotSharp.Plugin.AudioHandler.csproj", "{F57F4862-F8D4-44A1-AC12-5C131B5C9785}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.SideCar", "src\Infrastructure\BotSharp.Core.SideCar\BotSharp.Core.SideCar.csproj", "{6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -469,6 +471,14 @@ Global {F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|Any CPU.Build.0 = Release|Any CPU {F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|x64.ActiveCfg = Release|Any CPU {F57F4862-F8D4-44A1-AC12-5C131B5C9785}.Release|x64.Build.0 = Release|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Debug|x64.Build.0 = Debug|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|Any CPU.Build.0 = Release|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.ActiveCfg = Release|Any CPU + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -525,6 +535,7 @@ Global {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C} {EBFE97DA-D0BA-48BA-8B5D-083B60348D1D} = {97A0B191-64D7-4F8A-BFE8-1BFCC5E247E1} {F57F4862-F8D4-44A1-AC12-5C131B5C9785} = {51AFE054-AE99-497D-A593-69BAEFB5106F} + {6D3A54F9-4792-41DB-BE7D-4F7B1D918EAE} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 474dcf56..5be126b8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) @@ -38,6 +38,7 @@ + diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 4f604dc4..57900aa0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Shared; using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Translation.Models; using BotSharp.Abstraction.Users.Models; @@ -8,7 +9,7 @@ using BotSharp.Abstraction.VectorStorage.Models; namespace BotSharp.Abstraction.Repositories; -public interface IBotSharpRepository +public interface IBotSharpRepository : IHaveServiceProvider { #region Plugin PluginConfig GetPluginConfig(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Shared/IHaveServiceProvider.cs b/src/Infrastructure/BotSharp.Abstraction/Shared/IHaveServiceProvider.cs new file mode 100644 index 00000000..0a68cadd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Shared/IHaveServiceProvider.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Shared; + +public interface IHaveServiceProvider +{ + IServiceProvider ServiceProvider { get; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAspect.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAspect.cs new file mode 100644 index 00000000..6580c56c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAspect.cs @@ -0,0 +1,168 @@ +using AspectInjector.Broker; +using BotSharp.Abstraction.Shared; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace BotSharp.Abstraction.SideCar.Attributes; + +[Aspect(Scope.PerInstance)] +public class SideCarAspect +{ + [Advice(Kind.Around)] + public object Handle( + [Argument(Source.Target)] Func target, + [Argument(Source.Arguments)] object[] args, + [Argument(Source.Instance)] object instance, + [Argument(Source.ReturnType)] Type retType, + [Argument(Source.Name)] string name, + [Argument(Source.Metadata)] MethodBase metaData, + [Argument(Source.Triggers)] Attribute[] triggers) + { + object value; + var serviceProvider = ((IHaveServiceProvider)instance).ServiceProvider; + + if (typeof(Task).IsAssignableFrom(retType)) + { + var syncResultType = retType.IsConstructedGenericType ? retType.GenericTypeArguments[0] : typeof(void); + value = CallAsyncMethod(serviceProvider, syncResultType, name, target, args); + } + else + { + value = CallSyncMethod(serviceProvider, retType, name, target, args); + } + + return value; + } + + + private static MethodInfo GetMethod(string name) + { + return typeof(SideCarAspect).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Static); + } + + private object CallAsyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func target, object[] args) + { + var sidecar = serviceProvider.GetService(); + var sidecarMethod = sidecar?.GetType()?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + + object value; + var enabled = sidecar != null && sidecar.IsEnabled() && sidecarMethod != null; + + if (retType == typeof(void)) + { + if (enabled) + { + + value = GetMethod(nameof(CallAsync)).Invoke(this, [sidecar, sidecarMethod, args]); + } + else + { + value = GetMethod(nameof(WrapAsync)).Invoke(this, [target, args]); + } + } + else + { + if (enabled) + { + value = GetMethod(nameof(CallGenericAsync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]); + } + else + { + value = GetMethod(nameof(WrapGenericAsync)).MakeGenericMethod(retType).Invoke(this, [target, args]); + } + } + + return value; + } + + private object CallSyncMethod(IServiceProvider serviceProvider, Type retType, string methodName, Func target, object[] args) + { + var sidecar = serviceProvider.GetService(); + var sidecarMethod = sidecar?.GetType()?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); + + object value; + var enabled = sidecar != null && sidecarMethod != null && sidecar.IsEnabled(); + + if (retType == typeof(void)) + { + if (enabled) + { + value = GetMethod(nameof(CallSync)).Invoke(this, [sidecar, sidecarMethod, args]); + } + else + { + value = GetMethod(nameof(WrapSync)).Invoke(this, [target, args]); + } + } + else + { + if (enabled) + { + value = GetMethod(nameof(CallGenericSync)).MakeGenericMethod(retType).Invoke(this, [sidecar, sidecarMethod, args]); + } + else + { + value = GetMethod(nameof(WrapGenericSync)).MakeGenericMethod(retType).Invoke(this, [target, args]); + } + } + + return value; + } + + + #region Call Side car method + private static async Task CallGenericAsync(object instance, MethodInfo method, object[] args) + { + var res = await (Task)method.Invoke(instance, args); + return res; + } + + private static async Task CallAsync(object instance, MethodInfo method, object[] args) + { + await (Task)method.Invoke(instance, args); + return; + } + + private static T CallGenericSync(object instance, MethodInfo method, object[] args) + { + var res = (T)method.Invoke(instance, args); + return res; + } + + private static void CallSync(object instance, MethodInfo method, object[] args) + { + method.Invoke(instance, args); + return; + } + #endregion + + + #region Call original method + private static T WrapGenericSync(Func target, object[] args) + { + T res; + res = (T)target(args); + return res; + } + + private static async Task WrapGenericAsync(Func target, object[] args) + { + T res; + res = await (Task)target(args); + return res; + } + + + private static void WrapSync(Func target, object[] args) + { + target(args); + return; + } + + private static async Task WrapAsync(Func target, object[] args) + { + await (Task)target(args); + return; + } + #endregion +} diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs new file mode 100644 index 00000000..2c5c259f --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs @@ -0,0 +1,13 @@ +using AspectInjector.Broker; + +namespace BotSharp.Abstraction.SideCar.Attributes; + +[AttributeUsage(AttributeTargets.Method, Inherited = true)] +[Injection(typeof(SideCarAspect))] +public class SideCarAttribute : Attribute +{ + public SideCarAttribute() + { + + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs similarity index 65% rename from src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs rename to src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs index 8dab52e1..9a1316fb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs @@ -1,11 +1,13 @@ -namespace BotSharp.Abstraction.Conversations; +namespace BotSharp.Abstraction.SideCar; public interface IConversationSideCar { + string Provider { get; } + bool IsEnabled(); void AppendConversationDialogs(string conversationId, List messages); List GetConversationDialogs(string conversationId); void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); ConversationBreakpoint? GetConversationBreakpoint(string conversationId); - Task Execute(string agentId, string text, PostbackMessageModel? postback = null, List? states = null); + Task SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List? states = null); } diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj b/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj new file mode 100644 index 00000000..4b661c2a --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharp.Core.SideCar.csproj @@ -0,0 +1,16 @@ + + + + $(TargetFramework) + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + $(SolutionDir)packages + enable + + + + + + + diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs new file mode 100644 index 00000000..efacd308 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs @@ -0,0 +1,28 @@ +using BotSharp.Abstraction.Plugins; +using BotSharp.Abstraction.Settings; +using BotSharp.Core.SideCar.Services; +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Core.SideCar; + +public class BotSharpSideCarPlugin : IBotSharpPlugin +{ + public string Id => "06e5a276-bba0-45af-9625-889267c341c9"; + public string Name => "Side car"; + public string Description => "Provides side car for calling agent cluster in conversation"; + + public SettingsMeta Settings => new SettingsMeta("SideCar"); + public object GetNewSettingsInstance() => new SideCarSettings(); + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var settings = new SideCarSettings(); + config.Bind("SideCar", settings); + services.AddSingleton(settings); + + if (settings.Conversation.Provider == "botsharp") + { + services.AddScoped(); + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs similarity index 69% rename from src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs rename to src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index ae72fbdb..fda96b17 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -1,20 +1,19 @@ -using BotSharp.Abstraction.Conversations.Enums; -using BotSharp.Abstraction.Models; +namespace BotSharp.Core.SideCar.Services; -namespace BotSharp.Core.Conversations.Services; - -public class ConversationSideCar : IConversationSideCar +public class BotSharpConversationSideCar : IConversationSideCar { private readonly IServiceProvider _services; - private readonly ILogger _logger; + private readonly ILogger _logger; private Stack contextStack = new(); private bool enabled = false; - public ConversationSideCar( + public string Provider => "botsharp"; + + public BotSharpConversationSideCar( IServiceProvider services, - ILogger logger) + ILogger logger) { _services = services; _logger = logger; @@ -27,60 +26,42 @@ public class ConversationSideCar : IConversationSideCar public void AppendConversationDialogs(string conversationId, List messages) { - if (enabled) - { - var top = contextStack.Peek(); - top.Dialogs.AddRange(messages); - } - else - { - var db = _services.GetRequiredService(); - db.AppendConversationDialogs(conversationId, messages); - } + if (contextStack.IsNullOrEmpty()) return; + + var top = contextStack.Peek(); + top.Dialogs.AddRange(messages); } public List GetConversationDialogs(string conversationId) { - if (enabled) + if (contextStack.IsNullOrEmpty()) { - return contextStack.Peek().Dialogs; - } - else - { - var db = _services.GetRequiredService(); - return db.GetConversationDialogs(conversationId); + return new List(); } + + return contextStack.Peek().Dialogs; } public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) { - if (enabled) - { - var top = contextStack.Peek().Breakpoints; - top.Add(breakpoint); - } - else - { - var db = _services.GetRequiredService(); - db.UpdateConversationBreakpoint(conversationId, breakpoint); - } + if (contextStack.IsNullOrEmpty()) return; + + var top = contextStack.Peek().Breakpoints; + top.Add(breakpoint); } public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) { - if (enabled) + if (contextStack.IsNullOrEmpty()) { - var top = contextStack.Peek().Breakpoints; - return top.LastOrDefault(); - } - else - { - var db = _services.GetRequiredService(); - return db.GetConversationBreakpoint(conversationId); + return null; } + + var top = contextStack.Peek().Breakpoints; + return top.LastOrDefault(); } - public async Task Execute(string agentId, string text, + public async Task SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List? states = null) { BeforeExecute(); diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Settings/SideCarSettings.cs b/src/Infrastructure/BotSharp.Core.SideCar/Settings/SideCarSettings.cs new file mode 100644 index 00000000..24b60b2a --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.SideCar/Settings/SideCarSettings.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Core.SideCar.Settings; + +public class SideCarSettings +{ + public BaseSetting Conversation { get; set; } +} + +public class BaseSetting +{ + public string Provider { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Using.cs b/src/Infrastructure/BotSharp.Core.SideCar/Using.cs new file mode 100644 index 00000000..d047ee15 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.SideCar/Using.cs @@ -0,0 +1,20 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Threading.Tasks; +global using System.Linq; +global using System.Text.Json; +global using System.Net.Mime; +global using System.Net.Http; +global using System.Threading; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Conversations.Enums; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Models; +global using BotSharp.Abstraction.Routing; +global using BotSharp.Abstraction.SideCar; +global using BotSharp.Abstraction.Utilities; +global using BotSharp.Core.SideCar.Settings; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index 7db04623..a9be6fd2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -43,7 +43,6 @@ public class ConversationPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); // Rich content messaging diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs index 618095e4..8f88f44f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.UpdateBreakpoint.cs @@ -10,15 +10,7 @@ public partial class ConversationService : IConversationService var routingCtx = _services.GetRequiredService(); var messageId = routingCtx.MessageId; - //db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint - //{ - // MessageId = messageId, - // Breakpoint = DateTime.UtcNow, - // Reason = reason - //}); - - var sidecar = _services.GetRequiredService(); - sidecar.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint + db.UpdateConversationBreakpoint(_conversationId, new ConversationBreakpoint { MessageId = messageId, Breakpoint = DateTime.UtcNow, diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 302107ab..6c856591 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -140,11 +140,8 @@ public partial class ConversationService : IConversationService if (fromBreakpoint) { - //var db = _services.GetRequiredService(); - //var breakpoint = db.GetConversationBreakpoint(_conversationId); - - var sidecar = _services.GetRequiredService(); - var breakpoint = sidecar.GetConversationBreakpoint(_conversationId); + var db = _services.GetRequiredService(); + var breakpoint = db.GetConversationBreakpoint(_conversationId); if (breakpoint != null) { diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index 1ce112ca..ea145ac0 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -91,21 +91,13 @@ public class ConversationStorage : IConversationStorage }); } - //db.AppendConversationDialogs(conversationId, dialogElements); - - var sidecar = _services.GetRequiredService(); - sidecar.AppendConversationDialogs(conversationId, dialogElements); - + db.AppendConversationDialogs(conversationId, dialogElements); } public List GetDialogs(string conversationId) { - //var db = _services.GetRequiredService(); - //var dialogs = db.GetConversationDialogs(conversationId); - - var sidecar = _services.GetRequiredService(); - var dialogs = sidecar.GetConversationDialogs(conversationId); - + var db = _services.GetRequiredService(); + var dialogs = db.GetConversationDialogs(conversationId); var hooks = _services.GetServices(); var results = new List(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index 80c37f7a..d2c519eb 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -8,6 +8,8 @@ namespace BotSharp.Core.Repository; public class BotSharpDbContext : Database, IBotSharpRepository { + public IServiceProvider ServiceProvider => throw new NotImplementedException(); + #region Plugin public PluginConfig GetPluginConfig() => throw new NotImplementedException(); public void SavePluginConfig(PluginConfig config) => throw new NotImplementedException(); @@ -90,12 +92,14 @@ public class BotSharpDbContext : Database, IBotSharpRepository public List GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable excludeAgentIds) => throw new NotImplementedException(); + [SideCar] public List GetConversationDialogs(string conversationId) => throw new NotImplementedException(); public ConversationState GetConversationStates(string conversationId) => throw new NotImplementedException(); + [SideCar] public void AppendConversationDialogs(string conversationId, List dialogs) => throw new NotImplementedException(); @@ -108,9 +112,11 @@ public class BotSharpDbContext : Database, IBotSharpRepository public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request) => throw new NotImplementedException(); + [SideCar] public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) => throw new NotImplementedException(); + [SideCar] public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) => throw new NotImplementedException(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 7da6c849..28d0a6cc 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -57,6 +57,7 @@ namespace BotSharp.Core.Repository return true; } + [SideCar] public List GetConversationDialogs(string conversationId) { var dialogs = new List(); @@ -78,6 +79,7 @@ namespace BotSharp.Core.Repository return dialogs; } + [SideCar] public void AppendConversationDialogs(string conversationId, List dialogs) { var convDir = FindConversationDirectory(conversationId); @@ -182,6 +184,7 @@ namespace BotSharp.Core.Repository return true; } + [SideCar] public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) { var convDir = FindConversationDirectory(conversationId); @@ -220,6 +223,7 @@ namespace BotSharp.Core.Repository } } + [SideCar] public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) { var convDir = FindConversationDirectory(conversationId); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index 46f39aaa..f3e1fddf 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -171,6 +171,8 @@ public partial class FileRepository : IBotSharpRepository } } + public IServiceProvider ServiceProvider => _services; + #region Private methods private void DeleteBeforeCreateDirectory(string dir) diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index e28eed72..8a0ca2af 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -33,6 +33,7 @@ global using BotSharp.Abstraction.Files.Utilities; global using BotSharp.Abstraction.Translation.Attributes; global using BotSharp.Abstraction.Messaging.Enums; global using BotSharp.Abstraction.Knowledges.Models; +global using BotSharp.Abstraction.SideCar.Attributes; global using BotSharp.Core.Repository; global using BotSharp.Core.Routing; global using BotSharp.Core.Agents.Services; diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index 2d693603..a99538ab 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.SideCar; using Microsoft.AspNetCore.SignalR; namespace BotSharp.Plugin.ChatHub.Hooks; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index d8159da1..85fa1033 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -70,6 +70,7 @@ public partial class MongoRepository || contentLogDeleted.DeletedCount > 0 || stateLogDeleted.DeletedCount > 0; } + [SideCar] public List GetConversationDialogs(string conversationId) { var dialogs = new List(); @@ -83,6 +84,7 @@ public partial class MongoRepository return formattedDialog ?? new List(); } + [SideCar] public void AppendConversationDialogs(string conversationId, List dialogs) { if (string.IsNullOrEmpty(conversationId)) return; @@ -159,6 +161,7 @@ public partial class MongoRepository return true; } + [SideCar] public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) { if (string.IsNullOrEmpty(conversationId)) return; @@ -176,6 +179,7 @@ public partial class MongoRepository _dc.ConversationStates.UpdateOne(filterState, updateState); } + [SideCar] public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) { if (string.IsNullOrEmpty(conversationId)) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs index 689c06be..258c1883 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs @@ -22,4 +22,6 @@ public partial class MongoRepository : IBotSharpRepository IsUpsert = true, }; } + + public IServiceProvider ServiceProvider => _services; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Using.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Using.cs index 7c74b648..13b2739b 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Using.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Using.cs @@ -8,6 +8,7 @@ global using BotSharp.Abstraction.Agents.Enums; global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Plugins; global using BotSharp.Abstraction.Translation.Models; +global using BotSharp.Abstraction.SideCar.Attributes; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using MongoDB.Bson; diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 4bd62176..a8f819d6 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -29,6 +29,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 7f43ba28..8c4cd9aa 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -151,6 +151,12 @@ } }, + "SideCar": { + "Conversation": { + "Provider": "botsharp" + } + }, + "WebBrowsing": { "Driver": "Playwright" }, @@ -321,6 +327,7 @@ "PluginLoader": { "Assemblies": [ "BotSharp.Core", + "BotSharp.Core.SideCar", "BotSharp.Logger", "BotSharp.Plugin.MongoStorage", "BotSharp.Plugin.Dashboard", From 4260e927378959df925088ccba35f63af1f9ce10 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 4 Nov 2024 16:51:02 -0600 Subject: [PATCH 12/13] minor change --- .../BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index a99538ab..b282fa53 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -165,8 +165,8 @@ public class ChatHubConversationHook : ConversationHookBase #region Private methods private bool AllowSendingMessage() { - var sidecar = _services.GetRequiredService(); - return !sidecar.IsEnabled(); + var sidecar = _services.GetService(); + return sidecar == null || !sidecar.IsEnabled(); } private async Task InitClientConversation(ConversationViewModel conversation) From 344741c69e193933c5fec854335fb1e4c6924ee3 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Mon, 4 Nov 2024 20:53:22 -0600 Subject: [PATCH 13/13] remove affiliateId and employeeId --- .../BotSharp.Abstraction/Users/IUserIdentity.cs | 2 -- .../BotSharp.Core/Users/Services/UserIdentity.cs | 13 +++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs index f42b7f25..1e54c44e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs @@ -13,8 +13,6 @@ public interface IUserIdentity /// string UserLanguage { get; } string? Phone { get; } - string? AffiliateId { get; } - string? EmployeeId { get; } string Type { get; } string Role { get; } string? RegionCode { get; } diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs index 965be261..5f062a6f 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs @@ -70,18 +70,15 @@ public class UserIdentity : IUserIdentity [JsonPropertyName("phone")] public string? Phone => _claims?.FirstOrDefault(x => x.Type == "phone")?.Value; - [JsonPropertyName("affiliateId")] - public string? AffiliateId => _claims?.FirstOrDefault(x => x.Type == "affiliateId")?.Value; - - [JsonPropertyName("employeeId")] - public string? EmployeeId => _claims?.FirstOrDefault(x => x.Type == "employeeId")?.Value; - [JsonPropertyName("type")] public string? Type => _claims?.FirstOrDefault(x => x.Type == "type")?.Value; [JsonPropertyName("role")] public string? Role => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value; - [JsonPropertyName("regionCode")] - public string? RegionCode => _claims?.FirstOrDefault(x => x.Type == "regionCode")?.Value; + /// + /// US, CA, etc. + /// + [JsonPropertyName("region_code")] + public string? RegionCode => _claims?.FirstOrDefault(x => x.Type == "region_code")?.Value; }