Merge pull request #567 from GreenShadeZhang/tencent-cos
Add Tencent Cloud object storage support
This commit is contained in:
commit
25ecf41926
14
BotSharp.sln
14
BotSharp.sln
|
|
@ -101,6 +101,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.FileHandler
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Planner", "src\Plugins\BotSharp.Plugin.Planner\BotSharp.Plugin.Planner.csproj", "{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileStorages", "FileStorages", "{38B37C0D-1930-4D47-BCBF-E358EC1096B1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.TencentCos", "src\Plugins\BotSharp.Plugin.TencentCos\BotSharp.Plugin.TencentCos.csproj", "{BF029B0A-768B-43A1-8D91-E70B95505716}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -413,6 +417,14 @@ Global
|
|||
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -461,6 +473,8 @@ Global
|
|||
{A72B3BEB-E14B-4917-BE44-97EAE4E122D2} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{D6A99D4F-6248-419E-8A43-B38ADEBABA2C} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{54E83C6F-54EE-4ADC-8D72-93C009CC4FB4} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
|
||||
{38B37C0D-1930-4D47-BCBF-E358EC1096B1} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C}
|
||||
{BF029B0A-768B-43A1-8D91-E70B95505716} = {38B37C0D-1930-4D47-BCBF-E358EC1096B1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Repositories.Enums;
|
||||
|
||||
public static class FileStorageEnum
|
||||
{
|
||||
public const string LocalFileStorage = nameof(LocalFileStorage);
|
||||
public const string AmazonS3Storage = nameof(AmazonS3Storage);
|
||||
public const string AzureBlobStorage = nameof(AzureBlobStorage);
|
||||
public const string TencentCosStorage = nameof(TencentCosStorage);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public class FileStorageSettings
|
||||
{
|
||||
public string Default { get; set; }
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.IO;
|
||||
|
||||
namespace BotSharp.Abstraction.Files;
|
||||
|
||||
public interface IBotSharpFileService
|
||||
|
|
@ -74,5 +76,7 @@ public interface IBotSharpFileService
|
|||
(string, byte[]) GetFileInfoFromData(string data);
|
||||
string GetDirectory(string conversationId);
|
||||
string GetFileContentType(string filePath);
|
||||
byte[] GetFileBytes(string fileStorageUrl);
|
||||
bool SavefileToPath(string filePath, Stream stream);
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@
|
|||
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
|
||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.5.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.8.0" />
|
||||
<PackageReference Include="Fluid.Core" Version="2.11.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Nanoid" Version="3.1.0" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using BotSharp.Core.Files.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
|
|
@ -14,6 +15,12 @@ public class FilePlugin : IBotSharpPlugin
|
|||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
|
||||
var myFileStorageSettings = new FileStorageSettings();
|
||||
config.Bind("FileStorage", myFileStorageSettings);
|
||||
|
||||
if (myFileStorageSettings.Default == FileStorageEnum.LocalFileStorage)
|
||||
{
|
||||
services.AddScoped<IBotSharpFileService, BotSharpFileService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,4 +43,21 @@ public partial class BotSharpFileService
|
|||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] GetFileBytes(string fileStorageUrl)
|
||||
{
|
||||
using var stream = File.OpenRead(fileStorageUrl);
|
||||
var bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, (int)stream.Length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public bool SavefileToPath(string filePath, Stream stream)
|
||||
{
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,10 +356,7 @@ public class ConversationController : ControllerBase
|
|||
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);
|
||||
}
|
||||
fileService.SavefileToPath(filePath, file.OpenReadStream());
|
||||
}
|
||||
|
||||
return Ok(new { message = "File uploaded successfully." });
|
||||
|
|
|
|||
|
|
@ -158,9 +158,8 @@ public class UserController : ControllerBase
|
|||
#region Private methods
|
||||
private FileContentResult BuildFileResult(string file)
|
||||
{
|
||||
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);
|
||||
var fileService = _services.GetRequiredService<IBotSharpFileService>();
|
||||
var bytes = fileService.GetFileBytes(file);
|
||||
return File(bytes, "application/octet-stream", Path.GetFileName(file));
|
||||
}
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<LangVersion>$(LangVersion)</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<VersionPrefix>$(BotSharpVersion)</VersionPrefix>
|
||||
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
|
||||
<OutputPath>$(SolutionDir)packages</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.39" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
304
src/Plugins/BotSharp.Plugin.TencentCos/Modules/BucketClient.cs
Normal file
304
src/Plugins/BotSharp.Plugin.TencentCos/Modules/BucketClient.cs
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
using COSXML;
|
||||
using COSXML.CosException;
|
||||
using COSXML.Model.Bucket;
|
||||
using COSXML.Model.Object;
|
||||
using COSXML.Model.Tag;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCos.Modules
|
||||
{
|
||||
public class BucketClient
|
||||
{
|
||||
private readonly CosXmlServer _cosXml;
|
||||
private readonly string _fullBucketName;
|
||||
private readonly string _appId;
|
||||
private readonly string _region;
|
||||
public BucketClient(CosXmlServer cosXml, string fullBucketName, string appId, string region)
|
||||
{
|
||||
_cosXml = cosXml;
|
||||
_fullBucketName = fullBucketName;
|
||||
_appId = appId;
|
||||
_region = region;
|
||||
}
|
||||
|
||||
public bool UploadBytes(string key, byte[] fileData)
|
||||
{
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
var request = new PutObjectRequest(_fullBucketName, key, fileData);
|
||||
|
||||
var resultData = _cosXml.PutObject(request);
|
||||
|
||||
if (resultData != null && resultData.IsSuccessful())
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool UploadStream(string key, Stream stream)
|
||||
{
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
var request = new PutObjectRequest(_fullBucketName, key, stream);
|
||||
|
||||
var resultData = _cosXml.PutObject(request);
|
||||
|
||||
if (resultData != null && resultData.IsSuccessful())
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public (string, byte[]) DownloadDirDefaultFileBytes(string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetBucketRequest(_fullBucketName);
|
||||
request.SetPrefix($"{dir.TrimEnd('/')}/");
|
||||
request.SetDelimiter("/");
|
||||
|
||||
var result = _cosXml.GetBucket(request);
|
||||
|
||||
var info = result.listBucket;
|
||||
|
||||
var objects = info.contentsList;
|
||||
|
||||
var objectData = objects.FirstOrDefault(o => o.size > 0);
|
||||
|
||||
if (objectData != null)
|
||||
{
|
||||
var fileName = Path.GetFileName(objectData.key);
|
||||
var fileBytes = DownloadFileBytes(objectData.key);
|
||||
return (fileName, fileBytes);
|
||||
}
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
return (string.Empty, Array.Empty<byte>());
|
||||
}
|
||||
|
||||
public byte[] DownloadFileBytes(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetObjectBytesRequest(_fullBucketName, key);
|
||||
var result = _cosXml.GetObject(request);
|
||||
if (result != null)
|
||||
{
|
||||
return result.content;
|
||||
}
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public List<string> GetDirFiles(string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetBucketRequest(_fullBucketName);
|
||||
request.SetPrefix($"{dir.TrimEnd('/')}/");
|
||||
request.SetDelimiter("/");
|
||||
|
||||
var result = _cosXml.GetBucket(request);
|
||||
|
||||
var info = result.listBucket;
|
||||
|
||||
var objects = info.contentsList;
|
||||
|
||||
return objects.Where(o => o.size > 0).Select(o => o.key).ToList();
|
||||
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetDirectories(string dir)
|
||||
{
|
||||
var dirs = new List<string>();
|
||||
try
|
||||
{
|
||||
var request = new GetBucketRequest(_fullBucketName);
|
||||
request.SetPrefix($"{dir.TrimEnd('/')}/");
|
||||
request.SetDelimiter("/");
|
||||
|
||||
var result = _cosXml.GetBucket(request);
|
||||
|
||||
var info = result.listBucket;
|
||||
|
||||
var objects = info.contentsList;
|
||||
|
||||
var list = objects.Where(o => o.size == 0 && o.key != dir).Select(o => o.key).ToList();
|
||||
|
||||
dirs.AddRange(list);
|
||||
|
||||
var commonPrefixes = info.commonPrefixesList;
|
||||
|
||||
dirs.AddRange(commonPrefixes.Select(c => c.prefix));
|
||||
|
||||
return dirs;
|
||||
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DirExists(string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new GetBucketRequest(_fullBucketName);
|
||||
request.SetPrefix($"{dir.TrimEnd('/')}/");
|
||||
request.SetDelimiter("/");
|
||||
|
||||
var result = _cosXml.GetBucket(request);
|
||||
|
||||
var info = result.listBucket;
|
||||
|
||||
var objects = info.contentsList;
|
||||
|
||||
return objects.Count > 0 || info?.commonPrefixesList.Count > 0;
|
||||
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveDir(string sourceDir, string destDir)
|
||||
{
|
||||
var listRequest = new GetBucketRequest(_fullBucketName);
|
||||
|
||||
listRequest.SetPrefix($"{sourceDir.TrimEnd('/')}/");
|
||||
var listResult = _cosXml.GetBucket(listRequest);
|
||||
|
||||
var info = listResult.listBucket;
|
||||
|
||||
var objects = info.contentsList;
|
||||
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
string sourceKey = obj.key;
|
||||
string destinationKey = $"{destDir.TrimEnd('/')}/{sourceKey.Substring(sourceDir.Length)}";
|
||||
|
||||
var copySource = new CopySourceStruct(_appId, _fullBucketName, _region, sourceKey);
|
||||
|
||||
var request = new CopyObjectRequest(_fullBucketName, destinationKey);
|
||||
|
||||
request.SetCopySource(copySource);
|
||||
try
|
||||
{
|
||||
|
||||
var result = _cosXml.CopyObject(request);
|
||||
var deleteRequest = new DeleteObjectRequest(_fullBucketName, sourceKey);
|
||||
var deleteResult = _cosXml.DeleteObject(deleteRequest);
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void DeleteDir(string dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
string nextMarker = null;
|
||||
do
|
||||
{
|
||||
var listRequest = new GetBucketRequest(_fullBucketName);
|
||||
listRequest.SetPrefix($"{dir.TrimEnd('/')}/");
|
||||
listRequest.SetMarker(nextMarker);
|
||||
var listResult = _cosXml.GetBucket(listRequest);
|
||||
var info = listResult.listBucket;
|
||||
List<ListBucket.Contents> objects = info.contentsList;
|
||||
nextMarker = info.nextMarker;
|
||||
|
||||
var deleteRequest = new DeleteMultiObjectRequest(_fullBucketName);
|
||||
|
||||
deleteRequest.SetDeleteQuiet(false);
|
||||
var deleteObjects = new List<string>();
|
||||
foreach (var content in objects)
|
||||
{
|
||||
deleteObjects.Add(content.key);
|
||||
}
|
||||
deleteRequest.SetObjectKeys(deleteObjects);
|
||||
|
||||
var deleteResult = _cosXml.DeleteMultiObjects(deleteRequest);
|
||||
|
||||
} while (nextMarker != null);
|
||||
}
|
||||
catch (CosClientException clientEx)
|
||||
{
|
||||
throw new Exception(clientEx.Message);
|
||||
}
|
||||
catch (CosServerException serverEx)
|
||||
{
|
||||
throw new Exception(serverEx.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoesObjectExist(string key)
|
||||
{
|
||||
var request = new DoesObjectExistRequest(_fullBucketName, key);
|
||||
return _cosXml.DoesObjectExist(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using Microsoft.AspNetCore.StaticFiles;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService
|
||||
{
|
||||
public string GetDirectory(string conversationId)
|
||||
{
|
||||
return $"{CONVERSATION_FOLDER}/{conversationId}/attachments/";
|
||||
}
|
||||
|
||||
public (string, byte[]) GetFileInfoFromData(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return (string.Empty, new byte[0]);
|
||||
}
|
||||
|
||||
var typeStartIdx = data.IndexOf(':');
|
||||
var typeEndIdx = data.IndexOf(';');
|
||||
var contentType = data.Substring(typeStartIdx + 1, typeEndIdx - typeStartIdx - 1);
|
||||
|
||||
var base64startIdx = data.IndexOf(',');
|
||||
var base64Str = data.Substring(base64startIdx + 1);
|
||||
|
||||
return (contentType, Convert.FromBase64String(base64Str));
|
||||
}
|
||||
|
||||
public string GetFileContentType(string filePath)
|
||||
{
|
||||
string contentType;
|
||||
var provider = new FileExtensionContentTypeProvider();
|
||||
if (!provider.TryGetContentType(filePath, out contentType))
|
||||
{
|
||||
contentType = string.Empty;
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] GetFileBytes(string fileStorageUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileData = _cosClient.BucketClient.DownloadFileBytes(fileStorageUrl);
|
||||
|
||||
return fileData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when get file bytes: {ex.Message}\r\n{ex.InnerException}");
|
||||
}
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
public bool SavefileToPath(string filePath, Stream stream)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
return _cosClient.BucketClient.UploadStream(filePath, stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when saving file to path: {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
using BotSharp.Abstraction.Files.Converters;
|
||||
using BotSharp.Abstraction.Files.Enums;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService
|
||||
{
|
||||
public async Task<IEnumerable<MessageFileModel>> GetChatFiles(string conversationId, string source,
|
||||
IEnumerable<RoleDialogModel> conversations, IEnumerable<string> contentTypes,
|
||||
bool includeScreenShot = false, int? offset = null)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
if (string.IsNullOrEmpty(conversationId) || conversations.IsNullOrEmpty())
|
||||
{
|
||||
return files;
|
||||
}
|
||||
|
||||
var messageIds = GetMessageIds(conversations, offset);
|
||||
var pathPrefix = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}";
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var dir = $"{pathPrefix}/{messageId}/{source}";
|
||||
|
||||
foreach (var subDir in _cosClient.BucketClient.GetDirectories(dir))
|
||||
{
|
||||
var file = _cosClient.BucketClient.GetDirFiles(subDir).FirstOrDefault();
|
||||
if (file == null) continue;
|
||||
|
||||
var contentType = GetFileContentType(file);
|
||||
if (contentTypes?.Contains(contentType) != true) continue;
|
||||
|
||||
var foundFiles = await GetMessageFiles(file, subDir, contentType, messageId, source, includeScreenShot);
|
||||
if (foundFiles.IsNullOrEmpty()) continue;
|
||||
|
||||
files.AddRange(foundFiles);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public IEnumerable<MessageFileModel> GetMessageFiles(string conversationId, IEnumerable<string> messageIds,
|
||||
string source, bool imageOnly = false)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return files;
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var dir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}/{source}";
|
||||
if (!ExistDirectory(dir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var subDir in _cosClient.BucketClient.GetDirectories(dir))
|
||||
{
|
||||
foreach (var file in _cosClient.BucketClient.GetDirFiles(subDir))
|
||||
{
|
||||
var contentType = GetFileContentType(file);
|
||||
if (imageOnly && !_imageTypes.Contains(contentType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileUrl = $"https://{_fullBuketName}.cos.{_settings.Region}.myqcloud.com/{file}",
|
||||
FileStorageUrl = file,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public string GetMessageFile(string conversationId, string messageId, string source, string index, string fileName)
|
||||
{
|
||||
var dir = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{source}/{index}/";
|
||||
|
||||
var fileList = _cosClient.BucketClient.GetDirFiles(dir);
|
||||
|
||||
var found = fileList.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).IsEqualTo(fileName));
|
||||
return found;
|
||||
}
|
||||
|
||||
public IEnumerable<MessageFileModel> GetMessagesWithFile(string conversationId, IEnumerable<string> messageIds)
|
||||
{
|
||||
var foundMsgs = new List<MessageFileModel>();
|
||||
if (string.IsNullOrWhiteSpace(conversationId) || messageIds.IsNullOrEmpty()) return foundMsgs;
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var prefix = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}";
|
||||
var userDir = $"{prefix}/{FileSourceType.User}/";
|
||||
if (ExistDirectory(userDir))
|
||||
{
|
||||
foundMsgs.Add(new MessageFileModel { MessageId = messageId, FileSource = FileSourceType.User });
|
||||
}
|
||||
|
||||
var botDir = $"{prefix}/{FileSourceType.Bot}";
|
||||
if (ExistDirectory(botDir))
|
||||
{
|
||||
foundMsgs.Add(new MessageFileModel { MessageId = messageId, FileSource = FileSourceType.Bot });
|
||||
}
|
||||
}
|
||||
|
||||
return foundMsgs;
|
||||
}
|
||||
|
||||
public bool SaveMessageFiles(string conversationId, string messageId, string source, List<BotSharpFile> files)
|
||||
{
|
||||
if (files.IsNullOrEmpty()) return false;
|
||||
|
||||
var dir = GetConversationFileDirectory(conversationId, messageId, createNewDir: true);
|
||||
|
||||
for (int i = 0; i < files.Count; i++)
|
||||
{
|
||||
var file = files[i];
|
||||
if (string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var (_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
|
||||
var subDir = $"{dir}/{source}/{i + 1}";
|
||||
|
||||
_cosClient.BucketClient.UploadBytes($"{subDir}/{file.FileName}", bytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when saving message file {file.FileName}: {ex.Message}\r\n{ex.InnerException}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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 = $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{newMessageId}/";
|
||||
|
||||
if (ExistDirectory(prevDir))
|
||||
{
|
||||
if (ExistDirectory(newDir))
|
||||
{
|
||||
_cosClient.BucketClient.DeleteDir(newDir);
|
||||
}
|
||||
|
||||
_cosClient.BucketClient.MoveDir(prevDir, newDir);
|
||||
|
||||
var botDir = $"{newDir}/{BOT_FILE_FOLDER}";
|
||||
if (ExistDirectory(botDir))
|
||||
{
|
||||
_cosClient.BucketClient.DeleteDir(newDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
var dir = GetConversationFileDirectory(conversationId, messageId);
|
||||
if (!ExistDirectory(dir)) continue;
|
||||
_cosClient.BucketClient.DeleteDir(dir);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DeleteConversationFiles(IEnumerable<string> conversationIds)
|
||||
{
|
||||
if (conversationIds.IsNullOrEmpty()) return false;
|
||||
|
||||
foreach (var conversationId in conversationIds)
|
||||
{
|
||||
var convDir = GetConversationDirectory(conversationId);
|
||||
if (!ExistDirectory(convDir)) continue;
|
||||
|
||||
_cosClient.BucketClient.DeleteDir(convDir);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return $"{CONVERSATION_FOLDER}/{conversationId}/{FILE_FOLDER}/{messageId}";
|
||||
}
|
||||
|
||||
private string? GetConversationDirectory(string conversationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId)) return null;
|
||||
|
||||
var dir = $"{CONVERSATION_FOLDER}/{conversationId}";
|
||||
return dir;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetMessageIds(IEnumerable<RoleDialogModel> conversations, int? offset = null)
|
||||
{
|
||||
if (conversations.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
||||
|
||||
if (offset <= 0)
|
||||
{
|
||||
offset = MIN_OFFSET;
|
||||
}
|
||||
else if (offset > MAX_OFFSET)
|
||||
{
|
||||
offset = MAX_OFFSET;
|
||||
}
|
||||
|
||||
var messageIds = new List<string>();
|
||||
if (offset.HasValue)
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().TakeLast(offset.Value).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageIds = conversations.Select(x => x.MessageId).Distinct().ToList();
|
||||
}
|
||||
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<MessageFileModel>> GetMessageFiles(string file, string fileDir, string contentType,
|
||||
string messageId, string source, bool includeScreenShot)
|
||||
{
|
||||
var files = new List<MessageFileModel>();
|
||||
try
|
||||
{
|
||||
if (!_imageTypes.Contains(contentType) && includeScreenShot)
|
||||
{
|
||||
var screenShotDir = $"{fileDir}/{SCREENSHOT_FILE_FOLDER}/";
|
||||
|
||||
var fileList = _cosClient.BucketClient.GetDirFiles(screenShotDir);
|
||||
|
||||
if (!fileList.IsNullOrEmpty())
|
||||
{
|
||||
foreach (var screenShot in fileList)
|
||||
{
|
||||
contentType = GetFileContentType(screenShot);
|
||||
if (!_imageTypes.Contains(contentType)) continue;
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(screenShot);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = screenShot,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
else if (contentType == MediaTypeNames.Application.Pdf)
|
||||
{
|
||||
var images = await ConvertPdfToImages(file, screenShotDir);
|
||||
foreach (var image in images)
|
||||
{
|
||||
contentType = GetFileContentType(image);
|
||||
var fileName = Path.GetFileNameWithoutExtension(image);
|
||||
var fileType = Path.GetExtension(image).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = image,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
var fileType = Path.GetExtension(file).Substring(1);
|
||||
var model = new MessageFileModel()
|
||||
{
|
||||
MessageId = messageId,
|
||||
FileName = fileName,
|
||||
FileType = fileType,
|
||||
FileStorageUrl = file,
|
||||
ContentType = contentType,
|
||||
FileSource = source
|
||||
};
|
||||
files.Add(model);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when getting message files {file} (messageId: {messageId}), Error: {ex.Message}\r\n{ex.InnerException}");
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<string>> ConvertPdfToImages(string pdfLoc, string imageLoc)
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
if (converters.IsNullOrEmpty()) return Enumerable.Empty<string>();
|
||||
|
||||
var converter = GetPdf2ImageConverter();
|
||||
if (converter == null)
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
return await converter.ConvertPdfToImages(pdfLoc, imageLoc);
|
||||
}
|
||||
|
||||
private IPdf2ImageConverter? GetPdf2ImageConverter()
|
||||
{
|
||||
var converters = _services.GetServices<IPdf2ImageConverter>();
|
||||
return converters.FirstOrDefault();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService
|
||||
{
|
||||
public async Task<RoleDialogModel> GenerateImage(string? provider, string? model, string text)
|
||||
{
|
||||
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-3");
|
||||
var message = await completion.GetImageGeneration(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
}, new RoleDialogModel(AgentRole.User, text));
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> VaryImage(string? provider, string? model, BotSharpFile image)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData))
|
||||
{
|
||||
throw new ArgumentException($"Cannot find image url or data!");
|
||||
}
|
||||
|
||||
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
|
||||
var bytes = await DownloadFile(image);
|
||||
using var stream = new MemoryStream();
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
stream.Position = 0;
|
||||
|
||||
var message = await completion.GetImageVariation(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, string.Empty), stream, image.FileName ?? string.Empty);
|
||||
|
||||
stream.Close();
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData))
|
||||
{
|
||||
throw new ArgumentException($"Cannot find image url or data!");
|
||||
}
|
||||
|
||||
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
|
||||
var bytes = await DownloadFile(image);
|
||||
using var stream = new MemoryStream();
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
stream.Position = 0;
|
||||
|
||||
var message = await completion.GetImageEdits(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, text), stream, image.FileName ?? string.Empty);
|
||||
|
||||
stream.Close();
|
||||
return message;
|
||||
}
|
||||
|
||||
public async Task<RoleDialogModel> EditImage(string? provider, string? model, string text, BotSharpFile image, BotSharpFile mask)
|
||||
{
|
||||
if ((string.IsNullOrWhiteSpace(image?.FileUrl) && string.IsNullOrWhiteSpace(image?.FileData)) ||
|
||||
(string.IsNullOrWhiteSpace(mask?.FileUrl) && string.IsNullOrWhiteSpace(mask?.FileData)))
|
||||
{
|
||||
throw new ArgumentException($"Cannot find image/mask url or data");
|
||||
}
|
||||
|
||||
var completion = CompletionProvider.GetImageCompletion(_services, provider: provider ?? "openai", model: model ?? "dall-e-2");
|
||||
var imageBytes = await DownloadFile(image);
|
||||
var maskBytes = await DownloadFile(mask);
|
||||
|
||||
using var imageStream = new MemoryStream();
|
||||
imageStream.Write(imageBytes, 0, imageBytes.Length);
|
||||
imageStream.Position = 0;
|
||||
|
||||
using var maskStream = new MemoryStream();
|
||||
maskStream.Write(maskBytes, 0, maskBytes.Length);
|
||||
maskStream.Position = 0;
|
||||
|
||||
var message = await completion.GetImageEdits(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString()
|
||||
}, new RoleDialogModel(AgentRole.User, text), imageStream, image.FileName ?? string.Empty, maskStream, mask.FileName ?? string.Empty);
|
||||
|
||||
imageStream.Close();
|
||||
maskStream.Close();
|
||||
return message;
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private async Task<byte[]> DownloadFile(BotSharpFile file)
|
||||
{
|
||||
var bytes = new byte[0];
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var http = _services.GetRequiredService<IHttpClientFactory>();
|
||||
using var client = http.CreateClient();
|
||||
bytes = await client.GetByteArrayAsync(file.FileUrl);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
(_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService
|
||||
{
|
||||
public async Task<string> ReadPdf(string? provider, string? model, string? modelId, string prompt, List<BotSharpFile> files)
|
||||
{
|
||||
var content = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(prompt) || files.IsNullOrEmpty())
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var sessionDir = GetSessionDirectory(guid);
|
||||
|
||||
try
|
||||
{
|
||||
var pdfFiles = await DownloadFiles(sessionDir, files);
|
||||
var images = await ConvertPdfToImages(pdfFiles);
|
||||
if (images.IsNullOrEmpty()) return content;
|
||||
|
||||
var completion = CompletionProvider.GetChatCompletion(_services, provider: provider ?? "openai",
|
||||
model: model, modelId: modelId ?? "gpt-4", multiModal: true);
|
||||
var message = await completion.GetChatCompletions(new Agent()
|
||||
{
|
||||
Id = Guid.Empty.ToString(),
|
||||
}, new List<RoleDialogModel>
|
||||
{
|
||||
new RoleDialogModel(AgentRole.User, prompt)
|
||||
{
|
||||
Files = images.Select(x => new BotSharpFile { FileStorageUrl = x }).ToList()
|
||||
}
|
||||
});
|
||||
|
||||
content = message.Content;
|
||||
return content;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Error when analyzing pdf in file service: {ex.Message}\r\n{ex.InnerException}");
|
||||
return content;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(sessionDir, true);
|
||||
}
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private string GetSessionDirectory(string id)
|
||||
{
|
||||
var dir = $"{SESSION_FOLDER}/{id}";
|
||||
return dir;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> DownloadFiles(string dir, List<BotSharpFile> files, string extension = "pdf")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dir) || files.IsNullOrEmpty())
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var locs = new List<string>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = new byte[0];
|
||||
if (!string.IsNullOrEmpty(file.FileUrl))
|
||||
{
|
||||
var http = _services.GetRequiredService<IHttpClientFactory>();
|
||||
using var client = http.CreateClient();
|
||||
bytes = await client.GetByteArrayAsync(file.FileUrl);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(file.FileData))
|
||||
{
|
||||
(_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
}
|
||||
|
||||
if (!bytes.IsNullOrEmpty())
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString();
|
||||
var fileDir = $"{dir}/{guid}";
|
||||
|
||||
var pdfDir = $"{fileDir}/{guid}.{extension}";
|
||||
|
||||
|
||||
_cosClient.BucketClient.UploadBytes(pdfDir, bytes);
|
||||
locs.Add(pdfDir);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when saving pdf file: {ex.Message}\r\n{ex.InnerException}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<string>> ConvertPdfToImages(IEnumerable<string> files)
|
||||
{
|
||||
var images = new List<string>();
|
||||
var converter = GetPdf2ImageConverter();
|
||||
if (converter == null || files.IsNullOrEmpty())
|
||||
{
|
||||
return images;
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var segs = file.Split(Path.DirectorySeparatorChar);
|
||||
var dir = string.Join(Path.DirectorySeparatorChar, segs.SkipLast(1));
|
||||
var folder = Path.Combine(dir, "screenshots");
|
||||
var urls = await converter.ConvertPdfToImages(file, folder);
|
||||
images.AddRange(urls);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when converting pdf file to images ({file}): {ex.Message}\r\n{ex.InnerException}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return images;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService
|
||||
{
|
||||
public string GetUserAvatar()
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var user = db.GetUserById(_user.Id);
|
||||
var dir = GetUserAvatarDir(user?.Id);
|
||||
|
||||
if (!ExistDirectory(dir)) return string.Empty;
|
||||
|
||||
var found = _cosClient.BucketClient.GetDirFiles(dir).FirstOrDefault() ?? string.Empty;
|
||||
return found;
|
||||
}
|
||||
|
||||
public bool SaveUserAvatar(BotSharpFile file)
|
||||
{
|
||||
if (file == null || string.IsNullOrEmpty(file.FileData)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var user = db.GetUserById(_user.Id);
|
||||
var dir = GetUserAvatarDir(user?.Id);
|
||||
|
||||
if (string.IsNullOrEmpty(dir)) return false;
|
||||
|
||||
var (_, bytes) = GetFileInfoFromData(file.FileData);
|
||||
|
||||
var extension = Path.GetExtension(file.FileName);
|
||||
|
||||
var fileName = user?.Id == null ? file.FileName : $"{user?.Id}{extension}";
|
||||
|
||||
return _cosClient.BucketClient.UploadBytes($"{dir}/{fileName}", bytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"Error when saving user avatar: {ex.Message}\r\n{ex.InnerException}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Private methods
|
||||
private string GetUserAvatarDir(string? userId, bool createNewDir = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var dir = $"{USERS_FOLDER}/{userId}/{USER_AVATAR_FOLDER}/";
|
||||
|
||||
return dir;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Users;
|
||||
using BotSharp.Plugin.TencentCos.Settings;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCos.Services;
|
||||
|
||||
public partial class TencentCosService : IBotSharpFileService
|
||||
{
|
||||
private readonly TencentCosSettings _settings;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly ILogger<TencentCosService> _logger;
|
||||
private readonly string _fullBuketName;
|
||||
private readonly IEnumerable<string> _imageTypes = new List<string>
|
||||
{
|
||||
MediaTypeNames.Image.Png,
|
||||
MediaTypeNames.Image.Jpeg
|
||||
};
|
||||
|
||||
private const string CONVERSATION_FOLDER = "conversations";
|
||||
private const string FILE_FOLDER = "files";
|
||||
private const string USER_FILE_FOLDER = "user";
|
||||
private const string SCREENSHOT_FILE_FOLDER = "screenshot";
|
||||
private const string BOT_FILE_FOLDER = "bot";
|
||||
private const string USERS_FOLDER = "users";
|
||||
private const string USER_AVATAR_FOLDER = "avatar";
|
||||
private const string SESSION_FOLDER = "sessions";
|
||||
|
||||
private const int MIN_OFFSET = 1;
|
||||
private const int MAX_OFFSET = 5;
|
||||
|
||||
private readonly TencentCosClient _cosClient;
|
||||
|
||||
public TencentCosService(
|
||||
TencentCosSettings settings,
|
||||
IUserIdentity user,
|
||||
ILogger<TencentCosService> logger,
|
||||
IServiceProvider services,
|
||||
TencentCosClient cosClient)
|
||||
{
|
||||
_settings = settings;
|
||||
_user = user;
|
||||
_logger = logger;
|
||||
_services = services;
|
||||
_fullBuketName = $"{_settings.BucketName}-{_settings.AppId}";
|
||||
_cosClient = cosClient;
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private bool ExistDirectory(string? dir)
|
||||
{
|
||||
return !string.IsNullOrEmpty(dir) && _cosClient.BucketClient.DirExists(dir);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace BotSharp.Plugin.TencentCos.Settings
|
||||
{
|
||||
public class TencentCosSettings
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
public string SecretId { get; set; }
|
||||
public string SecretKey { get; set; }
|
||||
public string Region { get; set; }
|
||||
public string BucketName { get; set; }
|
||||
public int KeyDurationSecond { get; set; } = 600;
|
||||
}
|
||||
}
|
||||
26
src/Plugins/BotSharp.Plugin.TencentCos/TencentCosClient.cs
Normal file
26
src/Plugins/BotSharp.Plugin.TencentCos/TencentCosClient.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using BotSharp.Plugin.TencentCos.Modules;
|
||||
using BotSharp.Plugin.TencentCos.Settings;
|
||||
using COSXML;
|
||||
using COSXML.Auth;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCos
|
||||
{
|
||||
public class TencentCosClient
|
||||
{
|
||||
public BucketClient BucketClient { get; private set; }
|
||||
public TencentCosClient(TencentCosSettings settings)
|
||||
{
|
||||
var cosXmlConfig = new CosXmlConfig.Builder()
|
||||
.IsHttps(true)
|
||||
.SetAppid(settings.AppId)
|
||||
.SetRegion(settings.Region)
|
||||
.Build();
|
||||
var cosCredentialProvider = new DefaultQCloudCredentialProvider(
|
||||
settings.SecretId, settings.SecretKey, settings.KeyDurationSecond);
|
||||
|
||||
var cosXml = new CosXmlServer(cosXmlConfig, cosCredentialProvider);
|
||||
|
||||
BucketClient = new BucketClient(cosXml, $"{settings.BucketName}-{settings.AppId}", settings.AppId, settings.Region);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Plugins/BotSharp.Plugin.TencentCos/TencentCosPlugin.cs
Normal file
37
src/Plugins/BotSharp.Plugin.TencentCos/TencentCosPlugin.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using BotSharp.Abstraction.Files;
|
||||
using BotSharp.Abstraction.Repositories.Enums;
|
||||
using BotSharp.Abstraction.Settings;
|
||||
using BotSharp.Plugin.TencentCos;
|
||||
using BotSharp.Plugin.TencentCos.Services;
|
||||
using BotSharp.Plugin.TencentCos.Settings;
|
||||
|
||||
namespace BotSharp.Plugin.TencentCosFile.Files;
|
||||
|
||||
public class TencentCosPlugin : IBotSharpPlugin
|
||||
{
|
||||
public string Id => "3f55b702-8a28-4f9a-907c-affc24f845f1";
|
||||
|
||||
public string Name => "TencentCos";
|
||||
|
||||
public string Description => "Provides connection to Tencent Cloud object storage service.";
|
||||
|
||||
|
||||
public void RegisterDI(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
var myFileStorageSettings = new FileStorageSettings();
|
||||
config.Bind("FileStorage", myFileStorageSettings);
|
||||
|
||||
if (myFileStorageSettings.Default == FileStorageEnum.TencentCosStorage)
|
||||
{
|
||||
services.AddScoped(provider =>
|
||||
{
|
||||
var settingService = provider.GetRequiredService<ISettingService>();
|
||||
return settingService.Bind<TencentCosSettings>("TencentCos");
|
||||
});
|
||||
|
||||
services.AddScoped<TencentCosClient>();
|
||||
|
||||
services.AddScoped<IBotSharpFileService, TencentCosService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Plugins/BotSharp.Plugin.TencentCos/Using.cs
Normal file
17
src/Plugins/BotSharp.Plugin.TencentCos/Using.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
global using BotSharp.Abstraction.Agents.Enums;
|
||||
global using BotSharp.Abstraction.Agents.Models;
|
||||
global using BotSharp.Abstraction.Conversations.Models;
|
||||
global using BotSharp.Abstraction.Files.Models;
|
||||
global using BotSharp.Abstraction.Plugins;
|
||||
global using BotSharp.Abstraction.Repositories;
|
||||
global using BotSharp.Abstraction.Utilities;
|
||||
global using BotSharp.Core.Infrastructures;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading.Tasks;
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.MetaGLM\BotSharp.Plugin.MetaGLM.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.Planner\BotSharp.Plugin.Planner.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.SparkDesk\BotSharp.Plugin.SparkDesk.csproj" />
|
||||
<ProjectReference Include="..\Plugins\BotSharp.Plugin.TencentCos\BotSharp.Plugin.TencentCos.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$(SolutionName)==BotSharp">
|
||||
|
|
|
|||
|
|
@ -230,7 +230,16 @@
|
|||
"FileRepository": "data",
|
||||
"Assemblies": [ "BotSharp.Core" ]
|
||||
},
|
||||
|
||||
"FileStorage": {
|
||||
"Default": "LocalFileStorage"
|
||||
},
|
||||
"TencentCos": {
|
||||
"AppId": "",
|
||||
"SecretId": "",
|
||||
"SecretKey": "",
|
||||
"BucketName": "",
|
||||
"Region": ""
|
||||
},
|
||||
"Qdrant": {
|
||||
"Url": "",
|
||||
"ApiKey": ""
|
||||
|
|
@ -304,7 +313,8 @@
|
|||
"BotSharp.Plugin.MetaGLM",
|
||||
"BotSharp.Plugin.HttpHandler",
|
||||
"BotSharp.Plugin.FileHandler",
|
||||
"BotSharp.Plugin.EmailHandler"
|
||||
"BotSharp.Plugin.EmailHandler",
|
||||
"BotSharp.Plugin.TencentCos"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue