From 2a301c498390211d41e753fe7414d489592384a2 Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Sun, 3 Aug 2025 23:23:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/OpenAi/Images/ImageCreateRequest.cs | 54 +++++++++ .../Dtos/OpenAi/Images/ImageCreateResponse.cs | 19 +++ .../OpenAi/Images/ImageEditCreateRequest.cs | 51 ++++++++ .../Images/ImageVariationCreateRequest.cs | 14 +++ .../Images/SharedImageRequestBaseModel.cs | 42 +++++++ .../Dtos/OpenAi}/ThorBaseResponse.cs | 3 +- .../Services/AiChatService.cs | 3 + .../Services/OpenApiService.cs | 24 +++- .../Enums/ModelTypeEnum.cs | 8 ++ .../AiGateWay/HttpClientExtensions.cs | 1 + .../AiGateWay/IImageService.cs | 39 ++++++ .../Images/AzureOpenAIServiceImageService.cs | 112 ++++++++++++++++++ .../Entities/Model/AiModelEntity.cs | 10 +- .../Managers/AiGateWayManager.cs | 63 +++++++++- 14 files changed, 436 insertions(+), 7 deletions(-) create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateRequest.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateResponse.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageEditCreateRequest.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageVariationCreateRequest.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/SharedImageRequestBaseModel.cs rename Yi.Abp.Net8/module/ai-hub/{Yi.Framework.AiHub.Domain/AiGateWay => Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi}/ThorBaseResponse.cs (80%) create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelTypeEnum.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IImageService.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureOpenAI/Images/AzureOpenAIServiceImageService.cs diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateRequest.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateRequest.cs new file mode 100644 index 00000000..c41b6c7b --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateRequest.cs @@ -0,0 +1,54 @@ +using System.Text.Json.Serialization; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; + +/// +/// Image Create Request Model +/// +public record ImageCreateRequest : SharedImageRequestBaseModel +{ + public ImageCreateRequest() + { + } + + public ImageCreateRequest(string prompt) + { + Prompt = prompt; + } + + /// + /// A text description of the desired image(s). The maximum length is 1000 characters for dall-e-2 and 4000 characters for dall-e-3 + /// + [JsonPropertyName("prompt")] + public string Prompt { get; set; } + + /// + /// The quality of the image that will be generated. Possible values are 'standard' or 'hd' (default is 'standard'). + /// Hd creates images with finer details and greater consistency across the image. + /// This param is only supported for dall-e-3 model. + ///

Check for possible values + ///
+ [JsonPropertyName("quality")] + public string? Quality { get; set; } + + /// + /// The style of the generated images. Must be one of vivid or natural. + /// Vivid causes the model to lean towards generating hyper-real and dramatic images. + /// Natural causes the model to produce more natural, less hyper-real looking images. This param is only supported for dall-e-3. + ///

