Revert "feat: 支持尊享包渠道"

This reverts commit 70ae2fab44.
This commit is contained in:
ccnetcore
2025-12-31 00:10:44 +08:00
committed by Gsh
parent 33d28a8cb0
commit 6cc0059691
28 changed files with 34 additions and 1666 deletions

View File

@@ -1,42 +0,0 @@
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; }
}

View File

@@ -1,42 +0,0 @@
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; }
}

View File

@@ -1,14 +0,0 @@
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; }
}

View File

@@ -1,48 +0,0 @@
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; }
}

View File

@@ -1,96 +0,0 @@
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; }
}

View File

@@ -1,84 +0,0 @@
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; }
}

View File

@@ -1,24 +0,0 @@
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; }
}

View File

@@ -1,102 +0,0 @@
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; }
}

View File

@@ -1,86 +0,0 @@
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
}

View File

@@ -179,16 +179,10 @@ public class DailyTaskService : ApplicationService
var tomorrow = today.AddDays(1); var tomorrow = today.AddDays(1);
// 查询今日所有使用尊享包模型的消息role=system 表示消耗) // 查询今日所有使用尊享包模型的消息role=system 表示消耗)
// 先获取所有尊享模型的ModelId列表
var premiumModelIds = await _aiModelRepository._DbQueryable
.Where(x => x.IsPremium)
.Select(x => x.ModelId)
.ToListAsync();
var totalTokens = await _messageRepository._DbQueryable var totalTokens = await _messageRepository._DbQueryable
.Where(x => x.UserId == userId) .Where(x => x.UserId == userId)
.Where(x => x.Role == "system") // system角色表示实际消耗 .Where(x => x.Role == "system") // system角色表示实际消耗
.Where(x => premiumModelIds.Contains(x.ModelId)) // 尊享包模型 .Where(x => PremiumPackageConst.ModeIds.Contains(x.ModelId)) // 尊享包模型
.Where(x => x.CreationTime >= today && x.CreationTime < tomorrow) .Where(x => x.CreationTime >= today && x.CreationTime < tomorrow)
.SumAsync(x => x.TokenUsage.TotalTokenCount); .SumAsync(x => x.TokenUsage.TotalTokenCount);

View File

@@ -1,233 +0,0 @@
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
}

View File

@@ -109,7 +109,7 @@ public class AiChatService : ApplicationService
ApiHost = null, ApiHost = null,
ApiKey = null, ApiKey = null,
Remark = x.Description, Remark = x.Description,
IsPremiumPackage = x.IsPremium IsPremiumPackage = PremiumPackageConst.ModeIds.Contains(x.ModelId)
}).ToListAsync(); }).ToListAsync();
return output; return output;
} }
@@ -144,21 +144,13 @@ public class AiChatService : ApplicationService
} }
//如果是尊享包服务,需要校验是是否尊享包足够 //如果是尊享包服务,需要校验是是否尊享包足够
if (CurrentUser.IsAuthenticated) if (CurrentUser.IsAuthenticated && PremiumPackageConst.ModeIds.Contains(input.Model))
{ {
var isPremium = await _aiModelRepository._DbQueryable // 检查尊享token包用量
.Where(x => x.ModelId == input.Model) var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
.Select(x => x.IsPremium) if (availableTokens <= 0)
.FirstAsync();
if (isPremium)
{ {
// 检查尊享token包用量 throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
if (availableTokens <= 0)
{
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
} }
} }
@@ -227,12 +219,7 @@ public class AiChatService : ApplicationService
} }
//如果是尊享包服务,需要校验是是否尊享包足够 //如果是尊享包服务,需要校验是是否尊享包足够
var isPremium = await _aiModelRepository._DbQueryable if (PremiumPackageConst.ModeIds.Contains(input.ModelId))
.Where(x => x.ModelId == input.ModelId)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
// 检查尊享token包用量 // 检查尊享token包用量
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId); var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId);

View File

