Skip to content

Commit 5f459a0

Browse files
authored
Merge pull request #553 from iceljc/features/add-image-edit
Features/add image edit
2 parents 453779a + a64b703 commit 5f459a0

File tree

18 files changed

+570
-130
lines changed

18 files changed

+570
-130
lines changed

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

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ namespace BotSharp.Abstraction.Files;
22

33
public interface IBotSharpFileService
44
{
5-
string GetDirectory(string conversationId);
6-
5+
#region Conversation
76
/// <summary>
87
/// Get the files that have been uploaded in the chat.
98
/// If includeScreenShot is true, it will take the screenshots of non-image files, such as pdf, and return the screenshots instead of the original file.
@@ -19,14 +18,19 @@ Task<IEnumerable<MessageFileModel>> GetChatFiles(string conversationId, string s
1918
IEnumerable<RoleDialogModel> conversations, IEnumerable<string> contentTypes,
2019
bool includeScreenShot = false, int? offset = null);
2120

21+
/// <summary>
22+
/// Get the files that have been uploaded in the chat. No screenshot images are included.
23+
/// </summary>
24+
/// <param name="conversationId"></param>
25+
/// <param name="messageIds"></param>
26+
/// <param name="source"></param>
27+
/// <param name="imageOnly"></param>
28+
/// <returns></returns>
2229
IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds, string source, bool imageOnly = false);
2330
string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName);
2431
IEnumerable<MessageFileModel> GetMessagesWithFile(string conversationId, IEnumerable<string> messageIds);
2532
bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files);
2633

27-
string GetUserAvatar();
28-
bool SaveUserAvatar(BotSharpFile file);
29-
3034
/// <summary>
3135
/// Delete files under messages
3236
/// </summary>
@@ -37,21 +41,36 @@ Task<IEnumerable<MessageFileModel>> GetChatFiles(string conversationId, string s
3741
/// <returns></returns>
3842
bool DeleteMessageFiles(string conversationId, IEnumerable<string> messageIds, string targetMessageId, string? newMessageId = null);
3943
bool DeleteConversationFiles(IEnumerable<string> conversationIds);
44+
#endregion
4045

46+
#region Image
47+
Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text);
48+
Task<RoleDialogModel> VarifyImage(string? provider, string? model, BotSharpFile file);
49+
#endregion
50+
51+
#region Pdf
4152
/// <summary>
4253
/// Take screenshots of pdf pages and get response from llm
4354
/// </summary>
4455
/// <param name="prompt"></param>
4556
/// <param name="files">Pdf files</param>
4657
/// <returns></returns>
47-
Task<string> InstructPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files);
58+
Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files);
59+
#endregion
60+
61+
#region User
62+
string GetUserAvatar();
63+
bool SaveUserAvatar(BotSharpFile file);
64+
#endregion
4865

66+
#region Common
4967
/// <summary>
5068
/// Get file bytes and content type from data, e.g., "data:image/png;base64,aaaaaaaaa"
5169
/// </summary>
5270
/// <param name="data"></param>
5371
/// <returns></returns>
5472
(string, byte[]) GetFileInfoFromData(string data);
55-
73+
string GetDirectory(string conversationId);
5674
string GetFileContentType(string filePath);
75+
#endregion
5776
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace BotSharp.Abstraction.MLTasks;
2+
3+
public interface IImageEdit
4+
{
5+
}

src/Infrastructure/BotSharp.Abstraction/MLTasks/IImageGeneration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ public interface IImageGeneration
1313
/// <param name="model">deployment name</param>
1414
void SetModelName(string model);
1515

