Merge pull request #878 from iceljc/master

refine state search
This commit is contained in:
iceljc 2025-02-11 14:28:45 -06:00 committed by GitHub
commit 23483f263b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 122 additions and 11 deletions

View file

@ -63,4 +63,13 @@ public interface IConversationService
bool IsConversationMode(); bool IsConversationMode();
void SaveStates(); void SaveStates();
/// <summary>
/// Get conversation keys for searching
/// </summary>
/// <param name="query">search query</param>
/// <param name="convLimit">conversation limit</param>
/// <param name="preLoad">if pre-loading, then keys are not filter by the search query</param>
/// <returns></returns>
Task<List<string>> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false);
} }

View file

@ -146,7 +146,9 @@ public interface IBotSharpRepository : IHaveServiceProvider
=> throw new NotImplementedException(); => throw new NotImplementedException();
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds) List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours, IEnumerable<string> excludeAgentIds)
=> throw new NotImplementedException(); => throw new NotImplementedException();
IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false) List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
=> throw new NotImplementedException();
List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
=> throw new NotImplementedException(); => throw new NotImplementedException();
#endregion #endregion

View file

@ -221,4 +221,18 @@ public partial class ConversationService : IConversationService
{ {
_state.Save(); _state.Save();
} }
public async Task<List<string>> GetConversationSearhKeys(string query, int convlimit = 100, int keyLimit = 10, bool preLoad = false)
{
var keys = new List<string>();
if (!preLoad && string.IsNullOrWhiteSpace(query))
{
return keys;
}
var db = _services.GetRequiredService<IBotSharpRepository>();
keys = db.GetConversationSearchKeys(convlimit: convlimit);
keys = preLoad ? keys : keys.Where(x => x.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList();
return keys.Take(keyLimit).ToList();
}
} }

View file

@ -131,7 +131,7 @@ public class BotSharpDbContext : Database, IBotSharpRepository
public void UpdateConversationStatus(string conversationId, string status) public void UpdateConversationStatus(string conversationId, string status)
=> throw new NotImplementedException(); => throw new NotImplementedException();
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false) public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
=> throw new NotImplementedException(); => throw new NotImplementedException();
#endregion #endregion

View file

