feat: 准备构建图片生成

This commit is contained in:
ccnetcore
2025-12-25 23:25:54 +08:00
parent 46bc48d1c1
commit 599b6335d5
5 changed files with 220 additions and 52 deletions

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Users; using Volo.Abp.Users;
using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Managers;
@@ -30,11 +31,11 @@ public class OpenApiService : ApplicationService
private readonly AiBlacklistManager _aiBlacklistManager; private readonly AiBlacklistManager _aiBlacklistManager;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly PremiumPackageManager _premiumPackageManager; private readonly PremiumPackageManager _premiumPackageManager;
private readonly ISqlSugarRepository<ImageStoreTaskAggregateRoot> _imageStoreRepository;
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger, public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
TokenManager tokenManager, AiGateWayManager aiGateWayManager, TokenManager tokenManager, AiGateWayManager aiGateWayManager,
ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager, ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager,
IAccountService accountService, PremiumPackageManager premiumPackageManager) IAccountService accountService, PremiumPackageManager premiumPackageManager, ISqlSugarRepository<ImageStoreTaskAggregateRoot> imageStoreRepository)
{ {
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_logger = logger; _logger = logger;
@@ -44,6 +45,7 @@ public class OpenApiService : ApplicationService
_aiBlacklistManager = aiBlacklistManager; _aiBlacklistManager = aiBlacklistManager;
_accountService = accountService; _accountService = accountService;
_premiumPackageManager = premiumPackageManager; _premiumPackageManager = premiumPackageManager;
_imageStoreRepository = imageStoreRepository;
} }
/// <summary> /// <summary>
@@ -259,11 +261,13 @@ public class OpenApiService : ApplicationService
/// 生成-Gemini (尊享服务专用) /// 生成-Gemini (尊享服务专用)
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <param name="isAsync"></param>
/// <param name="modelId"></param> /// <param name="modelId"></param>
/// <param name="alt"></param> /// <param name="alt"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
[HttpPost("openApi/v1beta/models/{modelId}:{action:regex(^(generateContent|streamGenerateContent)$)}")] [HttpPost("openApi/v1beta/models/{modelId}:{action:regex(^(generateContent|streamGenerateContent)$)}")]
public async Task GenerateContentAsync([FromBody] JsonElement input, public async Task GenerateContentAsync([FromBody] JsonElement input,
[FromQuery] bool isAsync,
[FromRoute] string modelId, [FromRoute] string modelId,
[FromQuery] string? alt, CancellationToken cancellationToken) [FromQuery] string? alt, CancellationToken cancellationToken)
{ {
@@ -294,6 +298,18 @@ public class OpenApiService : ApplicationService
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包"); throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
} }
//如果异步直接走job处理进行存储
if (isAsync)
{
var task = new ImageStoreTaskAggregateRoot();
await _imageStoreRepository.InsertAsync(task);
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new
{
Id = task.Id
}, cancellationToken);
//todo 发送job,参数怎么办?需要先全存下来吗?全存下来,就要解析全部提示词 和 附件内容了
}
//ai网关代理httpcontext //ai网关代理httpcontext
if (alt == "sse") if (alt == "sse")
{ {

View File

@@ -30,4 +30,16 @@ public static class GeminiGenerateContentAcquirer
TotalTokens = inputTokens + outputTokens, TotalTokens = inputTokens + outputTokens,
}; };
} }
/// <summary>
/// 获取图片url不包含前缀
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
public static string GetImageBase64(JsonElement response)
{
//todo
//获取他的base64字符串
return string.Empty;
}
} }

View File

@@ -0,0 +1,8 @@
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
public enum TaskStatusEnum
{
Processing,
Success,
Fail
}

View File

@@ -0,0 +1,34 @@
using SqlSugar;
using Volo.Abp.Domain.Entities.Auditing;
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Domain.Entities.Chat;
[SugarTable("Ai_ImageStoreTask")]
public class ImageStoreTaskAggregateRoot : FullAuditedAggregateRoot<Guid>
{
/// <summary>
/// 图片绝对路径
/// </summary>
public string? StoreUrl { get; set; }
/// <summary>
/// 任务状态
/// </summary>
public TaskStatusEnum TaskStatus { get; set; } = TaskStatusEnum.Processing;
/// <summary>
/// 用户id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 设置成功
/// </summary>
/// <param name="storeUrl"></param>
public void SetSuccess(string storeUrl)
{
TaskStatus = TaskStatusEnum.Success;
StoreUrl = storeUrl;
}
}

View File