@@ -69,9 +69,6 @@ public class AiImageService : ApplicationService
} }
// 尊享包校验 // 尊享包校验
// 注意: AiImageService目前没有注入AiModelEntity仓储
// 由于图片生成功能使用频率较低,且当前所有图片模型都不是尊享模型
// 暂时保留原逻辑,等待后续重构时再注入仓储
if (PremiumPackageConst.ModeIds.Contains(input.ModelId)) if (PremiumPackageConst.ModeIds.Contains(input.ModelId))
{ {
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);

View File

@@ -41,7 +41,8 @@ public class ModelService : ApplicationService, IModelService
input.ModelTypes.Contains(x.ModelType)) input.ModelTypes.Contains(x.ModelType))
.WhereIF(input.ModelApiTypes is not null, x => .WhereIF(input.ModelApiTypes is not null, x =>
input.ModelApiTypes.Contains(x.ModelApiType)) input.ModelApiTypes.Contains(x.ModelApiType))
.WhereIF(input.IsPremiumOnly == true, x => x.IsPremium) .WhereIF(input.IsPremiumOnly == true, x =>
PremiumPackageConst.ModeIds.Contains(x.ModelId))
.GroupBy(x => x.ModelId) .GroupBy(x => x.ModelId)
.Select(x => x.ModelId) .Select(x => x.ModelId)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total)); .ToPageListAsync(input.SkipCount, input.MaxResultCount, total));
@@ -60,7 +61,7 @@ public class ModelService : ApplicationService, IModelService
MultiplierShow = x.First().MultiplierShow, MultiplierShow = x.First().MultiplierShow,
ProviderName = x.First().ProviderName, ProviderName = x.First().ProviderName,
IconUrl = x.First().IconUrl, IconUrl = x.First().IconUrl,
IsPremium = x.First().IsPremium, IsPremium = PremiumPackageConst.ModeIds.Contains(x.First().ModelId),
OrderNum = x.First().OrderNum OrderNum = x.First().OrderNum
}).ToList(); }).ToList();

View File

@@ -52,10 +52,7 @@ public class TokenService : ApplicationService
} }
// 获取尊享包模型ID列表 // 获取尊享包模型ID列表
var premiumModelIds = await _aiModelRepository._DbQueryable var premiumModelIds = PremiumPackageConst.ModeIds;
.Where(x => x.IsPremium)
.Select(x => x.ModelId)
.ToListAsync();
// 批量查询所有Token的尊享包已使用额度 // 批量查询所有Token的尊享包已使用额度
var tokenIds = tokens.Select(t => t.Id).ToList(); var tokenIds = tokens.Select(t => t.Id).ToList();

View File

@@ -65,12 +65,7 @@ public class OpenApiService : ApplicationService
await _aiBlacklistManager.VerifiyAiBlacklist(userId); await _aiBlacklistManager.VerifiyAiBlacklist(userId);
//如果是尊享包服务,需要校验是是否尊享包足够 //如果是尊享包服务,需要校验是是否尊享包足够
var isPremium = await _aiModelRepository._DbQueryable if (PremiumPackageConst.ModeIds.Contains(input.Model))
.Where(x => x.ModelId == input.Model)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
// 检查尊享token包用量 // 检查尊享token包用量
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);

View File

@@ -181,12 +181,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
public async Task<List<TokenPremiumUsageDto>> GetPremiumTokenUsageByTokenAsync() public async Task<List<TokenPremiumUsageDto>> GetPremiumTokenUsageByTokenAsync()
{ {
var userId = CurrentUser.GetId(); 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聚合 // 从UsageStatistics表获取尊享模型的token消耗统计按TokenId聚合
var tokenUsages = await _usageStatisticsRepository._DbQueryable var tokenUsages = await _usageStatisticsRepository._DbQueryable

View File

@@ -80,9 +80,4 @@ public class AiModelEntity : Entity<Guid>, IOrderNum, ISoftDelete
/// 模型图标URL /// 模型图标URL
/// </summary> /// </summary>
public string? IconUrl { get; set; } public string? IconUrl { get; set; }
/// <summary>
/// 是否为尊享模型
/// </summary>
public bool IsPremium { get; set; }
} }

View File

