Merge pull request #437 from iceljc/features/add-attachment

Features/add attachment
This commit is contained in:
C. Oceania 2024-05-07 04:23:08 -05:00 committed by GitHub
commit d3410c02c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 432 additions and 87 deletions

View file

@ -1,6 +0,0 @@
namespace BotSharp.Abstraction.Conversations;
public interface IConversationAttachmentService
{
string GetDirectory(string conversationId);
}

View file

@ -15,7 +15,15 @@ public interface IConversationService
Task<List<Conversation>> GetLastConversations();
Task<List<string>> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
Task<bool> DeleteConversations(IEnumerable<string> ids);
Task<bool> TruncateConversation(string conversationId, string messageId);
/// <summary>
/// Truncate conversation
/// </summary>
/// <param name="conversationId">Target conversation id</param>
/// <param name="messageId">Target message id to delete</param>
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise delete messages only</param>
/// <returns></returns>
Task<bool> TruncateConversation(string conversationId, string messageId, string? newMessageId = null);
Task<List<ContentLogOutputModel>> GetConversationContentLogs(string conversationId);
Task<List<ConversationStateLogModel>> GetConversationStateLogs(string conversationId);

View file

@ -9,4 +9,6 @@ public class IncomingMessageModel : MessageConfig
/// Postback message
/// </summary>
public PostbackMessageModel? Postback { get; set; }
public List<BotSharpFile> Files { get; set; } = new List<BotSharpFile>();
}

View file

@ -78,6 +78,8 @@ public class RoleDialogModel : ITrackableMessage
public FunctionCallFromLlm Instruction { get; set; }
public List<BotSharpFile> Files { get; set; } = new List<BotSharpFile>();
private RoleDialogModel()
{
}
@ -87,6 +89,7 @@ public class RoleDialogModel : ITrackableMessage
Role = role;
Content = text;
MessageId = Guid.NewGuid().ToString();
CreatedAt = DateTime.UtcNow;
}
public override string ToString()

View file

@ -0,0 +1,20 @@
namespace BotSharp.Abstraction.Files;
public interface IBotSharpFileService
{
string GetDirectory(string conversationId);
IEnumerable<OutputFileModel> GetConversationFiles(string conversationId, string messageId);
string? GetMessageFile(string conversationId, string messageId, string fileName);
void SaveMessageFiles(string conversationId, string messageId, List<BotSharpFile> files);
/// <summary>
/// Delete files under messages
/// </summary>
/// <param name="conversationId">Conversation Id</param>
/// <param name="messageIds">Files in these messages will be deleted</param>
/// <param name="targetMessageId">The starting message to delete</param>
/// <param name="newMessageId">If not null, delete messages while input a new message; otherwise, delete messages only</param>
/// <returns></returns>
bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null);
bool DeleteConversationFiles(IEnumerable<string> conversationIds);
}

View file

@ -0,0 +1,17 @@
namespace BotSharp.Abstraction.Files.Models;
public class BotSharpFile
{
[JsonPropertyName("file_name")]
public string FileName { get; set; }
[JsonPropertyName("file_data")]
public string FileData { get; set; }
[JsonPropertyName("content_type")]
public string ContentType { get; set; }
[JsonPropertyName("file_size")]
public int FileSize { get; set; }
}

View file

@ -0,0 +1,13 @@
namespace BotSharp.Abstraction.Files.Models;
public class OutputFileModel
{
[JsonPropertyName("file_url")]
public string FileUrl { get; set; }
[JsonPropertyName("file_name")]
public string FileName { get; set; }
[JsonPropertyName("file_type")]
public string FileType { get; set; }
}

View file

@ -63,7 +63,7 @@ public interface IBotSharpRepository
ConversationBreakpoint? GetConversationBreakpoint(string conversationId);
List<Conversation> GetLastConversations();
List<string> GetIdleConversations(int batchSize, int messageLimit, int bufferHours);
bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false);
#endregion
#region Execution Log

View file

@ -16,3 +16,4 @@ global using BotSharp.Abstraction.Routing.Planning;
global using BotSharp.Abstraction.Templating;
global using BotSharp.Abstraction.Translation.Attributes;
global using BotSharp.Abstraction.Messaging.Enums;
global using BotSharp.Abstraction.Files.Models;

