feat: 支持尊享包渠道
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI应用输入
|
||||
/// </summary>
|
||||
public class AiAppCreateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用名称不能为空")]
|
||||
[StringLength(100, ErrorMessage = "应用名称不能超过100个字符")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用终结点
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用终结点不能为空")]
|
||||
[StringLength(500, ErrorMessage = "应用终结点不能超过500个字符")]
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外URL
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "额外URL不能超过500个字符")]
|
||||
public string? ExtraUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用Key
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用Key不能为空")]
|
||||
[StringLength(500, ErrorMessage = "应用Key不能超过500个字符")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")]
|
||||
public int OrderNum { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// AI应用DTO
|
||||
/// </summary>
|
||||
public class AiAppDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用ID
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用终结点
|
||||
/// </summary>
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外URL
|
||||
/// </summary>
|
||||
public string? ExtraUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用Key
|
||||
/// </summary>
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreationTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Yi.Framework.Ddd.Application.Contracts;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI应用列表输入
|
||||
/// </summary>
|
||||
public class AiAppGetListInput : PagedAllResultRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索关键词(搜索应用名称)
|
||||
/// </summary>
|
||||
public string? SearchKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI应用输入
|
||||
/// </summary>
|
||||
public class AiAppUpdateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用ID不能为空")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用名称不能为空")]
|
||||
[StringLength(100, ErrorMessage = "应用名称不能超过100个字符")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用终结点
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用终结点不能为空")]
|
||||
[StringLength(500, ErrorMessage = "应用终结点不能超过500个字符")]
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外URL
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "额外URL不能超过500个字符")]
|
||||
public string? ExtraUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用Key
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "应用Key不能为空")]
|
||||
[StringLength(500, ErrorMessage = "应用Key不能超过500个字符")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")]
|
||||
public int OrderNum { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI模型输入
|
||||
/// </summary>
|
||||
public class AiModelCreateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "处理名不能为空")]
|
||||
[StringLength(100, ErrorMessage = "处理名不能超过100个字符")]
|
||||
public string HandlerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型ID不能为空")]
|
||||
[StringLength(200, ErrorMessage = "模型ID不能超过200个字符")]
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型名称不能为空")]
|
||||
[StringLength(200, ErrorMessage = "模型名称不能超过200个字符")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型描述
|
||||
/// </summary>
|
||||
[StringLength(1000, ErrorMessage = "模型描述不能超过1000个字符")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")]
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AI应用ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "AI应用ID不能为空")]
|
||||
public Guid AiAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外信息
|
||||
/// </summary>
|
||||
[StringLength(2000, ErrorMessage = "额外信息不能超过2000个字符")]
|
||||
public string? ExtraInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型类型不能为空")]
|
||||
public ModelTypeEnum ModelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型API类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型API类型不能为空")]
|
||||
public ModelApiTypeEnum ModelApiType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型倍率
|
||||
/// </summary>
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "模型倍率必须大于0")]
|
||||
public decimal Multiplier { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 模型显示倍率
|
||||
/// </summary>
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "模型显示倍率必须大于0")]
|
||||
public decimal MultiplierShow { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 供应商分组名称
|
||||
/// </summary>
|
||||
[StringLength(100, ErrorMessage = "供应商分组名称不能超过100个字符")]
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型图标URL
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "模型图标URL不能超过500个字符")]
|
||||
public string? IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为尊享模型
|
||||
/// </summary>
|
||||
public bool IsPremium { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// AI模型DTO
|
||||
/// </summary>
|
||||
public class AiModelDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理名
|
||||
/// </summary>
|
||||
public string HandlerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型描述
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AI应用ID
|
||||
/// </summary>
|
||||
public Guid AiAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外信息
|
||||
/// </summary>
|
||||
public string? ExtraInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型类型
|
||||
/// </summary>
|
||||
public ModelTypeEnum ModelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型API类型
|
||||
/// </summary>
|
||||
public ModelApiTypeEnum ModelApiType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型倍率
|
||||
/// </summary>
|
||||
public decimal Multiplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型显示倍率
|
||||
/// </summary>
|
||||
public decimal MultiplierShow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 供应商分组名称
|
||||
/// </summary>
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型图标URL
|
||||
/// </summary>
|
||||
public string? IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为尊享模型
|
||||
/// </summary>
|
||||
public bool IsPremium { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Yi.Framework.Ddd.Application.Contracts;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI模型列表输入
|
||||
/// </summary>
|
||||
public class AiModelGetListInput : PagedAllResultRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索关键词(搜索模型名称、模型ID)
|
||||
/// </summary>
|
||||
public string? SearchKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AI应用ID筛选
|
||||
/// </summary>
|
||||
public Guid? AiAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否只显示尊享模型
|
||||
/// </summary>
|
||||
public bool? IsPremiumOnly { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI模型输入
|
||||
/// </summary>
|
||||
public class AiModelUpdateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型ID不能为空")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "处理名不能为空")]
|
||||
[StringLength(100, ErrorMessage = "处理名不能超过100个字符")]
|
||||
public string HandlerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型ID不能为空")]
|
||||
[StringLength(200, ErrorMessage = "模型ID不能超过200个字符")]
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型名称不能为空")]
|
||||
[StringLength(200, ErrorMessage = "模型名称不能超过200个字符")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型描述
|
||||
/// </summary>
|
||||
[StringLength(1000, ErrorMessage = "模型描述不能超过1000个字符")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")]
|
||||
public int OrderNum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// AI应用ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "AI应用ID不能为空")]
|
||||
public Guid AiAppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外信息
|
||||
/// </summary>
|
||||
[StringLength(2000, ErrorMessage = "额外信息不能超过2000个字符")]
|
||||
public string? ExtraInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型类型不能为空")]
|
||||
public ModelTypeEnum ModelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型API类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模型API类型不能为空")]
|
||||
public ModelApiTypeEnum ModelApiType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型倍率
|
||||
/// </summary>
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "模型倍率必须大于0")]
|
||||
public decimal Multiplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型显示倍率
|
||||
/// </summary>
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "模型显示倍率必须大于0")]
|
||||
public decimal MultiplierShow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 供应商分组名称
|
||||
/// </summary>
|
||||
[StringLength(100, ErrorMessage = "供应商分组名称不能超过100个字符")]
|
||||
public string? ProviderName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型图标URL
|
||||
/// </summary>
|
||||
[StringLength(500, ErrorMessage = "模型图标URL不能超过500个字符")]
|
||||
public string? IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为尊享模型
|
||||
/// </summary>
|
||||
public bool IsPremium { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
|
||||
/// <summary>
|
||||
/// 渠道商管理服务接口
|
||||
/// </summary>
|
||||
public interface IChannelService
|
||||
{
|
||||
#region AI应用管理
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI应用列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页应用列表</returns>
|
||||
Task<PagedResultDto<AiAppDto>> GetAppListAsync(AiAppGetListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取AI应用
|
||||
/// </summary>
|
||||
/// <param name="id">应用ID</param>
|
||||
/// <returns>应用详情</returns>
|
||||
Task<AiAppDto> GetAppByIdAsync(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI应用
|
||||
/// </summary>
|
||||
/// <param name="input">创建输入</param>
|
||||
/// <returns>创建的应用</returns>
|
||||
Task<AiAppDto> CreateAppAsync(AiAppCreateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI应用
|
||||
/// </summary>
|
||||
/// <param name="input">更新输入</param>
|
||||
/// <returns>更新后的应用</returns>
|
||||
Task<AiAppDto> UpdateAppAsync(AiAppUpdateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除AI应用
|
||||
/// </summary>
|
||||
/// <param name="id">应用ID</param>
|
||||
Task DeleteAppAsync(Guid id);
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI模型管理
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI模型列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页模型列表</returns>
|
||||
Task<PagedResultDto<AiModelDto>> GetModelListAsync(AiModelGetListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取AI模型
|
||||
/// </summary>
|
||||
/// <param name="id">模型ID</param>
|
||||
/// <returns>模型详情</returns>
|
||||
Task<AiModelDto> GetModelByIdAsync(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI模型
|
||||
/// </summary>
|
||||
/// <param name="input">创建输入</param>
|
||||
/// <returns>创建的模型</returns>
|
||||
Task<AiModelDto> CreateModelAsync(AiModelCreateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI模型
|
||||
/// </summary>
|
||||
/// <param name="input">更新输入</param>
|
||||
/// <returns>更新后的模型</returns>
|
||||
Task<AiModelDto> UpdateModelAsync(AiModelUpdateInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除AI模型(软删除)
|
||||
/// </summary>
|
||||
/// <param name="id">模型ID</param>
|
||||
Task DeleteModelAsync(Guid id);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -179,10 +179,16 @@ public class DailyTaskService : ApplicationService
|
||||
var tomorrow = today.AddDays(1);
|
||||
|
||||
// 查询今日所有使用尊享包模型的消息(role=system 表示消耗)
|
||||
// 先获取所有尊享模型的ModelId列表
|
||||
var premiumModelIds = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.IsPremium)
|
||||
.Select(x => x.ModelId)
|
||||
.ToListAsync();
|
||||
|
||||
var totalTokens = await _messageRepository._DbQueryable
|
||||
.Where(x => x.UserId == userId)
|
||||
.Where(x => x.Role == "system") // system角色表示实际消耗
|
||||
.Where(x => PremiumPackageConst.ModeIds.Contains(x.ModelId)) // 尊享包模型
|
||||
.Where(x => premiumModelIds.Contains(x.ModelId)) // 尊享包模型
|
||||
.Where(x => x.CreationTime >= today && x.CreationTime < tomorrow)
|
||||
.SumAsync(x => x.TokenUsage.TotalTokenCount);
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||
using Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 渠道商管理服务实现
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class ChannelService : ApplicationService, IChannelService
|
||||
{
|
||||
private readonly ISqlSugarRepository<AiAppAggregateRoot, Guid> _appRepository;
|
||||
private readonly ISqlSugarRepository<AiModelEntity, Guid> _modelRepository;
|
||||
|
||||
public ChannelService(
|
||||
ISqlSugarRepository<AiAppAggregateRoot, Guid> appRepository,
|
||||
ISqlSugarRepository<AiModelEntity, Guid> modelRepository)
|
||||
{
|
||||
_appRepository = appRepository;
|
||||
_modelRepository = modelRepository;
|
||||
}
|
||||
|
||||
#region AI应用管理
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI应用列表
|
||||
/// </summary>
|
||||
public async Task<PagedResultDto<AiAppDto>> GetAppListAsync(AiAppGetListInput input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var entities = await _appRepository._DbQueryable
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), x => x.Name.Contains(input.SearchKey))
|
||||
.OrderBy(x => x.OrderNum)
|
||||
.OrderByDescending(x => x.CreationTime)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
|
||||
var output = entities.Adapt<List<AiAppDto>>();
|
||||
return new PagedResultDto<AiAppDto>(total, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取AI应用
|
||||
/// </summary>
|
||||
public async Task<AiAppDto> GetAppByIdAsync(Guid id)
|
||||
{
|
||||
var entity = await _appRepository.GetByIdAsync(id);
|
||||
return entity.Adapt<AiAppDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI应用
|
||||
/// </summary>
|
||||
public async Task<AiAppDto> CreateAppAsync(AiAppCreateInput input)
|
||||
{
|
||||
var entity = new AiAppAggregateRoot
|
||||
{
|
||||
Name = input.Name,
|
||||
Endpoint = input.Endpoint,
|
||||
ExtraUrl = input.ExtraUrl,
|
||||
ApiKey = input.ApiKey,
|
||||
OrderNum = input.OrderNum
|
||||
};
|
||||
|
||||
await _appRepository.InsertAsync(entity);
|
||||
return entity.Adapt<AiAppDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI应用
|
||||
/// </summary>
|
||||
public async Task<AiAppDto> UpdateAppAsync(AiAppUpdateInput input)
|
||||
{
|
||||
var entity = await _appRepository.GetByIdAsync(input.Id);
|
||||
|
||||
entity.Name = input.Name;
|
||||
entity.Endpoint = input.Endpoint;
|
||||
entity.ExtraUrl = input.ExtraUrl;
|
||||
entity.ApiKey = input.ApiKey;
|
||||
entity.OrderNum = input.OrderNum;
|
||||
|
||||
await _appRepository.UpdateAsync(entity);
|
||||
return entity.Adapt<AiAppDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除AI应用
|
||||
/// </summary>
|
||||
public async Task DeleteAppAsync(Guid id)
|
||||
{
|
||||
// 检查是否有关联的模型
|
||||
var hasModels = await _modelRepository._DbQueryable
|
||||
.Where(x => x.AiAppId == id && !x.IsDeleted)
|
||||
.AnyAsync();
|
||||
|
||||
if (hasModels)
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("该应用下存在模型,无法删除");
|
||||
}
|
||||
|
||||
await _appRepository.DeleteAsync(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AI模型管理
|
||||
|
||||
/// <summary>
|
||||
/// 获取AI模型列表
|
||||
/// </summary>
|
||||
public async Task<PagedResultDto<AiModelDto>> GetModelListAsync(AiModelGetListInput input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
|
||||
var query = _modelRepository._DbQueryable
|
||||
.Where(x => !x.IsDeleted)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), x =>
|
||||
x.Name.Contains(input.SearchKey) || x.ModelId.Contains(input.SearchKey))
|
||||
.WhereIF(input.AiAppId.HasValue, x => x.AiAppId == input.AiAppId.Value)
|
||||
.WhereIF(input.IsPremiumOnly == true, x => x.IsPremium);
|
||||
|
||||
var entities = await query
|
||||
.OrderBy(x => x.OrderNum)
|
||||
.OrderByDescending(x => x.Id)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||
|
||||
var output = entities.Adapt<List<AiModelDto>>();
|
||||
return new PagedResultDto<AiModelDto>(total, output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取AI模型
|
||||
/// </summary>
|
||||
public async Task<AiModelDto> GetModelByIdAsync(Guid id)
|
||||
{
|
||||
var entity = await _modelRepository.GetByIdAsync(id);
|
||||
return entity.Adapt<AiModelDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建AI模型
|
||||
/// </summary>
|
||||
public async Task<AiModelDto> CreateModelAsync(AiModelCreateInput input)
|
||||
{
|
||||
// 验证应用是否存在
|
||||
var appExists = await _appRepository._DbQueryable
|
||||
.Where(x => x.Id == input.AiAppId)
|
||||
.AnyAsync();
|
||||
|
||||
if (!appExists)
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("指定的AI应用不存在");
|
||||
}
|
||||
|
||||
var entity = new AiModelEntity
|
||||
{
|
||||
HandlerName = input.HandlerName,
|
||||
ModelId = input.ModelId,
|
||||
Name = input.Name,
|
||||
Description = input.Description,
|
||||
OrderNum = input.OrderNum,
|
||||
AiAppId = input.AiAppId,
|
||||
ExtraInfo = input.ExtraInfo,
|
||||
ModelType = input.ModelType,
|
||||
ModelApiType = input.ModelApiType,
|
||||
Multiplier = input.Multiplier,
|
||||
MultiplierShow = input.MultiplierShow,
|
||||
ProviderName = input.ProviderName,
|
||||
IconUrl = input.IconUrl,
|
||||
IsPremium = input.IsPremium,
|
||||
IsDeleted = false
|
||||
};
|
||||
|
||||
await _modelRepository.InsertAsync(entity);
|
||||
return entity.Adapt<AiModelDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新AI模型
|
||||
/// </summary>
|
||||
public async Task<AiModelDto> UpdateModelAsync(AiModelUpdateInput input)
|
||||
{
|
||||
var entity = await _modelRepository.GetByIdAsync(input.Id);
|
||||
|
||||
// 验证应用是否存在
|
||||
if (entity.AiAppId != input.AiAppId)
|
||||
{
|
||||
var appExists = await _appRepository._DbQueryable
|
||||
.Where(x => x.Id == input.AiAppId)
|
||||
.AnyAsync();
|
||||
|
||||
if (!appExists)
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("指定的AI应用不存在");
|
||||
}
|
||||
}
|
||||
|
||||
entity.HandlerName = input.HandlerName;
|
||||
entity.ModelId = input.ModelId;
|
||||
entity.Name = input.Name;
|
||||
entity.Description = input.Description;
|
||||
entity.OrderNum = input.OrderNum;
|
||||
entity.AiAppId = input.AiAppId;
|
||||
entity.ExtraInfo = input.ExtraInfo;
|
||||
entity.ModelType = input.ModelType;
|
||||
entity.ModelApiType = input.ModelApiType;
|
||||
entity.Multiplier = input.Multiplier;
|
||||
entity.MultiplierShow = input.MultiplierShow;
|
||||
entity.ProviderName = input.ProviderName;
|
||||
entity.IconUrl = input.IconUrl;
|
||||
entity.IsPremium = input.IsPremium;
|
||||
|
||||
await _modelRepository.UpdateAsync(entity);
|
||||
return entity.Adapt<AiModelDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除AI模型(软删除)
|
||||
/// </summary>
|
||||
public async Task DeleteModelAsync(Guid id)
|
||||
{
|
||||
await _modelRepository.DeleteByIdAsync(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class AiChatService : ApplicationService
|
||||
ApiHost = null,
|
||||
ApiKey = null,
|
||||
Remark = x.Description,
|
||||
IsPremiumPackage = PremiumPackageConst.ModeIds.Contains(x.ModelId)
|
||||
IsPremiumPackage = x.IsPremium
|
||||
}).ToListAsync();
|
||||
return output;
|
||||
}
|
||||
@@ -144,13 +144,21 @@ public class AiChatService : ApplicationService
|
||||
}
|
||||
|
||||
//如果是尊享包服务,需要校验是是否尊享包足够
|
||||
if (CurrentUser.IsAuthenticated && PremiumPackageConst.ModeIds.Contains(input.Model))
|
||||
if (CurrentUser.IsAuthenticated)
|
||||
{
|
||||
// 检查尊享token包用量
|
||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
|
||||
if (availableTokens <= 0)
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == input.Model)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
|
||||
// 检查尊享token包用量
|
||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
|
||||
if (availableTokens <= 0)
|
||||
{
|
||||
throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +227,12 @@ public class AiChatService : ApplicationService
|
||||
}
|
||||
|
||||
//如果是尊享包服务,需要校验是是否尊享包足够
|
||||
if (PremiumPackageConst.ModeIds.Contains(input.ModelId))
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == input.ModelId)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
// 检查尊享token包用量
|
||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId);
|
||||
|
||||
@@ -69,6 +69,9 @@ public class AiImageService : ApplicationService
|
||||
}
|
||||
|
||||
// 尊享包校验
|
||||
// 注意: AiImageService目前没有注入AiModelEntity仓储
|
||||
// 由于图片生成功能使用频率较低,且当前所有图片模型都不是尊享模型
|
||||
// 暂时保留原逻辑,等待后续重构时再注入仓储
|
||||
if (PremiumPackageConst.ModeIds.Contains(input.ModelId))
|
||||
{
|
||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
|
||||
|
||||
@@ -41,8 +41,7 @@ public class ModelService : ApplicationService, IModelService
|
||||
input.ModelTypes.Contains(x.ModelType))
|
||||
.WhereIF(input.ModelApiTypes is not null, x =>
|
||||
input.ModelApiTypes.Contains(x.ModelApiType))
|
||||
.WhereIF(input.IsPremiumOnly == true, x =>
|
||||
PremiumPackageConst.ModeIds.Contains(x.ModelId))
|
||||
.WhereIF(input.IsPremiumOnly == true, x => x.IsPremium)
|
||||
.GroupBy(x => x.ModelId)
|
||||
.Select(x => x.ModelId)
|
||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total));
|
||||
@@ -61,7 +60,7 @@ public class ModelService : ApplicationService, IModelService
|
||||
MultiplierShow = x.First().MultiplierShow,
|
||||
ProviderName = x.First().ProviderName,
|
||||
IconUrl = x.First().IconUrl,
|
||||
IsPremium = PremiumPackageConst.ModeIds.Contains(x.First().ModelId),
|
||||
IsPremium = x.First().IsPremium,
|
||||
OrderNum = x.First().OrderNum
|
||||
}).ToList();
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ public class TokenService : ApplicationService
|
||||
}
|
||||
|
||||
// 获取尊享包模型ID列表
|
||||
var premiumModelIds = PremiumPackageConst.ModeIds;
|
||||
var premiumModelIds = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.IsPremium)
|
||||
.Select(x => x.ModelId)
|
||||
.ToListAsync();
|
||||
|
||||
// 批量查询所有Token的尊享包已使用额度
|
||||
var tokenIds = tokens.Select(t => t.Id).ToList();
|
||||
|
||||
@@ -65,7 +65,12 @@ public class OpenApiService : ApplicationService
|
||||
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
|
||||
|
||||
//如果是尊享包服务,需要校验是是否尊享包足够
|
||||
if (PremiumPackageConst.ModeIds.Contains(input.Model))
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == input.Model)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
// 检查尊享token包用量
|
||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
|
||||
|
||||
@@ -181,7 +181,12 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
public async Task<List<TokenPremiumUsageDto>> GetPremiumTokenUsageByTokenAsync()
|
||||
{
|
||||
var userId = CurrentUser.GetId();
|
||||
var premiumModelIds = PremiumPackageConst.ModeIds;
|
||||
|
||||
// 先获取所有尊享模型的ModelId列表
|
||||
var premiumModelIds = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.IsPremium)
|
||||
.Select(x => x.ModelId)
|
||||
.ToListAsync();
|
||||
|
||||
// 从UsageStatistics表获取尊享模型的token消耗统计(按TokenId聚合)
|
||||
var tokenUsages = await _usageStatisticsRepository._DbQueryable
|
||||
|
||||
@@ -80,4 +80,9 @@ public class AiModelEntity : Entity<Guid>, IOrderNum, ISoftDelete
|
||||
/// 模型图标URL
|
||||
/// </summary>
|
||||
public string? IconUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为尊享模型
|
||||
/// </summary>
|
||||
public bool IsPremium { get; set; }
|
||||
}
|
||||
@@ -150,7 +150,12 @@ public class AiGateWayManager : DomainService
|
||||
await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.Usage, tokenId);
|
||||
|
||||
// 扣减尊享token包用量
|
||||
if (PremiumPackageConst.ModeIds.Contains(request.Model))
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == request.Model)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
var totalTokens = data.Usage?.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
@@ -300,12 +305,20 @@ public class AiGateWayManager : DomainService
|
||||
await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage, tokenId);
|
||||
|
||||
// 扣减尊享token包用量
|
||||
if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model))
|
||||
if (userId is not null)
|
||||
{
|
||||
var totalTokens = tokenUsage.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == request.Model)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
|
||||
var totalTokens = tokenUsage.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
{
|
||||
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,12 +376,20 @@ public class AiGateWayManager : DomainService
|
||||
await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage, tokenId);
|
||||
|
||||
// 扣减尊享token包用量
|
||||
if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model))
|
||||
if (userId is not null)
|
||||
{
|
||||
var totalTokens = response.Usage.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == request.Model)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
|
||||
var totalTokens = response.Usage.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
{
|
||||
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Volo.Abp.Domain.Services;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
@@ -34,12 +35,13 @@ public class ChatManager : DomainService
|
||||
private readonly UsageStatisticsManager _usageStatisticsManager;
|
||||
private readonly PremiumPackageManager _premiumPackageManager;
|
||||
private readonly AiGateWayManager _aiGateWayManager;
|
||||
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
|
||||
|
||||
public ChatManager(ILoggerFactory loggerFactory,
|
||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager,
|
||||
UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager,
|
||||
AiGateWayManager aiGateWayManager)
|
||||
AiGateWayManager aiGateWayManager, ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_messageRepository = messageRepository;
|
||||
@@ -48,6 +50,7 @@ public class ChatManager : DomainService
|
||||
_usageStatisticsManager = usageStatisticsManager;
|
||||
_premiumPackageManager = premiumPackageManager;
|
||||
_aiGateWayManager = aiGateWayManager;
|
||||
_aiModelRepository = aiModelRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,7 +210,12 @@ public class ChatManager : DomainService
|
||||
await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId);
|
||||
|
||||
//扣减尊享token包用量
|
||||
if (PremiumPackageConst.ModeIds.Contains(modelId))
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == modelId)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
var totalTokens = usage?.TotalTokens ?? 0;
|
||||
if (totalTokens > 0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
@@ -27,13 +28,16 @@ public class TokenManager : DomainService
|
||||
{
|
||||
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
||||
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
|
||||
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
|
||||
|
||||
public TokenManager(
|
||||
ISqlSugarRepository<TokenAggregateRoot> tokenRepository,
|
||||
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository)
|
||||
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
|
||||
ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository)
|
||||
{
|
||||
_tokenRepository = tokenRepository;
|
||||
_usageStatisticsRepository = usageStatisticsRepository;
|
||||
_aiModelRepository = aiModelRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,14 +80,20 @@ public class TokenManager : DomainService
|
||||
}
|
||||
|
||||
// 如果是尊享模型且Token设置了额度限制,检查是否超限
|
||||
if (!string.IsNullOrEmpty(modelId) &&
|
||||
PremiumPackageConst.ModeIds.Contains(modelId) &&
|
||||
entity.PremiumQuotaLimit.HasValue)
|
||||
if (!string.IsNullOrEmpty(modelId) && entity.PremiumQuotaLimit.HasValue)
|
||||
{
|
||||
var usedQuota = await GetTokenPremiumUsedQuotaAsync(entity.UserId, entity.Id);
|
||||
if (usedQuota >= entity.PremiumQuotaLimit.Value)
|
||||
var isPremium = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.ModelId == modelId)
|
||||
.Select(x => x.IsPremium)
|
||||
.FirstAsync();
|
||||
|
||||
if (isPremium)
|
||||
{
|
||||
throw new UserFriendlyException($"当前Token的尊享包额度已用完(已使用:{usedQuota},限制:{entity.PremiumQuotaLimit.Value}),请调整额度限制或使用其他Token", "403");
|
||||
var usedQuota = await GetTokenPremiumUsedQuotaAsync(entity.UserId, entity.Id);
|
||||
if (usedQuota >= entity.PremiumQuotaLimit.Value)
|
||||
{
|
||||
throw new UserFriendlyException($"当前Token的尊享包额度已用完(已使用:{usedQuota},限制:{entity.PremiumQuotaLimit.Value}),请调整额度限制或使用其他Token", "403");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +109,11 @@ public class TokenManager : DomainService
|
||||
/// </summary>
|
||||
private async Task<long> GetTokenPremiumUsedQuotaAsync(Guid userId, Guid tokenId)
|
||||
{
|
||||
var premiumModelIds = PremiumPackageConst.ModeIds;
|
||||
// 先获取所有尊享模型的ModelId列表
|
||||
var premiumModelIds = await _aiModelRepository._DbQueryable
|
||||
.Where(x => x.IsPremium)
|
||||
.Select(x => x.ModelId)
|
||||
.ToListAsync();
|
||||
|
||||
var usedQuota = await _usageStatisticsRepository._DbQueryable
|
||||
.Where(x => x.UserId == userId && x.TokenId == tokenId && premiumModelIds.Contains(x.ModelId))
|
||||
|
||||
Reference in New Issue
Block a user