Check for possible values + ///
+ [JsonPropertyName("style")] + public string? Style { get; set; } + + [JsonPropertyName("background")] + public string? Background { get; set; } + + [JsonPropertyName("moderation")] + public string? Moderation { get; set; } + + [JsonPropertyName("output_compression")] + public string? OutputCompression { get; set; } + + [JsonPropertyName("output_format")] + public string? OutputFormat { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateResponse.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateResponse.cs new file mode 100644 index 00000000..e5739815 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageCreateResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; + +public record ImageCreateResponse : ThorBaseResponse +{ + [JsonPropertyName("data")] public List Results { get; set; } + + [JsonPropertyName("usage")] public ThorUsageResponse? Usage { get; set; } = new(); + + + + public record ImageDataResult + { + [JsonPropertyName("url")] public string Url { get; set; } + [JsonPropertyName("b64_json")] public string B64 { get; set; } + [JsonPropertyName("revised_prompt")] public string RevisedPrompt { get; set; } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageEditCreateRequest.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageEditCreateRequest.cs new file mode 100644 index 00000000..cc3f3e51 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageEditCreateRequest.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; + +public record ImageEditCreateRequest : SharedImageRequestBaseModel +{ + /// + /// The image to edit. Must be a valid PNG file, less than 4MB, and square. + /// + public byte[]? Image { get; set; } + + /// + /// Image file name + /// + public string ImageName { get; set; } + + /// + /// An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where image should be edited. + /// Must be a valid PNG file, less than 4MB, and have the same dimensions as image. + /// + public byte[]? Mask { get; set; } + + /// + /// Mask file name + /// + public string? MaskName { get; set; } + + [JsonPropertyName("quality")] + public string Quality { get; set; } + + /// + /// A text description of the desired image(s). The maximum length is 1000 characters. + /// + [JsonPropertyName("prompt")] + public string Prompt { get; set; } + + [JsonPropertyName("background")] + public string? Background { get; set; } + + [JsonPropertyName("moderation")] + public string? Moderation { get; set; } + + [JsonPropertyName("output_compression")] + public string? OutputCompression { get; set; } + + [JsonPropertyName("output_format")] + public string? OutputFormat { get; set; } + + [JsonPropertyName("style")] + public string? Style { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageVariationCreateRequest.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageVariationCreateRequest.cs new file mode 100644 index 00000000..208c0aea --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/ImageVariationCreateRequest.cs @@ -0,0 +1,14 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; + +public record ImageVariationCreateRequest : SharedImageRequestBaseModel +{ + /// + /// The image to edit. Must be a valid PNG file, less than 4MB, and square. + /// + public byte[] Image { get; set; } + + /// + /// Image file name + /// + public string ImageName { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/SharedImageRequestBaseModel.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/SharedImageRequestBaseModel.cs new file mode 100644 index 00000000..987e2792 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/Images/SharedImageRequestBaseModel.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; + +public record SharedImageRequestBaseModel +{ + /// + /// The number of images to generate. Must be between 1 and 10. + /// For dall-e-3 model, only n=1 is supported. + /// + [JsonPropertyName("n")] + public int? N { get; set; } + + /// + /// The size of the generated images. + /// Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2. + /// Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models. + ///

Check for possible values + ///
+ [JsonPropertyName("size")] + public string? Size { get; set; } + + /// + /// The format in which the generated images are returned. Must be one of url or b64_json + /// + [JsonPropertyName("response_format")] + public string? ResponseFormat { get; set; } + + /// + /// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. + /// Learn more. + /// + [JsonPropertyName("user")] + public string? User { get; set; } + + /// + /// The model to use for image generation. Must be one of dall-e-2 or dall-e-3 + /// For ImageEditCreateRequest and for ImageVariationCreateRequest only dall-e-2 modell is supported at this time. + /// + [JsonPropertyName("model")] + public string? Model { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/ThorBaseResponse.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ThorBaseResponse.cs similarity index 80% rename from Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/ThorBaseResponse.cs rename to Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ThorBaseResponse.cs index bbbf4be4..9896f3d3 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/ThorBaseResponse.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/OpenAi/ThorBaseResponse.cs @@ -1,7 +1,6 @@ using System.Text.Json.Serialization; -using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi; -namespace Yi.Framework.AiHub.Domain.AiGateWay; +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi; public record ThorBaseResponse { diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs index 8f03ae7e..5d3954c1 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiChatService.cs @@ -18,6 +18,7 @@ using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Dtos; +using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.Rbac.Application.Contracts.IServices; using Yi.Framework.Rbac.Domain.Shared.Dtos; using Yi.Framework.SqlSugarCore.Abstractions; @@ -68,6 +69,7 @@ public class AiChatService : ApplicationService public async Task> GetModelAsync() { var output = await _aiModelRepository._DbQueryable + .Where(x => x.ModelType == ModelTypeEnum.Chat) .OrderByDescending(x => x.OrderNum) .Select(x => new ModelGetListOutput { @@ -115,6 +117,7 @@ public class AiChatService : ApplicationService throw new UserFriendlyException("未登录用户,只能使用未加速的DeepSeek-R1,请登录后重试"); } } + //ai网关代理httpcontext await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, CurrentUser.Id, sessionId, cancellationToken); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs index 4df08fe8..08c6a233 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/OpenApiService.cs @@ -3,9 +3,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Volo.Abp.Application.Services; using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi; +using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; +using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Application.Services; @@ -17,16 +19,17 @@ public class OpenApiService : ApplicationService private readonly TokenManager _tokenManager; private readonly AiGateWayManager _aiGateWayManager; private readonly ISqlSugarRepository _aiModelRepository; - + private readonly AiBlacklistManager _aiBlacklistManager; public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger logger, TokenManager tokenManager, AiGateWayManager aiGateWayManager, - ISqlSugarRepository aiModelRepository) + ISqlSugarRepository aiModelRepository, AiBlacklistManager aiBlacklistManager) { _httpContextAccessor = httpContextAccessor; _logger = logger; _tokenManager = tokenManager; _aiGateWayManager = aiGateWayManager; _aiModelRepository = aiModelRepository; + _aiBlacklistManager = aiBlacklistManager; } /// @@ -41,6 +44,7 @@ public class OpenApiService : ApplicationService //前面都是校验,后面才是真正的调用 var httpContext = this._httpContextAccessor.HttpContext; var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext)); + await _aiBlacklistManager.VerifiyAiBlacklist(userId); //ai网关代理httpcontext if (input.Stream == true) { @@ -55,6 +59,21 @@ public class OpenApiService : ApplicationService } } + /// + /// 图片生成 + /// + /// + /// + [HttpPost("openApi/v1/images/generations")] + public async Task ImagesGenerationsAsync([FromBody] ImageCreateRequest input, CancellationToken cancellationToken) + { + var httpContext = this._httpContextAccessor.HttpContext; + var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext)); + await _aiBlacklistManager.VerifiyAiBlacklist(userId); + await _aiGateWayManager.CreateImageForStatisticsAsync(httpContext, userId, null, input); + } + + /// /// 获取模型列表 /// @@ -63,6 +82,7 @@ public class OpenApiService : ApplicationService public async Task ModelsAsync() { var data = await _aiModelRepository._DbQueryable + .Where(x => x.ModelType == ModelTypeEnum.Chat) .OrderByDescending(x => x.OrderNum) .Select(x => new ModelsDataDto { diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelTypeEnum.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelTypeEnum.cs new file mode 100644 index 00000000..e1167ce2 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/ModelTypeEnum.cs @@ -0,0 +1,8 @@ +namespace Yi.Framework.AiHub.Domain.Shared.Enums; + + +public enum ModelTypeEnum +{ + Chat = 0, + Image = 1 +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/HttpClientExtensions.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/HttpClientExtensions.cs index dbfee79f..fb77d420 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/HttpClientExtensions.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/HttpClientExtensions.cs @@ -2,6 +2,7 @@ using System.Net.Http.Json; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi; namespace Yi.Framework.AiHub.Domain.AiGateWay; diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IImageService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IImageService.cs new file mode 100644 index 00000000..8f429686 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/IImageService.cs @@ -0,0 +1,39 @@ +using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; +using Yi.Framework.AiHub.Domain.Shared.Dtos; + +namespace Yi.Framework.AiHub.Domain.AiGateWay; + +public interface IImageService +{ + /// Creates an image given a prompt. + /// + /// + /// Propagates notification that operations should be canceled. + /// + Task CreateImage( + ImageCreateRequest imageCreate, + AiModelDescribe? aiModelDescribe = null, + CancellationToken cancellationToken = default); + + /// + /// Creates an edited or extended image given an original image and a prompt. + /// + /// + /// + /// Propagates notification that operations should be canceled. + /// + Task CreateImageEdit( + ImageEditCreateRequest imageEditCreateRequest, + AiModelDescribe? aiModelDescribe = null, + CancellationToken cancellationToken = default); + + /// Creates a variation of a given image. + /// + /// + /// Propagates notification that operations should be canceled. + /// + Task CreateImageVariation( + ImageVariationCreateRequest imageEditCreateRequest, + AiModelDescribe? aiModelDescribe = null, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureOpenAI/Images/AzureOpenAIServiceImageService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureOpenAI/Images/AzureOpenAIServiceImageService.cs new file mode 100644 index 00000000..1fb23295 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureOpenAI/Images/AzureOpenAIServiceImageService.cs @@ -0,0 +1,112 @@ +using OpenAI.Images; +using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; +using Yi.Framework.AiHub.Domain.AiGateWay; +using Yi.Framework.AiHub.Domain.Shared.Dtos; + +namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureOpenAI.Images; + +public class AzureOpenAIServiceImageService : IImageService +{ + public async Task CreateImage(ImageCreateRequest imageCreate, AiModelDescribe? options = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + var createClient = AzureOpenAIFactory.CreateClient(options); + + var client = createClient.GetImageClient(imageCreate.Model); + + // 将size字符串拆分为宽度和高度 + var size = imageCreate.Size.Split('x'); + if (size.Length != 2) + { + throw new ArgumentException("Size must be in the format of 'width x height'"); + } + + + var response = await client.GenerateImageAsync(imageCreate.Prompt, new ImageGenerationOptions() + { + Quality = imageCreate.Quality == "standard" ? GeneratedImageQuality.Standard : GeneratedImageQuality.High, + Size = new GeneratedImageSize(Convert.ToInt32(size[0]), Convert.ToInt32(size[1])), + Style = imageCreate.Style == "vivid" ? GeneratedImageStyle.Vivid : GeneratedImageStyle.Natural, + ResponseFormat = + imageCreate.ResponseFormat == "url" ? GeneratedImageFormat.Uri : GeneratedImageFormat.Bytes, + // User = imageCreate.User + EndUserId = imageCreate.User + }, cancellationToken); + + var ret = new ImageCreateResponse() + { + Results = new List() + }; + + if (response.Value.ImageUri != null) + { + ret.Results.Add(new ImageCreateResponse.ImageDataResult() + { + Url = response.Value.ImageUri.ToString() + }); + } + else + { + ret.Results.Add(new ImageCreateResponse.ImageDataResult() + { + B64 = Convert.ToBase64String(response.Value.ImageBytes.ToArray()) + }); + } + + return ret; + } + + public async Task CreateImageEdit(ImageEditCreateRequest imageEditCreateRequest, + AiModelDescribe? options = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + var url = AzureOpenAIFactory.GetEditImageAddress(options, imageEditCreateRequest.Model); + + var multipartContent = new MultipartFormDataContent(); + if (imageEditCreateRequest.User != null) + { + multipartContent.Add(new StringContent(imageEditCreateRequest.User), "user"); + } + + if (imageEditCreateRequest.ResponseFormat != null) + { + multipartContent.Add(new StringContent(imageEditCreateRequest.ResponseFormat), "response_format"); + } + + if (imageEditCreateRequest.Size != null) + { + multipartContent.Add(new StringContent(imageEditCreateRequest.Size), "size"); + } + + if (imageEditCreateRequest.N != null) + { + multipartContent.Add(new StringContent(imageEditCreateRequest.N.ToString()!), "n"); + } + + if (imageEditCreateRequest.Model != null) + { + multipartContent.Add(new StringContent(imageEditCreateRequest.Model!), "model"); + } + + if (imageEditCreateRequest.Mask != null) + { + multipartContent.Add(new ByteArrayContent(imageEditCreateRequest.Mask), "mask", + imageEditCreateRequest.MaskName); + } + + multipartContent.Add(new StringContent(imageEditCreateRequest.Prompt), "prompt"); + multipartContent.Add(new ByteArrayContent(imageEditCreateRequest.Image), "image", + imageEditCreateRequest.ImageName); + + return await HttpClientFactory.GetHttpClient(url).PostFileAndReadAsAsync( + url, + multipartContent, cancellationToken); + } + + public Task CreateImageVariation(ImageVariationCreateRequest imageEditCreateRequest, + AiModelDescribe? options = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs index ba363fda..b1149eb8 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs @@ -1,5 +1,6 @@ using SqlSugar; using Volo.Abp.Domain.Entities; +using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.Core.Data; namespace Yi.Framework.AiHub.Domain.Entities.Model; @@ -8,7 +9,7 @@ namespace Yi.Framework.AiHub.Domain.Entities.Model; /// ai模型定义 /// [SugarTable("Ai_Model")] -public class AiModelEntity : Entity, IOrderNum,ISoftDelete +public class AiModelEntity : Entity, IOrderNum, ISoftDelete { /// /// 处理名 @@ -44,9 +45,14 @@ public class AiModelEntity : Entity, IOrderNum,ISoftDelete /// ai应用id /// public Guid AiAppId { get; set; } - + /// /// 额外信息 /// public string? ExtraInfo { get; set; } + + /// + /// 模型类型 + /// + public ModelTypeEnum ModelType { get; set; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs index 70f47354..042693a5 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using Microsoft.AspNetCore.Http; @@ -9,9 +10,12 @@ using Newtonsoft.Json.Serialization; using Volo.Abp.Domain.Services; using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi; +using Yi.Framework.AiHub.Application.Contracts.Dtos.OpenAi.Images; using Yi.Framework.AiHub.Domain.AiGateWay; +using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions; using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Shared.Dtos; +using Yi.Framework.Core.Extensions; using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.AiHub.Domain.Managers; @@ -213,7 +217,7 @@ public class AiGateWayManager : DomainService catch (Exception e) { _logger.LogError(e, $"Ai对话异常"); - var errorContent = $"Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}"; + var errorContent = $"对话Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}"; var model = new ThorChatCompletionsResponse() { Choices = new List() @@ -261,4 +265,61 @@ public class AiGateWayManager : DomainService await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage); } + + + /// + /// 图片生成 + /// + /// + /// + /// + /// + /// + /// + public async Task CreateImageForStatisticsAsync(HttpContext context,Guid? userId,Guid? sessionId, ImageCreateRequest request) + { + try + { + var model = request.Model; + if (string.IsNullOrEmpty(model)) model = "dall-e-2"; + + var modelDescribe = await GetModelAsync(model); + + // 获取渠道指定的实现类型的服务 + var imageService = + LazyServiceProvider.GetRequiredKeyedService(modelDescribe.HandlerName); + + var response = await imageService.CreateImage(request, modelDescribe); + + if (response.Error != null || response.Results.Count == 0) + { + throw new BusinessException(response.Error?.Message ?? "图片生成失败", response.Error?.Code?.ToString()); + } + + await context.Response.WriteAsJsonAsync(response); + + await _aiMessageManager.CreateUserMessageAsync(userId, sessionId, + new MessageInputDto + { + Content = request.Prompt, + ModelId = model, + TokenUsage = response.Usage, + }); + + await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, + new MessageInputDto + { + Content = response.Results?.FirstOrDefault()?.Url, + ModelId = model, + TokenUsage = response.Usage + }); + + await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage); + } + catch (Exception e) + { + var errorContent = $"图片生成Ai异常,异常信息:\n当前Ai模型:{request.Model}\n异常信息:{e.Message}\n异常堆栈:{e}"; + throw new UserFriendlyException(errorContent); + } + } } \ No newline at end of file