View file

@ -1,9 +1,11 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Instructs;
using BotSharp.Abstraction.Messaging;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Routing.Planning;
using BotSharp.Abstraction.Settings;
using BotSharp.Abstraction.Templating;
using BotSharp.Core.Files;
using BotSharp.Core.Instructs;
using BotSharp.Core.Messaging;
using BotSharp.Core.Routing.Planning;
@ -35,7 +37,7 @@ public class ConversationPlugin : IBotSharpPlugin
services.AddScoped<IConversationStorage, ConversationStorage>();
services.AddScoped<IConversationService, ConversationService>();
services.AddScoped<IConversationStateService, ConversationStateService>();
services.AddScoped<IConversationAttachmentService, ConversationAttachmentService>();
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
services.AddScoped<ITranslationService, TranslationService>();
// Rich content messaging

View file

@ -1,28 +0,0 @@
using BotSharp.Abstraction.Repositories;
using System.IO;
namespace BotSharp.Core.Conversations.Services;
public class ConversationAttachmentService : IConversationAttachmentService
{
private readonly BotSharpDatabaseSettings _dbSettings;
private readonly IServiceProvider _services;
public ConversationAttachmentService(
BotSharpDatabaseSettings dbSettings,
IServiceProvider services)
{
_dbSettings = dbSettings;
_services = services;
}
public string GetDirectory(string conversationId)
{
var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId, "attachments");
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
return dir;
}
}

View file

@ -27,7 +27,6 @@ public partial class ConversationService
#endif
message.CurrentAgentId = agent.Id;
message.CreatedAt = DateTime.UtcNow;
if (string.IsNullOrEmpty(message.SenderId))
{
message.SenderId = _user.Id;
@ -47,6 +46,11 @@ public partial class ConversationService
routing.Context.SetMessageId(_conversationId, message.MessageId);
routing.Context.Push(agent.Id);
// Save message files
var fileService = _services.GetRequiredService<IBotSharpFileService>();
fileService.SaveMessageFiles(_conversationId, message.MessageId, message.Files);
message.Files?.Clear();
// Before chat completion hook
foreach (var hook in hooks)
{

View file

@ -2,15 +2,19 @@ namespace BotSharp.Core.Conversations.Services;
public partial class ConversationService : IConversationService
{
public async Task<bool> TruncateConversation(string conversationId, string messageId)
public async Task<bool> TruncateConversation(string conversationId, string messageId, string? newMessageId = null)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var isSaved = db.TruncateConversation(conversationId, messageId, true);
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var deleteMessageIds = db.TruncateConversation(conversationId, messageId, cleanLog: true);
fileService.DeleteMessageFiles(conversationId, deleteMessageIds, messageId, newMessageId);
var hooks = _services.GetServices<IConversationHook>().ToList();
foreach (var hook in hooks)
{
await hook.OnMessageDeleted(conversationId, messageId);
}
return await Task.FromResult(isSaved);
return await Task.FromResult(true);
}
}

View file

@ -35,7 +35,9 @@ public partial class ConversationService : IConversationService
public async Task<bool> DeleteConversations(IEnumerable<string> ids)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var isDeleted = db.DeleteConversations(ids);
fileService.DeleteConversationFiles(ids);
return await Task.FromResult(isDeleted);
}

View file

