Skip to content

Commit 37babd5

Browse files
authored
Merge pull request #491 from iceljc/features/add-pdf-upload
Features/add pdf upload
2 parents f1bbd95 + 50cdabc commit 37babd5

File tree

17 files changed

+417
-66
lines changed

17 files changed

+417
-66
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace BotSharp.Abstraction.Files.Enums;
2+
3+
public static class FileSourceType
4+
{
5+
public const string User = "user";
6+
public const string Bot = "bot";
7+
}

src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ namespace BotSharp.Abstraction.Files;
33
public interface IBotSharpFileService
44
{
55
string GetDirectory(string conversationId);
6-
IEnumerable<MessageFileModel> GetChatImages(string conversationId, List<RoleDialogModel> conversations, int offset = 2);
7-
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, bool imageOnly = false);
8-
string GetMessageFile(string conversationId, string messageId, string fileName);
9-
bool SaveMessageFiles(string conversationId, string messageId, List<BotSharpFile> files);
6+
Task<IEnumerable<MessageFileModel>> GetChatImages(string conversationId, string source, IEnumerable<string> fileTypes, List<RoleDialogModel> conversations, int? offset = null);
7+
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source, bool imageOnly = false);
8+
string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName);
9+
bool HasConversationUserFiles(string conversationId);
10+
bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files);
1011

1112
string GetUserAvatar();
1213
bool SaveUserAvatar(BotSharpFile file);

src/Infrastructure/BotSharp.Abstraction/Files/Models/BotSharpFile.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@ public class BotSharpFile
1414

1515
[JsonPropertyName("file_url")]
1616
public string FileUrl { get; set; } = string.Empty;
17+
18+
[JsonPropertyName("content_type")]
19+
public string ContentType { get; set; } = string.Empty;
20+
21+
[JsonPropertyName("file_storage_url")]
22+
public string FileStorageUrl { get; set; } = string.Empty;
1723
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace BotSharp.Abstraction.Files.Models;
2+
3+
public class LlmFileContext
4+
{
5+
[JsonPropertyName("user_request")]
6+
public string UserRequest { get; set; }
7+
8+
[JsonPropertyName("file_types")]
9+
public string FileTypes { get; set; }
10+
}

src/Infrastructure/BotSharp.Abstraction/Files/Models/MessageFileModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class MessageFileModel
2020
[JsonPropertyName("content_type")]
2121
public string ContentType { get; set; }
2222

