From 70ae2fab44f8de01437aa060d404b072f8b157b9 Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Wed, 31 Dec 2025 00:02:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=B0=8A=E4=BA=AB?= =?UTF-8?q?=E5=8C=85=E6=B8=A0=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/Channel/AiAppCreateInput.cs | 42 ++ .../Dtos/Channel/AiAppDto.cs | 42 ++ .../Dtos/Channel/AiAppGetListInput.cs | 14 + .../Dtos/Channel/AiAppUpdateInput.cs | 48 ++ .../Dtos/Channel/AiModelCreateInput.cs | 96 +++ .../Dtos/Channel/AiModelDto.cs | 84 +++ .../Dtos/Channel/AiModelGetListInput.cs | 24 + .../Dtos/Channel/AiModelUpdateInput.cs | 102 ++++ .../IServices/IChannelService.cs | 86 +++ .../Services/Activities/DailyTaskService.cs | 8 +- .../Services/ChannelService.cs | 233 ++++++++ .../Services/Chat/AiChatService.cs | 27 +- .../Services/Chat/AiImageService.cs | 3 + .../Services/Chat/ModelService.cs | 5 +- .../Services/Chat/TokenService.cs | 5 +- .../Services/OpenApiService.cs | 7 +- .../Services/UsageStatisticsService.cs | 7 +- .../Entities/Model/AiModelEntity.cs | 5 + .../Managers/AiGateWayManager.cs | 39 +- .../Managers/ChatManager.cs | 12 +- .../Managers/TokenManager.cs | 30 +- Yi.Ai.Vue3/src/api/channel/index.ts | 100 ++++ Yi.Ai.Vue3/src/api/channel/types.ts | 121 ++++ .../src/pages/console/channel/index.vue | 548 ++++++++++++++++++ Yi.Ai.Vue3/src/pages/console/index.vue | 1 + .../src/routers/modules/staticRouter.ts | 8 + Yi.Ai.Vue3/types/components.d.ts | 2 + Yi.Ai.Vue3/types/import_meta.d.ts | 1 - 28 files changed, 1666 insertions(+), 34 deletions(-) create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppCreateInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppDto.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppGetListInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppUpdateInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelCreateInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelDto.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelGetListInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelUpdateInput.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IChannelService.cs create mode 100644 Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ChannelService.cs create mode 100644 Yi.Ai.Vue3/src/api/channel/index.ts create mode 100644 Yi.Ai.Vue3/src/api/channel/types.ts create mode 100644 Yi.Ai.Vue3/src/pages/console/channel/index.vue diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppCreateInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppCreateInput.cs new file mode 100644 index 00000000..f3c8ee6f --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppCreateInput.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 创建AI应用输入 +/// +public class AiAppCreateInput +{ + /// + /// 应用名称 + /// + [Required(ErrorMessage = "应用名称不能为空")] + [StringLength(100, ErrorMessage = "应用名称不能超过100个字符")] + public string Name { get; set; } + + /// + /// 应用终结点 + /// + [Required(ErrorMessage = "应用终结点不能为空")] + [StringLength(500, ErrorMessage = "应用终结点不能超过500个字符")] + public string Endpoint { get; set; } + + /// + /// 额外URL + /// + [StringLength(500, ErrorMessage = "额外URL不能超过500个字符")] + public string? ExtraUrl { get; set; } + + /// + /// 应用Key + /// + [Required(ErrorMessage = "应用Key不能为空")] + [StringLength(500, ErrorMessage = "应用Key不能超过500个字符")] + public string ApiKey { get; set; } + + /// + /// 排序 + /// + [Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")] + public int OrderNum { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppDto.cs new file mode 100644 index 00000000..95be653d --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppDto.cs @@ -0,0 +1,42 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// AI应用DTO +/// +public class AiAppDto +{ + /// + /// 应用ID + /// + public Guid Id { get; set; } + + /// + /// 应用名称 + /// + public string Name { get; set; } + + /// + /// 应用终结点 + /// + public string Endpoint { get; set; } + + /// + /// 额外URL + /// + public string? ExtraUrl { get; set; } + + /// + /// 应用Key + /// + public string ApiKey { get; set; } + + /// + /// 排序 + /// + public int OrderNum { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreationTime { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppGetListInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppGetListInput.cs new file mode 100644 index 00000000..c1e98085 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppGetListInput.cs @@ -0,0 +1,14 @@ +using Yi.Framework.Ddd.Application.Contracts; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 获取AI应用列表输入 +/// +public class AiAppGetListInput : PagedAllResultRequestDto +{ + /// + /// 搜索关键词(搜索应用名称) + /// + public string? SearchKey { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppUpdateInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppUpdateInput.cs new file mode 100644 index 00000000..ee14667c --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiAppUpdateInput.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 更新AI应用输入 +/// +public class AiAppUpdateInput +{ + /// + /// 应用ID + /// + [Required(ErrorMessage = "应用ID不能为空")] + public Guid Id { get; set; } + + /// + /// 应用名称 + /// + [Required(ErrorMessage = "应用名称不能为空")] + [StringLength(100, ErrorMessage = "应用名称不能超过100个字符")] + public string Name { get; set; } + + /// + /// 应用终结点 + /// + [Required(ErrorMessage = "应用终结点不能为空")] + [StringLength(500, ErrorMessage = "应用终结点不能超过500个字符")] + public string Endpoint { get; set; } + + /// + /// 额外URL + /// + [StringLength(500, ErrorMessage = "额外URL不能超过500个字符")] + public string? ExtraUrl { get; set; } + + /// + /// 应用Key + /// + [Required(ErrorMessage = "应用Key不能为空")] + [StringLength(500, ErrorMessage = "应用Key不能超过500个字符")] + public string ApiKey { get; set; } + + /// + /// 排序 + /// + [Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")] + public int OrderNum { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelCreateInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelCreateInput.cs new file mode 100644 index 00000000..9e956c52 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelCreateInput.cs @@ -0,0 +1,96 @@ +using System.ComponentModel.DataAnnotations; +using Yi.Framework.AiHub.Domain.Shared.Enums; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 创建AI模型输入 +/// +public class AiModelCreateInput +{ + /// + /// 处理名 + /// + [Required(ErrorMessage = "处理名不能为空")] + [StringLength(100, ErrorMessage = "处理名不能超过100个字符")] + public string HandlerName { get; set; } + + /// + /// 模型ID + /// + [Required(ErrorMessage = "模型ID不能为空")] + [StringLength(200, ErrorMessage = "模型ID不能超过200个字符")] + public string ModelId { get; set; } + + /// + /// 模型名称 + /// + [Required(ErrorMessage = "模型名称不能为空")] + [StringLength(200, ErrorMessage = "模型名称不能超过200个字符")] + public string Name { get; set; } + + /// + /// 模型描述 + /// + [StringLength(1000, ErrorMessage = "模型描述不能超过1000个字符")] + public string? Description { get; set; } + + /// + /// 排序 + /// + [Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")] + public int OrderNum { get; set; } + + /// + /// AI应用ID + /// + [Required(ErrorMessage = "AI应用ID不能为空")] + public Guid AiAppId { get; set; } + + /// + /// 额外信息 + /// + [StringLength(2000, ErrorMessage = "额外信息不能超过2000个字符")] + public string? ExtraInfo { get; set; } + + /// + /// 模型类型 + /// + [Required(ErrorMessage = "模型类型不能为空")] + public ModelTypeEnum ModelType { get; set; } + + /// + /// 模型API类型 + /// + [Required(ErrorMessage = "模型API类型不能为空")] + public ModelApiTypeEnum ModelApiType { get; set; } + + /// + /// 模型倍率 + /// + [Range(0.01, double.MaxValue, ErrorMessage = "模型倍率必须大于0")] + public decimal Multiplier { get; set; } = 1; + + /// + /// 模型显示倍率 + /// + [Range(0.01, double.MaxValue, ErrorMessage = "模型显示倍率必须大于0")] + public decimal MultiplierShow { get; set; } = 1; + + /// + /// 供应商分组名称 + /// + [StringLength(100, ErrorMessage = "供应商分组名称不能超过100个字符")] + public string? ProviderName { get; set; } + + /// + /// 模型图标URL + /// + [StringLength(500, ErrorMessage = "模型图标URL不能超过500个字符")] + public string? IconUrl { get; set; } + + /// + /// 是否为尊享模型 + /// + public bool IsPremium { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelDto.cs new file mode 100644 index 00000000..3b93b680 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelDto.cs @@ -0,0 +1,84 @@ +using Yi.Framework.AiHub.Domain.Shared.Enums; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// AI模型DTO +/// +public class AiModelDto +{ + /// + /// 模型ID + /// + public Guid Id { get; set; } + + /// + /// 处理名 + /// + public string HandlerName { get; set; } + + /// + /// 模型ID + /// + public string ModelId { get; set; } + + /// + /// 模型名称 + /// + public string Name { get; set; } + + /// + /// 模型描述 + /// + public string? Description { get; set; } + + /// + /// 排序 + /// + public int OrderNum { get; set; } + + /// + /// AI应用ID + /// + public Guid AiAppId { get; set; } + + /// + /// 额外信息 + /// + public string? ExtraInfo { get; set; } + + /// + /// 模型类型 + /// + public ModelTypeEnum ModelType { get; set; } + + /// + /// 模型API类型 + /// + public ModelApiTypeEnum ModelApiType { get; set; } + + /// + /// 模型倍率 + /// + public decimal Multiplier { get; set; } + + /// + /// 模型显示倍率 + /// + public decimal MultiplierShow { get; set; } + + /// + /// 供应商分组名称 + /// + public string? ProviderName { get; set; } + + /// + /// 模型图标URL + /// + public string? IconUrl { get; set; } + + /// + /// 是否为尊享模型 + /// + public bool IsPremium { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelGetListInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelGetListInput.cs new file mode 100644 index 00000000..c71cfc9b --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelGetListInput.cs @@ -0,0 +1,24 @@ +using Yi.Framework.Ddd.Application.Contracts; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 获取AI模型列表输入 +/// +public class AiModelGetListInput : PagedAllResultRequestDto +{ + /// + /// 搜索关键词(搜索模型名称、模型ID) + /// + public string? SearchKey { get; set; } + + /// + /// AI应用ID筛选 + /// + public Guid? AiAppId { get; set; } + + /// + /// 是否只显示尊享模型 + /// + public bool? IsPremiumOnly { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelUpdateInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelUpdateInput.cs new file mode 100644 index 00000000..b35065d9 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Channel/AiModelUpdateInput.cs @@ -0,0 +1,102 @@ +using System.ComponentModel.DataAnnotations; +using Yi.Framework.AiHub.Domain.Shared.Enums; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +/// +/// 更新AI模型输入 +/// +public class AiModelUpdateInput +{ + /// + /// 模型ID + /// + [Required(ErrorMessage = "模型ID不能为空")] + public Guid Id { get; set; } + + /// + /// 处理名 + /// + [Required(ErrorMessage = "处理名不能为空")] + [StringLength(100, ErrorMessage = "处理名不能超过100个字符")] + public string HandlerName { get; set; } + + /// + /// 模型ID + /// + [Required(ErrorMessage = "模型ID不能为空")] + [StringLength(200, ErrorMessage = "模型ID不能超过200个字符")] + public string ModelId { get; set; } + + /// + /// 模型名称 + /// + [Required(ErrorMessage = "模型名称不能为空")] + [StringLength(200, ErrorMessage = "模型名称不能超过200个字符")] + public string Name { get; set; } + + /// + /// 模型描述 + /// + [StringLength(1000, ErrorMessage = "模型描述不能超过1000个字符")] + public string? Description { get; set; } + + /// + /// 排序 + /// + [Range(0, int.MaxValue, ErrorMessage = "排序必须大于等于0")] + public int OrderNum { get; set; } + + /// + /// AI应用ID + /// + [Required(ErrorMessage = "AI应用ID不能为空")] + public Guid AiAppId { get; set; } + + /// + /// 额外信息 + /// + [StringLength(2000, ErrorMessage = "额外信息不能超过2000个字符")] + public string? ExtraInfo { get; set; } + + /// + /// 模型类型 + /// + [Required(ErrorMessage = "模型类型不能为空")] + public ModelTypeEnum ModelType { get; set; } + + /// + /// 模型API类型 + /// + [Required(ErrorMessage = "模型API类型不能为空")] + public ModelApiTypeEnum ModelApiType { get; set; } + + /// + /// 模型倍率 + /// + [Range(0.01, double.MaxValue, ErrorMessage = "模型倍率必须大于0")] + public decimal Multiplier { get; set; } + + /// + /// 模型显示倍率 + /// + [Range(0.01, double.MaxValue, ErrorMessage = "模型显示倍率必须大于0")] + public decimal MultiplierShow { get; set; } + + /// + /// 供应商分组名称 + /// + [StringLength(100, ErrorMessage = "供应商分组名称不能超过100个字符")] + public string? ProviderName { get; set; } + + /// + /// 模型图标URL + /// + [StringLength(500, ErrorMessage = "模型图标URL不能超过500个字符")] + public string? IconUrl { get; set; } + + /// + /// 是否为尊享模型 + /// + public bool IsPremium { get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IChannelService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IChannelService.cs new file mode 100644 index 00000000..d45b542e --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/IServices/IChannelService.cs @@ -0,0 +1,86 @@ +using Volo.Abp.Application.Dtos; +using Yi.Framework.AiHub.Application.Contracts.Dtos.Channel; + +namespace Yi.Framework.AiHub.Application.Contracts.IServices; + +/// +/// 渠道商管理服务接口 +/// +public interface IChannelService +{ + #region AI应用管理 + + /// + /// 获取AI应用列表 + /// + /// 查询参数 + /// 分页应用列表 + Task> GetAppListAsync(AiAppGetListInput input); + + /// + /// 根据ID获取AI应用 + /// + /// 应用ID + /// 应用详情 + Task GetAppByIdAsync(Guid id); + + /// + /// 创建AI应用 + /// + /// 创建输入 + /// 创建的应用 + Task CreateAppAsync(AiAppCreateInput input); + + /// + /// 更新AI应用 + /// + /// 更新输入 + /// 更新后的应用 + Task UpdateAppAsync(AiAppUpdateInput input); + + /// + /// 删除AI应用 + /// + /// 应用ID + Task DeleteAppAsync(Guid id); + + #endregion + + #region AI模型管理 + + /// + /// 获取AI模型列表 + /// + /// 查询参数 + /// 分页模型列表 + Task> GetModelListAsync(AiModelGetListInput input); + + /// + /// 根据ID获取AI模型 + /// + /// 模型ID + /// 模型详情 + Task GetModelByIdAsync(Guid id); + + /// + /// 创建AI模型 + /// + /// 创建输入 + /// 创建的模型 + Task CreateModelAsync(AiModelCreateInput input); + + /// + /// 更新AI模型 + /// + /// 更新输入 + /// 更新后的模型 + Task UpdateModelAsync(AiModelUpdateInput input); + + /// + /// 删除AI模型(软删除) + /// + /// 模型ID + Task DeleteModelAsync(Guid id); + + #endregion +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/DailyTaskService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/DailyTaskService.cs index 013a82f4..a7e65a0a 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/DailyTaskService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/DailyTaskService.cs @@ -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); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ChannelService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ChannelService.cs new file mode 100644 index 00000000..7e7a2f9b --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ChannelService.cs @@ -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; + +/// +/// 渠道商管理服务实现 +/// +[Authorize] +public class ChannelService : ApplicationService, IChannelService +{ + private readonly ISqlSugarRepository _appRepository; + private readonly ISqlSugarRepository _modelRepository; + + public ChannelService( + ISqlSugarRepository appRepository, + ISqlSugarRepository modelRepository) + { + _appRepository = appRepository; + _modelRepository = modelRepository; + } + + #region AI应用管理 + + /// + /// 获取AI应用列表 + /// + public async Task> GetAppListAsync(AiAppGetListInput input) + { + RefAsync 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>(); + return new PagedResultDto(total, output); + } + + /// + /// 根据ID获取AI应用 + /// + public async Task GetAppByIdAsync(Guid id) + { + var entity = await _appRepository.GetByIdAsync(id); + return entity.Adapt(); + } + + /// + /// 创建AI应用 + /// + public async Task 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(); + } + + /// + /// 更新AI应用 + /// + public async Task 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(); + } + + /// + /// 删除AI应用 + /// + 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模型管理 + + /// + /// 获取AI模型列表 + /// + public async Task> GetModelListAsync(AiModelGetListInput input) + { + RefAsync 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>(); + return new PagedResultDto(total, output); + } + + /// + /// 根据ID获取AI模型 + /// + public async Task GetModelByIdAsync(Guid id) + { + var entity = await _modelRepository.GetByIdAsync(id); + return entity.Adapt(); + } + + /// + /// 创建AI模型 + /// + public async Task 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(); + } + + /// + /// 更新AI模型 + /// + public async Task 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(); + } + + /// + /// 删除AI模型(软删除) + /// + public async Task DeleteModelAsync(Guid id) + { + await _modelRepository.DeleteByIdAsync(id); + } + + #endregion +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs index 341638ce..42f0b1a5 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiChatService.cs @@ -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); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiImageService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiImageService.cs index 5bffa03d..caa7313d 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiImageService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/AiImageService.cs @@ -69,6 +69,9 @@ public class AiImageService : ApplicationService } // 尊享包校验 + // 注意: AiImageService目前没有注入AiModelEntity仓储 + // 由于图片生成功能使用频率较低,且当前所有图片模型都不是尊享模型 + // 暂时保留原逻辑,等待后续重构时再注入仓储 if (PremiumPackageConst.ModeIds.Contains(input.ModelId)) { var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/ModelService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/ModelService.cs index 98faf61c..0eee3438 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/ModelService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/ModelService.cs @@ -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(); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/TokenService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/TokenService.cs index 23e7e666..d18de550 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/TokenService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/TokenService.cs @@ -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(); 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 ccb05089..eef91f3b 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 @@ -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); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs index bffce1e6..f8c0b3a4 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs @@ -181,7 +181,12 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic public async Task> 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 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 991d0ff2..ef0f1ebd 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 @@ -80,4 +80,9 @@ public class AiModelEntity : Entity, IOrderNum, ISoftDelete /// 模型图标URL /// public string? IconUrl { get; set; } + + /// + /// 是否为尊享模型 + /// + public bool IsPremium { 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 971fef75..34cf3486 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 @@ -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); + } } } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs index b4d9110c..5e411fbb 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs @@ -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 _aiModelRepository; public ChatManager(ILoggerFactory loggerFactory, ISqlSugarRepository messageRepository, ISqlSugarRepository agentStoreRepository, AiMessageManager aiMessageManager, UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager, - AiGateWayManager aiGateWayManager) + AiGateWayManager aiGateWayManager, ISqlSugarRepository aiModelRepository) { _loggerFactory = loggerFactory; _messageRepository = messageRepository; @@ -48,6 +50,7 @@ public class ChatManager : DomainService _usageStatisticsManager = usageStatisticsManager; _premiumPackageManager = premiumPackageManager; _aiGateWayManager = aiGateWayManager; + _aiModelRepository = aiModelRepository; } /// @@ -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) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/TokenManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/TokenManager.cs index dd2ae774..28595a48 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/TokenManager.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/TokenManager.cs @@ -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 _tokenRepository; private readonly ISqlSugarRepository _usageStatisticsRepository; + private readonly ISqlSugarRepository _aiModelRepository; public TokenManager( ISqlSugarRepository tokenRepository, - ISqlSugarRepository usageStatisticsRepository) + ISqlSugarRepository usageStatisticsRepository, + ISqlSugarRepository aiModelRepository) { _tokenRepository = tokenRepository; _usageStatisticsRepository = usageStatisticsRepository; + _aiModelRepository = aiModelRepository; } /// @@ -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 /// private async Task 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)) diff --git a/Yi.Ai.Vue3/src/api/channel/index.ts b/Yi.Ai.Vue3/src/api/channel/index.ts new file mode 100644 index 00000000..cd31154f --- /dev/null +++ b/Yi.Ai.Vue3/src/api/channel/index.ts @@ -0,0 +1,100 @@ +import { del, get, post, put } from '@/utils/request'; +import type { + AiAppDto, + AiAppCreateInput, + AiAppUpdateInput, + AiAppGetListInput, + AiModelDto, + AiModelCreateInput, + AiModelUpdateInput, + AiModelGetListInput, + PagedResultDto, +} from './types'; + +// ==================== AI应用管理 ==================== + +// 获取AI应用列表 +export function getAppList(params?: AiAppGetListInput) { + const queryParams = new URLSearchParams(); + if (params?.searchKey) { + queryParams.append('SearchKey', params.searchKey); + } + if (params?.skipCount !== undefined) { + queryParams.append('SkipCount', params.skipCount.toString()); + } + if (params?.maxResultCount !== undefined) { + queryParams.append('MaxResultCount', params.maxResultCount.toString()); + } + + const queryString = queryParams.toString(); + const url = queryString ? `/channel/app?${queryString}` : '/channel/app'; + + return get>(url).json(); +} + +// 根据ID获取AI应用 +export function getAppById(id: string) { + return get(`/channel/app/${id}`).json(); +} + +// 创建AI应用 +export function createApp(data: AiAppCreateInput) { + return post('/channel/app', data).json(); +} + +// 更新AI应用 +export function updateApp(data: AiAppUpdateInput) { + return put('/channel/app', data).json(); +} + +// 删除AI应用 +export function deleteApp(id: string) { + return del(`/channel/app/${id}`).json(); +} + +// ==================== AI模型管理 ==================== + +// 获取AI模型列表 +export function getModelList(params?: AiModelGetListInput) { + const queryParams = new URLSearchParams(); + if (params?.searchKey) { + queryParams.append('SearchKey', params.searchKey); + } + if (params?.aiAppId) { + queryParams.append('AiAppId', params.aiAppId); + } + if (params?.isPremiumOnly !== undefined) { + queryParams.append('IsPremiumOnly', params.isPremiumOnly.toString()); + } + if (params?.skipCount !== undefined) { + queryParams.append('SkipCount', params.skipCount.toString()); + } + if (params?.maxResultCount !== undefined) { + queryParams.append('MaxResultCount', params.maxResultCount.toString()); + } + + const queryString = queryParams.toString(); + const url = queryString ? `/channel/model?${queryString}` : '/channel/model'; + + return get>(url).json(); +} + +// 根据ID获取AI模型 +export function getModelById(id: string) { + return get(`/channel/model/${id}`).json(); +} + +// 创建AI模型 +export function createModel(data: AiModelCreateInput) { + return post('/channel/model', data).json(); +} + +// 更新AI模型 +export function updateModel(data: AiModelUpdateInput) { + return put('/channel/model', data).json(); +} + +// 删除AI模型 +export function deleteModel(id: string) { + return del(`/channel/model/${id}`).json(); +} diff --git a/Yi.Ai.Vue3/src/api/channel/types.ts b/Yi.Ai.Vue3/src/api/channel/types.ts new file mode 100644 index 00000000..76a6306a --- /dev/null +++ b/Yi.Ai.Vue3/src/api/channel/types.ts @@ -0,0 +1,121 @@ +// 模型类型枚举 +export enum ModelTypeEnum { + Chat = 0, + Image = 1, + Embedding = 2, + PremiumChat = 3, +} + +// 模型API类型枚举 +export enum ModelApiTypeEnum { + OpenAi = 0, + Claude = 1, +} + +// AI应用DTO +export interface AiAppDto { + id: string; + name: string; + endpoint: string; + extraUrl?: string; + apiKey: string; + orderNum: number; + creationTime: string; +} + +// 创建AI应用输入 +export interface AiAppCreateInput { + name: string; + endpoint: string; + extraUrl?: string; + apiKey: string; + orderNum: number; +} + +// 更新AI应用输入 +export interface AiAppUpdateInput { + id: string; + name: string; + endpoint: string; + extraUrl?: string; + apiKey: string; + orderNum: number; +} + +// 获取AI应用列表输入 +export interface AiAppGetListInput { + searchKey?: string; + skipCount?: number; + maxResultCount?: number; +} + +// AI模型DTO +export interface AiModelDto { + id: string; + handlerName: string; + modelId: string; + name: string; + description?: string; + orderNum: number; + aiAppId: string; + extraInfo?: string; + modelType: ModelTypeEnum; + modelApiType: ModelApiTypeEnum; + multiplier: number; + multiplierShow: number; + providerName?: string; + iconUrl?: string; + isPremium: boolean; +} + +// 创建AI模型输入 +export interface AiModelCreateInput { + handlerName: string; + modelId: string; + name: string; + description?: string; + orderNum: number; + aiAppId: string; + extraInfo?: string; + modelType: ModelTypeEnum; + modelApiType: ModelApiTypeEnum; + multiplier: number; + multiplierShow: number; + providerName?: string; + iconUrl?: string; + isPremium: boolean; +} + +// 更新AI模型输入 +export interface AiModelUpdateInput { + id: string; + handlerName: string; + modelId: string; + name: string; + description?: string; + orderNum: number; + aiAppId: string; + extraInfo?: string; + modelType: ModelTypeEnum; + modelApiType: ModelApiTypeEnum; + multiplier: number; + multiplierShow: number; + providerName?: string; + iconUrl?: string; + isPremium: boolean; +} + +// 获取AI模型列表输入 +export interface AiModelGetListInput { + searchKey?: string; + aiAppId?: string; + isPremiumOnly?: boolean; + skipCount?: number; + maxResultCount?: number; +} + +// 分页结果 +export interface PagedResultDto { + items: T[]; + totalCount: number; +} diff --git a/Yi.Ai.Vue3/src/pages/console/channel/index.vue b/Yi.Ai.Vue3/src/pages/console/channel/index.vue new file mode 100644 index 00000000..c780fe70 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/console/channel/index.vue @@ -0,0 +1,548 @@ + + + + + diff --git a/Yi.Ai.Vue3/src/pages/console/index.vue b/Yi.Ai.Vue3/src/pages/console/index.vue index 44aece8f..15ae21dd 100644 --- a/Yi.Ai.Vue3/src/pages/console/index.vue +++ b/Yi.Ai.Vue3/src/pages/console/index.vue @@ -19,6 +19,7 @@ const navItems = [ { name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' }, { name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' }, { name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' }, + { name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' }, ]; // 当前激活的菜单 diff --git a/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts b/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts index b94431ec..496bb214 100644 --- a/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts +++ b/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts @@ -207,6 +207,14 @@ export const layoutRouter: RouteRecordRaw[] = [ title: '激活码兑换', }, }, + { + path: 'channel', + name: 'consoleChannel', + component: () => import('@/pages/console/channel/index.vue'), + meta: { + title: '渠道商管理', + }, + }, ], }, ], diff --git a/Yi.Ai.Vue3/types/components.d.ts b/Yi.Ai.Vue3/types/components.d.ts index 9ef439fb..16d28441 100644 --- a/Yi.Ai.Vue3/types/components.d.ts +++ b/Yi.Ai.Vue3/types/components.d.ts @@ -25,6 +25,8 @@ declare module 'vue' { ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] ElDrawer: typeof import('element-plus/es')['ElDrawer'] diff --git a/Yi.Ai.Vue3/types/import_meta.d.ts b/Yi.Ai.Vue3/types/import_meta.d.ts index c98d612e..8f2a798b 100644 --- a/Yi.Ai.Vue3/types/import_meta.d.ts +++ b/Yi.Ai.Vue3/types/import_meta.d.ts @@ -7,7 +7,6 @@ interface ImportMetaEnv { readonly VITE_WEB_BASE_API: string; readonly VITE_API_URL: string; readonly VITE_FILE_UPLOAD_API: string; - readonly VITE_BUILD_COMPRESS: string; readonly VITE_SSO_SEVER_URL: string; readonly VITE_APP_VERSION: string; }