diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index a606045b..01aba146 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -71,5 +71,5 @@ public interface IConversationService /// conversation limit /// if pre-loading, then keys are not filter by the search query /// - Task> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false); + Task> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index caf58c43..a58600e5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -148,7 +148,7 @@ public interface IBotSharpRepository : IHaveServiceProvider => throw new NotImplementedException(); List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) => throw new NotImplementedException(); - List GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100) + List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) => throw new NotImplementedException(); #endregion diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index beb4e33a..63a257e5 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -222,7 +222,7 @@ public partial class ConversationService : IConversationService _state.Save(); } - public async Task> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false) + public async Task> GetConversationStateSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false) { var keys = new List(); if (!preLoad && string.IsNullOrWhiteSpace(query)) @@ -231,7 +231,7 @@ public partial class ConversationService : IConversationService } var db = _services.GetRequiredService(); - keys = db.GetConversationSearchKeys(convlimit: convlimit); + keys = db.GetConversationStateSearchKeys(convUpperlimit: convlimit); keys = preLoad ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList(); return keys.OrderBy(x => x).Take(keyLimit).ToList(); } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 4591b694..25682662 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -1,276 +1,235 @@ using BotSharp.Abstraction.Loggers.Models; using System.IO; -namespace BotSharp.Core.Repository +namespace BotSharp.Core.Repository; + +public partial class FileRepository { - public partial class FileRepository + public void CreateNewConversation(Conversation conversation) { - public void CreateNewConversation(Conversation conversation) + var utcNow = DateTime.UtcNow; + conversation.CreatedTime = utcNow; + conversation.UpdatedTime = utcNow; + conversation.Tags ??= new(); + + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id); + if (!Directory.Exists(dir)) { - var utcNow = DateTime.UtcNow; - conversation.CreatedTime = utcNow; - conversation.UpdatedTime = utcNow; - conversation.Tags ??= new(); - - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - - var convFile = Path.Combine(dir, CONVERSATION_FILE); - if (!File.Exists(convFile)) - { - File.WriteAllText(convFile, JsonSerializer.Serialize(conversation, _options)); - } - - var dialogFile = Path.Combine(dir, DIALOG_FILE); - if (!File.Exists(dialogFile)) - { - File.WriteAllText(dialogFile, "[]"); - } - - var stateFile = Path.Combine(dir, STATE_FILE); - if (!File.Exists(stateFile)) - { - File.WriteAllText(stateFile, JsonSerializer.Serialize(new List(), _options)); - } - - var breakpointFile = Path.Combine(dir, BREAKPOINT_FILE); - if (!File.Exists(breakpointFile)) - { - File.WriteAllText(breakpointFile, JsonSerializer.Serialize(new List(), _options)); - } + Directory.CreateDirectory(dir); } - public bool DeleteConversations(IEnumerable conversationIds) + var convFile = Path.Combine(dir, CONVERSATION_FILE); + if (!File.Exists(convFile)) { - if (conversationIds.IsNullOrEmpty()) return false; - - foreach (var conversationId in conversationIds) - { - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) continue; - - Directory.Delete(convDir, true); - } - - return true; + File.WriteAllText(convFile, JsonSerializer.Serialize(conversation, _options)); } - [SideCar] - public List GetConversationDialogs(string conversationId) + var dialogFile = Path.Combine(dir, DIALOG_FILE); + if (!File.Exists(dialogFile)) { - var dialogs = new List(); - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) - { - var dialogDir = Path.Combine(convDir, DIALOG_FILE); - var texts = File.ReadAllText(dialogDir); - try - { - dialogs = JsonSerializer.Deserialize>(texts, _options) ?? new List(); - } - catch - { - dialogs = new List(); - } - } - - return dialogs; + File.WriteAllText(dialogFile, "[]"); } - [SideCar] - public void AppendConversationDialogs(string conversationId, List dialogs) + var stateFile = Path.Combine(dir, STATE_FILE); + if (!File.Exists(stateFile)) + { + File.WriteAllText(stateFile, JsonSerializer.Serialize(new List(), _options)); + } + + var breakpointFile = Path.Combine(dir, BREAKPOINT_FILE); + if (!File.Exists(breakpointFile)) + { + File.WriteAllText(breakpointFile, JsonSerializer.Serialize(new List(), _options)); + } + } + + public bool DeleteConversations(IEnumerable conversationIds) + { + if (conversationIds.IsNullOrEmpty()) return false; + + foreach (var conversationId in conversationIds) { var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) + if (string.IsNullOrEmpty(convDir)) continue; + + Directory.Delete(convDir, true); + } + + return true; + } + + [SideCar] + public List GetConversationDialogs(string conversationId) + { + var dialogs = new List(); + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { + var dialogDir = Path.Combine(convDir, DIALOG_FILE); + var texts = File.ReadAllText(dialogDir); + try { - var dialogFile = Path.Combine(convDir, DIALOG_FILE); - if (File.Exists(dialogFile)) - { - var prevDialogs = File.ReadAllText(dialogFile); - var elements = JsonSerializer.Deserialize>(prevDialogs, _options); - if (elements != null) - { - elements.AddRange(dialogs); - } - else - { - elements = elements ?? new List(); - } - - File.WriteAllText(dialogFile, JsonSerializer.Serialize(elements, _options)); - } - - 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.DialogCount += dialogs.Count(); - conv.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); - } - } + dialogs = JsonSerializer.Deserialize>(texts, _options) ?? new List(); + } + catch + { + dialogs = new List(); } } - public void UpdateConversationTitle(string conversationId, string title) + return dialogs; + } + + [SideCar] + public void AppendConversationDialogs(string conversationId, List dialogs) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) { - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) - { - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - var content = File.ReadAllText(convFile); - var record = JsonSerializer.Deserialize(content, _options); - if (record != null) - { - record.Title = title; - record.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options)); - } - } - } - public void UpdateConversationTitleAlias(string conversationId, string titleAlias) - { - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) - { - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - var content = File.ReadAllText(convFile); - var record = JsonSerializer.Deserialize(content, _options); - if (record != null) - { - record.TitleAlias = titleAlias; - record.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options)); - } - } - } - - public bool UpdateConversationTags(string conversationId, List tags) - { - if (string.IsNullOrEmpty(conversationId)) return false; - - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; - - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - if (!File.Exists(convFile)) return false; - - var json = File.ReadAllText(convFile); - var conv = JsonSerializer.Deserialize(json, _options); - conv.Tags = tags ?? new(); - conv.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); - return true; - } - - public bool AppendConversationTags(string conversationId, List tags) - { - if (string.IsNullOrEmpty(conversationId) || tags.IsNullOrEmpty()) return false; - - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; - - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - if (!File.Exists(convFile)) return false; - - var json = File.ReadAllText(convFile); - var conv = JsonSerializer.Deserialize(json, _options); - - var curTags = conv.Tags ?? new(); - var newTags = curTags.Concat(tags).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); - conv.Tags = newTags; - conv.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); - return true; - } - - public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request) - { - if (string.IsNullOrEmpty(conversationId)) return false; - - var dialogs = GetConversationDialogs(conversationId); - var candidates = dialogs.Where(x => x.MetaData.MessageId == request.Message.MetaData.MessageId - && x.MetaData.Role == request.Message.MetaData.Role).ToList(); - - var found = candidates.Where((_, idx) => idx == request.InnderIndex).FirstOrDefault(); - if (found == null) return false; - - found.Content = request.Message.Content; - found.RichContent = request.Message.RichContent; - - if (!string.IsNullOrEmpty(found.SecondaryContent)) - { - found.SecondaryContent = request.Message.Content; - } - - if (!string.IsNullOrEmpty(found.SecondaryRichContent)) - { - found.SecondaryRichContent = request.Message.RichContent; - } - - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return false; - var dialogFile = Path.Combine(convDir, DIALOG_FILE); - File.WriteAllText(dialogFile, JsonSerializer.Serialize(dialogs, _options)); - return true; - } - - [SideCar] - public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) - { - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) + if (File.Exists(dialogFile)) { - var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE); - - if (!File.Exists(breakpointFile)) + var prevDialogs = File.ReadAllText(dialogFile); + var elements = JsonSerializer.Deserialize>(prevDialogs, _options); + if (elements != null) { - File.Create(breakpointFile); - } - - var content = File.ReadAllText(breakpointFile); - var records = JsonSerializer.Deserialize>(content, _options); - var newBreakpoint = new List() - { - new ConversationBreakpoint - { - MessageId = breakpoint.MessageId, - Breakpoint = breakpoint.Breakpoint, - Reason = breakpoint.Reason, - CreatedTime = DateTime.UtcNow, - } - }; - - if (records != null && !records.IsNullOrEmpty()) - { - records = records.Concat(newBreakpoint).ToList(); + elements.AddRange(dialogs); } else { - records = newBreakpoint; + elements = elements ?? new List(); } - File.WriteAllText(breakpointFile, JsonSerializer.Serialize(records, _options)); + File.WriteAllText(dialogFile, JsonSerializer.Serialize(elements, _options)); + } + + 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.DialogCount += dialogs.Count(); + conv.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + } } } + } - [SideCar] - public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) + public void UpdateConversationTitle(string conversationId, string title) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) { - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + var content = File.ReadAllText(convFile); + var record = JsonSerializer.Deserialize(content, _options); + if (record != null) { - return null; + record.Title = title; + record.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options)); } + } + } + public void UpdateConversationTitleAlias(string conversationId, string titleAlias) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + var content = File.ReadAllText(convFile); + var record = JsonSerializer.Deserialize(content, _options); + if (record != null) + { + record.TitleAlias = titleAlias; + record.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(record, _options)); + } + } + } + public bool UpdateConversationTags(string conversationId, List tags) + { + if (string.IsNullOrEmpty(conversationId)) return false; + + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) return false; + + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + if (!File.Exists(convFile)) return false; + + var json = File.ReadAllText(convFile); + var conv = JsonSerializer.Deserialize(json, _options); + conv.Tags = tags ?? new(); + conv.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + return true; + } + + public bool AppendConversationTags(string conversationId, List tags) + { + if (string.IsNullOrEmpty(conversationId) || tags.IsNullOrEmpty()) return false; + + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) return false; + + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + if (!File.Exists(convFile)) return false; + + var json = File.ReadAllText(convFile); + var conv = JsonSerializer.Deserialize(json, _options); + + var curTags = conv.Tags ?? new(); + var newTags = curTags.Concat(tags).Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + conv.Tags = newTags; + conv.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + return true; + } + + public bool UpdateConversationMessage(string conversationId, UpdateMessageRequest request) + { + if (string.IsNullOrEmpty(conversationId)) return false; + + var dialogs = GetConversationDialogs(conversationId); + var candidates = dialogs.Where(x => x.MetaData.MessageId == request.Message.MetaData.MessageId + && x.MetaData.Role == request.Message.MetaData.Role).ToList(); + + var found = candidates.Where((_, idx) => idx == request.InnderIndex).FirstOrDefault(); + if (found == null) return false; + + found.Content = request.Message.Content; + found.RichContent = request.Message.RichContent; + + if (!string.IsNullOrEmpty(found.SecondaryContent)) + { + found.SecondaryContent = request.Message.Content; + } + + if (!string.IsNullOrEmpty(found.SecondaryRichContent)) + { + found.SecondaryRichContent = request.Message.RichContent; + } + + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) return false; + + var dialogFile = Path.Combine(convDir, DIALOG_FILE); + File.WriteAllText(dialogFile, JsonSerializer.Serialize(dialogs, _options)); + return true; + } + + [SideCar] + public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE); + if (!File.Exists(breakpointFile)) { File.Create(breakpointFile); @@ -278,557 +237,599 @@ namespace BotSharp.Core.Repository var content = File.ReadAllText(breakpointFile); var records = JsonSerializer.Deserialize>(content, _options); - - return records?.LastOrDefault(); - } - - public ConversationState GetConversationStates(string conversationId) - { - var states = new List(); - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) + var newBreakpoint = new List() { - var stateFile = Path.Combine(convDir, STATE_FILE); - states = CollectConversationStates(stateFile); - } - - return new ConversationState(states); - } - - public void UpdateConversationStates(string conversationId, List states) - { - if (states.IsNullOrEmpty()) return; - - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) - { - var stateFile = Path.Combine(convDir, STATE_FILE); - if (File.Exists(stateFile)) + new ConversationBreakpoint { - var stateStr = JsonSerializer.Serialize(states, _options); - File.WriteAllText(stateFile, stateStr); + MessageId = breakpoint.MessageId, + Breakpoint = breakpoint.Breakpoint, + Reason = breakpoint.Reason, + CreatedTime = DateTime.UtcNow, } - } - } - - public void UpdateConversationStatus(string conversationId, string status) - { - var convDir = FindConversationDirectory(conversationId); - if (!string.IsNullOrEmpty(convDir)) - { - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - if (File.Exists(convFile)) - { - var json = File.ReadAllText(convFile); - var conv = JsonSerializer.Deserialize(json, _options); - conv.Status = status; - conv.UpdatedTime = DateTime.UtcNow; - File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); - } - } - } - - public Conversation GetConversation(string conversationId) - { - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) return null; - - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - var content = File.ReadAllText(convFile); - var record = JsonSerializer.Deserialize(content, _options); - - var dialogFile = Path.Combine(convDir, DIALOG_FILE); - if (record != null) - { - record.Dialogs = CollectDialogElements(dialogFile); - } - - var stateFile = Path.Combine(convDir, STATE_FILE); - if (record != null) - { - var states = CollectConversationStates(stateFile); - var curStates = new Dictionary(); - states.ForEach(x => - { - curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty; - }); - record.States = curStates; - } - - return record; - } - - public PagedItems GetConversations(ConversationFilter filter) - { - if (filter == null) - { - filter = ConversationFilter.Empty(); - } - - var records = new List(); - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - var pager = filter?.Pager ?? new Pagination(); - - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - - var totalDirs = Directory.GetDirectories(dir); - foreach (var d in totalDirs) - { - var convFile = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(convFile)) continue; - - var json = File.ReadAllText(convFile); - var record = JsonSerializer.Deserialize(json, _options); - if (record == null) continue; - - var matched = true; - if (filter?.Id != null) - { - matched = matched && record.Id == filter.Id; - } - if (filter?.Title != null) - { - matched = matched && record.Title.Contains(filter.Title); - } - if (filter?.TitleAlias != null) - { - matched = matched && record.TitleAlias.Contains(filter.TitleAlias); - } - if (filter?.AgentId != null) - { - matched = matched && record.AgentId == filter.AgentId; - } - if (filter?.Status != null) - { - matched = matched && record.Status == filter.Status; - } - if (filter?.Channel != null) - { - matched = matched && record.Channel == filter.Channel; - } - if (filter?.UserId != null) - { - matched = matched && record.UserId == filter.UserId; - } - if (filter?.TaskId != null) - { - matched = matched && record.TaskId == filter.TaskId; - } - if (filter?.StartTime != null) - { - matched = matched && record.CreatedTime >= filter.StartTime.Value; - } - if (filter?.Tags != null && filter.Tags.Any()) - { - matched = matched && !record.Tags.IsNullOrEmpty() && record.Tags.Exists(t => filter.Tags.Contains(t)); - } - - // 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); - } - - return new PagedItems - { - Items = records.OrderByDescending(x => x.CreatedTime).Skip(pager.Offset).Take(pager.Size), - Count = records.Count(), }; + + if (records != null && !records.IsNullOrEmpty()) + { + records = records.Concat(newBreakpoint).ToList(); + } + else + { + records = newBreakpoint; + } + + File.WriteAllText(breakpointFile, JsonSerializer.Serialize(records, _options)); + } + } + + [SideCar] + public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) + { + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) + { + return null; } - public List GetLastConversations() + var breakpointFile = Path.Combine(convDir, BREAKPOINT_FILE); + if (!File.Exists(breakpointFile)) { - var records = new List(); - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - - foreach (var d in Directory.GetDirectories(dir)) - { - var path = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(path)) continue; - - var json = File.ReadAllText(path); - var record = JsonSerializer.Deserialize(json, _options); - if (record == null) continue; - - records.Add(record); - } - return records.GroupBy(r => r.UserId) - .Select(g => g.OrderByDescending(x => x.CreatedTime).First()) - .ToList(); + File.Create(breakpointFile); } - public List GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable excludeAgentIds) + var content = File.ReadAllText(breakpointFile); + var records = JsonSerializer.Deserialize>(content, _options); + + return records?.LastOrDefault(); + } + + public ConversationState GetConversationStates(string conversationId) + { + var states = new List(); + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) { - var ids = new List(); - var batchLimit = 100; - var utcNow = DateTime.UtcNow; - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + var stateFile = Path.Combine(convDir, STATE_FILE); + states = CollectConversationStates(stateFile); + } - if (!Directory.Exists(dir)) + return new ConversationState(states); + } + + public void UpdateConversationStates(string conversationId, List states) + { + if (states.IsNullOrEmpty()) return; + + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { + var stateFile = Path.Combine(convDir, STATE_FILE); + if (File.Exists(stateFile)) { - Directory.CreateDirectory(dir); + var stateStr = JsonSerializer.Serialize(states, _options); + File.WriteAllText(stateFile, stateStr); } + } + } - if (batchSize <= 0 || batchSize > batchLimit) + public void UpdateConversationStatus(string conversationId, string status) + { + var convDir = FindConversationDirectory(conversationId); + if (!string.IsNullOrEmpty(convDir)) + { + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + if (File.Exists(convFile)) { - batchSize = batchLimit; - } - - if (bufferHours <= 0) - { - bufferHours = 12; - } - - if (messageLimit <= 0) - { - messageLimit = 2; - } - - foreach (var d in Directory.GetDirectories(dir)) - { - var convFile = Path.Combine(d, CONVERSATION_FILE); - if (!File.Exists(convFile)) - { - Directory.Delete(d, true); - continue; - } - var json = File.ReadAllText(convFile); var conv = JsonSerializer.Deserialize(json, _options); + conv.Status = status; + conv.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + } + } + } - if (conv == null) - { - Directory.Delete(d, true); - continue; - } + public Conversation GetConversation(string conversationId) + { + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) return null; - if (conv.UpdatedTime > utcNow.AddHours(-bufferHours)) - { - continue; - } + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + var content = File.ReadAllText(convFile); + var record = JsonSerializer.Deserialize(content, _options); - if ((excludeAgentIds.Contains(conv.AgentId) && conv.DialogCount == 0) - || (!excludeAgentIds.Contains(conv.AgentId) && conv.DialogCount <= messageLimit)) + var dialogFile = Path.Combine(convDir, DIALOG_FILE); + if (record != null) + { + record.Dialogs = CollectDialogElements(dialogFile); + } + + var stateFile = Path.Combine(convDir, STATE_FILE); + if (record != null) + { + var states = CollectConversationStates(stateFile); + var curStates = new Dictionary(); + states.ForEach(x => + { + curStates[x.Key] = x.Values?.LastOrDefault()?.Data ?? string.Empty; + }); + record.States = curStates; + } + + return record; + } + + public PagedItems GetConversations(ConversationFilter filter) + { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + + var records = new List(); + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + var pager = filter?.Pager ?? new Pagination(); + + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + var totalDirs = Directory.GetDirectories(dir); + foreach (var d in totalDirs) + { + var convFile = Path.Combine(d, CONVERSATION_FILE); + if (!File.Exists(convFile)) continue; + + var json = File.ReadAllText(convFile); + var record = JsonSerializer.Deserialize(json, _options); + if (record == null) continue; + + var matched = true; + if (filter?.Id != null) + { + matched = matched && record.Id == filter.Id; + } + if (filter?.Title != null) + { + matched = matched && record.Title.Contains(filter.Title); + } + if (filter?.TitleAlias != null) + { + matched = matched && record.TitleAlias.Contains(filter.TitleAlias); + } + if (filter?.AgentId != null) + { + matched = matched && record.AgentId == filter.AgentId; + } + if (filter?.Status != null) + { + matched = matched && record.Status == filter.Status; + } + if (filter?.Channel != null) + { + matched = matched && record.Channel == filter.Channel; + } + if (filter?.UserId != null) + { + matched = matched && record.UserId == filter.UserId; + } + if (filter?.TaskId != null) + { + matched = matched && record.TaskId == filter.TaskId; + } + if (filter?.StartTime != null) + { + matched = matched && record.CreatedTime >= filter.StartTime.Value; + } + if (filter?.Tags != null && filter.Tags.Any()) + { + matched = matched && !record.Tags.IsNullOrEmpty() && record.Tags.Exists(t => filter.Tags.Contains(t)); + } + + // 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) { - ids.Add(conv.Id); - if (ids.Count >= batchSize) + if (pair == null || string.IsNullOrWhiteSpace(pair.Key)) continue; + + var foundState = convStates.FirstOrDefault(x => x.Key.IsEqualTo(pair.Key)); + if (foundState == null) { - return ids; + matched = false; + break; + } + + if (!string.IsNullOrWhiteSpace(pair.Value)) + { + var curValue = foundState.Values.LastOrDefault()?.Data; + matched = matched && pair.Value.IsEqualTo(curValue); } } } - return ids; + + if (!matched) continue; + + records.Add(record); } - - public List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) + return new PagedItems { - var deletedMessageIds = new List(); - if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) + Items = records.OrderByDescending(x => x.CreatedTime).Skip(pager.Offset).Take(pager.Size), + Count = records.Count(), + }; + } + + public List GetLastConversations() + { + var records = new List(); + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + + foreach (var d in Directory.GetDirectories(dir)) + { + var path = Path.Combine(d, CONVERSATION_FILE); + if (!File.Exists(path)) continue; + + var json = File.ReadAllText(path); + var record = JsonSerializer.Deserialize(json, _options); + if (record == null) continue; + + records.Add(record); + } + return records.GroupBy(r => r.UserId) + .Select(g => g.OrderByDescending(x => x.CreatedTime).First()) + .ToList(); + } + + public List GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable excludeAgentIds) + { + var ids = new List(); + var batchLimit = 100; + var utcNow = DateTime.UtcNow; + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + if (batchSize <= 0 || batchSize > batchLimit) + { + batchSize = batchLimit; + } + + if (bufferHours <= 0) + { + bufferHours = 12; + } + + if (messageLimit <= 0) + { + messageLimit = 2; + } + + foreach (var d in Directory.GetDirectories(dir)) + { + var convFile = Path.Combine(d, CONVERSATION_FILE); + if (!File.Exists(convFile)) { - return deletedMessageIds; + Directory.Delete(d, true); + continue; } - var dialogs = new List(); - - var convDir = FindConversationDirectory(conversationId); - if (string.IsNullOrEmpty(convDir)) + var json = File.ReadAllText(convFile); + var conv = JsonSerializer.Deserialize(json, _options); + + if (conv == null) { - return deletedMessageIds; + Directory.Delete(d, true); + continue; } - var dialogDir = Path.Combine(convDir, DIALOG_FILE); - dialogs = CollectDialogElements(dialogDir); - if (dialogs.IsNullOrEmpty()) + if (conv.UpdatedTime > utcNow.AddHours(-bufferHours)) { - return deletedMessageIds; + continue; } - var foundIdx = dialogs.FindIndex(x => x.MetaData?.MessageId == messageId); - if (foundIdx < 0) + if ((excludeAgentIds.Contains(conv.AgentId) && conv.DialogCount == 0) + || (!excludeAgentIds.Contains(conv.AgentId) && conv.DialogCount <= messageLimit)) { - return deletedMessageIds; + ids.Add(conv.Id); + if (ids.Count >= batchSize) + { + return ids; + } } + } + return ids; + } - deletedMessageIds = dialogs.Where((x, idx) => idx >= foundIdx && !string.IsNullOrEmpty(x.MetaData?.MessageId)) - .Select(x => x.MetaData.MessageId).Distinct().ToList(); - - // Handle truncated dialogs - var isSaved = HandleTruncatedDialogs(convDir, dialogDir, dialogs, foundIdx); - - // Handle truncated states - var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime; - var stateDir = Path.Combine(convDir, STATE_FILE); - var states = CollectConversationStates(stateDir); - isSaved = HandleTruncatedStates(stateDir, states, messageId, refTime); - - // Handle truncated breakpoints - var breakpointDir = Path.Combine(convDir, BREAKPOINT_FILE); - var breakpoints = CollectConversationBreakpoints(breakpointDir); - isSaved = HandleTruncatedBreakpoints(breakpointDir, breakpoints, refTime); - - // Remove logs - if (cleanLog) - { - HandleTruncatedLogs(convDir, refTime); - } + public List TruncateConversation(string conversationId, string messageId, bool cleanLog = false) + { + var deletedMessageIds = new List(); + if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) + { return deletedMessageIds; } - - public List GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100) + var dialogs = new List(); + + var convDir = FindConversationDirectory(conversationId); + if (string.IsNullOrEmpty(convDir)) { - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); - if (!Directory.Exists(dir)) return []; - - var count = 0; - var keys = new List(); - - 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)) - { - continue; - } - - var convJson = File.ReadAllText(convFile); - var stateJson = File.ReadAllText(stateFile); - var conv = JsonSerializer.Deserialize(convJson, _options); - var states = JsonSerializer.Deserialize>(stateJson, _options); - if (conv == null || conv.DialogCount < messageLimit) - { - continue; - } - - var stateKeys = states?.Select(x => x.Key)?.Distinct()?.ToList() ?? []; - keys.AddRange(stateKeys); - count++; - - if (count > convlimit) - { - break; - } - } - - return keys.Distinct().ToList(); + return deletedMessageIds; } - - #region Private methods - private string? FindConversationDirectory(string conversationId) + var dialogDir = Path.Combine(convDir, DIALOG_FILE); + dialogs = CollectDialogElements(dialogDir); + if (dialogs.IsNullOrEmpty()) { - if (string.IsNullOrEmpty(conversationId)) return null; - - var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversationId); - if (!Directory.Exists(dir)) return null; - - return dir; + return deletedMessageIds; } - private List CollectDialogElements(string dialogDir) + var foundIdx = dialogs.FindIndex(x => x.MetaData?.MessageId == messageId); + if (foundIdx < 0) { - var dialogs = new List(); - - if (!File.Exists(dialogDir)) return dialogs; - - var texts = File.ReadAllText(dialogDir); - dialogs = JsonSerializer.Deserialize>(texts) ?? new List(); - return dialogs; + return deletedMessageIds; } - private string ParseDialogElements(List dialogs) - { - if (dialogs.IsNullOrEmpty()) return "[]"; + deletedMessageIds = dialogs.Where((x, idx) => idx >= foundIdx && !string.IsNullOrEmpty(x.MetaData?.MessageId)) + .Select(x => x.MetaData.MessageId).Distinct().ToList(); - return JsonSerializer.Serialize(dialogs, _options) ?? "[]"; + // Handle truncated dialogs + var isSaved = HandleTruncatedDialogs(convDir, dialogDir, dialogs, foundIdx); + + // Handle truncated states + var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime; + var stateDir = Path.Combine(convDir, STATE_FILE); + var states = CollectConversationStates(stateDir); + isSaved = HandleTruncatedStates(stateDir, states, messageId, refTime); + + // Handle truncated breakpoints + var breakpointDir = Path.Combine(convDir, BREAKPOINT_FILE); + var breakpoints = CollectConversationBreakpoints(breakpointDir); + isSaved = HandleTruncatedBreakpoints(breakpointDir, breakpoints, refTime); + + // Remove logs + if (cleanLog) + { + HandleTruncatedLogs(convDir, refTime); } - private List CollectConversationStates(string stateFile) - { - var states = new List(); - if (!File.Exists(stateFile)) return states; - - var stateStr = File.ReadAllText(stateFile); - if (string.IsNullOrEmpty(stateStr)) return states; - - states = JsonSerializer.Deserialize>(stateStr, _options); - return states ?? new List(); - } - - private List CollectConversationBreakpoints(string breakpointFile) - { - var breakpoints = new List(); - if (!File.Exists(breakpointFile)) return breakpoints; - - var content = File.ReadAllText(breakpointFile); - if (string.IsNullOrEmpty(content)) return breakpoints; - - breakpoints = JsonSerializer.Deserialize>(content, _options); - return breakpoints ?? new List(); - } - - private bool HandleTruncatedDialogs(string convDir, string dialogDir, List dialogs, int foundIdx) - { - var truncatedDialogs = dialogs.Where((x, idx) => idx < foundIdx).ToList(); - var isSaved = SaveTruncatedDialogs(dialogDir, truncatedDialogs); - var convFile = Path.Combine(convDir, CONVERSATION_FILE); - var convJson = File.ReadAllText(convFile); - var conv = JsonSerializer.Deserialize(convJson, _options); - if (conv != null) - { - conv.DialogCount = truncatedDialogs.Count; - File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); - } - return isSaved; - } - - private bool HandleTruncatedStates(string stateDir, List states, string refMsgId, DateTime refTime) - { - var truncatedStates = new List(); - foreach (var state in states) - { - if (!state.Versioning) - { - truncatedStates.Add(state); - continue; - } - - var values = state.Values.Where(x => x.MessageId != refMsgId) - .Where(x => x.UpdateTime < refTime) - .ToList(); - if (values.Count == 0) continue; - - state.Values = values; - truncatedStates.Add(state); - } - - var isSaved = SaveTruncatedStates(stateDir, truncatedStates); - return isSaved; - } - - private bool HandleTruncatedBreakpoints(string breakpointDir, List breakpoints, DateTime refTime) - { - var truncatedBreakpoints = breakpoints?.Where(x => x.CreatedTime < refTime)? - .ToList() ?? new List(); - - var isSaved = SaveTruncatedBreakpoints(breakpointDir, truncatedBreakpoints); - return isSaved; - } - - private bool HandleTruncatedLogs(string convDir, DateTime refTime) - { - var contentLogDir = Path.Combine(convDir, "content_log"); - var stateLogDir = Path.Combine(convDir, "state_log"); - - if (Directory.Exists(contentLogDir)) - { - foreach (var file in Directory.GetFiles(contentLogDir)) - { - var text = File.ReadAllText(file); - var log = JsonSerializer.Deserialize(text); - if (log == null) continue; - - if (log.CreateTime >= refTime) - { - File.Delete(file); - } - } - } - - if (Directory.Exists(stateLogDir)) - { - foreach (var file in Directory.GetFiles(stateLogDir)) - { - var text = File.ReadAllText(file); - var log = JsonSerializer.Deserialize(text); - if (log == null) continue; - - if (log.CreateTime >= refTime) - { - File.Delete(file); - } - } - } - - return true; - } - - private bool SaveTruncatedDialogs(string dialogDir, List dialogs) - { - if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false; - if (!File.Exists(dialogDir)) File.Create(dialogDir); - - var texts = ParseDialogElements(dialogs); - File.WriteAllText(dialogDir, texts); - return true; - } - - private bool SaveTruncatedStates(string stateDir, List states) - { - if (string.IsNullOrEmpty(stateDir) || states == null) return false; - if (!File.Exists(stateDir)) File.Create(stateDir); - - var stateStr = JsonSerializer.Serialize(states, _options); - File.WriteAllText(stateDir, stateStr); - return true; - } - - private bool SaveTruncatedBreakpoints(string breakpointDir, List breakpoints) - { - if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) return false; - if (!File.Exists(breakpointDir)) File.Create(breakpointDir); - - var breakpointStr = JsonSerializer.Serialize(breakpoints, _options); - File.WriteAllText(breakpointDir, breakpointStr); - return true; - } - - private string? EncodeText(string? text) - { - if (string.IsNullOrEmpty(text)) return text; - - var bytes = Encoding.UTF8.GetBytes(text); - var encoded = Convert.ToBase64String(bytes); - return encoded; - } - - private string? DecodeText(string? text) - { - if (string.IsNullOrEmpty(text)) return text; - - var decoded = Convert.FromBase64String(text); - var origin = Encoding.UTF8.GetString(decoded); - return origin; - } - #endregion + return deletedMessageIds; } + +#if !DEBUG + [SharpCache(10)] +#endif + public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) + { + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); + if (!Directory.Exists(dir)) return []; + + var count = 0; + var keys = new List(); + + 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)) + { + continue; + } + + var convJson = File.ReadAllText(convFile); + var stateJson = File.ReadAllText(stateFile); + var conv = JsonSerializer.Deserialize(convJson, _options); + var states = JsonSerializer.Deserialize>(stateJson, _options); + if (conv == null || conv.DialogCount < messageLowerLimit) + { + continue; + } + + var stateKeys = states?.Select(x => x.Key)?.Distinct()?.ToList() ?? []; + keys.AddRange(stateKeys); + count++; + + if (count >= convUpperlimit) + { + break; + } + } + + return keys.Distinct().ToList(); + } + + + #region Private methods + private string? FindConversationDirectory(string conversationId) + { + if (string.IsNullOrEmpty(conversationId)) return null; + + var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversationId); + if (!Directory.Exists(dir)) return null; + + return dir; + } + + private List CollectDialogElements(string dialogDir) + { + var dialogs = new List(); + + if (!File.Exists(dialogDir)) return dialogs; + + var texts = File.ReadAllText(dialogDir); + dialogs = JsonSerializer.Deserialize>(texts) ?? new List(); + return dialogs; + } + + private string ParseDialogElements(List dialogs) + { + if (dialogs.IsNullOrEmpty()) return "[]"; + + return JsonSerializer.Serialize(dialogs, _options) ?? "[]"; + } + + private List CollectConversationStates(string stateFile) + { + var states = new List(); + if (!File.Exists(stateFile)) return states; + + var stateStr = File.ReadAllText(stateFile); + if (string.IsNullOrEmpty(stateStr)) return states; + + states = JsonSerializer.Deserialize>(stateStr, _options); + return states ?? new List(); + } + + private List CollectConversationBreakpoints(string breakpointFile) + { + var breakpoints = new List(); + if (!File.Exists(breakpointFile)) return breakpoints; + + var content = File.ReadAllText(breakpointFile); + if (string.IsNullOrEmpty(content)) return breakpoints; + + breakpoints = JsonSerializer.Deserialize>(content, _options); + return breakpoints ?? new List(); + } + + private bool HandleTruncatedDialogs(string convDir, string dialogDir, List dialogs, int foundIdx) + { + var truncatedDialogs = dialogs.Where((x, idx) => idx < foundIdx).ToList(); + var isSaved = SaveTruncatedDialogs(dialogDir, truncatedDialogs); + var convFile = Path.Combine(convDir, CONVERSATION_FILE); + var convJson = File.ReadAllText(convFile); + var conv = JsonSerializer.Deserialize(convJson, _options); + if (conv != null) + { + conv.DialogCount = truncatedDialogs.Count; + File.WriteAllText(convFile, JsonSerializer.Serialize(conv, _options)); + } + return isSaved; + } + + private bool HandleTruncatedStates(string stateDir, List states, string refMsgId, DateTime refTime) + { + var truncatedStates = new List(); + foreach (var state in states) + { + if (!state.Versioning) + { + truncatedStates.Add(state); + continue; + } + + var values = state.Values.Where(x => x.MessageId != refMsgId) + .Where(x => x.UpdateTime < refTime) + .ToList(); + if (values.Count == 0) continue; + + state.Values = values; + truncatedStates.Add(state); + } + + var isSaved = SaveTruncatedStates(stateDir, truncatedStates); + return isSaved; + } + + private bool HandleTruncatedBreakpoints(string breakpointDir, List breakpoints, DateTime refTime) + { + var truncatedBreakpoints = breakpoints?.Where(x => x.CreatedTime < refTime)? + .ToList() ?? new List(); + + var isSaved = SaveTruncatedBreakpoints(breakpointDir, truncatedBreakpoints); + return isSaved; + } + + private bool HandleTruncatedLogs(string convDir, DateTime refTime) + { + var contentLogDir = Path.Combine(convDir, "content_log"); + var stateLogDir = Path.Combine(convDir, "state_log"); + + if (Directory.Exists(contentLogDir)) + { + foreach (var file in Directory.GetFiles(contentLogDir)) + { + var text = File.ReadAllText(file); + var log = JsonSerializer.Deserialize(text); + if (log == null) continue; + + if (log.CreateTime >= refTime) + { + File.Delete(file); + } + } + } + + if (Directory.Exists(stateLogDir)) + { + foreach (var file in Directory.GetFiles(stateLogDir)) + { + var text = File.ReadAllText(file); + var log = JsonSerializer.Deserialize(text); + if (log == null) continue; + + if (log.CreateTime >= refTime) + { + File.Delete(file); + } + } + } + + return true; + } + + private bool SaveTruncatedDialogs(string dialogDir, List dialogs) + { + if (string.IsNullOrEmpty(dialogDir) || dialogs == null) return false; + if (!File.Exists(dialogDir)) File.Create(dialogDir); + + var texts = ParseDialogElements(dialogs); + File.WriteAllText(dialogDir, texts); + return true; + } + + private bool SaveTruncatedStates(string stateDir, List states) + { + if (string.IsNullOrEmpty(stateDir) || states == null) return false; + if (!File.Exists(stateDir)) File.Create(stateDir); + + var stateStr = JsonSerializer.Serialize(states, _options); + File.WriteAllText(stateDir, stateStr); + return true; + } + + private bool SaveTruncatedBreakpoints(string breakpointDir, List breakpoints) + { + if (string.IsNullOrEmpty(breakpointDir) || breakpoints == null) return false; + if (!File.Exists(breakpointDir)) File.Create(breakpointDir); + + var breakpointStr = JsonSerializer.Serialize(breakpoints, _options); + File.WriteAllText(breakpointDir, breakpointStr); + return true; + } + + private string? EncodeText(string? text) + { + if (string.IsNullOrEmpty(text)) return text; + + var bytes = Encoding.UTF8.GetBytes(text); + var encoded = Convert.ToBase64String(bytes); + return encoded; + } + + private string? DecodeText(string? text) + { + if (string.IsNullOrEmpty(text)) return text; + + var decoded = Convert.FromBase64String(text); + var origin = Encoding.UTF8.GetString(decoded); + return origin; + } + #endregion } diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 4dd2e8f0..7461518d 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -558,7 +558,7 @@ public class ConversationController : ControllerBase public async Task> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] bool preLoad = false) { var convService = _services.GetRequiredService(); - var keys = await convService.GetConversationSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad); + var keys = await convService.GetConversationStateSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad); return keys; } #endregion diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index ae5082ce..cabaecd2 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -611,13 +611,15 @@ public partial class MongoRepository return deletedMessageIds; } - - public List GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100) +#if !DEBUG + [SharpCache(10)] +#endif + public List GetConversationStateSearchKeys(int messageLowerLimit = 2, int convUpperlimit = 100) { - var convFilter = Builders.Filter.Gte(x => x.DialogCount, messageLimit); + var convFilter = Builders.Filter.Gte(x => x.DialogCount, messageLowerLimit); var conversations = _dc.Conversations.Find(convFilter) .SortByDescending(x => x.UpdatedTime) - .Limit(convlimit) + .Limit(convUpperlimit) .ToList(); if (conversations.IsNullOrEmpty()) return [];