23+
[JsonPropertyName("file_source")]
24+
public string FileSource { get; set; } = FileSourceType.User;
25+
2326
public MessageFileModel()
2427
{
2528

src/Infrastructure/BotSharp.Abstraction/Using.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
global using BotSharp.Abstraction.Templating;
1717
global using BotSharp.Abstraction.Translation.Attributes;
1818
global using BotSharp.Abstraction.Messaging.Enums;
19-
global using BotSharp.Abstraction.Files.Models;
19+
global using BotSharp.Abstraction.Files.Models;
20+
global using BotSharp.Abstraction.Files.Enums;

src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
using BotSharp.Abstraction.Files;
21
using BotSharp.Abstraction.Google.Settings;
32
using BotSharp.Abstraction.Instructs;
43
using BotSharp.Abstraction.Messaging;
54
using BotSharp.Abstraction.Plugins.Models;
65
using BotSharp.Abstraction.Routing.Planning;
76
using BotSharp.Abstraction.Settings;
87
using BotSharp.Abstraction.Templating;
9-
using BotSharp.Core.Files;
108
using BotSharp.Core.Instructs;
119
using BotSharp.Core.Messaging;
1210
using BotSharp.Core.Routing.Planning;
@@ -44,7 +42,6 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
4442
services.AddScoped<IConversationStorage, ConversationStorage>();
4543
services.AddScoped<IConversationService, ConversationService>();
4644
services.AddScoped<IConversationStateService, ConversationStateService>();
47-
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
4845
services.AddScoped<ITranslationService, TranslationService>();
4946

5047
// Rich content messaging

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public async Task<bool> SendMessage(string agentId,
4848

4949
// Save message files
5050
var fileService = _services.GetRequiredService<IBotSharpFileService>();
51-
fileService.SaveMessageFiles(_conversationId, message.MessageId, message.Files);
51+
fileService.SaveMessageFiles(_conversationId, message.MessageId, FileSourceType.User, message.Files);
5252
message.Files?.Clear();
5353

5454
// Save payload

src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.Conversation.cs

Lines changed: 163 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
using Microsoft.AspNetCore.StaticFiles;
1+
using BotSharp.Abstraction.Browsing;
2+
using Microsoft.EntityFrameworkCore;
23
using System.IO;
4+
using System.Linq;
35
using System.Threading;
46

57
namespace BotSharp.Core.Files;
68

79
public partial class BotSharpFileService
810
{
9-
public IEnumerable<MessageFileModel> GetChatImages(string conversationId, List<RoleDialogModel> conversations, int offset = 1)
11+
public async Task<IEnumerable<MessageFileModel>> GetChatImages(string conversationId, string source, IEnumerable<string> fileTypes,
12+
List<RoleDialogModel> conversations, int? offset = null)
1013
{
1114
var files = new List<MessageFileModel>();
1215
if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
1316
{
14-
return files;
17+
return new List<MessageFileModel>();
1518
}
1619

1720
if (offset <= 0)
@@ -23,55 +26,161 @@ public IEnumerable<MessageFileModel> GetChatImages(string conversationId, List<R
2326
offset = MAX_OFFSET;
2427
}
2528

26-
var messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset).ToList();
27-
files = GetMessageFiles(conversationId, messageIds, imageOnly: true).ToList();
29+
var messageIds = new List<string>();
30+
if (offset.HasValue)
31+
{
32+
messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
33+
}
34+
else
35+
{
36+
messageIds = conversations.Select(x => x.MessageId).Distinct().ToList();
37+
}
38+
39+
files = await GetMessageFiles(conversationId, messageIds, source, fileTypes);
40+
return files;
41+
}
42+
43+
private async Task<List<MessageFileModel>> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source, IEnumerable<string> fileTypes)
44+
{
45+
var files = new List<MessageFileModel>();
46+
if (string.IsNullOrEmpty(conversationId) || messageIds.IsNullOrEmpty() || fileTypes.IsNullOrEmpty()) return files;
47+
48+
var isNeedScreenShot = fileTypes.Any(x => _allowScreenShotTypes.Contains(x));
49+
var onlyScreenShot = fileTypes.All(x => _allowScreenShotTypes.Contains(x));
50+
51+
try
52+
{
53+
var contextId = string.Empty;
54+
var web = _services.GetRequiredService<IWebBrowser>();
55+
var preFixPath = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER);
56+
57+
if (isNeedScreenShot)
58+
{
59+
contextId = Guid.NewGuid().ToString();
60+
await web.LaunchBrowser(contextId, string.Empty);
61+
}
62+
63+
foreach (var messageId in messageIds)
64+
{
65+
var dir = Path.Combine(preFixPath, messageId, source);
66+
if (!ExistDirectory(dir)) continue;
67+
68+
foreach (var subDir in Directory.GetDirectories(dir))
69+
{
70+
var file = Directory.GetFiles(subDir).FirstOrDefault();
71+
if (file == null) continue;
72+
73+
var index = subDir.Split(Path.DirectorySeparatorChar).Last();
74+
var contentType = GetFileContentType(file);
75+
76+
if ((!isNeedScreenShot || (isNeedScreenShot && !onlyScreenShot)) && _allowedImageTypes.Contains(contentType))
77+
{
78+
var model = new MessageFileModel()
79+
{
80+
MessageId = messageId,
81+
FileStorageUrl = file,
82+
ContentType = contentType
83+
};
84+
files.Add(model);
85+
}
86+
else if ((isNeedScreenShot && !onlyScreenShot || onlyScreenShot) && !_allowedImageTypes.Contains(contentType))
87+
{
88+
var screenShotDir = Path.Combine(subDir, SCREENSHOT_FILE_FOLDER);
89+
if (ExistDirectory(screenShotDir) && Directory.GetFiles(screenShotDir).Any())
90+
{
91+
file = Directory.GetFiles(screenShotDir).First();
92+
contentType = GetFileContentType(file);
93+
94+
var model = new MessageFileModel()
95+
{
96+
MessageId = messageId,
97+
FileStorageUrl = file,
98+
ContentType = contentType
99+
};
100+
files.Add(model);
101+
}
102+
else
103+
{
104+
await web.GoToPage(contextId, file);
105+
var path = Path.Combine(subDir, SCREENSHOT_FILE_FOLDER, $"{Guid.NewGuid()}.png");
106+
await web.ScreenshotAsync(contextId, path);
107+
contentType = GetFileContentType(path);
108+
109+
var model = new MessageFileModel()
110+
{
111+
MessageId = messageId,
112+
FileStorageUrl = path,
113+
ContentType = contentType
114+
};
115+
files.Add(model);
116+
}
117+
}
118+
}
119+
}
120+
121+
if (isNeedScreenShot)
122+
{
123+
await web.CloseBrowser(contextId);
124+
}
125+
}
126+
catch (Exception ex)
127+
{
128+
_logger.LogWarning($"Error when reading conversation ({conversationId}) files: {ex.Message}");
129+
}
130+
28131
return files;
29132
}
30133

