From 0d91a2b558f3e9668fb326ff5b29ed4008011490 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Fri, 8 Mar 2024 16:02:08 -0600 Subject: [PATCH] add conv state filter --- .../Filters/ConversationFilter.cs | 11 ++++ .../FileRepository.Conversation.cs | 50 ++++++++++++++++--- .../ConversationTimeoutService.cs | 3 +- .../Controllers/ConversationController.cs | 4 +- .../MongoRepository.Conversation.cs | 37 ++++++++++++-- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index 33571223..432b34bb 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -16,4 +16,15 @@ public class ConversationFilter /// Agent task id /// public string? TaskId { get; set; } + + /// + /// 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; } = new List(); } + +public class KeyValue +{ + public string Key { get; set; } + public string? Value { get; set; } +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index ab587c14..49d695f0 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -103,11 +103,23 @@ namespace BotSharp.Core.Repository var convDir = FindConversationDirectory(conversationId); if (!string.IsNullOrEmpty(convDir)) { - var dialogDir = Path.Combine(convDir, DIALOG_FILE); - if (File.Exists(dialogDir)) + var dialogFile = Path.Combine(convDir, DIALOG_FILE); + if (File.Exists(dialogFile)) { var texts = ParseDialogElements(dialogs); - File.AppendAllLines(dialogDir, texts); + File.AppendAllLines(dialogFile, texts); + } + + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + if (File.Exists(convFile)) + { + var json = File.ReadAllText(convFile); + var conv = JsonSerializer.Deserialize(json, _options); + if (conv != null) + { + conv.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + } } } } @@ -219,10 +231,10 @@ namespace BotSharp.Core.Repository var totalDirs = Directory.GetDirectories(dir); foreach (var d in totalDirs) { - var path = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(path)) continue; + var convFile = Path.Combine(d, CONVERSATION_FILE); + if (!File.Exists(convFile)) continue; - var json = File.ReadAllText(path); + var json = File.ReadAllText(convFile); var record = JsonSerializer.Deserialize(json, _options); if (record == null) continue; @@ -234,6 +246,30 @@ namespace BotSharp.Core.Repository if (filter?.UserId != null) matched = matched && record.UserId == filter.UserId; if (filter?.TaskId != null) matched = matched && record.TaskId == filter.TaskId; + // Check states + if (filter != null && !filter.States.IsNullOrEmpty()) + { + var stateFile = Path.Combine(d, STATE_FILE); + var convStates = CollectConversationStates(stateFile); + foreach (var pair in filter.States) + { + if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue; + + var foundState = convStates.FirstOrDefault(x => x.Key.IsEqualTo(pair.Key)); + if (foundState == null) + { + matched = false; + break; + } + + if (!string.IsNullOrWhiteSpace(pair.Value)) + { + var curValue = foundState.Values.LastOrDefault()?.Data; + matched = matched && pair.Value.IsEqualTo(curValue); + } + } + } + if (!matched) continue; records.Add(record); } @@ -288,7 +324,7 @@ namespace BotSharp.Core.Repository var json = File.ReadAllText(convFile); var conv = JsonSerializer.Deserialize(json, _options); - if (conv == null || conv.CreatedTime > utcNow.AddHours(-bufferHours)) + if (conv == null || conv.UpdatedTime > utcNow.AddHours(-bufferHours)) { continue; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs index fe149ff0..3f453485 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/BackgroundServices/ConversationTimeoutService.cs @@ -24,9 +24,8 @@ namespace BotSharp.OpenAPI.BackgroundServices var delay = Task.Delay(TimeSpan.FromHours(1)); try { - await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10)); await CleanIdleConversationsAsync(); - + await CloseIdleConversationsAsync(TimeSpan.FromMinutes(10)); } catch (Exception ex) { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 92a17d63..fee57e18 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -33,8 +33,8 @@ public class ConversationController : ControllerBase return ConversationViewModel.FromSession(conv); } - [HttpGet("/conversations")] - public async Task> GetConversations([FromQuery] ConversationFilter filter) + [HttpPost("/conversations")] + public async Task> GetConversations([FromBody] ConversationFilter filter) { var service = _services.GetRequiredService(); var conversations = await service.GetConversations(filter); diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 047b7884..968c6b2c 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -3,6 +3,7 @@ using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Models; using BotSharp.Plugin.MongoStorage.Collections; using BotSharp.Plugin.MongoStorage.Models; +using System.Text.RegularExpressions; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -224,6 +225,35 @@ public partial class MongoRepository if (!string.IsNullOrEmpty(filter.UserId)) filters.Add(builder.Eq(x => x.UserId, filter.UserId)); if (!string.IsNullOrEmpty(filter.TaskId)) filters.Add(builder.Eq(x => x.TaskId, filter.TaskId)); + // Check states + if (!filter.States.IsNullOrEmpty()) + { + var targetConvIds = new List(); + + foreach (var pair in filter.States) + { + if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue; + + var query = _dc.ConversationStates.AsQueryable(); + var convIds = query.AsEnumerable().Where(x => + { + var foundState = x.States.FirstOrDefault(s => s.Key.IsEqualTo(pair.Key)); + if (foundState == null) return false; + + if (!string.IsNullOrWhiteSpace(pair.Value)) + { + return pair.Value.IsEqualTo(foundState.Values.LastOrDefault()?.Data); + } + + return true; + }).Select(x => x.ConversationId).ToList(); + + targetConvIds = targetConvIds.Concat(convIds).Distinct().ToList(); + } + + filters.Add(builder.In(x => x.Id, targetConvIds)); + } + var filterDef = builder.And(filters); var sortDef = Builders.Sort.Descending(x => x.CreatedTime); var pager = filter?.Pager ?? new Pagination(); @@ -258,7 +288,7 @@ public partial class MongoRepository { var records = new List(); var conversations = _dc.Conversations.Aggregate() - .Group(c => c.UserId, g => g.OrderByDescending(x => x.CreatedTime).First()) + .Group(c => c.UserId, g => g.First(x => x.CreatedTime == g.Select(y => y.CreatedTime).Max())) .ToList(); return conversations.Select(c => new Conversation() { @@ -276,7 +306,6 @@ public partial class MongoRepository public List GetIdleConversations(int batchSize, int messageLimit, int bufferHours) { var page = 1; - var pageLimit = 10; var batchLimit = 50; var utcNow = DateTime.UtcNow; var conversationIds = new List(); @@ -286,11 +315,11 @@ public partial class MongoRepository batchSize = batchLimit; } - while (true && page < pageLimit) + while (true) { var skip = (page - 1) * batchSize; var candidates = _dc.Conversations.AsQueryable() - .Where(x => x.CreatedTime <= utcNow.AddHours(-bufferHours)) + .Where(x => x.UpdatedTime <= utcNow.AddHours(-bufferHours)) .Skip(skip) .Take(batchSize) .Select(x => x.Id)