@ -0,0 +1,223 @@
using System.IO;
using System.Threading;
namespace BotSharp.Core.Files;
public class BotSharpFileService : IBotSharpFileService
{
private readonly BotSharpDatabaseSettings _dbSettings;
private readonly IServiceProvider _services;
private readonly string _baseDir;
private const string CONVERSATION_FOLDER = "conversations";
private const string FILE_FOLDER = "files";
public BotSharpFileService(
BotSharpDatabaseSettings dbSettings,
IServiceProvider services)
{
_dbSettings = dbSettings;
_services = services;
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository);
}
public string GetDirectory(string conversationId)
{
var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments");
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
return dir;
}
public IEnumerable<OutputFileModel> GetConversationFiles(string conversationId, string messageId)
{
var outputFiles = new List<OutputFileModel>();
var dir = GetConversationFileDirectory(conversationId, messageId);
if (string.IsNullOrEmpty(dir))
{
return outputFiles;
}
foreach (var file in Directory.GetFiles(dir))
{
var fileName = Path.GetFileNameWithoutExtension(file);
var extension = Path.GetExtension(file);
var fileType = extension.Substring(1);
var model = new OutputFileModel()
{
FileUrl = $"/conversation/{conversationId}/message/{messageId}/file/{fileName}",
FileName = fileName,
FileType = fileType
};
outputFiles.Add(model);
}
return outputFiles;
}
public string? GetMessageFile(string conversationId, string messageId, string fileName)
{
var dir = GetConversationFileDirectory(conversationId, messageId);
if (string.IsNullOrEmpty(dir))
{
return null;
}
var found = Directory.GetFiles(dir).FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName));
return found;
}
public void SaveMessageFiles(string conversationId, string messageId, List<BotSharpFile> files)
{
if (files.IsNullOrEmpty()) return;
var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
if (string.IsNullOrEmpty(dir)) return;
for (int i = 0; i < files.Count; i++)
{
var file = files[i];
if (string.IsNullOrEmpty(file.FileData))
{
continue;
}
var bytes = GetFileBytes(file.FileData);
var fileType = Path.GetExtension(file.FileName);
var fileName = $"{i + 1}{fileType}";
Thread.Sleep(100);
File.WriteAllBytes(Path.Combine(dir, fileName), bytes);
}
}
public bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null)
{
if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
if (!string.IsNullOrEmpty(targetMessageId) && !string.IsNullOrEmpty(newMessageId))
{
var prevDir = GetConversationFileDirectory(conversationId, targetMessageId);
var newDir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, newMessageId);
if (Directory.Exists(prevDir))
{
if (Directory.Exists(newDir))
{
Directory.Delete(newDir, true);
}
Directory.Move(prevDir, newDir);
}
}
foreach ( var messageId in messageIds)
{
var dir = GetConversationFileDirectory(conversationId, messageId);
if (string.IsNullOrEmpty(dir)) continue;
Thread.Sleep(100);
Directory.Delete(dir, true);
}
return true;
}
public bool DeleteConversationFiles(IEnumerable<string> conversationIds)
{
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;
}
#region Private methods
private string GetConversationFileDirectory(string? conversationId, string? messageId, bool createNewDir = false)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
{
return string.Empty;
}
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId);
if (!Directory.Exists(dir))
{
if (createNewDir)
{
Directory.CreateDirectory(dir);
}
else
{
return string.Empty;
}
}
return dir;
}
private string? FindConversationDirectory(string conversationId)
{
if (string.IsNullOrEmpty(conversationId)) return null;
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId);
if (!Directory.Exists(dir)) return null;
return dir;
}
private byte[] GetFileBytes(string data)
{
if (string.IsNullOrEmpty(data))
{
return new byte[0];
}
var startIdx = data.IndexOf(',');
var base64Str = data.Substring(startIdx + 1);
return Convert.FromBase64String(base64Str);
}
private string GetFileType(string data)
{
if (string.IsNullOrEmpty(data))
{
return string.Empty;
}
var startIdx = data.IndexOf(':');
var endIdx = data.IndexOf(';');
var fileType = data.Substring(startIdx + 1, endIdx - startIdx - 1);
return fileType;
}
private string ParseFileFormat(string type)
{
var parsed = string.Empty;
switch (type)
{
case "image/png":
parsed = ".png";
break;
case "image/jpeg":
case "image/jpg":
parsed = ".jpeg";
break;
case "application/pdf":
parsed = ".pdf";
break;
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
parsed = ".xlsx";
break;
case "text/plain":
parsed = ".txt";
break;
}
return parsed;
}
#endregion
}

View file

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

View file