16-
Task<RoleDialogModel> GetImageGeneration(Agent agent, List<RoleDialogModel> conversations);
16+
Task<RoleDialogModel> GetImageGeneration(Agent agent, RoleDialogModel message);
1717
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.IO;
2+
3+
namespace BotSharp.Abstraction.MLTasks;
4+
5+
public interface IImageVariation
6+
{
7+
/// <summary>
8+
/// The LLM provider like Microsoft Azure, OpenAI, ClaudAI
9+
/// </summary>
10+
string Provider { get; }
11+
12+
/// <summary>
13+
/// Set model name, one provider can consume different model or version(s)
14+
/// </summary>
15+
/// <param name="model">deployment name</param>
16+
void SetModelName(string model);
17+
18+
Task<RoleDialogModel> GetImageVariation(Agent agent, RoleDialogModel message, Stream image, string imageFileName);
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Microsoft.AspNetCore.StaticFiles;
2+
using System.IO;
3+
4+
namespace BotSharp.Core.Files.Services;
5+
6+
public partial class BotSharpFileService
7+
{
8+
public string GetDirectory(string conversationId)
9+
{
10+
var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments");
11+
if (!Directory.Exists(dir))
12+
{
13+
Directory.CreateDirectory(dir);
14+
}
15+
return dir;
16+
}
17+
18+
public (string, byte[]) GetFileInfoFromData(string data)
19+
{
20+
if (string.IsNullOrEmpty(data))
21+
{
22+
return (string.Empty, new byte[0]);
23+
}
24+
25+
var typeStartIdx = data.IndexOf(':');
26+
var typeEndIdx = data.IndexOf(';');
27+
var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
28+
29+
var base64startIdx = data.IndexOf(',');
30+
var base64Str = data.Substring(base64startIdx + 1);
31+
32+
return (contentType, Convert.FromBase64String(base64Str));
33+
}
34+
35+
public string GetFileContentType(string filePath)
36+
{
37+
string contentType;
38+
var provider = new FileExtensionContentTypeProvider();
39+
if (!provider.TryGetContentType(filePath, out contentType))
40+
{
41+
contentType = string.Empty;
42+
}
43+
44+
return contentType;
45+
}
46+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.IO;
2+
3+
namespace BotSharp.Core.Files.Services;
4+
5+
public partial class BotSharpFileService
6+
{
7+
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text)
8+
{
9+
var completion = CompletionProvider.GetImageGeneration(_services, provider: provider ?? "openai", model: model ?? "dall-e-3");
10+
var message = await completion.GetImageGeneration(new Agent()
11+
{
12+
Id = Guid.Empty.ToString(),
13+
}, new RoleDialogModel(AgentRole.User, text));
14+
return message;
15+
}
16+
17+
public async Task<RoleDialogModel> VarifyImage(string? provider, string? model, BotSharpFile file)
18+
{
19+
if (string.IsNullOrWhiteSpace(file?.FileUrl) && string.IsNullOrWhiteSpace(file?.FileData))
20+
{
21+
throw new ArgumentException($"Please fill in at least file url or file data!");
22+
}
23+
24+
var completion = CompletionProvider.GetImageVariation(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
25+
var bytes = await DownloadFile(file);
26+
using var stream = new MemoryStream();
27+
stream.Write(bytes, 0, bytes.Length);
28+
stream.Position = 0;
29+
30+
var message = await completion.GetImageVariation(new Agent()
31+
{
32+
Id = Guid.Empty.ToString()
33+
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, file.FileName ?? string.Empty);
34+
stream.Close();
35+
36+
return message;
37+
}
38+
39+
#region Private methods
40+
private async Task<byte[]> DownloadFile(BotSharpFile file)
41+
{
42+
var bytes = new byte[0];
43+
if (!string.IsNullOrEmpty(file.FileUrl))
44+
{
45+
var http = _services.GetRequiredService<IHttpClientFactory>();
46+
using var client = http.CreateClient();
47+
bytes = await client.GetByteArrayAsync(file.FileUrl);
48+
}
49+
else if (!string.IsNullOrEmpty(file.FileData))
50+
{
51+
(_, bytes) = GetFileInfoFromData(file.FileData);
52+
}
53+
54+
return bytes;
55+
}
56+
#endregion
57+
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace BotSharp.Core.Files.Services;
44

55
public partial class BotSharpFileService
66
{
7-
public async Task<string> InstructPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files)
7+
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files)
88
{
99
var content = string.Empty;
1010

@@ -22,7 +22,7 @@ public async Task<string> InstructPdf(string? provider, string? model, string? m
2222

2323
try
2424
{
25-
var pdfFiles = await SaveFiles(sessionDir, files);
25+
var pdfFiles = await DownloadFiles(sessionDir, files);
2626
var images = await ConvertPdfToImages(pdfFiles);
2727
if (images.IsNullOrEmpty()) return content;
2828

@@ -44,7 +44,7 @@ public async Task<string> InstructPdf(string? provider, string? model, string? m
4444
}
4545
catch (Exception ex)
4646
{
47-
_logger.LogError($"Error when analyzing pdf in file service: {ex.Message}");
47+
_logger.LogError($"Error when analyzing pdf in file service: {ex.Message}\r\n{ex.InnerException}");
4848
return content;
4949
}
5050
finally
@@ -60,7 +60,7 @@ private string GetSessionDirectory(string id)
6060
return dir;
6161
}
6262

63-
private async Task<IEnumerable<string>> SaveFiles(string dir, List<BotSharpFile> files, string extension = "pdf")
63+
private async Task<IEnumerable<string>> DownloadFiles(string dir, List<BotSharpFile> files, string extension = "pdf")
6464
{
6565
if (string.IsNullOrWhiteSpace(dir) || files.IsNullOrEmpty())
6666
{
@@ -105,7 +105,7 @@ private async Task<IEnumerable<string>> SaveFiles(string dir, List<BotSharpFile>
105105
}
106106
catch (Exception ex)
107107
{
108-
_logger.LogWarning($"Error when saving pdf file: {ex.Message}");
108+
_logger.LogWarning($"Error when saving pdf file: {ex.Message}\r\n{ex.InnerException}");
109109
continue;
110110
}
111111
}
@@ -133,7 +133,7 @@ private async Task<IEnumerable<string>> ConvertPdfToImages(IEnumerable<string> f
133133
}
134134
catch (Exception ex)
135135
{
136-
_logger.LogWarning($"Error when converting pdf file to images ({file}): {ex.Message}");
136+
_logger.LogWarning($"Error when converting pdf file to images ({file}): {ex.Message}\r\n{ex.InnerException}");
137137
continue;
138138
}
139139
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public bool SaveUserAvatar(BotSharpFile file)
4040
}
4141
catch (Exception ex)
4242
{
43-
_logger.LogWarning($"Error when saving user avatar: {ex.Message}");
43+
_logger.LogWarning($"Error when saving user avatar: {ex.Message}\r\n{ex.InnerException}");
4444
return false;
4545
}
4646
}

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

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -41,45 +41,6 @@ public BotSharpFileService(
4141
_baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbSettings.FileRepository);
4242
}
4343

44-
public string GetDirectory(string conversationId)
45-
{
46-
var dir = Path.Combine(_dbSettings.FileRepository, CONVERSATION_FOLDER, conversationId, "attachments");
47-
if (!Directory.Exists(dir))
48-
{
49-
Directory.CreateDirectory(dir);
50-
}
51-
return dir;
52-
}
53-
54-
public (string, byte[]) GetFileInfoFromData(string data)
55-
{
56-
if (string.IsNullOrEmpty(data))
57-
{
58-
return (string.Empty, new byte[0]);
59-
}
60-
61-
var typeStartIdx = data.IndexOf(':');
62-
var typeEndIdx = data.IndexOf(';');
63-
var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
64-
65-
var base64startIdx = data.IndexOf(',');
66-
var base64Str = data.Substring(base64startIdx + 1);
67-
68-
return (contentType, Convert.FromBase64String(base64Str));
69-
}
70-
71-
public string GetFileContentType(string filePath)
72-
{
73-
string contentType;
74-
var provider = new FileExtensionContentTypeProvider();
75-
if (!provider.TryGetContentType(filePath, out contentType))
76-
{
77-
contentType = string.Empty;
78-
}
79-
80-
return contentType;
81-
}
82-
8344
#region Private methods
8445
private bool ExistDirectory(string? dir)
8546
{

0 commit comments

Comments
 (0)