@ -547,7 +547,7 @@ namespace BotSharp.Core.Repository
} }
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false) public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{ {
var deletedMessageIds = new List<string>(); var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
@ -603,6 +603,46 @@ namespace BotSharp.Core.Repository
} }
public List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
{
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
if (!Directory.Exists(dir)) return [];
var count = 0;
var keys = new List<string>();
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<Conversation>(convJson, _options);
var states = JsonSerializer.Deserialize<List<StateKeyValue>>(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();
}
#region Private methods #region Private methods
private string? FindConversationDirectory(string conversationId) private string? FindConversationDirectory(string conversationId)
{ {

View file

@ -553,6 +553,16 @@ public class ConversationController : ControllerBase
} }
#endregion #endregion
#region Search state keys
[HttpGet("/conversation/state/keys")]
public async Task<List<string>> GetConversationStateKeys([FromQuery] string query, [FromQuery] int keyLimit = 10, [FromQuery] bool preLoad = false)
{
var convService = _services.GetRequiredService<IConversationService>();
var keys = await convService.GetConversationSearhKeys(query, keyLimit: keyLimit, preLoad: preLoad);
return keys;
}
#endregion
#region Private methods #region Private methods
private void SetStates(IConversationService conv, NewMessageModel input) private void SetStates(IConversationService conv, NewMessageModel input)
{ {

View file

@ -4,5 +4,6 @@ public class ConversationDialogDocument : MongoBase
{ {
public string ConversationId { get; set; } public string ConversationId { get; set; }
public string AgentId { get; set; } public string AgentId { get; set; }
public DateTime UpdatedTime { get; set; }
public List<DialogMongoElement> Dialogs { get; set; } public List<DialogMongoElement> Dialogs { get; set; }
} }

View file

@ -4,6 +4,7 @@ public class ConversationStateDocument : MongoBase
{ {
public string ConversationId { get; set; } public string ConversationId { get; set; }
public string AgentId { get; set; } public string AgentId { get; set; }
public DateTime UpdatedTime { get; set; }
public List<StateMongoElement> States { get; set; } = new List<StateMongoElement>(); public List<StateMongoElement> States { get; set; } = new List<StateMongoElement>();
public List<BreakpointMongoElement> Breakpoints { get; set; } = new List<BreakpointMongoElement>(); public List<BreakpointMongoElement> Breakpoints { get; set; } = new List<BreakpointMongoElement>();
} }

View file

@ -30,7 +30,8 @@ public partial class MongoRepository
Id = Guid.NewGuid().ToString(), Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id, ConversationId = convDoc.Id,
AgentId = conversation.AgentId, AgentId = conversation.AgentId,
Dialogs = new List<DialogMongoElement>() Dialogs = [],
UpdatedTime = utcNow
}; };
var stateDoc = new ConversationStateDocument var stateDoc = new ConversationStateDocument
@ -38,8 +39,9 @@ public partial class MongoRepository
Id = Guid.NewGuid().ToString(), Id = Guid.NewGuid().ToString(),
ConversationId = convDoc.Id, ConversationId = convDoc.Id,
AgentId = conversation.AgentId, AgentId = conversation.AgentId,
States = new List<StateMongoElement>(), States = [],
Breakpoints = new List<BreakpointMongoElement>() Breakpoints = [],
UpdatedTime = utcNow
}; };
_dc.Conversations.InsertOne(convDoc); _dc.Conversations.InsertOne(convDoc);
@ -97,7 +99,8 @@ public partial class MongoRepository
var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId); var filterConv = Builders<ConversationDocument>.Filter.Eq(x => x.Id, conversationId);
var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId); var filterDialog = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var dialogElements = dialogs.Select(x => DialogMongoElement.ToMongoElement(x)).ToList(); var dialogElements = dialogs.Select(x => DialogMongoElement.ToMongoElement(x)).ToList();
var updateDialog = Builders<ConversationDialogDocument>.Update.PushEach(x => x.Dialogs, dialogElements); var updateDialog = Builders<ConversationDialogDocument>.Update.PushEach(x => x.Dialogs, dialogElements)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow) var updateConv = Builders<ConversationDocument>.Update.Set(x => x.UpdatedTime, DateTime.UtcNow)
.Inc(x => x.DialogCount, dialogs.Count); .Inc(x => x.DialogCount, dialogs.Count);
@ -190,7 +193,8 @@ public partial class MongoRepository
found.SecondaryRichContent = request.Message.RichContent; found.SecondaryRichContent = request.Message.RichContent;
} }
var update = Builders<ConversationDialogDocument>.Update.Set(x => x.Dialogs, dialogs); var update = Builders<ConversationDialogDocument>.Update.Set(x => x.Dialogs, dialogs)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationDialogs.UpdateOne(filter, update); _dc.ConversationDialogs.UpdateOne(filter, update);
return true; return true;
} }
@ -208,7 +212,8 @@ public partial class MongoRepository
Reason = breakpoint.Reason Reason = breakpoint.Reason
}; };
var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId); var filterState = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var updateState = Builders<ConversationStateDocument>.Update.Push(x => x.Breakpoints, newBreakpoint); var updateState = Builders<ConversationStateDocument>.Update.Push(x => x.Breakpoints, newBreakpoint)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterState, updateState); _dc.ConversationStates.UpdateOne(filterState, updateState);
} }
@ -258,7 +263,8 @@ public partial class MongoRepository
var filterStates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId); var filterStates = Builders<ConversationStateDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var saveStates = states.Select(x => StateMongoElement.ToMongoElement(x)).ToList(); var saveStates = states.Select(x => StateMongoElement.ToMongoElement(x)).ToList();
var updateStates = Builders<ConversationStateDocument>.Update.Set(x => x.States, saveStates); var updateStates = Builders<ConversationStateDocument>.Update.Set(x => x.States, saveStates)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.ConversationStates.UpdateOne(filterStates, updateStates); _dc.ConversationStates.UpdateOne(filterStates, updateStates);
} }
@ -500,7 +506,7 @@ public partial class MongoRepository
return conversationIds.Take(batchSize).ToList(); return conversationIds.Take(batchSize).ToList();
} }
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false) public List<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{ {
var deletedMessageIds = new List<string>(); var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
@ -566,11 +572,13 @@ public partial class MongoRepository
} }
// Update // Update
foundStates.UpdatedTime = DateTime.UtcNow;
_dc.ConversationStates.ReplaceOne(stateFilter, foundStates); _dc.ConversationStates.ReplaceOne(stateFilter, foundStates);
} }
// Save dialogs // Save dialogs
foundDialog.Dialogs = truncatedDialogs; foundDialog.Dialogs = truncatedDialogs;
foundDialog.UpdatedTime = DateTime.UtcNow;
_dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog); _dc.ConversationDialogs.ReplaceOne(dialogFilter, foundDialog);
// Update conversation // Update conversation
@ -603,6 +611,27 @@ public partial class MongoRepository
return deletedMessageIds; return deletedMessageIds;
} }
public List<string> GetConversationSearchKeys(int messageLimit = 2, int convlimit = 100)
{
var convFilter = Builders<ConversationDocument>.Filter.Gte(x => x.DialogCount, messageLimit);
var conversations = _dc.Conversations.Find(convFilter)
.SortByDescending(x => x.UpdatedTime)
.Limit(convlimit)
.ToList();
if (conversations.IsNullOrEmpty()) return [];
var convIds = conversations.Select(x => x.Id).ToList();
var stateFilter = Builders<ConversationStateDocument>.Filter.In(x => x.ConversationId, convIds);
var states = _dc.ConversationStates.Find(stateFilter).ToList();
var keys = states.SelectMany(x => x.States.Select(x => x.Key)).Distinct().ToList();
return keys;
}
private string ConvertSnakeCaseToPascalCase(string snakeCase) private string ConvertSnakeCaseToPascalCase(string snakeCase)
{ {
string[] words = snakeCase.Split('_'); string[] words = snakeCase.Split('_');

View file

@ -429,6 +429,11 @@ public partial class MongoRepository
return true; return true;
} }
public Dashboard? GetDashboard(string userId = null)
{
return null;
}
public void AddDashboardConversation(string userId, string conversationId) public void AddDashboardConversation(string userId, string conversationId)
{ {
var user = _dc.Users.AsQueryable() var user = _dc.Users.AsQueryable()