@ -446,24 +446,40 @@ namespace BotSharp.Core.Repository
}
public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;
var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
{
return deletedMessageIds;
}
var dialogs = new List<DialogElement>();
var convDir = FindConversationDirectory(conversationId);
if (string.IsNullOrEmpty(convDir)) return false;
if (string.IsNullOrEmpty(convDir))
{
return deletedMessageIds;
}
var dialogDir = Path.Combine(convDir, DIALOG_FILE);
dialogs = CollectDialogElements(dialogDir);
if (dialogs.IsNullOrEmpty()) return false;
if (dialogs.IsNullOrEmpty())
{
return deletedMessageIds;
}
var foundIdx = dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;
if (foundIdx < 0)
{
return deletedMessageIds;
}
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);
if (!isSaved) return false;
// Handle truncated states
var refTime = dialogs.ElementAt(foundIdx).MetaData.CreateTime;
@ -482,7 +498,7 @@ namespace BotSharp.Core.Repository
HandleTruncatedLogs(convDir, refTime);
}
return isSaved;
return deletedMessageIds;
}

View file

@ -23,6 +23,8 @@ global using BotSharp.Abstraction.Functions.Models;
global using BotSharp.Abstraction.Repositories;
global using BotSharp.Abstraction.Repositories.Filters;
global using BotSharp.Abstraction.Translation;
global using BotSharp.Abstraction.Files;
global using BotSharp.Abstraction.Files.Models;
global using BotSharp.Abstraction.Translation.Attributes;
global using BotSharp.Abstraction.Messaging.Enums;
global using BotSharp.Core.Repository;

View file

@ -1,6 +1,8 @@
using BotSharp.Abstraction.Routing;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using BotSharp.Abstraction.Files.Models;
using BotSharp.Abstraction.Files;
namespace BotSharp.OpenAPI.Controllers;
@ -166,12 +168,15 @@ public class ConversationController : ControllerBase
[FromBody] NewMessageModel input)
{
var conv = _services.GetRequiredService<IConversationService>();
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
{
Files = input.Files
};
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
await conv.TruncateConversation(conversationId, input.TruncateMessageId);
await conv.TruncateConversation(conversationId, input.TruncateMessageId, inputMsg.MessageId);
}
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
@ -207,12 +212,15 @@ public class ConversationController : ControllerBase
[FromBody] NewMessageModel input)
{
var conv = _services.GetRequiredService<IConversationService>();
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
{
Files = input.Files
};
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
await conv.TruncateConversation(conversationId, input.TruncateMessageId);
await conv.TruncateConversation(conversationId, input.TruncateMessageId, inputMsg.MessageId);
}
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
@ -286,30 +294,4 @@ public class ConversationController : ControllerBase
buffer = Encoding.UTF8.GetBytes("\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
}
[HttpPost("/conversation/{conversationId}/attachments")]
public IActionResult UploadAttachments([FromRoute] string conversationId,
IFormFile[] files)
{
if (files != null && files.Length > 0)
{
var attachmentService = _services.GetRequiredService<IConversationAttachmentService>();
var dir = attachmentService.GetDirectory(conversationId);
foreach (var file in files)
{
// Save the file, process it, etc.
var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
var filePath = Path.Combine(dir, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
}
return Ok(new { message = "File uploaded successfully." });
}
return BadRequest(new { message = "Invalid file." });
}
}

View file

@ -0,0 +1,62 @@
namespace BotSharp.OpenAPI.Controllers;
[Authorize]
[ApiController]
public class FileController : ControllerBase
{
private readonly IServiceProvider _services;
public FileController(IServiceProvider services)
{
_services = services;
}
[HttpPost("/conversation/{conversationId}/attachments")]
public IActionResult UploadAttachments([FromRoute] string conversationId,
IFormFile[] files)
{
if (files != null && files.Length > 0)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var dir = fileService.GetDirectory(conversationId);
foreach (var file in files)
{
// Save the file, process it, etc.
var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
var filePath = Path.Combine(dir, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
}
return Ok(new { message = "File uploaded successfully." });
}
return BadRequest(new { message = "Invalid file." });
}
[HttpGet("/conversation/{conversationId}/files/{messageId}")]
public IEnumerable<OutputFileModel> GetConversationFiles([FromRoute] string conversationId, [FromRoute] string messageId)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
return fileService.GetConversationFiles(conversationId, messageId);
}
[HttpGet("/conversation/{conversationId}/message/{messageId}/file/{fileName}")]
public async Task<IActionResult> GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string fileName)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var file = fileService.GetMessageFile(conversationId, messageId, fileName);
if (string.IsNullOrEmpty(file))
{
return NotFound();
}
using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
return File(bytes, "application/octet-stream", Path.GetFileName(file));
}
}