@@ -150,12 +150,7 @@ public class AiGateWayManager : DomainService
await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.Usage, tokenId); await _usageStatisticsManager.SetUsageAsync(userId.Value, request.Model, data.Usage, tokenId);
// 扣减尊享token包用量 // 扣减尊享token包用量
var isPremium = await _aiModelRepository._DbQueryable if (PremiumPackageConst.ModeIds.Contains(request.Model))
.Where(x => x.ModelId == request.Model)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
var totalTokens = data.Usage?.TotalTokens ?? 0; var totalTokens = data.Usage?.TotalTokens ?? 0;
if (totalTokens > 0) if (totalTokens > 0)
@@ -305,20 +300,12 @@ public class AiGateWayManager : DomainService
await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage, tokenId); await _usageStatisticsManager.SetUsageAsync(userId, request.Model, tokenUsage, tokenId);
// 扣减尊享token包用量 // 扣减尊享token包用量
if (userId is not null) if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model))
{ {
var isPremium = await _aiModelRepository._DbQueryable var totalTokens = tokenUsage.TotalTokens ?? 0;
.Where(x => x.ModelId == request.Model) if (totalTokens > 0)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
var totalTokens = tokenUsage.TotalTokens ?? 0; await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
if (totalTokens > 0)
{
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
}
} }
} }
} }
@@ -376,20 +363,12 @@ public class AiGateWayManager : DomainService
await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage, tokenId); await _usageStatisticsManager.SetUsageAsync(userId, model, response.Usage, tokenId);
// 扣减尊享token包用量 // 扣减尊享token包用量
if (userId is not null) if (userId is not null && PremiumPackageConst.ModeIds.Contains(request.Model))
{ {
var isPremium = await _aiModelRepository._DbQueryable var totalTokens = response.Usage.TotalTokens ?? 0;
.Where(x => x.ModelId == request.Model) if (totalTokens > 0)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
var totalTokens = response.Usage.TotalTokens ?? 0; await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
if (totalTokens > 0)
{
await PremiumPackageManager.TryConsumeTokensAsync(userId.Value, totalTokens);
}
} }
} }
} }

View File

@@ -15,7 +15,6 @@ using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
using Yi.Framework.AiHub.Domain.AiGateWay; using Yi.Framework.AiHub.Domain.AiGateWay;
using Yi.Framework.AiHub.Domain.Entities.Chat; 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.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Shared.Attributes; using Yi.Framework.AiHub.Domain.Shared.Attributes;
using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Consts;
@@ -35,13 +34,12 @@ public class ChatManager : DomainService
private readonly UsageStatisticsManager _usageStatisticsManager; private readonly UsageStatisticsManager _usageStatisticsManager;
private readonly PremiumPackageManager _premiumPackageManager; private readonly PremiumPackageManager _premiumPackageManager;
private readonly AiGateWayManager _aiGateWayManager; private readonly AiGateWayManager _aiGateWayManager;
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
public ChatManager(ILoggerFactory loggerFactory, public ChatManager(ILoggerFactory loggerFactory,
ISqlSugarRepository<MessageAggregateRoot> messageRepository, ISqlSugarRepository<MessageAggregateRoot> messageRepository,
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager, ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager,
UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager, UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager,
AiGateWayManager aiGateWayManager, ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository) AiGateWayManager aiGateWayManager)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_messageRepository = messageRepository; _messageRepository = messageRepository;
@@ -50,7 +48,6 @@ public class ChatManager : DomainService
_usageStatisticsManager = usageStatisticsManager; _usageStatisticsManager = usageStatisticsManager;
_premiumPackageManager = premiumPackageManager; _premiumPackageManager = premiumPackageManager;
_aiGateWayManager = aiGateWayManager; _aiGateWayManager = aiGateWayManager;
_aiModelRepository = aiModelRepository;
} }
/// <summary> /// <summary>
@@ -210,12 +207,7 @@ public class ChatManager : DomainService
await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId); await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId);
//扣减尊享token包用量 //扣减尊享token包用量
var isPremium = await _aiModelRepository._DbQueryable if (PremiumPackageConst.ModeIds.Contains(modelId))
.Where(x => x.ModelId == modelId)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
var totalTokens = usage?.TotalTokens ?? 0; var totalTokens = usage?.TotalTokens ?? 0;
if (totalTokens > 0) if (totalTokens > 0)

View File

