diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index cf49da421..7c2466d9f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -12,7 +12,7 @@ public interface IAgentService Task CreateAgent(Agent agent); Task RefreshAgents(); Task> GetAgents(AgentFilter filter); - Task> GetAgentOptions(); + Task> GetAgentOptions(List? agentIds = null); /// /// Load agent configurations and trigger hooks diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs index c13f26a81..e59622bb6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/MessageTypeName.cs @@ -6,4 +6,5 @@ public static class MessageTypeName public const string Notification = "notification"; public const string FunctionCall = "function"; public const string Audio = "audio"; + public const string Error = "error"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 6e558b1b7..d93202578 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -62,14 +62,7 @@ Task SendMessage(string agentId, void SaveStates(); - /// - /// Get conversation keys for searching - /// - /// search query - /// conversation limit - /// if pre-loading, then keys are not filter by the search query - /// - Task> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false); + Task> GetConversationStateSearhKeys(ConversationStateKeysFilter filter); Task MigrateLatestStates(int batchSize = 100, int errorLimit = 10); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/Services/ILoggerService.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/Services/ILoggerService.cs index 23a4809fb..d704b1540 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/Services/ILoggerService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/Services/ILoggerService.cs @@ -1,5 +1,5 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; +using BotSharp.Abstraction.Repositories.Filters; namespace BotSharp.Abstraction.Loggers.Services; @@ -12,5 +12,6 @@ public interface ILoggerService #region Instruction Task> GetInstructionLogs(InstructLogFilter filter); + Task> GetInstructionLogSearchKeys(InstructLogKeysFilter filter); #endregion } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index 235430499..d05c49c6a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -23,9 +23,9 @@ public class ConversationFilter /// /// Check whether each key in the list is in the conversation states and its value equals to target value if not empty /// - public IEnumerable? States { get; set; } = []; + public List? States { get; set; } - public IEnumerable? Tags { get; set; } = []; + public List? Tags { get; set; } public static ConversationFilter Empty() { diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs new file mode 100644 index 000000000..bff4b43cf --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationStateKeysFilter.cs @@ -0,0 +1,21 @@ +namespace BotSharp.Abstraction.Repositories.Filters; + +public class ConversationStateKeysFilter +{ + public string? Query { get; set; } + public int KeyLimit { get; set; } = 10; + public int ConvLimit { get; set; } = 100; + public bool PreLoad { get; set; } + public List? AgentIds { get; set; } + public List? UserIds { get; set; } + + public ConversationStateKeysFilter() + { + + } + + public static ConversationStateKeysFilter Empty() + { + return new(); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructLogFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs similarity index 81% rename from src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructLogFilter.cs rename to src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs index 43d655cee..c1040f944 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/Models/InstructLogFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogFilter.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Instructs.Models; +namespace BotSharp.Abstraction.Repositories.Filters; public class InstructLogFilter : Pagination { @@ -11,6 +11,6 @@ public class InstructLogFilter : Pagination public static InstructLogFilter Empty() { - return new InstructLogFilter(); + return new(); } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs new file mode 100644 index 000000000..c3eade898 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/InstructLogKeysFilter.cs @@ -0,0 +1,21 @@ +namespace BotSharp.Abstraction.Repositories.Filters; + +public class InstructLogKeysFilter +{ + public string? Query { get; set; } + public int KeyLimit { get; set; } = 10; + public int LogLimit { get; set; } = 100; + public bool PreLoad { get; set; } + public List? AgentIds { get; set; } + public List? UserIds { get; set; } + + public InstructLogKeysFilter() + { + + } + + public static InstructLogKeysFilter Empty() + { + return new(); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index d523dd09c..5b92d3221 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; @@ -149,7 +148,7 @@ List GetIdleConversations(int batchSize, int messageLimit, int bufferHou => throw new NotImplementedException(); List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) => throw new NotImplementedException(); - List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) + List GetConversationStateSearchKeys(ConversationStateKeysFilter filter) => throw new NotImplementedException(); List GetConversationsToMigrate(int batchSize = 100) => throw new NotImplementedException(); @@ -182,6 +181,9 @@ bool SaveInstructionLogs(IEnumerable logs) PagedItems GetInstructionLogs(InstructLogFilter filter) => throw new NotImplementedException(); + + List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) + => throw new NotImplementedException(); #endregion #region Statistics diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 15b04f28c..1499ea175 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -10,7 +10,7 @@ public interface IUserService Task> GetUsers(UserFilter filter); Task> SearchLoginUsers(User filter); Task GetUserDetails(string userId, bool includeAgent = false); - Task IsAdminUser(string userId); + Task<(bool, User?)> IsAdminUser(string userId); Task GetUserAuthorizations(IEnumerable? agentIds = null); Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index ec95a51ec..092273fa0 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -30,9 +30,12 @@ public async Task> GetAgents(AgentFilter filter) #if !DEBUG [SharpCache(10, perInstanceCache: true)] #endif - public async Task> GetAgentOptions() + public async Task> GetAgentOptions(List? agentIds) { - var agents = _db.GetAgents(AgentFilter.Empty()); + var agents = _db.GetAgents(new AgentFilter + { + AgentIds = !agentIds.IsNullOrEmpty() ? agentIds : null + }); return agents?.Select(x => new IdName(x.Id, x.Name))?.OrderBy(x => x.Name)?.ToList() ?? []; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index eb0497aeb..c9b3c90cb 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -18,7 +18,7 @@ public async Task RefreshAgents() var userIdentity = _services.GetRequiredService(); var userService = _services.GetRequiredService(); - var isValid = await userService.IsAdminUser(userIdentity.Id); + var (isValid, _) = await userService.IsAdminUser(userIdentity.Id); if (!isValid) { return "Unauthorized user."; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 0cec37d8f..a1a0868a9 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -222,17 +222,26 @@ public void SaveStates() _state.Save(); } - public async Task> GetConversationStateSearhKeys(string query, int convLimit = 100, int keyLimit = 10, bool preload = false) + public async Task> GetConversationStateSearhKeys(ConversationStateKeysFilter filter) { + if (filter == null) + { + filter = ConversationStateKeysFilter.Empty(); + } + var keys = new List(); - if (!preload && string.IsNullOrWhiteSpace(query)) + if (!filter.PreLoad && string.IsNullOrWhiteSpace(filter.Query)) { return keys; } + var userService = _services.GetRequiredService(); var db = _services.GetRequiredService(); - keys = db.GetConversationStateSearchKeys(convUpperLimit: convLimit); - keys = preload ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList(); - return keys.OrderBy(x => x).Take(keyLimit).ToList(); + + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); + filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : []; + keys = db.GetConversationStateSearchKeys(filter); + keys = filter.PreLoad ? keys : keys.Where(x => x.Contains(filter.Query ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList(); + return keys.OrderBy(x => x).Take(filter.KeyLimit).ToList(); } } diff --git a/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs b/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs index 84efae995..42ad45ab0 100644 --- a/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs +++ b/src/Infrastructure/BotSharp.Core/Loggers/Services/LoggerService.Instruction.cs @@ -1,6 +1,4 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; -using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; namespace BotSharp.Core.Loggers.Services; @@ -15,11 +13,10 @@ public async Task> GetInstructionLogs(InstructLo } var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); - var isAdmin = UserConstant.AdminRoles.Contains(user?.Role); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); if (!isAdmin && user?.Id == null) return new(); - filter.UserIds = isAdmin ? [] : user?.Id != null ? [user.Id] : []; + filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : null; var agents = new List(); var users = new List(); @@ -61,4 +58,27 @@ public async Task> GetInstructionLogs(InstructLo Count = logs.Count }; } + + public async Task> GetInstructionLogSearchKeys(InstructLogKeysFilter filter) + { + if (filter == null) + { + filter = InstructLogKeysFilter.Empty(); + } + + var keys = new List(); + if (!filter.PreLoad && string.IsNullOrWhiteSpace(filter.Query)) + { + return keys; + } + + var userService = _services.GetRequiredService(); + var db = _services.GetRequiredService(); + + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); + filter.UserIds = !isAdmin && user?.Id != null ? [user.Id] : null; + keys = db.GetInstructionLogSearchKeys(filter); + keys = filter.PreLoad ? keys : keys.Where(x => x.Contains(filter.Query ?? string.Empty, StringComparison.OrdinalIgnoreCase)).ToList(); + return keys.OrderBy(x => x).Take(filter.KeyLimit).ToList(); + } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 8880a4e0d..3d70264c4 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Loggers.Models; +using BotSharp.Abstraction.Users.Models; using System.IO; namespace BotSharp.Core.Repository; @@ -647,7 +648,7 @@ public List TruncateConversation(string conversationId, string messageId #if !DEBUG [SharpCache(10)] #endif - public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) + public List GetConversationStateSearchKeys(ConversationStateKeysFilter filter) { var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); if (!Directory.Exists(dir)) return []; @@ -658,26 +659,29 @@ public List GetConversationStateSearchKeys(int messageLowerLimit = 2, in foreach (var d in Directory.GetDirectories(dir)) { var convFile = Path.Combine(d, CONVERSATION_FILE); - var stateFile = Path.Combine(d, STATE_FILE); - if (!File.Exists(convFile) || !File.Exists(stateFile)) + var latestStateFile = Path.Combine(d, CONV_LATEST_STATE_FILE); + if (!File.Exists(convFile) || !File.Exists(latestStateFile)) { continue; } var convJson = File.ReadAllText(convFile); - var stateJson = File.ReadAllText(stateFile); + var stateJson = File.ReadAllText(latestStateFile); var conv = JsonSerializer.Deserialize(convJson, _options); - var states = JsonSerializer.Deserialize>(stateJson, _options); - if (conv == null || conv.DialogCount < messageLowerLimit) + var states = JsonSerializer.Deserialize>(stateJson, _options); + if (conv == null + || states.IsNullOrEmpty() + || (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(conv.AgentId)) + || (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(conv.UserId))) { continue; } - var stateKeys = states?.Select(x => x.Key)?.Distinct()?.ToList() ?? []; + var stateKeys = states?.Select(x => x.Key)?.ToList() ?? []; keys.AddRange(stateKeys); count++; - if (count >= convUpperLimit) + if (count >= filter.ConvLimit) { break; } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs index 0b9b27a7d..780279874 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; using System.IO; @@ -271,6 +270,44 @@ public PagedItems GetInstructionLogs(InstructLogFilter filt Count = logs.Count() }; } + + public List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) + { + var keys = new List(); + var baseDir = Path.Combine(_dbSettings.FileRepository, INSTRUCTION_LOG_FOLDER); + if (!Directory.Exists(baseDir)) + { + return keys; + } + + var count = 0; + var files = Directory.GetFiles(baseDir); + foreach (var file in files) + { + var json = File.ReadAllText(file); + var log = JsonSerializer.Deserialize(json, _options); + if (log == null) continue; + + if (log == null + || log.InnerStates.IsNullOrEmpty() + || (!filter.UserIds.IsNullOrEmpty() && !filter.UserIds.Contains(log.UserId)) + || (!filter.AgentIds.IsNullOrEmpty() && !filter.AgentIds.Contains(log.AgentId))) + { + continue; + } + + var stateKeys = log.InnerStates.Select(x => x.Key)?.ToList() ?? []; + keys.AddRange(stateKeys); + count++; + + if (count >= filter.LogLimit) + { + break; + } + } + + return keys.Distinct().ToList(); + } #endregion #region Private methods diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index 60192e5f4..e4ebe311b 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -418,23 +418,24 @@ public async Task> GetUsers(UserFilter filter) return users; } - public async Task IsAdminUser(string userId) + [SharpCache(10, perInstanceCache: true)] + public async Task<(bool, User?)> IsAdminUser(string userId) { var db = _services.GetRequiredService(); var user = db.GetUserById(userId); - return user != null && UserConstant.AdminRoles.Contains(user.Role); + var isAdmin = user != null && UserConstant.AdminRoles.Contains(user.Role); + return (isAdmin, user); } public async Task GetUserAuthorizations(IEnumerable? agentIds = null) { var db = _services.GetRequiredService(); - var user = db.GetUserById(_user.Id); + var (isAdmin, user) = await IsAdminUser(_user.Id); var auth = new UserAuthorization(); if (user == null) return auth; - auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role); - + auth.IsAdmin = isAdmin; var role = db.GetRoles(new RoleFilter { Names = [user.Role] }).FirstOrDefault(); var permissions = user.Permissions?.Any() == true ? user.Permissions : role?.Permissions ?? []; auth.Permissions = permissions; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 10317815a..d8b713390 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -1,10 +1,8 @@ -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; using BotSharp.Core.Infrastructures; namespace BotSharp.OpenAPI.Controllers; @@ -45,27 +43,33 @@ public async Task NewConversation([FromRoute] string agen return ConversationViewModel.FromSession(conv); } - [HttpPost("/conversations")] - public async Task> GetConversations([FromBody] ConversationFilter filter) + [HttpGet("/conversations")] + public async Task> GetConversations([FromQuery] ConversationFilter filter) { var convService = _services.GetRequiredService(); var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); if (user == null) { return new PagedItems(); } - filter.UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : filter.UserId; + filter.UserId = !isAdmin ? user.Id : filter.UserId; var conversations = await convService.GetConversations(filter); var agentService = _services.GetRequiredService(); var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList(); + var agentIds = list.Select(x => x.AgentId).ToList(); + var agents = await agentService.GetAgentOptions(agentIds); + + var userIds = list.Select(x => x.User.Id).ToList(); + var users = await userService.GetUsers(new UserFilter { UserIds = userIds, Size = filter.Pager.Size }); + foreach (var item in list) { - user = await userService.GetUser(item.User.Id); + user = users.Items.FirstOrDefault(x => x.Id == item.User.Id); item.User = UserViewModel.FromUser(user); - var agent = await agentService.GetAgent(item.AgentId); + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); item.AgentName = agent?.Name ?? "Unkown"; } @@ -138,7 +142,7 @@ public async Task> GetDialogs([FromRoute] string { var service = _services.GetRequiredService(); var userService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); if (user == null) { return null; @@ -147,7 +151,7 @@ public async Task> GetDialogs([FromRoute] string var filter = new ConversationFilter { Id = conversationId, - UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null + UserId = !isAdmin ? user.Id : null }; var conversations = await service.GetConversations(filter); if (conversations.Items.IsNullOrEmpty()) @@ -206,11 +210,11 @@ public async Task UpdateConversationTitle([FromRoute] string conversationI var userService = _services.GetRequiredService(); var conv = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); var filter = new ConversationFilter { Id = conversationId, - UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null + UserId = !isAdmin ? user?.Id : null }; var conversations = await conv.GetConversations(filter); @@ -229,11 +233,11 @@ public async Task UpdateConversationTitleAlias([FromRoute] string conversa var userService = _services.GetRequiredService(); var conversationService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !isAdmin ? user?.Id : null }; var conversations = await conversationService.GetConversations(filter); @@ -283,11 +287,11 @@ public async Task DeleteConversation([FromRoute] string conversationId) var userService = _services.GetRequiredService(); var conversationService = _services.GetRequiredService(); - var user = await userService.GetUser(_user.Id); + var (isAdmin, user) = await userService.IsAdminUser(_user.Id); var filter = new ConversationFilter { Id = conversationId, - UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null + UserId = !isAdmin ? user?.Id : null }; var conversations = await conversationService.GetConversations(filter); @@ -555,10 +559,10 @@ public async Task UnpinConversationFromDashboard([FromRoute] string agentI #region Search state keys [HttpGet("/conversation/state/keys")] - public async Task> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] int convLimit = 100, [FromQuery] bool preload = false) + public async Task> GetConversationStateKeys([FromQuery] ConversationStateKeysFilter request) { var convService = _services.GetRequiredService(); - var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, convLimit: convLimit, preload: preload); + var keys = await convService.GetConversationStateSearhKeys(request); return keys; } #endregion diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs index d67ba8cd4..892c3195d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/LoggerController.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Loggers.Services; using BotSharp.OpenAPI.ViewModels.Instructs; @@ -39,6 +38,7 @@ public async Task GetFullLog() } } + #region Conversation log [HttpGet("/logger/conversation/{conversationId}/content-log")] public async Task> GetConversationContentLogs([FromRoute] string conversationId) { @@ -52,7 +52,9 @@ public async Task> GetConversationStateLogs([Fro var logging = _services.GetRequiredService(); return await logging.GetConversationStateLogs(conversationId); } + #endregion + #region Instruction log [HttpGet("/logger/instruction/log")] public async Task> GetInstructionLogs([FromQuery] InstructLogFilter request) { @@ -65,4 +67,13 @@ public async Task> GetInstructionLogs([FromQ Count = logs.Count }; } + + [HttpGet("/logger/instruction/log/keys")] + public async Task> GetInstructionLogKeys([FromQuery] InstructLogKeysFilter request) + { + var logging = _services.GetRequiredService(); + var keys = await logging.GetInstructionLogSearchKeys(request); + return keys; + } + #endregion } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index 55a5f1a82..4f6d3f2bd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -89,6 +89,7 @@ public PluginDef RemovePluginStats([FromRoute] string id) private async Task IsValidUser() { var userService = services.GetRequiredService(); - return await userService.IsAdminUser(_user.Id); + var (isAdmin, _) = await userService.IsAdminUser(_user.Id); + return isAdmin; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs index d4ca64fc2..ee3ac863b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Roles; -using BotSharp.Abstraction.Users.Enums; namespace BotSharp.OpenAPI.Controllers; @@ -83,6 +82,7 @@ public async Task UpdateRole([FromBody] RoleUpdateModel model) private async Task IsValidUser() { var userService = _services.GetRequiredService(); - return await userService.IsAdminUser(_user.Id); + var (isAdmin, _) = await userService.IsAdminUser(_user.Id); + return isAdmin; } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index a853e1eb0..7cb1aa2b2 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -254,7 +254,8 @@ public IActionResult GetUserAvatar() private async Task IsValidUser() { var userService = _services.GetRequiredService(); - return await userService.IsAdminUser(_user.Id); + var (isAdmin, _) = await userService.IsAdminUser(_user.Id); + return isAdmin; } private FileContentResult BuildFileResult(string file) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index f39ad2dc6..908e66b5c 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -644,21 +644,31 @@ public List TruncateConversation(string conversationId, string messageId #if !DEBUG [SharpCache(10)] #endif - public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperLimit = 100) + public List GetConversationStateSearchKeys(ConversationStateKeysFilter filter) { - var stateBuilder = Builders.Filter; - var sortDef = Builders.Sort.Descending(x => x.UpdatedTime); - var stateFilters = new List>() + var builder = Builders.Filter; + var sortDef = Builders.Sort.Descending(x => x.UpdatedTime); + var filters = new List>() { - stateBuilder.Exists(x => x.States), - stateBuilder.Ne(x => x.States, []) + builder.Exists(x => x.LatestStates), + builder.Ne(x => x.LatestStates, []) }; - var states = _dc.ConversationStates.Find(stateBuilder.And(stateFilters)) - .Sort(sortDef) - .Limit(convUpperLimit) - .ToList(); - var keys = states.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList(); + if (!filter.AgentIds.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.AgentId, filter.AgentIds)); + } + + if (!filter.UserIds.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.UserId, filter.UserIds)); + } + + var convDocs = _dc.Conversations.Find(builder.And(filters)) + .Sort(sortDef) + .Limit(filter.ConvLimit) + .ToList(); + var keys = convDocs.SelectMany(x => x.LatestStates.Select(x => x.Key)).Distinct().ToList(); return keys; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs index d94b1d6a9..457faba26 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Repositories.Filters; using System.Text.Json; @@ -241,5 +240,33 @@ public PagedItems GetInstructionLogs(InstructLogFilter filt Count = (int)count }; } + + public List GetInstructionLogSearchKeys(InstructLogKeysFilter filter) + { + var builder = Builders.Filter; + var sortDef = Builders.Sort.Descending(x => x.CreatedTime); + var filters = new List>() + { + builder.Exists(x => x.States), + builder.Ne(x => x.States, []) + }; + + if (!filter.AgentIds.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.AgentId, filter.AgentIds)); + } + + if (!filter.UserIds.IsNullOrEmpty()) + { + filters.Add(builder.In(x => x.UserId, filter.UserIds)); + } + + var convDocs = _dc.InstructionLogs.Find(builder.And(filters)) + .Sort(sortDef) + .Limit(filter.LogLimit) + .ToList(); + var keys = convDocs.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList(); + return keys; + } #endregion }