View file

@ -24,6 +24,8 @@ global using BotSharp.Abstraction.Conversations.Enums;
global using BotSharp.Abstraction.Conversations.Models;
global using BotSharp.Abstraction.Models;
global using BotSharp.Abstraction.Repositories.Filters;
global using BotSharp.Abstraction.Files.Models;
global using BotSharp.Abstraction.Files;
global using BotSharp.OpenAPI.ViewModels.Conversations;
global using BotSharp.OpenAPI.ViewModels.Users;
global using BotSharp.OpenAPI.ViewModels.Agents;

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using System.Text.RegularExpressions;
namespace BotSharp.Plugin.ChatHub;
@ -13,11 +14,13 @@ public class WebSocketsMiddleware
public async Task Invoke(HttpContext httpContext)
{
var request = httpContext.Request;
var request = httpContext.Request;;
var messageFileRegex = new Regex(@"/conversation/[a-z0-9-]+/message/[a-z0-9-]+/file/[a-z0-9-]+", RegexOptions.IgnoreCase);
// web sockets cannot pass headers so we must take the access token from query param and
// add it to the header before authentication middleware runs
if (request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase) &&
if ((request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase)
|| messageFileRegex.IsMatch(request.Path.Value ?? string.Empty)) &&
request.Query.TryGetValue("access_token", out var accessToken))
{
request.Headers["Authorization"] = $"Bearer {accessToken}";

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Repositories.Models;
using BotSharp.Plugin.MongoStorage.Collections;
@ -411,16 +412,29 @@ public partial class MongoRepository
return conversationIds.Take(batchSize).ToList();
}
public bool TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
public IEnumerable<string> TruncateConversation(string conversationId, string messageId, bool cleanLog = false)
{
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId)) return false;
var deletedMessageIds = new List<string>();
if (string.IsNullOrEmpty(conversationId) || string.IsNullOrEmpty(messageId))
{
return deletedMessageIds;
}
var dialogFilter = Builders<ConversationDialogDocument>.Filter.Eq(x => x.ConversationId, conversationId);
var foundDialog = _dc.ConversationDialogs.Find(dialogFilter).FirstOrDefault();
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty()) return false;
if (foundDialog == null || foundDialog.Dialogs.IsNullOrEmpty())
{
return deletedMessageIds;
}
var foundIdx = foundDialog.Dialogs.FindIndex(x => x.MetaData?.MessageId == messageId);
if (foundIdx < 0) return false;
if (foundIdx < 0)
{
return deletedMessageIds;
}
deletedMessageIds = foundDialog.Dialogs.Where((x, idx) => idx >= foundIdx && !string.IsNullOrEmpty(x.MetaData?.MessageId))
.Select(x => x.MetaData.MessageId).Distinct().ToList();
// Handle truncated dialogs
var truncatedDialogs = foundDialog.Dialogs.Where((x, idx) => idx < foundIdx).ToList();
@ -499,6 +513,6 @@ public partial class MongoRepository
_dc.StateLogs.DeleteMany(stateLogBuilder.And(stateLogFilters));
}
return true;
return deletedMessageIds;
}
}

View file

@ -38,7 +38,6 @@
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MongoStorage\BotSharp.Plugin.MongoStorage.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.AzureOpenAI\BotSharp.Plugin.AzureOpenAI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.AnthropicAI\BotSharp.Plugin.AnthropicAI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.ChatbotUI\BotSharp.Plugin.ChatbotUI.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.HuggingFace\BotSharp.Plugin.HuggingFace.csproj" />
<ProjectReference Include="..\Plugins\BotSharp.Plugin.KnowledgeBase\BotSharp.Plugin.KnowledgeBase.csproj" />