31-
public IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, bool imageOnly = false)
134+
public IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds,
135+
string source, bool imageOnly = false)
32136
{
33137
var files = new List<MessageFileModel>();
34138
if (messageIds.IsNullOrEmpty()) return files;
35139

36140
foreach (var messageId in messageIds)
37141
{
38-
var dir = GetConversationFileDirectory(conversationId, messageId);
142+
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId, source);
39143
if (!ExistDirectory(dir))
40144
{
41145
continue;
42146
}
43147

44-
foreach (var file in Directory.GetFiles(dir))
148+
foreach (var subDir in Directory.GetDirectories(dir))
45149
{
46-
var contentType = GetFileContentType(file);
47-
if (imageOnly && !_allowedTypes.Contains(contentType))
48-
{
49-
continue;
50-
}
51-
52-
var fileName = Path.GetFileNameWithoutExtension(file);
53-
var extension = Path.GetExtension(file);
54-
var fileType = extension.Substring(1);
150+
var index = subDir.Split(Path.DirectorySeparatorChar).Last();
55151

56-
var model = new MessageFileModel()
152+
foreach (var file in Directory.GetFiles(subDir))
57153
{
58-
MessageId = messageId,
59-
FileUrl = $"/conversation/{conversationId}/message/{messageId}/file/{fileName}",
60-
FileStorageUrl = file,
61-
FileName = fileName,
62-
FileType = fileType,
63-
ContentType = contentType
64-
};
65-
files.Add(model);
154+
var contentType = GetFileContentType(file);
155+
if (imageOnly && !_allowedImageTypes.Contains(contentType))
156+
{
157+
continue;
158+
}
159+
160+
var fileName = Path.GetFileNameWithoutExtension(file);
161+
var extension = Path.GetExtension(file);
162+
var fileType = extension.Substring(1);
163+
164+
var model = new MessageFileModel()
165+
{
166+
MessageId = messageId,
167+
FileUrl = $"/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}",
168+
FileStorageUrl = file,
169+
FileName = fileName,
170+
FileType = fileType,
171+
ContentType = contentType
172+
};
173+
files.Add(model);
174+
}
66175
}
67176
}
68177

69178
return files;
70179
}
71180

72-
public string GetMessageFile(string conversationId, string messageId, string fileName)
181+
public string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName)
73182
{
74-
var dir = GetConversationFileDirectory(conversationId, messageId);
183+
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER, messageId, source, index);
75184
if (!ExistDirectory(dir))
76185
{
77186
return string.Empty;
@@ -81,7 +190,17 @@ public string GetMessageFile(string conversationId, string messageId, string fil
81190
return found;
82191
}
83192

84-
public bool SaveMessageFiles(string conversationId, string messageId, List<BotSharpFile> files)
193+
public bool HasConversationUserFiles(string conversationId)
194+
{
195+
if (string.IsNullOrEmpty(conversationId)) return false;
196+
197+
var dir = Path.Combine(_baseDir, CONVERSATION_FOLDER, conversationId, FILE_FOLDER);
198+
if (!ExistDirectory(dir)) return false;
199+
200+
return Directory.GetDirectories(dir).Any();
201+
}
202+
203+
public bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files)
85204
{
86205
if (files.IsNullOrEmpty()) return false;
87206

@@ -99,11 +218,16 @@ public bool SaveMessageFiles(string conversationId, string messageId, List<BotSh
99218
}
100219

101220
var (_, bytes) = GetFileInfoFromData(file.FileData);
102-
var fileType = Path.GetExtension(file.FileName);
103-
var fileName = $"{i + 1}{fileType}";
104221
Thread.Sleep(100);
105-
File.WriteAllBytes(Path.Combine(dir, fileName), bytes);
222+
var subDir = Path.Combine(dir, source, $"{i + 1}");
223+
if (!ExistDirectory(subDir))
224+
{
225+
Directory.CreateDirectory(subDir);
226+
}
227+
228+
File.WriteAllBytes(Path.Combine(subDir, file.FileName), bytes);
106229
}
230+
107231
return true;
108232
}
109233
catch (Exception ex)
@@ -114,7 +238,6 @@ public bool SaveMessageFiles(string conversationId, string messageId, List<BotSh
114238
}
115239

116240

117-
118241
public bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null)
119242
{
120243
if (string.IsNullOrEmpty(conversationId) || messageIds == null) return false;
@@ -132,6 +255,13 @@ public bool DeleteMessageFiles(string conversationId, IEnumerable<string> messag
132255
}
133256

134257
Directory.Move(prevDir, newDir);
258+
Thread.Sleep(100);
259+
260+
var botDir = Path.Combine(newDir, BOT_FILE_FOLDER);
261+
if (ExistDirectory(botDir))
262+
{
263+
Directory.Delete(botDir, true);
264+
}
135265
}
136266
}
137267

src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ public partial class BotSharpFileService : IBotSharpFileService
1212
private readonly IUserIdentity _user;
1313
private readonly ILogger<BotSharpFileService> _logger;
1414
private readonly string _baseDir;
15-
private readonly IEnumerable<string> _allowedTypes = new List<string> { "image/png", "image/jpeg" };
15+
private readonly IEnumerable<string> _allowedImageTypes = new List<string> { "image/png", "image/jpeg" };
16+
private readonly IEnumerable<string> _allowScreenShotTypes = new List<string> { "pdf" };
1617

1718
private const string CONVERSATION_FOLDER = "conversations";
1819
private const string FILE_FOLDER = "files";
20+
private const string USER_FILE_FOLDER = "user";
21+
private const string SCREENSHOT_FILE_FOLDER = "screenshot";
22+
private const string BOT_FILE_FOLDER = "bot";
1923
private const string USERS_FOLDER = "users";
2024
private const string USER_AVATAR_FOLDER = "avatar";
2125

0 commit comments

Comments
 (0)