@@ -12,6 +12,7 @@ using Newtonsoft.Json.Serialization;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Domain.AiGateWay; using Yi.Framework.AiHub.Domain.AiGateWay;
using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions; using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.AiHub.Domain.Shared.Dtos; using Yi.Framework.AiHub.Domain.Shared.Dtos;
@@ -39,11 +40,12 @@ public class AiGateWayManager : DomainService
private readonly UsageStatisticsManager _usageStatisticsManager; private readonly UsageStatisticsManager _usageStatisticsManager;
private readonly ISpecialCompatible _specialCompatible; private readonly ISpecialCompatible _specialCompatible;
private PremiumPackageManager? _premiumPackageManager; private PremiumPackageManager? _premiumPackageManager;
private readonly ISqlSugarRepository<ImageStoreTaskAggregateRoot> _imageStoreTaskRepository;
public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository, ILogger<AiGateWayManager> logger, public AiGateWayManager(ISqlSugarRepository<AiAppAggregateRoot> aiAppRepository, ILogger<AiGateWayManager> logger,
AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager,
ISpecialCompatible specialCompatible, ISqlSugarRepository<AiModelEntity> aiModelRepository) ISpecialCompatible specialCompatible, ISqlSugarRepository<AiModelEntity> aiModelRepository,
ISqlSugarRepository<ImageStoreTaskAggregateRoot> imageStoreTaskRepository)
{ {
_aiAppRepository = aiAppRepository; _aiAppRepository = aiAppRepository;
_logger = logger; _logger = logger;
@@ -51,6 +53,7 @@ public class AiGateWayManager : DomainService
_usageStatisticsManager = usageStatisticsManager; _usageStatisticsManager = usageStatisticsManager;
_specialCompatible = specialCompatible; _specialCompatible = specialCompatible;
_aiModelRepository = aiModelRepository; _aiModelRepository = aiModelRepository;
_imageStoreTaskRepository = imageStoreTaskRepository;
} }
private PremiumPackageManager PremiumPackageManager => private PremiumPackageManager PremiumPackageManager =>
@@ -890,7 +893,9 @@ public class AiGateWayManager : DomainService
tokenUsage = GeminiGenerateContentAcquirer.GetUsage(responseResult!.Value); tokenUsage = GeminiGenerateContentAcquirer.GetUsage(responseResult!.Value);
tokenUsage.SetSupplementalMultiplier(modelDescribe.Multiplier); tokenUsage.SetSupplementalMultiplier(modelDescribe.Multiplier);
} }
await response.WriteAsync($"data: {JsonSerializer.Serialize(responseResult)}\n\n", Encoding.UTF8, cancellationToken).ConfigureAwait(false);
await response.WriteAsync($"data: {JsonSerializer.Serialize(responseResult)}\n\n", Encoding.UTF8,
cancellationToken).ConfigureAwait(false);
await response.Body.FlushAsync(cancellationToken).ConfigureAwait(false); await response.Body.FlushAsync(cancellationToken).ConfigureAwait(false);
} }
} }
@@ -931,6 +936,99 @@ public class AiGateWayManager : DomainService
} }
/// <summary>
/// Gemini 生成(Image)-非流式-缓存处理
/// 返回图片绝对路径
/// </summary>
/// <param name="taskId"></param>
/// <param name="modelId"></param>
/// <param name="request"></param>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <param name="tokenId"></param>
/// <param name="cancellationToken"></param>
public async Task GeminiGenerateContentImageForStatisticsAsync(
Guid taskId,
string modelId,
JsonElement request,
Guid userId,
Guid? sessionId = null,
Guid? tokenId = null,
CancellationToken cancellationToken = default)
{
var imageStoreTask = await _imageStoreTaskRepository.GetFirstAsync(x => x.Id == taskId);
var modelDescribe = await GetModelAsync(ModelApiTypeEnum.GenerateContent, modelId);
var chatService =
LazyServiceProvider.GetRequiredKeyedService<IGeminiGenerateContentService>(modelDescribe.HandlerName);
var data = await chatService.GenerateContentAsync(modelDescribe, request, cancellationToken);
//解析json获取base64字符串
var imageBase64 = GeminiGenerateContentAcquirer.GetImageBase64(data);
//base64字符串存储返回绝对路径用于最后存储
var storeUrl = Base64ToPng(imageBase64, "存储的路径?这个放什么");
var tokenUsage = new ThorUsageResponse
{
InputTokens = (int)modelDescribe.Multiplier,
OutputTokens = (int)modelDescribe.Multiplier,
TotalTokens = (int)modelDescribe.Multiplier,
};
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId,
new MessageInputDto
{
Content = "不予存储",
ModelId = modelId,
TokenUsage = tokenUsage
}, tokenId);
await _usageStatisticsManager.SetUsageAsync(userId, modelId, tokenUsage, tokenId);
// 扣减尊享token包用量
var totalTokens = tokenUsage.TotalTokens ?? 0;
if (totalTokens > 0)
{
await PremiumPackageManager.TryConsumeTokensAsync(userId, totalTokens);
}
//设置存储url
imageStoreTask.SetSuccess(storeUrl);
await _imageStoreTaskRepository.UpdateAsync(imageStoreTask);
}
/// <summary>
/// 将 Base64 字符串转换为 PNG 图片并保存
/// </summary>
/// <param name="base64String">Base64 字符串(可以包含或不包含 data:image/png;base64, 前缀)</param>
/// <param name="outputPath">输出文件路径</param>
private string Base64ToPng(string base64String, string outputPath)
{
try
{
// 移除可能存在的 data URI scheme 前缀
if (base64String.Contains(","))
{
base64String = base64String.Substring(base64String.IndexOf(",") + 1);
}
// 将 base64 字符串转换为字节数组
byte[] imageBytes = Convert.FromBase64String(base64String);
// 将字节数组写入文件
File.WriteAllBytes(outputPath, imageBytes);
}
catch (Exception ex)
{
throw new UserFriendlyException("gemini Base64转图像失败", innerException: ex);
}
//todo
//路径拼接一下?
return outputPath;
}
#region Http响应 #region Http响应