diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs index feec3fab..8020ebf5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs @@ -3,4 +3,5 @@ namespace BotSharp.Abstraction.Conversations; public interface IConversationAttachmentService { string GetDirectory(string conversationId); + void SaveConversationFiles(List files); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs index 2176c7fa..cd4aad61 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs @@ -9,4 +9,6 @@ public class IncomingMessageModel : MessageConfig /// Postback message /// public PostbackMessageModel? Postback { get; set; } + + public List Files { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index bb84b55d..8085a2c5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -75,6 +75,8 @@ public class RoleDialogModel : ITrackableMessage public FunctionCallFromLlm Instruction { get; set; } + public List Files { get; set; } = new List(); + private RoleDialogModel() { } diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs new file mode 100644 index 00000000..acf47fbe --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs @@ -0,0 +1,24 @@ + +namespace BotSharp.Abstraction.Files.Models; + +public class BotSharpFile +{ + [JsonPropertyName("conversation_id")] + public string ConversationId { get; set; } + + [JsonPropertyName("message_id")] + public string MessageId { get; set; } + + [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; } + +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 91f672b8..75a9d00e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -14,4 +14,5 @@ global using BotSharp.Abstraction.Models; global using BotSharp.Abstraction.Routing.Models; global using BotSharp.Abstraction.Routing.Planning; global using BotSharp.Abstraction.Templating; -global using BotSharp.Abstraction.Translation.Attributes; \ No newline at end of file +global using BotSharp.Abstraction.Translation.Attributes; +global using BotSharp.Abstraction.Files.Models; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs index b0625fa8..f5bc7bcd 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs @@ -1,5 +1,6 @@ -using BotSharp.Abstraction.Repositories; using System.IO; +using System.IO.Enumeration; +using System.Threading; namespace BotSharp.Core.Conversations.Services; @@ -7,6 +8,10 @@ public class ConversationAttachmentService : IConversationAttachmentService { 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 ConversationAttachmentService( BotSharpDatabaseSettings dbSettings, @@ -14,15 +19,107 @@ public class ConversationAttachmentService : IConversationAttachmentService { _dbSettings = dbSettings; _services = services; + _baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository); } public string GetDirectory(string conversationId) { - var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId, "attachments"); + var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } return dir; } + + public string GetConversationFileDirectory(string conversationId) + { + var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + return dir; + } + + public void SaveConversationFiles(List files) + { + if (files.IsNullOrEmpty()) return; + + var converationId = files.First().ConversationId; + var dir = GetConversationFileDirectory(converationId); + + for (int i = 0; i < files.Count; i++) + { + var file = files[i]; + if (string.IsNullOrEmpty(file.ConversationId) + || string.IsNullOrEmpty(file.MessageId) + || string.IsNullOrEmpty(file.FileData)) + { + continue; + } + + var fileType = GetFileType(file.FileData); + var bytes = GetFileBytes(file.FileData); + var parsedFormat = ParseFileFormat(fileType); + if (string.IsNullOrEmpty(parsedFormat)) + { + continue; + } + + var fileName = $"{file.MessageId}-{i+1}{parsedFormat}"; + Thread.Sleep(100); + File.WriteAllBytes(Path.Combine(dir, fileName), bytes); + } + } + + 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 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 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; + } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 213d5a67..dbfdf7dc 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Messaging; +using BotSharp.Abstraction.Messaging.Enums; using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Routing.Settings; using System.Drawing; @@ -141,6 +142,13 @@ public partial class ConversationService Message = new TextMessage(response.SecondaryContent ?? response.Content) }; + response.RichContent = new RichContent + { + Recipient = new Recipient { Id = state.GetConversationId() }, + Editor = EditorTypeEnum.File, + Message = new TextMessage(response.SecondaryContent ?? response.Content) + }; + // Patch return function name if (response.PostbackFunctionName != null) { diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index 4ab447b8..13cbdedd 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -25,6 +25,7 @@ public class ConversationStorage : IConversationStorage { var agentId = dialog.CurrentAgentId; var db = _services.GetRequiredService(); + var attachment = _services.GetRequiredService(); var dialogElements = new List(); // Prevent duplicate record to be inserted @@ -76,6 +77,8 @@ public class ConversationStorage : IConversationStorage } db.AppendConversationDialogs(conversationId, dialogElements); + attachment.SaveConversationFiles(dialog.Files); + dialog.Files.Clear(); } public List GetDialogs(string conversationId) diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 769f318f..4478466d 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -23,6 +23,7 @@ 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.Models; global using BotSharp.Core.Repository; global using BotSharp.Core.Routing; global using BotSharp.Core.Agents.Services; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index a9c8d1b8..a20f9b6b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -171,7 +171,10 @@ public class ConversationController : ControllerBase await conv.TruncateConversation(conversationId, input.TruncateMessageId); } - var inputMsg = new RoleDialogModel(AgentRole.User, input.Text); + var inputMsg = new RoleDialogModel(AgentRole.User, input.Text) + { + Files = input.Files + }; var routing = _services.GetRequiredService(); routing.Context.SetMessageId(conversationId, inputMsg.MessageId);