@@ -1,7 +1,6 @@
using SqlSugar; using SqlSugar;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Domain.Entities; 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.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
@@ -28,16 +27,13 @@ public class TokenManager : DomainService
{ {
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository; private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository; private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
public TokenManager( public TokenManager(
ISqlSugarRepository<TokenAggregateRoot> tokenRepository, ISqlSugarRepository<TokenAggregateRoot> tokenRepository,
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository, ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository)
ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository)
{ {
_tokenRepository = tokenRepository; _tokenRepository = tokenRepository;
_usageStatisticsRepository = usageStatisticsRepository; _usageStatisticsRepository = usageStatisticsRepository;
_aiModelRepository = aiModelRepository;
} }
/// <summary> /// <summary>
@@ -80,20 +76,14 @@ public class TokenManager : DomainService
} }
// 如果是尊享模型且Token设置了额度限制检查是否超限 // 如果是尊享模型且Token设置了额度限制检查是否超限
if (!string.IsNullOrEmpty(modelId) && entity.PremiumQuotaLimit.HasValue) if (!string.IsNullOrEmpty(modelId) &&
PremiumPackageConst.ModeIds.Contains(modelId) &&
entity.PremiumQuotaLimit.HasValue)
{ {
var isPremium = await _aiModelRepository._DbQueryable var usedQuota = await GetTokenPremiumUsedQuotaAsync(entity.UserId, entity.Id);
.Where(x => x.ModelId == modelId) if (usedQuota >= entity.PremiumQuotaLimit.Value)
.Select(x => x.IsPremium)
.FirstAsync();
if (isPremium)
{ {
var usedQuota = await GetTokenPremiumUsedQuotaAsync(entity.UserId, entity.Id); throw new UserFriendlyException($"当前Token的尊享包额度已用完已使用{usedQuota},限制:{entity.PremiumQuotaLimit.Value}请调整额度限制或使用其他Token", "403");
if (usedQuota >= entity.PremiumQuotaLimit.Value)
{
throw new UserFriendlyException($"当前Token的尊享包额度已用完已使用{usedQuota},限制:{entity.PremiumQuotaLimit.Value}请调整额度限制或使用其他Token", "403");
}
} }
} }
@@ -109,11 +99,7 @@ public class TokenManager : DomainService
/// </summary> /// </summary>
private async Task<long> GetTokenPremiumUsedQuotaAsync(Guid userId, Guid tokenId) private async Task<long> GetTokenPremiumUsedQuotaAsync(Guid userId, Guid tokenId)
{ {
// 先获取所有尊享模型的ModelId列表 var premiumModelIds = PremiumPackageConst.ModeIds;
var premiumModelIds = await _aiModelRepository._DbQueryable
.Where(x => x.IsPremium)
.Select(x => x.ModelId)
.ToListAsync();
var usedQuota = await _usageStatisticsRepository._DbQueryable var usedQuota = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId && x.TokenId == tokenId && premiumModelIds.Contains(x.ModelId)) .Where(x => x.UserId == userId && x.TokenId == tokenId && premiumModelIds.Contains(x.ModelId))

View File

@@ -1,100 +0,0 @@
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<PagedResultDto<AiAppDto>>(url).json();
}
// 根据ID获取AI应用
export function getAppById(id: string) {
return get<AiAppDto>(`/channel/app/${id}`).json();
}
// 创建AI应用
export function createApp(data: AiAppCreateInput) {
return post<AiAppDto>('/channel/app', data).json();
}
// 更新AI应用
export function updateApp(data: AiAppUpdateInput) {
return put<AiAppDto>('/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<PagedResultDto<AiModelDto>>(url).json();
}
// 根据ID获取AI模型
export function getModelById(id: string) {
return get<AiModelDto>(`/channel/model/${id}`).json();
}
// 创建AI模型
export function createModel(data: AiModelCreateInput) {
return post<AiModelDto>('/channel/model', data).json();
}
// 更新AI模型
export function updateModel(data: AiModelUpdateInput) {
return put<AiModelDto>('/channel/model', data).json();
}
// 删除AI模型
export function deleteModel(id: string) {
return del(`/channel/model/${id}`).json();
}

View File

@@ -1,121 +0,0 @@
// 模型类型枚举
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<T> {
items: T[];
totalCount: number;
}

View File

@@ -1,548 +0,0 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Edit, Plus, Refresh, View } from '@element-plus/icons-vue';
import type { AiAppDto, AiModelDto } from '@/api/channel/types';
import {
getAppList,
createApp,
updateApp,
deleteApp,
getModelList,
createModel,
updateModel,
deleteModel,
} from '@/api/channel';
// ==================== 应用管理 ====================
const appList = ref<AiAppDto[]>([]);
const appLoading = ref(false);
const selectedAppId = ref<string>('');
// 应用对话框
const appDialogVisible = ref(false);
const appDialogTitle = ref('');
const appForm = ref<Partial<AiAppDto>>({});
const appDetailDialogVisible = ref(false);
const appDetailData = ref<AiAppDto | null>(null);
// 获取应用列表
async function fetchAppList() {
appLoading.value = true;
try {
const res = await getAppList({
skipCount: 0,
maxResultCount: 100,
});
appList.value = res.data.items;
// 默认选中第一个应用
if (appList.value.length > 0 && !selectedAppId.value) {
selectedAppId.value = appList.value[0].id;
fetchModelList();
}
}
catch (error: any) {
ElMessage.error(error.message || '获取应用列表失败');
}
finally {
appLoading.value = false;
}
}
// 选择应用
function handleSelectApp(appId: string) {
selectedAppId.value = appId;
fetchModelList();
}
// 查看应用详情
function handleViewAppDetail(app: AiAppDto) {
appDetailData.value = app;
appDetailDialogVisible.value = true;
}
// 打开应用对话框
function openAppDialog(type: 'create' | 'edit', row?: AiAppDto) {
appDialogTitle.value = type === 'create' ? '创建应用' : '编辑应用';
if (type === 'create') {
appForm.value = {
name: '',
endpoint: '',
extraUrl: '',
apiKey: '',
orderNum: 0,
};
}
else {
appForm.value = { ...row };
}
appDialogVisible.value = true;
}
// 保存应用
async function saveApp() {
try {
if (appForm.value.id) {
await updateApp(appForm.value as any);
ElMessage.success('更新成功');
}
else {
await createApp(appForm.value as any);
ElMessage.success('创建成功');
}
appDialogVisible.value = false;
fetchAppList();
}
catch (error: any) {
ElMessage.error(error.message || '保存失败');
}
}
// 删除应用
async function handleDeleteApp(row: AiAppDto) {
try {
await ElMessageBox.confirm('确定要删除该应用吗?删除后该应用下的所有模型将无法使用。', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await deleteApp(row.id);
ElMessage.success('删除成功');
// 如果删除的是当前选中的应用,清空选中状态
if (selectedAppId.value === row.id) {
selectedAppId.value = '';
modelList.value = [];
}
fetchAppList();
}
catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败');
}
}
}
// ==================== 模型管理 ====================
const modelList = ref<AiModelDto[]>([]);
const modelLoading = ref(false);
const modelSearchKey = ref('');
const modelDialogVisible = ref(false);
const modelDialogTitle = ref('');
const modelForm = ref<Partial<AiModelDto>>({});
// 获取模型列表
async function fetchModelList() {
if (!selectedAppId.value) {
modelList.value = [];
return;
}
modelLoading.value = true;
try {
const res = await getModelList({
aiAppId: selectedAppId.value,
searchKey: modelSearchKey.value,
skipCount: 0,
maxResultCount: 100,
});
modelList.value = res.data.items;
}
catch (error: any) {
ElMessage.error(error.message || '获取模型列表失败');
}
finally {
modelLoading.value = false;
}
}
// 打开模型对话框
function openModelDialog(type: 'create' | 'edit', row?: AiModelDto) {
if (!selectedAppId.value) {
ElMessage.warning('请先选择一个应用');
return;
}
modelDialogTitle.value = type === 'create' ? '创建模型' : '编辑模型';
if (type === 'create') {
modelForm.value = {
handlerName: '',
modelId: '',
name: '',
description: '',
orderNum: 0,
aiAppId: selectedAppId.value,
extraInfo: '',
modelType: 0,
modelApiType: 0,
multiplier: 1,
multiplierShow: 1,
providerName: '',
iconUrl: '',
isPremium: false,
};
}
else {
modelForm.value = { ...row };
}
modelDialogVisible.value = true;
}
// 保存模型
async function saveModel() {
try {
if (modelForm.value.id) {
await updateModel(modelForm.value as any);
ElMessage.success('更新成功');
}
else {
await createModel(modelForm.value as any);
ElMessage.success('创建成功');
}
modelDialogVisible.value = false;
fetchModelList();
}
catch (error: any) {
ElMessage.error(error.message || '保存失败');
}
}
// 删除模型
async function handleDeleteModel(row: AiModelDto) {
try {
await ElMessageBox.confirm('确定要删除该模型吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await deleteModel(row.id);
ElMessage.success('删除成功');
fetchModelList();
}
catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || '删除失败');
}
}
}
// 初始化
onMounted(() => {
fetchAppList();
});
</script>
<template>
<div class="channel-management">
<div class="channel-container">
<!-- 左侧应用列表 -->
<div class="app-list-panel">
<div class="panel-header">
<h3>应用列表</h3>
<el-button type="primary" size="small" :icon="Plus" @click="openAppDialog('create')">
新建
</el-button>
</div>
<el-scrollbar class="app-list-scrollbar">
<div v-loading="appLoading" class="app-list">
<div
v-for="app in appList"
:key="app.id"
class="app-item"
:class="{ active: selectedAppId === app.id }"
@click="handleSelectApp(app.id)"
>
<div class="app-item-content">
<div class="app-name">{{ app.name }}</div>
<div class="app-actions">
<el-button
link
type="primary"
size="small"
:icon="View"
@click.stop="handleViewAppDetail(app)"
>
详情
</el-button>
<el-button
link
type="primary"
size="small"
:icon="Edit"
@click.stop="openAppDialog('edit', app)"
>
编辑
</el-button>
<el-button
link
type="danger"
size="small"
:icon="Delete"
@click.stop="handleDeleteApp(app)"
>
删除
</el-button>
</div>
</div>
</div>
<el-empty v-if="!appLoading && appList.length === 0" description="暂无应用" />
</div>
</el-scrollbar>
</div>
<!-- 右侧模型列表 -->
<div class="model-list-panel">
<div class="panel-header">
<h3>模型列表</h3>
<div class="header-actions">
<el-input
v-model="modelSearchKey"
placeholder="搜索模型"
style="width: 200px; margin-right: 10px"
clearable
@keyup.enter="fetchModelList"
/>
<el-button type="primary" size="small" :icon="Plus" @click="openModelDialog('create')">
新建
</el-button>
<el-button size="small" :icon="Refresh" @click="fetchModelList">
刷新
</el-button>
</div>
</div>
<div v-if="!selectedAppId" class="empty-tip">
<el-empty description="请先选择左侧的应用" />
</div>
<el-table
v-else
v-loading="modelLoading"
:data="modelList"
border
stripe
height="calc(100vh - 220px)"
>
<el-table-column prop="name" label="模型名称" min-width="150" />
<el-table-column prop="modelId" label="模型ID" min-width="200" show-overflow-tooltip />
<el-table-column prop="handlerName" label="处理名" min-width="120" />
<el-table-column prop="providerName" label="供应商" width="100" />
<el-table-column label="是否尊享" width="100">
<template #default="{ row }">
<el-tag :type="row.isPremium ? 'warning' : 'info'">
{{ row.isPremium ? '尊享' : '普通' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="multiplierShow" label="显示倍率" width="100" />
<el-table-column prop="orderNum" label="排序" width="80" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :icon="Edit" @click="openModelDialog('edit', row)">
编辑
</el-button>
<el-button link type="danger" :icon="Delete" @click="handleDeleteModel(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 应用详情对话框 -->
<el-dialog v-model="appDetailDialogVisible" title="应用详情" width="600px">
<el-descriptions v-if="appDetailData" :column="1" border>
<el-descriptions-item label="应用名称">{{ appDetailData.name }}</el-descriptions-item>
<el-descriptions-item label="终结点">{{ appDetailData.endpoint }}</el-descriptions-item>
<el-descriptions-item label="额外URL">{{ appDetailData.extraUrl || '-' }}</el-descriptions-item>
<el-descriptions-item label="API Key">
<el-input :model-value="appDetailData.apiKey" type="textarea" readonly />
</el-descriptions-item>
<el-descriptions-item label="排序">{{ appDetailData.orderNum }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ appDetailData.creationTime }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
<!-- 应用编辑对话框 -->
<el-dialog v-model="appDialogVisible" :title="appDialogTitle" width="600px">
<el-form :model="appForm" label-width="120px">
<el-form-item label="应用名称" required>
<el-input v-model="appForm.name" placeholder="请输入应用名称" />
</el-form-item>
<el-form-item label="终结点" required>
<el-input v-model="appForm.endpoint" placeholder="请输入应用终结点URL" />
</el-form-item>
<el-form-item label="额外URL">
<el-input v-model="appForm.extraUrl" placeholder="请输入额外URL可选" />
</el-form-item>
<el-form-item label="API Key" required>
<el-input v-model="appForm.apiKey" type="textarea" placeholder="请输入API Key" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="appForm.orderNum" :min="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="appDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveApp">保存</el-button>
</template>
</el-dialog>
<!-- 模型编辑对话框 -->
<el-dialog v-model="modelDialogVisible" :title="modelDialogTitle" width="700px">
<el-form :model="modelForm" label-width="120px">
<el-form-item label="模型名称" required>
<el-input v-model="modelForm.name" placeholder="请输入模型名称" />
</el-form-item>
<el-form-item label="模型ID" required>
<el-input v-model="modelForm.modelId" placeholder="请输入模型ID" />
</el-form-item>
<el-form-item label="处理名" required>
<el-input v-model="modelForm.handlerName" placeholder="请输入处理名" />
</el-form-item>
<el-form-item label="供应商名称">
<el-input v-model="modelForm.providerName" placeholder="如OpenAI、Anthropic等" />
</el-form-item>
<el-form-item label="模型描述">
<el-input v-model="modelForm.description" type="textarea" placeholder="请输入模型描述" />
</el-form-item>
<el-form-item label="是否尊享模型">
<el-switch v-model="modelForm.isPremium" />
</el-form-item>
<el-form-item label="模型倍率">
<el-input-number v-model="modelForm.multiplier" :min="0.01" :step="0.1" />
</el-form-item>
<el-form-item label="显示倍率">
<el-input-number v-model="modelForm.multiplierShow" :min="0.01" :step="0.1" />
</el-form-item>
<el-form-item label="模型类型" required>
<el-select v-model="modelForm.modelType" placeholder="请选择模型类型">
<el-option label="聊天" :value="0" />
<el-option label="图片" :value="1" />
<el-option label="嵌入" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="API类型" required>
<el-select v-model="modelForm.modelApiType" placeholder="请选择API类型">
<el-option label="OpenAI" :value="0" />
<el-option label="Claude" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="图标URL">
<el-input v-model="modelForm.iconUrl" placeholder="请输入模型图标URL" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="modelForm.orderNum" :min="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="modelDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveModel">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.channel-management {
padding: 20px;
height: calc(100vh - 40px);
.channel-container {
display: flex;
gap: 20px;
height: 100%;
}
.app-list-panel {
width: 350px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.model-list-panel {
flex: 1;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.panel-header {
padding: 16px 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 10px;
}
}
.app-list-scrollbar {
flex: 1;
height: 0;
}
.app-list {
padding: 10px;
}
.app-item {
padding: 12px 16px;
margin-bottom: 8px;
border: 1px solid #e4e7ed;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: #409eff;
background: #f0f9ff;
}
&.active {
border-color: #409eff;
background: #ecf5ff;
}
.app-item-content {
.app-name {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
color: #303133;
}
.app-actions {
display: flex;
gap: 8px;
}
}
}
.empty-tip {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -19,7 +19,6 @@ const navItems = [
{ name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' }, { name: 'daily-task', label: '每日任务(限时)', icon: 'Trophy', path: '/console/daily-task' },
{ name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' }, { name: 'invite', label: '每周邀请(限时)', icon: 'Present', path: '/console/invite' },
{ name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' }, { name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' },
{ name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' },
]; ];
// 当前激活的菜单 // 当前激活的菜单

View File

@@ -207,14 +207,6 @@ export const layoutRouter: RouteRecordRaw[] = [
title: '激活码兑换', title: '激活码兑换',
}, },
}, },
{
path: 'channel',
name: 'consoleChannel',
component: () => import('@/pages/console/channel/index.vue'),
meta: {
title: '渠道商管理',
},
},
], ],
}, },
], ],

View File

@@ -25,8 +25,6 @@ declare module 'vue' {
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] 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'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider'] ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']

View File

@@ -7,6 +7,7 @@ interface ImportMetaEnv {
readonly VITE_WEB_BASE_API: string; readonly VITE_WEB_BASE_API: string;
readonly VITE_API_URL: string; readonly VITE_API_URL: string;
readonly VITE_FILE_UPLOAD_API: string; readonly VITE_FILE_UPLOAD_API: string;
readonly VITE_BUILD_COMPRESS: string;
readonly VITE_SSO_SEVER_URL: string; readonly VITE_SSO_SEVER_URL: string;
readonly VITE_APP_VERSION: string; readonly VITE_APP_VERSION: string;
} }