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/Dtos/Chat/AgentSendInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs index 85425145..e2eed240 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/AgentSendInput.cs @@ -15,7 +15,7 @@ public class AgentSendInput /// /// api密钥Id /// - public string Token { get; set; } + public Guid TokenId { get; set; } /// /// 模型id diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageGenerationInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageGenerationInput.cs index c9bade65..7526b00f 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageGenerationInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageGenerationInput.cs @@ -5,6 +5,11 @@ namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; /// public class ImageGenerationInput { + /// + /// 密钥id + /// + public Guid? TokenId { get; set; } + /// /// 提示词 /// @@ -16,7 +21,7 @@ public class ImageGenerationInput public string ModelId { get; set; } = string.Empty; /// - /// 参考图Base64列表(可选,包含前缀如 data:image/png;base64,...) + /// 参考图PrefixBase64列表(可选,包含前缀如 data:image/png;base64,...) /// - public List? ReferenceImagesBase64 { get; set; } + public List? ReferenceImagesPrefixBase64 { get; set; } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskPageInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageMyTaskPageInput.cs similarity index 57% rename from Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskPageInput.cs rename to Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageMyTaskPageInput.cs index c42985d5..1f977cbf 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskPageInput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageMyTaskPageInput.cs @@ -1,24 +1,26 @@ +using Volo.Abp.Application.Dtos; using Yi.Framework.AiHub.Domain.Shared.Enums; +using Yi.Framework.Ddd.Application.Contracts; namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; /// /// 图片任务分页查询输入 /// -public class ImageTaskPageInput +public class ImageMyTaskPageInput: PagedAllResultRequestDto { /// - /// 页码(从1开始) + /// 提示词 /// - public int PageIndex { get; set; } = 1; - - /// - /// 每页数量 - /// - public int PageSize { get; set; } = 10; - + public string? Prompt { get; set; } + /// /// 任务状态筛选(可选) /// public TaskStatusEnum? TaskStatus { get; set; } + + /// + /// 发布状态 + /// + public PublishStatusEnum? PublishStatus { get; set; } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImagePlazaPageInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImagePlazaPageInput.cs new file mode 100644 index 00000000..184e75d7 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImagePlazaPageInput.cs @@ -0,0 +1,31 @@ +using Volo.Abp.Application.Dtos; +using Yi.Framework.AiHub.Domain.Shared.Enums; +using Yi.Framework.Ddd.Application.Contracts; + +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; + +/// +/// 图片任务分页查询输入 +/// +public class ImagePlazaPageInput: PagedAllResultRequestDto +{ + /// + /// 分类 + /// + public string? Categories { get; set; } + + /// + /// 提示词 + /// + public string? Prompt { get; set; } + + /// + /// 任务状态筛选(可选) + /// + public TaskStatusEnum? TaskStatus { get; set; } + + /// + /// 用户名 + /// + public string? UserName{ get; set; } +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskOutput.cs index 1834b5a0..97ec97fa 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskOutput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/ImageTaskOutput.cs @@ -18,19 +18,9 @@ public class ImageTaskOutput public string Prompt { get; set; } = string.Empty; /// - /// 参考图Base64列表 + /// 是否匿名 /// - public List? ReferenceImagesBase64 { get; set; } - - /// - /// 参考图URL列表 - /// - public List? ReferenceImagesUrl { get; set; } - - /// - /// 生成图片Base64(包含前缀) - /// - public string? StoreBase64 { get; set; } + public bool IsAnonymous { get; set; } /// /// 生成图片URL @@ -42,8 +32,35 @@ public class ImageTaskOutput /// public TaskStatusEnum TaskStatus { get; set; } + /// + /// 发布状态 + /// + public PublishStatusEnum PublishStatus { get; set; } + + /// + /// 分类标签 + /// + [SqlSugar.SugarColumn( IsJson = true)] + public List Categories { get; set; } = new(); + /// /// 创建时间 /// public DateTime CreationTime { get; set; } + + /// + /// 错误信息 + /// + public string? ErrorInfo { get; set; } + + /// + /// 用户名称 + /// + public string? UserName { get; set; } + + /// + /// 用户名称Id + /// + public Guid? UserId { get; set; } + } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/PublishImageInput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/PublishImageInput.cs new file mode 100644 index 00000000..bb33ad01 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/Chat/PublishImageInput.cs @@ -0,0 +1,22 @@ +namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; + +/// +/// 发布图片输入 +/// +public class PublishImageInput +{ + /// + /// 是否匿名 + /// + public bool IsAnonymous { get; set; } = false; + + /// + /// 任务ID + /// + public Guid TaskId { get; set; } + + /// + /// 分类标签 + /// + public List Categories { get; set; } = new(); +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs index 6ebf2f2c..b89560aa 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/ModelGetListOutput.cs @@ -6,13 +6,7 @@ public class ModelGetListOutput /// 模型ID /// public Guid Id { get; set; } - - /// - /// 模型分类 - /// - public string Category { get; set; } - - + /// /// 模型id /// @@ -28,36 +22,6 @@ public class ModelGetListOutput /// public string? ModelDescribe { get; set; } - /// - /// 模型价格 - /// - public double ModelPrice { get; set; } - - /// - /// 模型类型 - /// - public string ModelType { get; set; } - - /// - /// 模型展示状态 - /// - public string ModelShow { get; set; } - - /// - /// 系统提示 - /// - public string SystemPrompt { get; set; } - - /// - /// API 主机地址 - /// - public string ApiHost { get; set; } - - /// - /// API 密钥 - /// - public string ApiKey { 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/Jobs/ImageGenerationJob.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJob.cs index fca09694..67f9856d 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJob.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJob.cs @@ -30,32 +30,94 @@ public class ImageGenerationJob : AsyncBackgroundJob, IT public override async Task ExecuteAsync(ImageGenerationJobArgs args) { - _logger.LogInformation("开始执行图片生成任务,TaskId: {TaskId}, ModelId: {ModelId}, UserId: {UserId}", - args.TaskId, args.ModelId, args.UserId); + var task = await _imageStoreTaskRepository.GetFirstAsync(x => x.Id == args.TaskId); + if (task is null) + { + throw new UserFriendlyException($"{args.TaskId} 图片生成任务不存在"); + } + _logger.LogInformation("开始执行图片生成任务,TaskId: {TaskId}, ModelId: {ModelId}, UserId: {UserId}", + task.Id, task.ModelId, task.UserId); try { - var request = JsonSerializer.Deserialize(args.RequestJson); + // 构建 Gemini API 请求对象 + var parts = new List + { + new { text = task.Prompt } + }; + // 添加参考图(如果有) + foreach (var prefixBase64 in task.ReferenceImagesPrefixBase64) + { + var (mimeType, base64Data) = ParsePrefixBase64(prefixBase64); + parts.Add(new + { + inline_data = new + { + mime_type = mimeType, + data = base64Data + } + }); + } + + var requestObj = new + { + contents = new[] + { + new { role = "user", parts } + } + }; + + var request = JsonSerializer.Deserialize( + JsonSerializer.Serialize(requestObj)); + + //里面生成成功已经包含扣款了 await _aiGateWayManager.GeminiGenerateContentImageForStatisticsAsync( - args.TaskId, - args.ModelId, + task.Id, + task.ModelId, request, - args.UserId); + task.UserId, + tokenId: task.TokenId); + _logger.LogInformation("图片生成任务完成,TaskId: {TaskId}", args.TaskId); } catch (Exception ex) { - _logger.LogError(ex, "图片生成任务失败,TaskId: {TaskId}, Error: {Error}", args.TaskId, ex.Message); + var error = $"图片任务失败,TaskId: {args.TaskId},错误信息: {ex.Message},错误堆栈:{ex.StackTrace}"; + _logger.LogError(ex, error); - // 更新任务状态为失败 - var task = await _imageStoreTaskRepository.GetFirstAsync(x => x.Id == args.TaskId); - if (task != null) - { - task.TaskStatus = TaskStatusEnum.Fail; - await _imageStoreTaskRepository.UpdateAsync(task); - } + task.TaskStatus = TaskStatusEnum.Fail; + task.ErrorInfo = error; + + await _imageStoreTaskRepository.UpdateAsync(task); } } -} + + /// + /// 解析带前缀的 Base64 字符串,提取 mimeType 和纯 base64 数据 + /// + private static (string mimeType, string base64Data) ParsePrefixBase64(string prefixBase64) + { + // 默认值 + var mimeType = "image/png"; + var base64Data = prefixBase64; + + if (prefixBase64.Contains(",")) + { + var parts = prefixBase64.Split(','); + if (parts.Length == 2) + { + var header = parts[0]; + if (header.Contains(":") && header.Contains(";")) + { + mimeType = header.Split(':')[1].Split(';')[0]; + } + + base64Data = parts[1]; + } + } + + return (mimeType, base64Data); + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJobArgs.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJobArgs.cs index 666b6b59..7da50f81 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJobArgs.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Jobs/ImageGenerationJobArgs.cs @@ -9,19 +9,4 @@ public class ImageGenerationJobArgs /// 图片任务ID /// public Guid TaskId { get; set; } - - /// - /// 模型ID - /// - public string ModelId { get; set; } = string.Empty; - - /// - /// 请求JSON字符串 - /// - public string RequestJson { get; set; } = string.Empty; - - /// - /// 用户ID - /// - public Guid UserId { get; set; } } diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ActivationCodeService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/ActivationCodeService.cs similarity index 100% rename from Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ActivationCodeService.cs rename to Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Activities/ActivationCodeService.cs 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..33394c9a 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 @@ -7,6 +7,7 @@ using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask; using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities.Chat; +using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; @@ -25,6 +26,7 @@ public class DailyTaskService : ApplicationService private readonly ISqlSugarRepository _premiumPackageRepository; private readonly ILogger _logger; private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService(); + private readonly ISqlSugarRepository _aiModelRepository; // 任务配置 private readonly Dictionary _taskConfigs = new() @@ -37,12 +39,13 @@ public class DailyTaskService : ApplicationService ISqlSugarRepository dailyTaskRepository, ISqlSugarRepository messageRepository, ISqlSugarRepository premiumPackageRepository, - ILogger logger) + ILogger logger, ISqlSugarRepository aiModelRepository) { _dailyTaskRepository = dailyTaskRepository; _messageRepository = messageRepository; _premiumPackageRepository = premiumPackageRepository; _logger = logger; + _aiModelRepository = aiModelRepository; } /// @@ -179,10 +182,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..750603d4 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/ChannelService.cs @@ -0,0 +1,240 @@ +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +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(Roles = "admin")] +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应用列表 + /// + [HttpGet("channel/app")] + 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)) + .OrderByDescending(x => x.OrderNum) + .OrderByDescending(x => x.CreationTime) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + + var output = entities.Adapt>(); + return new PagedResultDto(total, output); + } + + /// + /// 根据ID获取AI应用 + /// + [HttpGet("channel/app/{id}")] + public async Task GetAppByIdAsync([FromRoute]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应用 + /// + [HttpDelete("channel/app/{id}")] + public async Task DeleteAppAsync([FromRoute]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模型列表 + /// + [HttpGet("channel/model")] + 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模型 + /// + [HttpGet("channel/model/{id}")] + public async Task GetModelByIdAsync([FromRoute]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模型(软删除) + /// + [HttpDelete("channel/model/{id}")] + 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..69ef325e 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 @@ -40,35 +40,37 @@ namespace Yi.Framework.AiHub.Application.Services; public class AiChatService : ApplicationService { private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ISqlSugarRepository _aiModelRepository; private readonly AiBlacklistManager _aiBlacklistManager; private readonly ILogger _logger; private readonly AiGateWayManager _aiGateWayManager; + private readonly ModelManager _modelManager; private readonly PremiumPackageManager _premiumPackageManager; private readonly ChatManager _chatManager; private readonly TokenManager _tokenManager; private readonly IAccountService _accountService; private readonly ISqlSugarRepository _agentStoreRepository; + private readonly ISqlSugarRepository _aiModelRepository; public AiChatService(IHttpContextAccessor httpContextAccessor, AiBlacklistManager aiBlacklistManager, - ISqlSugarRepository aiModelRepository, ILogger logger, AiGateWayManager aiGateWayManager, + ModelManager modelManager, PremiumPackageManager premiumPackageManager, ChatManager chatManager, TokenManager tokenManager, IAccountService accountService, - ISqlSugarRepository agentStoreRepository) + ISqlSugarRepository agentStoreRepository, ISqlSugarRepository aiModelRepository) { _httpContextAccessor = httpContextAccessor; _aiBlacklistManager = aiBlacklistManager; - _aiModelRepository = aiModelRepository; _logger = logger; _aiGateWayManager = aiGateWayManager; + _modelManager = modelManager; _premiumPackageManager = premiumPackageManager; _chatManager = chatManager; _tokenManager = tokenManager; _accountService = accountService; _agentStoreRepository = agentStoreRepository; + _aiModelRepository = aiModelRepository; } @@ -86,7 +88,7 @@ public class AiChatService : ApplicationService } /// - /// 获取模型列表 + /// 获取对话模型列表 /// /// public async Task> GetModelAsync() @@ -98,18 +100,11 @@ public class AiChatService : ApplicationService .Select(x => new ModelGetListOutput { Id = x.Id, - Category = "chat", ModelId = x.ModelId, ModelName = x.Name, ModelDescribe = x.Description, - ModelPrice = 0, - ModelType = "1", - ModelShow = "0", - SystemPrompt = null, - ApiHost = null, - ApiKey = null, Remark = x.Description, - IsPremiumPackage = PremiumPackageConst.ModeIds.Contains(x.ModelId) + IsPremiumPackage = x.IsPremium }).ToListAsync(); return output; } @@ -144,19 +139,24 @@ 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 _modelManager.IsPremiumModelAsync(input.Model); + + if (isPremium) { - throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包"); + // 检查尊享token包用量 + var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId()); + if (availableTokens <= 0) + { + throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包"); + } } } //ai网关代理httpcontext await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, - CurrentUser.Id, sessionId, null, cancellationToken); + CurrentUser.Id, sessionId, null, CancellationToken.None); } /// @@ -192,7 +192,7 @@ public class AiChatService : ApplicationService //ai网关代理httpcontext await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, - CurrentUser.Id, null, null, cancellationToken); + CurrentUser.Id, null, null, CancellationToken.None); } @@ -202,7 +202,7 @@ public class AiChatService : ApplicationService [HttpPost("ai-chat/agent/send")] public async Task PostAgentSendAsync([FromBody] AgentSendInput input, CancellationToken cancellationToken) { - var tokenValidation = await _tokenManager.ValidateTokenAsync(input.Token, input.ModelId); + var tokenValidation = await _tokenManager.ValidateTokenAsync(input.TokenId, input.ModelId); await _aiBlacklistManager.VerifiyAiBlacklist(tokenValidation.UserId); // 验证用户是否为VIP @@ -219,7 +219,9 @@ public class AiChatService : ApplicationService } //如果是尊享包服务,需要校验是是否尊享包足够 - if (PremiumPackageConst.ModeIds.Contains(input.ModelId)) + var isPremium = await _modelManager.IsPremiumModelAsync(input.ModelId); + + if (isPremium) { // 检查尊享token包用量 var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(tokenValidation.UserId); @@ -232,12 +234,12 @@ public class AiChatService : ApplicationService await _chatManager.AgentCompleteChatStreamAsync(_httpContextAccessor.HttpContext, input.SessionId, input.Content, - input.Token, + tokenValidation.Token, tokenValidation.TokenId, input.ModelId, tokenValidation.UserId, input.Tools, - cancellationToken); + CancellationToken.None); } /// 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..fad3ecc7 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 @@ -3,14 +3,17 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using SqlSugar; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.BackgroundJobs; using Volo.Abp.Guids; using Volo.Abp.Users; +using Yi.Framework.AiHub.Application.Contracts.Dtos; using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat; using Yi.Framework.AiHub.Application.Jobs; using Yi.Framework.AiHub.Domain.Entities.Chat; +using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Extensions; using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; @@ -29,23 +32,31 @@ public class AiImageService : ApplicationService private readonly IBackgroundJobManager _backgroundJobManager; private readonly AiBlacklistManager _aiBlacklistManager; private readonly PremiumPackageManager _premiumPackageManager; + private readonly ModelManager _modelManager; private readonly IGuidGenerator _guidGenerator; private readonly IWebHostEnvironment _webHostEnvironment; + private readonly TokenManager _tokenManager; + private readonly ISqlSugarRepository _aiModelRepository; public AiImageService( ISqlSugarRepository imageTaskRepository, IBackgroundJobManager backgroundJobManager, AiBlacklistManager aiBlacklistManager, PremiumPackageManager premiumPackageManager, + ModelManager modelManager, IGuidGenerator guidGenerator, - IWebHostEnvironment webHostEnvironment) + IWebHostEnvironment webHostEnvironment, TokenManager tokenManager, + ISqlSugarRepository aiModelRepository) { _imageTaskRepository = imageTaskRepository; _backgroundJobManager = backgroundJobManager; _aiBlacklistManager = aiBlacklistManager; _premiumPackageManager = premiumPackageManager; + _modelManager = modelManager; _guidGenerator = guidGenerator; _webHostEnvironment = webHostEnvironment; + _tokenManager = tokenManager; + _aiModelRepository = aiModelRepository; } /// @@ -62,14 +73,22 @@ public class AiImageService : ApplicationService // 黑名单校验 await _aiBlacklistManager.VerifiyAiBlacklist(userId); + //校验token + if (input.TokenId is not null) + { + await _tokenManager.ValidateTokenAsync(input.TokenId, input.ModelId); + } + + // VIP校验 if (!CurrentUser.IsAiVip()) { throw new UserFriendlyException("图片生成功能需要VIP用户才能使用,请购买VIP后重新登录重试"); } - // 尊享包校验 - if (PremiumPackageConst.ModeIds.Contains(input.ModelId)) + // 尊享包校验 - 使用ModelManager统一判断 + var isPremium = await _modelManager.IsPremiumModelAsync(input.ModelId); + if (isPremium) { var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); if (availableTokens <= 0) @@ -82,32 +101,23 @@ public class AiImageService : ApplicationService var task = new ImageStoreTaskAggregateRoot { Prompt = input.Prompt, - ReferenceImagesBase64 = input.ReferenceImagesBase64 ?? new List(), + ReferenceImagesPrefixBase64 = input.ReferenceImagesPrefixBase64 ?? new List(), ReferenceImagesUrl = new List(), TaskStatus = TaskStatusEnum.Processing, - UserId = userId + UserId = userId, + UserName = CurrentUser.UserName, + TokenId = input.TokenId, + ModelId = input.ModelId }; await _imageTaskRepository.InsertAsync(task); - var taskId = task.Id; - - // 构建请求JSON - var requestJson = JsonSerializer.Serialize(new - { - prompt = input.Prompt, - referenceImages = input.ReferenceImagesBase64 - }); - // 入队后台任务 await _backgroundJobManager.EnqueueAsync(new ImageGenerationJobArgs { - TaskId = taskId, - ModelId = input.ModelId, - RequestJson = requestJson, - UserId = userId + TaskId = task.Id, }); - return taskId; + return task.Id; } /// @@ -130,12 +140,15 @@ public class AiImageService : ApplicationService { Id = task.Id, Prompt = task.Prompt, - ReferenceImagesBase64 = task.ReferenceImagesBase64, - ReferenceImagesUrl = task.ReferenceImagesUrl, - StoreBase64 = task.StoreBase64, + // ReferenceImagesBase64 = task.ReferenceImagesBase64, + // ReferenceImagesUrl = task.ReferenceImagesUrl, + // StoreBase64 = task.StoreBase64, StoreUrl = task.StoreUrl, TaskStatus = task.TaskStatus, - CreationTime = task.CreationTime + PublishStatus = task.PublishStatus, + Categories = task.Categories, + CreationTime = task.CreationTime, + ErrorInfo = task.ErrorInfo, }; } @@ -145,6 +158,7 @@ public class AiImageService : ApplicationService /// Base64图片数据(包含前缀如 data:image/png;base64,) /// 图片访问URL [HttpPost("ai-image/upload-base64")] + [AllowAnonymous] public async Task UploadBase64ToUrlAsync([FromBody] string base64Data) { if (string.IsNullOrWhiteSpace(base64Data)) @@ -167,6 +181,7 @@ public class AiImageService : ApplicationService { mimeType = header.Split(':')[1].Split(';')[0]; } + base64Content = parts[1]; } } @@ -193,57 +208,166 @@ public class AiImageService : ApplicationService throw new UserFriendlyException("Base64格式无效"); } - // 创建存储目录 - var uploadPath = Path.Combine(_webHostEnvironment.ContentRootPath, "wwwroot", "ai-images"); + // ============================== + // ✅ 按日期创建目录(yyyyMMdd) + // ============================== + var dateFolder = DateTime.Now.ToString("yyyyMMdd"); + var uploadPath = Path.Combine( + _webHostEnvironment.ContentRootPath, + "wwwroot", + "ai-images", + dateFolder + ); + if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } - // 生成文件名并保存 + // 保存文件 var fileId = _guidGenerator.Create(); var fileName = $"{fileId}{extension}"; var filePath = Path.Combine(uploadPath, fileName); await File.WriteAllBytesAsync(filePath, imageBytes); - // 返回访问URL - return $"/ai-images/{fileName}"; + // 返回包含日期目录的访问URL + return $"/wwwroot/ai-images/{dateFolder}/{fileName}"; } /// - /// 分页查询任务列表 + /// 分页查询我的任务列表 /// - /// 分页查询参数 - /// 任务列表 - [HttpGet("ai-image/tasks")] - public async Task> GetTaskPageAsync([FromQuery] ImageTaskPageInput input) + [HttpGet("ai-image/my-tasks")] + public async Task> GetMyTaskPageAsync([FromQuery] ImageMyTaskPageInput input) { var userId = CurrentUser.GetId(); - var query = _imageTaskRepository._DbQueryable + RefAsync total = 0; + var output = await _imageTaskRepository._DbQueryable .Where(x => x.UserId == userId) - .WhereIF(input.TaskStatus.HasValue, x => x.TaskStatus == input.TaskStatus!.Value) - .OrderByDescending(x => x.CreationTime); - - var total = await query.CountAsync(); - var items = await query - .Skip((input.PageIndex - 1) * input.PageSize) - .Take(input.PageSize) + .WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus) + .WhereIF(!string.IsNullOrWhiteSpace(input.Prompt), x => x.Prompt.Contains(input.Prompt)) + .WhereIF(input.PublishStatus is not null, x => x.PublishStatus == input.PublishStatus) + .WhereIF(input.StartTime is not null && input.EndTime is not null, + x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) + .OrderByDescending(x => x.CreationTime) .Select(x => new ImageTaskOutput { Id = x.Id, Prompt = x.Prompt, - ReferenceImagesBase64 = x.ReferenceImagesBase64, - ReferenceImagesUrl = x.ReferenceImagesUrl, - StoreBase64 = x.StoreBase64, StoreUrl = x.StoreUrl, TaskStatus = x.TaskStatus, - CreationTime = x.CreationTime + PublishStatus = x.PublishStatus, + Categories = x.Categories, + CreationTime = x.CreationTime, + ErrorInfo = x.ErrorInfo }) - .ToListAsync(); + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); - return new PagedResult(total, items); + + return new PagedResult(total, output); + } + + /// + /// 分页查询图片广场(已发布的图片) + /// + [HttpGet("ai-image/plaza")] + [AllowAnonymous] + public async Task> GetPlazaPageAsync([FromQuery] ImagePlazaPageInput input) + { + RefAsync total = 0; + var output = await _imageTaskRepository._DbQueryable + .Where(x => x.PublishStatus == PublishStatusEnum.Published) + .Where(x => x.TaskStatus == TaskStatusEnum.Success) + .WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus) + .WhereIF(!string.IsNullOrWhiteSpace(input.Prompt), x => x.Prompt.Contains(input.Prompt)) + .WhereIF(!string.IsNullOrWhiteSpace(input.Categories), x => SqlFunc.JsonLike(x.Categories, input.Categories)) + .WhereIF(!string.IsNullOrWhiteSpace(input.UserName),x=>x.UserName.Contains(input.UserName) ) + .WhereIF(input.StartTime is not null && input.EndTime is not null, + x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime) + .OrderByDescending(x => x.CreationTime) + .Select(x => new ImageTaskOutput + { + Id = x.Id, + Prompt = x.Prompt, + IsAnonymous = x.IsAnonymous, + StoreUrl = x.StoreUrl, + TaskStatus = x.TaskStatus, + PublishStatus = x.PublishStatus, + Categories = x.Categories, + CreationTime = x.CreationTime, + ErrorInfo = null, + UserName = x.UserName, + UserId = x.UserId, + + }) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); ; + + + output.ForEach(x => + { + if (x.IsAnonymous) + { + x.UserName = null; + x.UserId = null; + } + }); + + return new PagedResult(total, output); + } + + /// + /// 发布图片到广场 + /// + [HttpPost("ai-image/publish")] + public async Task PublishAsync([FromBody] PublishImageInput input) + { + var userId = CurrentUser.GetId(); + + var task = await _imageTaskRepository.GetFirstAsync(x => x.Id == input.TaskId && x.UserId == userId); + if (task == null) + { + throw new UserFriendlyException("任务不存在或无权访问"); + } + + if (task.TaskStatus != TaskStatusEnum.Success) + { + throw new UserFriendlyException("只有已完成的任务才能发布"); + } + + if (task.PublishStatus == PublishStatusEnum.Published) + { + throw new UserFriendlyException("该任务已发布"); + } + + //设置发布 + task.SetPublish(input.IsAnonymous,input.Categories); + await _imageTaskRepository.UpdateAsync(task); + } + + /// + /// 获取图片模型列表 + /// + /// + [HttpPost("ai-image/model")] + [AllowAnonymous] + public async Task> GetModelAsync() + { + var output = await _aiModelRepository._DbQueryable + .Where(x => x.ModelType == ModelTypeEnum.Image) + .Where(x => x.ModelApiType == ModelApiTypeEnum.GenerateContent) + .OrderByDescending(x => x.OrderNum) + .Select(x => new ModelGetListOutput + { + Id = x.Id, + ModelId = x.ModelId, + ModelName = x.Name, + ModelDescribe = x.Description, + Remark = x.Description, + IsPremiumPackage = x.IsPremium + }).ToListAsync(); + return output; } } @@ -268,4 +392,4 @@ public class PagedResult Total = total; Items = items; } -} +} \ No newline at end of file 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..957931e8 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 @@ -1,10 +1,12 @@ using Mapster; +using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Yi.Framework.AiHub.Application.Contracts.Dtos.Model; using Yi.Framework.AiHub.Application.Contracts.IServices; using Yi.Framework.AiHub.Domain.Entities.Model; +using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.AiHub.Domain.Shared.Enums; using Yi.Framework.AiHub.Domain.Shared.Extensions; @@ -18,10 +20,12 @@ namespace Yi.Framework.AiHub.Application.Services.Chat; public class ModelService : ApplicationService, IModelService { private readonly ISqlSugarRepository _modelRepository; + private readonly ModelManager _modelManager; - public ModelService(ISqlSugarRepository modelRepository) + public ModelService(ISqlSugarRepository modelRepository, ModelManager modelManager) { _modelRepository = modelRepository; + _modelManager = modelManager; } /// @@ -41,8 +45,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 +64,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(); @@ -77,11 +80,11 @@ public class ModelService : ApplicationService, IModelService .Where(x => !x.IsDeleted) .Where(x => !string.IsNullOrEmpty(x.ProviderName)) .GroupBy(x => x.ProviderName) - .OrderBy(x => x.ProviderName) + .OrderBy(x => x.OrderNum) .Select(x => x.ProviderName) .ToListAsync(); - return providers; + return providers!; } /// @@ -115,4 +118,13 @@ public class ModelService : ApplicationService, IModelService return Task.FromResult(options); } + + /// + /// 清除尊享模型ID缓存 + /// + [HttpPost("model/clear-premium-cache")] + public async Task ClearPremiumModelCacheAsync() + { + await _modelManager.ClearPremiumModelIdsCacheAsync(); + } } \ No newline at end of file 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..204fda55 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 @@ -7,8 +7,10 @@ using Volo.Abp.Application.Services; using Volo.Abp.Users; using Yi.Framework.AiHub.Application.Contracts.Dtos.Token; 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.Extensions; +using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.Ddd.Application.Contracts; using Yi.Framework.SqlSugarCore.Abstractions; @@ -23,13 +25,16 @@ public class TokenService : ApplicationService { private readonly ISqlSugarRepository _tokenRepository; private readonly ISqlSugarRepository _usageStatisticsRepository; + private readonly ModelManager _modelManager; public TokenService( ISqlSugarRepository tokenRepository, - ISqlSugarRepository usageStatisticsRepository) + ISqlSugarRepository usageStatisticsRepository, + ModelManager modelManager) { _tokenRepository = tokenRepository; _usageStatisticsRepository = usageStatisticsRepository; + _modelManager = modelManager; } /// @@ -51,8 +56,8 @@ public class TokenService : ApplicationService return new PagedResultDto(); } - // 获取尊享包模型ID列表 - var premiumModelIds = PremiumPackageConst.ModeIds; + // 通过ModelManager获取尊享包模型ID列表 + var premiumModelIds = await _modelManager.GetPremiumModelIdsAsync(); // 批量查询所有Token的尊享包已使用额度 var tokenIds = tokens.Select(t => t.Id).ToList(); @@ -86,7 +91,7 @@ public class TokenService : ApplicationService } [HttpGet("token/select-list")] - public async Task> GetSelectListAsync() + public async Task> GetSelectListAsync([FromQuery] bool? includeDefault = true) { var userId = CurrentUser.GetId(); var tokens = await _tokenRepository._DbQueryable @@ -99,13 +104,17 @@ public class TokenService : ApplicationService Name = x.Name, IsDisabled = x.IsDisabled }).ToListAsync(); - - tokens.Insert(0,new TokenSelectListOutputDto + + if (includeDefault == true) { - TokenId = Guid.Empty, - Name = "默认", - IsDisabled = false - }); + tokens.Insert(0, new TokenSelectListOutputDto + { + TokenId = Guid.Empty, + Name = "默认", + IsDisabled = false + }); + } + return tokens; } 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..5e4b0cf9 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 @@ -27,25 +27,27 @@ public class OpenApiService : ApplicationService private readonly ILogger _logger; private readonly TokenManager _tokenManager; private readonly AiGateWayManager _aiGateWayManager; - private readonly ISqlSugarRepository _aiModelRepository; + private readonly ModelManager _modelManager; private readonly AiBlacklistManager _aiBlacklistManager; private readonly IAccountService _accountService; private readonly PremiumPackageManager _premiumPackageManager; private readonly ISqlSugarRepository _imageStoreRepository; + private readonly ISqlSugarRepository _aiModelRepository; public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger logger, TokenManager tokenManager, AiGateWayManager aiGateWayManager, - ISqlSugarRepository aiModelRepository, AiBlacklistManager aiBlacklistManager, - IAccountService accountService, PremiumPackageManager premiumPackageManager, ISqlSugarRepository imageStoreRepository) + ModelManager modelManager, AiBlacklistManager aiBlacklistManager, + IAccountService accountService, PremiumPackageManager premiumPackageManager, ISqlSugarRepository imageStoreRepository, ISqlSugarRepository aiModelRepository) { _httpContextAccessor = httpContextAccessor; _logger = logger; _tokenManager = tokenManager; _aiGateWayManager = aiGateWayManager; - _aiModelRepository = aiModelRepository; + _modelManager = modelManager; _aiBlacklistManager = aiBlacklistManager; _accountService = accountService; _premiumPackageManager = premiumPackageManager; _imageStoreRepository = imageStoreRepository; + _aiModelRepository = aiModelRepository; } /// @@ -65,7 +67,9 @@ public class OpenApiService : ApplicationService await _aiBlacklistManager.VerifiyAiBlacklist(userId); //如果是尊享包服务,需要校验是是否尊享包足够 - if (PremiumPackageConst.ModeIds.Contains(input.Model)) + var isPremium = await _modelManager.IsPremiumModelAsync(input.Model); + + if (isPremium) { // 检查尊享token包用量 var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId); @@ -79,13 +83,13 @@ public class OpenApiService : ApplicationService if (input.Stream == true) { await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, - userId, null, tokenId, cancellationToken); + userId, null, tokenId,CancellationToken.None); } else { await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId, null, tokenId, - cancellationToken); + CancellationToken.None); } } @@ -193,14 +197,14 @@ public class OpenApiService : ApplicationService { await _aiGateWayManager.AnthropicCompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, - userId, null, tokenId, cancellationToken); + userId, null, tokenId, CancellationToken.None); } else { await _aiGateWayManager.AnthropicCompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId, null, tokenId, - cancellationToken); + CancellationToken.None); } } @@ -245,14 +249,14 @@ public class OpenApiService : ApplicationService { await _aiGateWayManager.OpenAiResponsesStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input, - userId, null, tokenId, cancellationToken); + userId, null, tokenId, CancellationToken.None); } else { await _aiGateWayManager.OpenAiResponsesAsyncForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId, null, tokenId, - cancellationToken); + CancellationToken.None); } } @@ -304,7 +308,7 @@ public class OpenApiService : ApplicationService modelId, input, userId, null, tokenId, - cancellationToken); + CancellationToken.None); } else { @@ -312,7 +316,7 @@ public class OpenApiService : ApplicationService modelId, input, userId, null, tokenId, - cancellationToken); + CancellationToken.None); } } 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..6f988d41 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 @@ -9,8 +9,10 @@ using Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics; using Yi.Framework.AiHub.Application.Contracts.IServices; using Yi.Framework.AiHub.Domain.Entities; using Yi.Framework.AiHub.Domain.Entities.Chat; +using Yi.Framework.AiHub.Domain.Entities.Model; using Yi.Framework.AiHub.Domain.Entities.OpenApi; using Yi.Framework.AiHub.Domain.Extensions; +using Yi.Framework.AiHub.Domain.Managers; using Yi.Framework.AiHub.Domain.Shared.Consts; using Yi.Framework.Ddd.Application.Contracts; using Yi.Framework.SqlSugarCore.Abstractions; @@ -27,17 +29,19 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic private readonly ISqlSugarRepository _usageStatisticsRepository; private readonly ISqlSugarRepository _premiumPackageRepository; private readonly ISqlSugarRepository _tokenRepository; - + private readonly ModelManager _modelManager; public UsageStatisticsService( ISqlSugarRepository messageRepository, ISqlSugarRepository usageStatisticsRepository, ISqlSugarRepository premiumPackageRepository, - ISqlSugarRepository tokenRepository) + ISqlSugarRepository tokenRepository, + ModelManager modelManager) { _messageRepository = messageRepository; _usageStatisticsRepository = usageStatisticsRepository; _premiumPackageRepository = premiumPackageRepository; _tokenRepository = tokenRepository; + _modelManager = modelManager; } /// @@ -181,7 +185,9 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic public async Task> GetPremiumTokenUsageByTokenAsync() { var userId = CurrentUser.GetId(); - var premiumModelIds = PremiumPackageConst.ModeIds; + + // 通过ModelManager获取所有尊享模型的ModelId列表 + var premiumModelIds = await _modelManager.GetPremiumModelIdsAsync(); // 从UsageStatistics表获取尊享模型的token消耗统计(按TokenId聚合) var tokenUsages = await _usageStatisticsRepository._DbQueryable diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Gemini/GeminiGenerateContentAcquirer.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Gemini/GeminiGenerateContentAcquirer.cs index d06eb1c2..d0dfbf81 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Gemini/GeminiGenerateContentAcquirer.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Gemini/GeminiGenerateContentAcquirer.cs @@ -20,7 +20,6 @@ public static class GeminiGenerateContentAcquirer + usage.Value.GetPath("thoughtsTokenCount").GetInt() + usage.Value.GetPath("toolUsePromptTokenCount").GetInt(); - return new ThorUsageResponse { PromptTokens = inputTokens, @@ -32,14 +31,47 @@ public static class GeminiGenerateContentAcquirer } /// - /// 获取图片url,包含前缀 + /// 获取图片 base64(包含 data:image 前缀) + /// 优先从 inlineData.data 中获取,其次从 markdown text 中解析 /// - /// - /// - public static string GetImageBase64(JsonElement response) + public static string GetImagePrefixBase64(JsonElement response) { - //todo - //获取他的base64字符串 - return string.Empty; + // Step 1: 优先尝试从 candidates[0].content.parts[0].inlineData.data 获取 + var inlineBase64 = response + .GetPath("candidates", 0, "content", "parts", 0, "inlineData", "data") + .GetString(); + + if (!string.IsNullOrEmpty(inlineBase64)) + { + // 默认按 png 格式拼接前缀 + return $"data:image/png;base64,{inlineBase64}"; + } + + // Step 2: fallback,从 candidates[0].content.parts[0].text 中解析 markdown 图片 + var text = response + .GetPath("candidates", 0, "content", "parts", 0, "text") + .GetString(); + + if (string.IsNullOrEmpty(text)) + { + return string.Empty; + } + + // markdown 图片格式:  + var startMarker = "(data:image/"; + var startIndex = text.IndexOf(startMarker, StringComparison.Ordinal); + if (startIndex < 0) + { + return string.Empty; + } + + startIndex += 1; // 跳过 "(" + var endIndex = text.IndexOf(')', startIndex); + if (endIndex <= startIndex) + { + return string.Empty; + } + + return text.Substring(startIndex, endIndex - startIndex); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/PublishStatusEnum.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/PublishStatusEnum.cs new file mode 100644 index 00000000..45792255 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Enums/PublishStatusEnum.cs @@ -0,0 +1,17 @@ +namespace Yi.Framework.AiHub.Domain.Shared.Enums; + +/// +/// 发布状态枚举 +/// +public enum PublishStatusEnum +{ + /// + /// 未发布 + /// + Unpublished = 0, + + /// + /// 已发布 + /// + Published = 1 +} diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureDatabricks/Chats/AzureDatabricksChatCompletionsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureDatabricks/Chats/AzureDatabricksChatCompletionsService.cs index f1382b38..8026da63 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureDatabricks/Chats/AzureDatabricksChatCompletionsService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorAzureDatabricks/Chats/AzureDatabricksChatCompletionsService.cs @@ -107,35 +107,6 @@ public class AzureDatabricksChatCompletionsService(ILogger logger,IHttpClientFactory httpClientFactory) +public sealed class OpenAiChatCompletionsService( + ILogger logger, + IHttpClientFactory httpClientFactory) : IChatCompletionService { public async IAsyncEnumerable CompleteChatStreamAsync(AiModelDescribe options, @@ -19,8 +21,18 @@ public sealed class OpenAiChatCompletionsService(ILogger= HttpStatusCode.BadRequest) { var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - logger.LogError("OpenAI对话异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}", options.Endpoint, + logger.LogError("OpenAI对话异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}", + options.Endpoint, response.StatusCode, error); throw new BusinessException("OpenAI对话异常", response.StatusCode.ToString()); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAiResponseService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAiResponseService.cs index 975c28a1..42e80f9d 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAiResponseService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorCustomOpenAI/Chats/OpenAiResponseService.cs @@ -22,7 +22,16 @@ public class OpenAiResponseService(ILogger logger,IHttpCl var client = httpClientFactory.CreateClient(); - var response = await client.HttpRequestRaw(options.Endpoint.TrimEnd('/') + "/responses", input, options.ApiKey); + var endpoint = options?.Endpoint.TrimEnd('/'); + + //兼容 v1结尾 + if (endpoint != null && endpoint.EndsWith("/v1", StringComparison.OrdinalIgnoreCase)) + { + endpoint = endpoint.Substring(0, endpoint.Length - "/v1".Length); + } + var requestUri = endpoint + "/v1/responses"; + + var response = await client.HttpRequestRaw(requestUri, input, options.ApiKey); openai?.SetTag("Model", input.Model); openai?.SetTag("Response", response.StatusCode.ToString()); @@ -86,8 +95,17 @@ public class OpenAiResponseService(ILogger logger,IHttpCl using var openai = Activity.Current?.Source.StartActivity("OpenAI 响应"); + var endpoint = options?.Endpoint.TrimEnd('/'); + + //兼容 v1结尾 + if (endpoint != null && endpoint.EndsWith("/v1", StringComparison.OrdinalIgnoreCase)) + { + endpoint = endpoint.Substring(0, endpoint.Length - "/v1".Length); + } + var requestUri = endpoint + "/v1/responses"; + var response = await httpClientFactory.CreateClient().PostJsonAsync( - options?.Endpoint.TrimEnd('/') + "/responses", + requestUri, chatCompletionCreate, options.ApiKey).ConfigureAwait(false); openai?.SetTag("Model", chatCompletionCreate.Model); diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorDeepSeek/Chats/DeepSeekChatCompletionsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorDeepSeek/Chats/DeepSeekChatCompletionsService.cs index dde9b819..b93fce50 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorDeepSeek/Chats/DeepSeekChatCompletionsService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/AiGateWay/Impl/ThorDeepSeek/Chats/DeepSeekChatCompletionsService.cs @@ -23,9 +23,17 @@ public sealed class DeepSeekChatCompletionsService(ILogger(line, ThorJsonSerializer.DefaultOptions); - - // var content = result?.Choices?.FirstOrDefault()?.Delta; - // - // // if (first && string.IsNullOrWhiteSpace(content?.Content) && string.IsNullOrEmpty(content?.ReasoningContent)) - // // { - // // continue; - // // } - // - // if (first && content.Content == OpenAIConstant.ThinkStart) - // { - // isThink = true; - // //continue; - // // 需要将content的内容转换到其他字段 - // } - // - // if (isThink && content.Content.Contains(OpenAIConstant.ThinkEnd)) - // { - // isThink = false; - // // 需要将content的内容转换到其他字段 - // //continue; - // } - // - // if (isThink) - // { - // // 需要将content的内容转换到其他字段 - // foreach (var choice in result.Choices) - // { - // //choice.Delta.ReasoningContent = choice.Delta.Content; - // //choice.Delta.Content = string.Empty; - // } - // } - - // first = false; - yield return result; } } @@ -142,8 +116,16 @@ public sealed class DeepSeekChatCompletionsService(ILogger public string Prompt { get; set; } /// - /// 参考图Base64 + /// 参考图PrefixBase64(带前缀,如 data:image/png;base64,xxx) /// - [SugarColumn(IsJson = true)] - public List ReferenceImagesBase64 { get; set; } + [SugarColumn(IsJson = true, ColumnDataType = StaticConfig.CodeFirst_BigString)] + public List ReferenceImagesPrefixBase64 { get; set; } /// /// 参考图url /// [SugarColumn(IsJson = true)] public List ReferenceImagesUrl { get; set; } - - - /// - /// 图片base64 - /// - public string? StoreBase64 { get; set; } - + + /// /// 图片绝对路径 /// @@ -46,6 +41,43 @@ public class ImageStoreTaskAggregateRoot : FullAuditedAggregateRoot /// public Guid UserId { get; set; } + /// + /// 用户名称 + /// + public string? UserName { get; set; } + + /// + /// 模型id + /// + public string ModelId { get; set; } + + /// + /// 错误信息 + /// + [SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string? ErrorInfo { get; set; } + + /// + /// 发布状态 + /// + public PublishStatusEnum PublishStatus { get; set; } = PublishStatusEnum.Unpublished; + + /// + /// 分类标签 + /// + [SugarColumn(IsJson = true)] + public List Categories { get; set; } = new(); + + /// + /// 是否匿名 + /// + public bool IsAnonymous { get; set; } = false; + + /// + /// 密钥id + /// + public Guid? TokenId { get; set; } + /// /// 设置成功 /// @@ -55,4 +87,18 @@ public class ImageStoreTaskAggregateRoot : FullAuditedAggregateRoot TaskStatus = TaskStatusEnum.Success; StoreUrl = storeUrl; } + + /// + /// 设置发布 + /// + /// + /// + public void SetPublish(bool isAnonymous,List categories) + { + this.PublishStatus = PublishStatusEnum.Published; + this.IsAnonymous = isAnonymous; + this.Categories = categories; + } + + } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Entities/Model/AiModelEntity.cs index 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 9678cc1d..6c4aec81 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 @@ -92,7 +92,7 @@ public class AiGateWayManager : DomainService { throw new UserFriendlyException($"【{modelId}】模型当前版本【{modelApiType}】格式不支持"); } - // ✅ 统一处理 -nx 后缀(网关层模型规范化) + // ✅ 统一处理 yi- 后缀(网关层模型规范化) if (!string.IsNullOrEmpty(aiModelDescribe.ModelId) && aiModelDescribe.ModelId.StartsWith("yi-", StringComparison.OrdinalIgnoreCase)) { @@ -158,7 +158,12 @@ public class AiGateWayManager : DomainService await _usageStatisticsManager.SetUsageAsync(userId.Value, sourceModelId, data.Usage, tokenId); // 扣减尊享token包用量 - if (PremiumPackageConst.ModeIds.Contains(sourceModelId)) + 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) @@ -315,12 +320,20 @@ public class AiGateWayManager : DomainService await _usageStatisticsManager.SetUsageAsync(userId, sourceModelId, tokenUsage, tokenId); // 扣减尊享token包用量 - if (userId is not null && PremiumPackageConst.ModeIds.Contains(sourceModelId)) + 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); + } } } } @@ -378,12 +391,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); + } } } } @@ -982,7 +1003,7 @@ public class AiGateWayManager : DomainService } } - + private const string ImageStoreHost = "http://localhost:19001/api/app"; /// /// Gemini 生成(Image)-非流式-缓存处理 /// 返回图片绝对路径 @@ -1011,16 +1032,16 @@ public class AiGateWayManager : DomainService var data = await chatService.GenerateContentAsync(modelDescribe, request, cancellationToken); //解析json,获取base64字符串 - var imageBase64 = GeminiGenerateContentAcquirer.GetImageBase64(data); + var imagePrefixBase64 = GeminiGenerateContentAcquirer.GetImagePrefixBase64(data); //远程调用上传接口,将base64转换为URL var httpClient = LazyServiceProvider.LazyGetRequiredService().CreateClient(); - var uploadUrl = $"https://ccnetcore.com/prod-api/ai-hub/ai-image/upload-base64"; - var content = new StringContent(JsonSerializer.Serialize(imageBase64), Encoding.UTF8, "application/json"); + // var uploadUrl = $"https://ccnetcore.com/prod-api/ai-hub/ai-image/upload-base64"; + var uploadUrl = $"{ImageStoreHost}/ai-image/upload-base64"; + var content = new StringContent(JsonSerializer.Serialize(imagePrefixBase64), Encoding.UTF8, "application/json"); var uploadResponse = await httpClient.PostAsync(uploadUrl, content, cancellationToken); uploadResponse.EnsureSuccessStatusCode(); var storeUrl = await uploadResponse.Content.ReadAsStringAsync(cancellationToken); - storeUrl = storeUrl.Trim('"'); // 移除JSON字符串的引号 var tokenUsage = new ThorUsageResponse { @@ -1047,8 +1068,7 @@ public class AiGateWayManager : DomainService } //设置存储base64和url - imageStoreTask.StoreBase64 = imageBase64; - imageStoreTask.SetSuccess(storeUrl); + imageStoreTask.SetSuccess($"{ImageStoreHost}{storeUrl}"); await _imageStoreTaskRepository.UpdateAsync(imageStoreTask); } 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/ModelManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs new file mode 100644 index 00000000..e3a0df3a --- /dev/null +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Logging; +using Volo.Abp.Caching; +using Volo.Abp.Domain.Services; +using Yi.Framework.AiHub.Domain.Entities.Model; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace Yi.Framework.AiHub.Domain.Managers; + +/// +/// 模型管理器 +/// +public class ModelManager : DomainService +{ + private readonly ISqlSugarRepository _aiModelRepository; + private readonly IDistributedCache, string> _distributedCache; + private readonly ILogger _logger; + private const string PREMIUM_MODEL_IDS_CACHE_KEY = "PremiumModelIds"; + + public ModelManager( + ISqlSugarRepository aiModelRepository, + IDistributedCache, string> distributedCache, + ILogger logger) + { + _aiModelRepository = aiModelRepository; + _distributedCache = distributedCache; + _logger = logger; + } + + /// + /// 获取所有尊享模型ID列表(使用分布式缓存,10分钟过期) + /// + /// 尊享模型ID列表 + public async Task> GetPremiumModelIdsAsync() + { + var output = await _distributedCache.GetOrAddAsync( + PREMIUM_MODEL_IDS_CACHE_KEY, + async () => + { + // 从数据库查询 + var premiumModelIds = await _aiModelRepository._DbQueryable + .Where(x => x.IsPremium) + .Select(x => x.ModelId) + .ToListAsync(); + return premiumModelIds; + }, + () => new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) + } + ); + return output ?? new List(); + } + + /// + /// 判断指定模型是否为尊享模型 + /// + /// 模型ID + /// 是否为尊享模型 + public async Task IsPremiumModelAsync(string modelId) + { + if (string.IsNullOrWhiteSpace(modelId)) + { + return false; + } + + var premiumModelIds = await GetPremiumModelIdsAsync(); + return premiumModelIds.Contains(modelId); + } + + /// + /// 清除尊享模型ID缓存 + /// + public async Task ClearPremiumModelIdsCacheAsync() + { + await _distributedCache.RemoveAsync(PREMIUM_MODEL_IDS_CACHE_KEY); + _logger.LogInformation("已清除尊享模型ID分布式缓存"); + } +} \ No newline at end of file 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..9dc1fad9 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; @@ -21,43 +22,62 @@ public class TokenValidationResult /// Token Id /// public Guid TokenId { get; set; } + + /// + /// token + /// + public string Token { get; set; } } 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; } /// /// 验证Token并返回用户Id和TokenId /// - /// Token密钥 + /// Token密钥或者TokenId /// 模型Id(用于判断是否是尊享模型需要检查额度) /// Token验证结果 - public async Task ValidateTokenAsync(string? token, string? modelId = null) + public async Task ValidateTokenAsync(object tokenOrId, string? modelId = null) { - if (token is null) + + if (tokenOrId is null) { throw new UserFriendlyException("当前请求未包含token", "401"); } - - if (!token.StartsWith("yi-")) + + TokenAggregateRoot entity; + if (tokenOrId is Guid tokenId) { - throw new UserFriendlyException("当前请求token非法", "401"); + entity = await _tokenRepository._DbQueryable + .Where(x => x.Id == tokenId) + .FirstAsync(); } - - var entity = await _tokenRepository._DbQueryable - .Where(x => x.Token == token) - .FirstAsync(); - + else + { + var tokenStr = tokenOrId.ToString(); + if (!tokenStr.StartsWith("yi-")) + { + throw new UserFriendlyException("当前请求token非法", "401"); + } + entity = await _tokenRepository._DbQueryable + .Where(x => x.Token == tokenStr) + .FirstAsync(); + } + if (entity is null) { throw new UserFriendlyException("当前请求token无效", "401"); @@ -76,21 +96,28 @@ 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"); + } } } return new TokenValidationResult { UserId = entity.UserId, - TokenId = entity.Id + TokenId = entity.Id, + Token = entity.Token }; } @@ -99,7 +126,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.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj index 64255401..d1b9778a 100644 --- a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj @@ -1,6 +1,7 @@ + diff --git a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Yi.Framework.ChatHub.Domain.csproj b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Yi.Framework.ChatHub.Domain.csproj index 0826808c..9fbbf556 100644 --- a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Yi.Framework.ChatHub.Domain.csproj +++ b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Yi.Framework.ChatHub.Domain.csproj @@ -7,6 +7,7 @@ + diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateNewsJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateNewsJob.cs index 382f85e6..89c18cf8 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateNewsJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateNewsJob.cs @@ -21,6 +21,7 @@ namespace Yi.Abp.Web.Jobs.ai_stock public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + return; // 每次触发只有2/24的概率执行生成新闻 var random = new Random(); var probability = random.Next(0, 24); diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateStockPricesJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateStockPricesJob.cs index c412fcee..59d3e7c4 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateStockPricesJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/ai-stock/GenerateStockPricesJob.cs @@ -20,6 +20,7 @@ namespace Yi.Abp.Web.Jobs.ai_stock public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + return; await _stockMarketManager.GenerateStocksAsync(); } } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoPassInGoodsJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoPassInGoodsJob.cs index 03ff06d0..255f3614 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoPassInGoodsJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoPassInGoodsJob.cs @@ -32,6 +32,7 @@ public class AutoPassInGoodsJob: HangfireBackgroundWorkerBase } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + return; await _marketManager.AutoPassInGoodsAsync(); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoRefreshMiningPoolJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoRefreshMiningPoolJob.cs index f5cb4efa..e9f7e803 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoRefreshMiningPoolJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoRefreshMiningPoolJob.cs @@ -37,7 +37,7 @@ public class AutoRefreshMiningPoolJob : HangfireBackgroundWorkerBase } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { - + return; //刷新矿池 await _miningPoolManager.RefreshMiningPoolAsync(); //刷新用户限制 diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoUpdateCollectiblesValueJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoUpdateCollectiblesValueJob.cs index 46b52d98..e54d04b3 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoUpdateCollectiblesValueJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/AutoUpdateCollectiblesValueJob.cs @@ -20,6 +20,7 @@ public class AutoUpdateCollectiblesValueJob : HangfireBackgroundWorkerBase public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + return; await _collectiblesManager.UpdateAllValueAsync(); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/OnHookAutoMiningJob.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/OnHookAutoMiningJob.cs index 6df2f6cf..6f4f5eb3 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/OnHookAutoMiningJob.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Jobs/digital-collectibles/OnHookAutoMiningJob.cs @@ -31,6 +31,7 @@ public class OnHookAutoMiningJob : HangfireBackgroundWorkerBase } public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken()) { + return; await _miningPoolManager.OnHookMiningAsync(); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index 62cf6531..06f01662 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -113,7 +113,7 @@ namespace Yi.Abp.Web //本地开发环境,可以禁用作业执行 if (host.IsDevelopment()) { - Configure(options => { options.IsEnabled = false; }); + //Configure(options => { options.IsEnabled = false; }); } //请求日志 @@ -280,6 +280,7 @@ namespace Yi.Abp.Web { options.TokenValidationParameters = new TokenValidationParameters { + RoleClaimType = "Roles", ClockSkew = TimeSpan.Zero, ValidateIssuerSigningKey = true, ValidIssuer = jwtOptions.Issuer, @@ -298,7 +299,8 @@ namespace Yi.Abp.Web } else { - if (messageContext.Request.Cookies.TryGetValue("Token", out var cookiesToken)) + if (!messageContext.Request.Headers.ContainsKey("Authorization") && + messageContext.Request.Cookies.TryGetValue("Token", out var cookiesToken)) { messageContext.Token = cookiesToken; } @@ -358,8 +360,8 @@ namespace Yi.Abp.Web var app = context.GetApplicationBuilder(); app.UseRouting(); - //app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); - // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); + // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); + // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); // app.ApplicationServices.GetRequiredService().SqlSugarClient.CodeFirst.InitTables(); //跨域 diff --git a/Yi.Ai.Vue3/.claude/settings.local.json b/Yi.Ai.Vue3/.claude/settings.local.json index 131db92e..13e6d318 100644 --- a/Yi.Ai.Vue3/.claude/settings.local.json +++ b/Yi.Ai.Vue3/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(npx vue-tsc --noEmit)", - "Bash(timeout 60 npx vue-tsc:*)" + "Bash(timeout 60 npx vue-tsc:*)", + "Bash(npm run dev:*)" ], "deny": [], "ask": [] diff --git a/Yi.Ai.Vue3/index.html b/Yi.Ai.Vue3/index.html index 109c532f..1e8b8a28 100644 --- a/Yi.Ai.Vue3/index.html +++ b/Yi.Ai.Vue3/index.html @@ -112,7 +112,7 @@ - 意心Ai 2.9 + 意心Ai 3.0 海外地址,仅首次访问预计加载约10秒,无需梯子 diff --git a/Yi.Ai.Vue3/src/App.vue b/Yi.Ai.Vue3/src/App.vue index 3ea0046e..8a074912 100644 --- a/Yi.Ai.Vue3/src/App.vue +++ b/Yi.Ai.Vue3/src/App.vue @@ -4,5 +4,11 @@ + - + diff --git a/Yi.Ai.Vue3/src/api/aiImage/index.ts b/Yi.Ai.Vue3/src/api/aiImage/index.ts new file mode 100644 index 00000000..e4c55e67 --- /dev/null +++ b/Yi.Ai.Vue3/src/api/aiImage/index.ts @@ -0,0 +1,33 @@ +import { get, post } from '@/utils/request'; +import type { + GenerateImageRequest, + ImageModel, + PublishImageRequest, + TaskListRequest, + TaskListResponse, + TaskStatusResponse, +} from './types'; + +export function generateImage(data: GenerateImageRequest) { + return post('/ai-image/generate', data).json(); +} + +export function getTaskStatus(taskId: string) { + return get(`/ai-image/task/${taskId}`).json(); +} + +export function getMyTasks(params: TaskListRequest) { + return get('/ai-image/my-tasks', params).json(); +} + +export function getImagePlaza(params: TaskListRequest) { + return get('/ai-image/plaza', params).json(); +} + +export function publishImage(data: PublishImageRequest) { + return post('/ai-image/publish', data).json(); +} + +export function getImageModels() { + return post('/ai-image/model').json(); +} \ No newline at end of file diff --git a/Yi.Ai.Vue3/src/api/aiImage/types.ts b/Yi.Ai.Vue3/src/api/aiImage/types.ts new file mode 100644 index 00000000..0ba3ed0f --- /dev/null +++ b/Yi.Ai.Vue3/src/api/aiImage/types.ts @@ -0,0 +1,69 @@ +export interface GenerateImageRequest { + tokenId: string; + prompt: string; + modelId: string; + referenceImagesPrefixBase64?: string[]; +} + +export interface TaskStatusResponse { + id: string; + prompt: string; + storePrefixBase64?: string; + storeUrl?: string; + taskStatus: 'Processing' | 'Success' | 'Fail'; + publishStatus: string; + categories: string[]; + creationTime: string; + errorInfo?: string; +} + +export interface TaskListRequest { + SkipCount: number; + MaxResultCount: number; + TaskStatus?: 'Processing' | 'Success' | 'Fail'; + Prompt?: string; + PublishStatus?: 'Unpublished' | 'Published'; + StartTime?: string; + EndTime?: string; + OrderByColumn?: string; + IsAsc?: string; + IsAscending?: boolean; + Sorting?: string; + Categories?: string; + UserName?: string; +} + +export interface TaskItem { + id: string; + prompt: string; + storePrefixBase64?: string; + storeUrl?: string; + taskStatus: 'Processing' | 'Success' | 'Fail'; + publishStatus: string; + categories: string[]; + creationTime: string; + errorInfo?: string; + isAnonymous?: boolean; + userName?: string | null; + userId?: string | null; +} + +export interface TaskListResponse { + total: number; + items: TaskItem[]; +} + +export interface PublishImageRequest { + taskId: string; + categories: string[]; + isAnonymous?: boolean; +} + +export interface ImageModel { + id: string; + modelId: string; + modelName: string; + modelDescribe: string; + remark: string; + isPremiumPackage: boolean; +} 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/api/index.ts b/Yi.Ai.Vue3/src/api/index.ts index 4bfb8e92..b165d819 100644 --- a/Yi.Ai.Vue3/src/api/index.ts +++ b/Yi.Ai.Vue3/src/api/index.ts @@ -6,3 +6,4 @@ export * from './model'; export * from './pay'; export * from './session'; export * from './user'; +export * from './aiImage'; diff --git a/Yi.Ai.Vue3/src/api/model/index.ts b/Yi.Ai.Vue3/src/api/model/index.ts index 05ade05d..28a3a81c 100644 --- a/Yi.Ai.Vue3/src/api/model/index.ts +++ b/Yi.Ai.Vue3/src/api/model/index.ts @@ -138,7 +138,8 @@ export function disableToken(id: string) { // 新增接口2 // 获取可选择的token信息 export function getSelectableTokenInfo() { - return get('/token/select-list').json(); + // return get('/token/select-list').json(); + return get('/token/select-list?includeDefault=false').json(); } /* 返回数据 diff --git a/Yi.Ai.Vue3/src/components/ProductPackage/index.vue b/Yi.Ai.Vue3/src/components/ProductPackage/index.vue index 41cd99d9..4e6f63ce 100644 --- a/Yi.Ai.Vue3/src/components/ProductPackage/index.vue +++ b/Yi.Ai.Vue3/src/components/ProductPackage/index.vue @@ -3,7 +3,6 @@ import type { GoodsItem } from '@/api/pay'; import { CircleCheck, Loading } from '@element-plus/icons-vue'; import { ElMessage } from 'element-plus'; import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'; -import { useRouter } from 'vue-router'; import { createOrder, getOrderStatus } from '@/api'; import { getGoodsList, GoodsCategoryType } from '@/api/pay'; import SupportModelList from '@/components/userPersonalCenter/components/SupportModelList.vue'; @@ -305,8 +304,6 @@ async function checkPaymentStatus(outTradeNo: string) { } } -const router = useRouter(); - function toggleDetails() { showDetails.value = !showDetails.value; } @@ -322,7 +319,8 @@ function onClose() { function goToActivation() { close(); - userStore.openUserCenter('activationCode'); + // 使用 window.location 进行跳转,避免 router 注入问题 + window.location.href = '/console/activation'; } diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/ActivationCode.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/ActivationCode.vue index 88541897..48c2766d 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/ActivationCode.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/ActivationCode.vue @@ -31,33 +31,33 @@ class Particle { this.x = x; this.y = y; this.hue = hue; - + // Explosive physics const angle = Math.random() * Math.PI * 2; - const speed = Math.random() * 15 + 2; + const speed = Math.random() * 15 + 2; this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; - + this.alpha = 1; this.decay = Math.random() * 0.015 + 0.005; this.gravity = 0.05; - this.friction = 0.96; - + this.friction = 0.96; + this.size = Math.random() * 3 + 1; this.brightness = 50; // Standard brightness for white bg visibility (0-100% HSL L value) - this.flicker = Math.random() > 0.5; + this.flicker = Math.random() > 0.5; } update() { this.vx *= this.friction; this.vy *= this.friction; this.vy += this.gravity; - + this.x += this.vx; this.y += this.vy; - + this.alpha -= this.decay; - this.hue += 0.5; + this.hue += 0.5; } draw(ctx: CanvasRenderingContext2D) { @@ -65,9 +65,9 @@ class Particle { // On white background: // We want high saturation (100%) and medium lightness (50%) to make colors pop against white. // If lightness is too high (like 80-100), it fades into white. - const lightness = this.flicker ? Math.random() * 20 + 40 : this.brightness; + const lightness = this.flicker ? Math.random() * 20 + 40 : this.brightness; ctx.fillStyle = `hsla(${this.hue}, 100%, ${lightness}%, ${this.alpha})`; - + ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); @@ -93,7 +93,7 @@ class Shockwave { } update() { - this.radius += 15; + this.radius += 15; this.alpha -= 0.05; this.lineWidth -= 0.2; } @@ -132,10 +132,10 @@ function animate() { if (!ctx) return; // Clear with transparent fade for trails on white - // 'destination-out' erases content. + // 'destination-out' erases content. // To leave a trail on a white background (canvas is transparent over white gradient): // We need to gently erase what's there. - + ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; // Alpha controls trail length ctx.fillRect(0, 0, canvas.width, canvas.height); @@ -177,14 +177,14 @@ function triggerCelebration() { canvas.width = parent.clientWidth; canvas.height = parent.clientHeight; } - + const cx = canvas.width / 2; const cy = canvas.height / 2; // 1. Initial Mega Explosion triggerShake(); createExplosion(cx, cy, Math.random() * 360); - + // Start loop animate(); @@ -194,15 +194,15 @@ function triggerCelebration() { count++; const rx = cx + (Math.random() - 0.5) * canvas.width * 0.8; const ry = cy + (Math.random() - 0.5) * canvas.height * 0.8; - + createExplosion(rx, ry, Math.random() * 360); - - if (count % 3 === 0) triggerShake(); + + if (count % 3 === 0) triggerShake(); if (count > 25) { clearInterval(timer); } - }, 120); + }, 120); } async function handleRedeem() { @@ -221,7 +221,7 @@ async function handleRedeem() { duration: 3000, showClose: true, }); - activationCode.value = ''; + activationCode.value = ''; } catch (error: any) { // console.error(error); } finally { @@ -238,13 +238,13 @@ onUnmounted(() => { - + 🎁 - + 激活码兑换 开启您的专属惊喜权益 @@ -258,9 +258,9 @@ onUnmounted(() => { clearable @keyup.enter="handleRedeem" /> - - @@ -321,7 +321,7 @@ onUnmounted(() => { .content-wrapper { position: relative; - z-index: 11; + z-index: 11; width: 100%; max-width: 480px; padding: 40px; diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue index 666f5900..14e47109 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue @@ -408,7 +408,10 @@ async function handleFlipCard(record: CardFlipRecord) { await new Promise(resolve => requestAnimationFrame(resolve)); // 4. 移动克隆卡片到屏幕中心并放大(考虑边界限制) - const scale = Math.min(1.8, window.innerWidth / rect.width * 0.6); // 动态计算缩放比例 + // 移动端使用更小的缩放比例 + const isMobile = window.innerWidth <= 768; + const maxScale = isMobile ? 1.5 : 1.8; + const scale = Math.min(maxScale, window.innerWidth / rect.width * (isMobile ? 0.5 : 0.6)); const scaledWidth = rect.width * scale; const scaledHeight = rect.height * scale; @@ -416,8 +419,8 @@ async function handleFlipCard(record: CardFlipRecord) { let centerX = window.innerWidth / 2; let centerY = window.innerHeight / 2; - // 边界检查:确保卡片完全在视口内(留20px边距) - const margin = 20; + // 边界检查:确保卡片完全在视口内(移动端留更多边距) + const margin = isMobile ? 30 : 20; const minX = scaledWidth / 2 + margin; const maxX = window.innerWidth - scaledWidth / 2 - margin; const minY = scaledHeight / 2 + margin; @@ -1253,6 +1256,11 @@ function getCardClass(record: CardFlipRecord): string[] { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; + @media (max-width: 768px) { + padding: 12px; + border-radius: 12px; + } + /* 自定义滚动条 */ &::-webkit-scrollbar { width: 6px; @@ -1277,15 +1285,36 @@ function getCardClass(record: CardFlipRecord): string[] { .lucky-float-ball { position: fixed; left: 50%; - /* left: 20px; */ - /* top: 20px; */ + transform: translateX(-50%); z-index: 999; bottom: 20px; - transition: all 0.3s - cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; + &:hover { - transform: scale(1.1); + transform: translateX(-50%) scale(1.1); + } + + @media (max-width: 768px) { + bottom: 15px; + + .lucky-circle { + width: 70px; + height: 70px; + } + + .lucky-content .lucky-icon { + font-size: 20px; + } + + .lucky-content .lucky-text { + font-size: 12px; + } + + .lucky-label { + font-size: 11px; + margin-top: 4px; + } } &.lucky-full { @@ -1408,6 +1437,12 @@ function getCardClass(record: CardFlipRecord): string[] { animation: slideIn 0.5s ease; flex-wrap: wrap; + @media (max-width: 768px) { + padding: 8px 10px; + gap: 8px; + margin-bottom: 10px; + } + .compact-stats { display: flex; align-items: center; @@ -1497,6 +1532,11 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 12px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + + @media (max-width: 768px) { + padding: 12px 20px; + border-radius: 8px; + } } .shuffle-text { @@ -1505,6 +1545,15 @@ function getCardClass(record: CardFlipRecord): string[] { color: #fff; text-shadow: 0 2px 8px rgba(255, 215, 0, 0.6); animation: textPulse 1.5s ease-in-out infinite; + white-space: nowrap; + + @media (max-width: 768px) { + font-size: 14px; + } + + @media (max-width: 480px) { + font-size: 12px; + } } } @@ -1515,8 +1564,13 @@ function getCardClass(record: CardFlipRecord): string[] { max-width: 100%; @media (max-width: 768px) { - grid-template-columns: repeat(5, 1fr); - gap: 6px; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + } + + @media (max-width: 480px) { + grid-template-columns: repeat(2, 1fr); + gap: 12px; } // 洗牌阶段样式 @@ -1683,6 +1737,11 @@ function getCardClass(record: CardFlipRecord): string[] { background: rgba(0, 0, 0, 0.2); padding: 2px 6px; border-radius: 4px; + + @media (max-width: 768px) { + font-size: 9px; + padding: 2px 5px; + } } .card-content { @@ -1691,6 +1750,10 @@ function getCardClass(record: CardFlipRecord): string[] { align-items: center; gap: 8px; z-index: 1; + + @media (max-width: 768px) { + gap: 6px; + } } // 系统logo样式(优化为居中圆形,更丰富的效果) @@ -1709,6 +1772,12 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 3; filter: brightness(1.1); + @media (max-width: 768px) { + width: 40px; + height: 40px; + border: 3px solid rgba(255, 255, 255, 1); + } + // 外层光晕效果 &::before { content: ''; @@ -1741,6 +1810,10 @@ function getCardClass(record: CardFlipRecord): string[] { font-weight: bold; color: #fff; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + + @media (max-width: 768px) { + font-size: 24px; + } } .card-type-badge { @@ -1753,6 +1826,12 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 10px; backdrop-filter: blur(4px); z-index: 2; + + @media (max-width: 768px) { + font-size: 9px; + padding: 2px 6px; + bottom: 4px; + } } .card-shine { @@ -1827,6 +1906,10 @@ function getCardClass(record: CardFlipRecord): string[] { position: relative; z-index: 1; + @media (max-width: 768px) { + padding: 8px; + } + // logo水印样式 .result-watermark { position: absolute; @@ -1869,6 +1952,10 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; filter: drop-shadow(0 4px 8px rgba(255, 215, 0, 0.5)); margin-bottom: 4px; + + @media (max-width: 768px) { + font-size: 36px; + } } .result-text { @@ -1883,6 +1970,11 @@ function getCardClass(record: CardFlipRecord): string[] { letter-spacing: 2px; position: relative; + @media (max-width: 768px) { + font-size: 16px; + letter-spacing: 1px; + } + // 文字外发光 &::after { content: attr(data-text); @@ -1907,6 +1999,11 @@ function getCardClass(record: CardFlipRecord): string[] { position: relative; filter: drop-shadow(0 3px 10px rgba(255, 215, 0, 0.6)); + @media (max-width: 768px) { + font-size: 32px; + margin: 8px 0; + } + // 金色光效边框 &::before { content: attr(data-amount); @@ -1932,6 +2029,11 @@ function getCardClass(record: CardFlipRecord): string[] { letter-spacing: 3px; text-transform: uppercase; margin-top: 4px; + + @media (max-width: 768px) { + font-size: 14px; + letter-spacing: 2px; + } } } @@ -1953,6 +2055,10 @@ function getCardClass(record: CardFlipRecord): string[] { margin-bottom: 6px; filter: drop-shadow(0 2px 6px rgba(147, 112, 219, 0.3)); animation: gentleBounce 2s ease-in-out infinite; + + @media (max-width: 768px) { + font-size: 36px; + } } .result-text { @@ -1964,6 +2070,10 @@ function getCardClass(record: CardFlipRecord): string[] { margin: 8px 0; z-index: 1; letter-spacing: 1px; + + @media (max-width: 768px) { + font-size: 15px; + } } .result-tip { @@ -1972,6 +2082,10 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; margin-top: 6px; font-weight: 500; + + @media (max-width: 768px) { + font-size: 12px; + } } } @@ -1994,6 +2108,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; filter: drop-shadow(0 4px 12px rgba(255, 215, 0, 0.8)); margin: 10px 0; + + @media (max-width: 768px) { + font-size: 42px; + margin: 8px 0; + } } .mystery-text { @@ -2004,6 +2123,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; letter-spacing: 4px; margin: 8px 0; + + @media (max-width: 768px) { + font-size: 18px; + letter-spacing: 2px; + } } .mystery-hint { @@ -2012,6 +2136,11 @@ function getCardClass(record: CardFlipRecord): string[] { z-index: 1; letter-spacing: 2px; margin-top: 6px; + + @media (max-width: 768px) { + font-size: 12px; + letter-spacing: 1px; + } } .mystery-stars { @@ -2060,6 +2189,11 @@ function getCardClass(record: CardFlipRecord): string[] { margin-bottom: 16px; text-align: center; line-height: 1.6; + + @media (max-width: 768px) { + font-size: 13px; + margin-bottom: 12px; + } } .code-input { @@ -2078,10 +2212,19 @@ function getCardClass(record: CardFlipRecord): string[] { border: 1px solid #bae6fd; border-radius: 12px; + @media (max-width: 768px) { + padding: 20px 12px; + } + .filled-icon { font-size: 48px; display: block; margin-bottom: 12px; + + @media (max-width: 768px) { + font-size: 40px; + margin-bottom: 10px; + } } .filled-text { @@ -2090,6 +2233,10 @@ function getCardClass(record: CardFlipRecord): string[] { line-height: 1.6; margin: 0; font-weight: 500; + + @media (max-width: 768px) { + font-size: 14px; + } } } } @@ -2128,12 +2275,22 @@ function getCardClass(record: CardFlipRecord): string[] { border-radius: 12px; margin-bottom: 16px; + @media (max-width: 768px) { + padding: 14px; + margin-bottom: 12px; + } + .code-text { font-size: 28px; font-weight: bold; color: #fff; letter-spacing: 6px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + + @media (max-width: 768px) { + font-size: 24px; + letter-spacing: 4px; + } } } @@ -2142,8 +2299,17 @@ function getCardClass(record: CardFlipRecord): string[] { gap: 8px; margin-bottom: 12px; + @media (max-width: 768px) { + flex-direction: column; + gap: 10px; + } + .el-button { flex: 1; + + @media (max-width: 768px) { + width: 100%; + } } } @@ -2153,6 +2319,10 @@ function getCardClass(record: CardFlipRecord): string[] { line-height: 1.5; margin: 0 0 12px 0; + @media (max-width: 768px) { + font-size: 12px; + } + strong { color: #f56c6c; font-weight: 600; @@ -2199,12 +2369,20 @@ function getCardClass(record: CardFlipRecord): string[] { padding: 12px; text-align: left; + @media (max-width: 768px) { + padding: 10px; + } + .share-preview-title { font-size: 14px; font-weight: bold; color: #303133; margin-bottom: 8px; text-align: center; + + @media (max-width: 768px) { + font-size: 13px; + } } .share-preview-content { @@ -2218,6 +2396,12 @@ function getCardClass(record: CardFlipRecord): string[] { max-height: 200px; overflow-y: auto; + @media (max-width: 768px) { + font-size: 12px; + padding: 8px; + max-height: 150px; + } + /* 自定义滚动条 */ &::-webkit-scrollbar { width: 4px; @@ -2276,9 +2460,17 @@ function getCardClass(record: CardFlipRecord): string[] { display: inline-block; margin-bottom: 20px; + @media (max-width: 768px) { + margin-bottom: 16px; + } + .double-icon { font-size: 64px; animation: bounce 1s infinite; + + @media (max-width: 768px) { + font-size: 52px; + } } .double-sparkle { @@ -2287,6 +2479,10 @@ function getCardClass(record: CardFlipRecord): string[] { right: -10px; font-size: 32px; animation: spin 2s linear infinite; + + @media (max-width: 768px) { + font-size: 26px; + } } } @@ -2295,6 +2491,11 @@ function getCardClass(record: CardFlipRecord): string[] { font-weight: bold; color: #303133; margin-bottom: 16px; + + @media (max-width: 768px) { + font-size: 18px; + margin-bottom: 12px; + } } .double-text { @@ -2303,10 +2504,19 @@ function getCardClass(record: CardFlipRecord): string[] { color: #606266; margin-bottom: 24px; + @media (max-width: 768px) { + font-size: 14px; + margin-bottom: 20px; + } + .highlight { color: #f56c6c; font-weight: bold; font-size: 16px; + + @media (max-width: 768px) { + font-size: 15px; + } } } diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue index c459944a..c33f4549 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/DailyTask.vue @@ -1,8 +1,8 @@ @@ -86,15 +93,21 @@ function getProgressColor(task: DailyTaskItem): string { 每日任务 - 完成每日任务,领取额外尊享包 Token 奖励,可累加重复 + + 完成每日任务,领取额外尊享包 Token 奖励,可累加重复 + - 🔥 + + 🔥 + - 今日尊享包消耗 + + 今日尊享包消耗 + {{ formatTokenDisplay(taskData.todayConsumedTokens) }} Tokens @@ -109,7 +122,7 @@ function getProgressColor(task: DailyTaskItem): string { class="task-item" :class="{ 'task-completed': task.status === 2, - 'task-claimable': task.status === 1 + 'task-claimable': task.status === 1, }" > @@ -187,7 +200,6 @@ function getProgressColor(task: DailyTaskItem): string { diff --git a/Yi.Ai.Vue3/src/layouts/LayoutMobile/index.vue b/Yi.Ai.Vue3/src/layouts/LayoutMobile/index.vue index 29fc25ab..60d7f8dd 100644 --- a/Yi.Ai.Vue3/src/layouts/LayoutMobile/index.vue +++ b/Yi.Ai.Vue3/src/layouts/LayoutMobile/index.vue @@ -1,8 +1,288 @@ - - + + - + + + + + + + 意心AI + + + {{ userStore.userInfo.name?.charAt(0) }} + + + + + + + + + + + + + + + + + {{ menu.label }} + + + + + + + + + + {{ userStore.userInfo.name?.charAt(0) }} + + + {{ userStore.userInfo.name }} + {{ userStore.userInfo.email }} + + + + + + + + + + {{ menu.label }} + + + + - + diff --git a/Yi.Ai.Vue3/src/layouts/LayoutVertical/index.vue b/Yi.Ai.Vue3/src/layouts/LayoutVertical/index.vue index 32264f60..5d752ac4 100644 --- a/Yi.Ai.Vue3/src/layouts/LayoutVertical/index.vue +++ b/Yi.Ai.Vue3/src/layouts/LayoutVertical/index.vue @@ -2,8 +2,6 @@ - - - - - - - - 意心AI - - - - - - - - - - - 新对话 - - - - - - - - - - - - - - - - - diff --git a/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue b/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue new file mode 100644 index 00000000..10b5baa9 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue @@ -0,0 +1,804 @@ + + + + + + + + + + 会话 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 新对话 + + + + + + + + + + + + + handleContextMenu(e, item)" + > + {{ item.sessionTitle?.charAt(0) || 'A' }} + + + {{ item.unreadCount > 99 ? '99+' : item.unreadCount }} + + + handleCollapsedMenuClick(e, item)" + @contextmenu.stop="(e) => handleContextMenu(e, item)" + > + + + + + + + + + {{ item.sessionTitle }} + + + {{ item.sessionContent.substring(0, 30) }}{{ item.sessionContent.length > 30 ? '...' : '' }} + + + + + + + + + + + 暂无对话记录 + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components/Header/components/AiTutorialBtn.vue b/Yi.Ai.Vue3/src/layouts/components/Header/components/AiTutorialBtn.vue index bd7f2647..566f5d7c 100644 --- a/Yi.Ai.Vue3/src/layouts/components/Header/components/AiTutorialBtn.vue +++ b/Yi.Ai.Vue3/src/layouts/components/Header/components/AiTutorialBtn.vue @@ -14,27 +14,6 @@ function openTutorial() { > 文档 - - - - - @@ -70,19 +49,4 @@ function openTutorial() { } } } - -// 移动端显示图标,隐藏文字 -@media (max-width: 768px) { - .ai-tutorial-btn-container { - .ai-tutorial-btn { - .pc-text { - display: none; - } - - .mobile-icon { - display: inline; - } - } - } -} diff --git a/Yi.Ai.Vue3/src/layouts/components/Header/components/AnnouncementBtn.vue b/Yi.Ai.Vue3/src/layouts/components/Header/components/AnnouncementBtn.vue index a6c6adda..22dbe36a 100644 --- a/Yi.Ai.Vue3/src/layouts/components/Header/components/AnnouncementBtn.vue +++ b/Yi.Ai.Vue3/src/layouts/components/Header/components/AnnouncementBtn.vue @@ -1,18 +1,8 @@ - + - + {{ userStore.userInfo?.user.nick ?? '未登录用户' }} @@ -354,6 +149,7 @@ defineExpose({ 普通用户 @@ -361,7 +157,7 @@ defineExpose({ - + 普通用户 @@ -403,7 +200,6 @@ defineExpose({ @@ -412,78 +208,15 @@ defineExpose({ {{ item.title }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Yi.Ai.Vue3/src/layouts/components/Header/components/ThemeBtn.vue b/Yi.Ai.Vue3/src/layouts/components/Header/components/ThemeBtn.vue new file mode 100644 index 00000000..975fd521 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components/Header/components/ThemeBtn.vue @@ -0,0 +1,95 @@ + + + + + + + + + + 主题 + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components/Header/index.vue b/Yi.Ai.Vue3/src/layouts/components/Header/index.vue index aaacdec3..78290e2b 100644 --- a/Yi.Ai.Vue3/src/layouts/components/Header/index.vue +++ b/Yi.Ai.Vue3/src/layouts/components/Header/index.vue @@ -1,117 +1,624 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + 意心AI + + + + + + + AI应用 + + + AI对话 + + + AI图片 + + + AI视频 + + + AI智能体 + + + + + + + + + + + + + + + + + + + + + + + + + 用户信息 + + + API密钥 + + + 充值记录 + + + 用量统计 + + + 尊享服务 + + + 每日任务 + + + 每周邀请 + + + 激活码兑换 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 意心AI + + + + + + + + + + + + + + + + + + + + + + 菜单 + + + + + + + + + AI应用 + + AI对话 + AI图片 + AI视频 + AI智能体 + + + + + + 模型库 + + + + + + + 控制台 + + 用户信息 + API密钥 + 充值记录 + 用量统计 + 尊享服务 + 每日任务 + 每周邀请 + 激活码兑换 + + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Aside/index.vue b/Yi.Ai.Vue3/src/layouts/components0/Aside/index.vue new file mode 100644 index 00000000..8a4f0cf4 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Aside/index.vue @@ -0,0 +1,725 @@ + + + + + + + + + + 会话 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 新对话 + + + + + + + + + + + + + handleContextMenu(e, item)" + > + {{ item.sessionTitle?.charAt(0) || 'A' }} + + + {{ item.unreadCount }} + + + handleContextMenu(e, item)" + > + + + + + + + + + {{ item.sessionTitle }} + + + {{ item.sessionContent.substring(0, 30) }}{{ item.sessionContent.length > 30 ? '...' : '' }} + + + + + + + + + + + + 暂无对话记录 + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/DesignConfig/index.vue b/Yi.Ai.Vue3/src/layouts/components0/DesignConfig/index.vue new file mode 100644 index 00000000..f3409aa6 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/DesignConfig/index.vue @@ -0,0 +1,8 @@ + + + + + 配置页面 + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/AiTutorialBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/AiTutorialBtn.vue new file mode 100644 index 00000000..bd7f2647 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/AiTutorialBtn.vue @@ -0,0 +1,88 @@ + + + + + + + 文档 + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/AnnouncementBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/AnnouncementBtn.vue new file mode 100644 index 00000000..a6c6adda --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/AnnouncementBtn.vue @@ -0,0 +1,112 @@ + + + + + + + + + + + 公告 + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/Avatar.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/Avatar.vue new file mode 100644 index 00000000..7f1ed3c1 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/Avatar.vue @@ -0,0 +1,499 @@ + + + + + + + + + {{ userStore.userInfo?.user.nick ?? '未登录用户' }} + + + + + + YiXinAI-VIP + + + + 普通用户 + + + + + + + + + + + + + + + + + + {{ userStore.userInfo?.user.nick ?? '未登录用户' }} + + + + YiXinAI-VIP + + + + 普通用户 + + + + + + + + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/BuyBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/BuyBtn.vue new file mode 100644 index 00000000..9e5b512a --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/BuyBtn.vue @@ -0,0 +1,68 @@ + + + + + + 立即购买 + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/Collapse.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/Collapse.vue new file mode 100644 index 00000000..58469ad4 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/Collapse.vue @@ -0,0 +1,39 @@ + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/ConsoleBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ConsoleBtn.vue new file mode 100644 index 00000000..3091d22e --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ConsoleBtn.vue @@ -0,0 +1,91 @@ + + + + + + + 控制台 + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/CreateChat.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/CreateChat.vue new file mode 100644 index 00000000..9553e1bf --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/CreateChat.vue @@ -0,0 +1,47 @@ + + + + + + + + + 新对话 + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/LoginBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/LoginBtn.vue new file mode 100644 index 00000000..64b43c4c --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/LoginBtn.vue @@ -0,0 +1,29 @@ + + + + + + + 登录 + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/ModelLibraryBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ModelLibraryBtn.vue new file mode 100644 index 00000000..73347ac9 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ModelLibraryBtn.vue @@ -0,0 +1,88 @@ + + + + + + + 模型库 + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/StartChatBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/StartChatBtn.vue new file mode 100644 index 00000000..970e1509 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/StartChatBtn.vue @@ -0,0 +1,84 @@ + + + + + + + + + 开始聊天 + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/ThemeBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ThemeBtn.vue new file mode 100644 index 00000000..975fd521 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/ThemeBtn.vue @@ -0,0 +1,95 @@ + + + + + + + + + + 主题 + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/TitleEditing.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/TitleEditing.vue new file mode 100644 index 00000000..767e2d53 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/TitleEditing.vue @@ -0,0 +1,87 @@ + + + + + + + + + {{ currentSession.sessionTitle }} + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/components/TutorialBtn.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/components/TutorialBtn.vue new file mode 100644 index 00000000..524ae1cd --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/components/TutorialBtn.vue @@ -0,0 +1,75 @@ + + + + + + + 新手引导 + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Header/index.vue b/Yi.Ai.Vue3/src/layouts/components0/Header/index.vue new file mode 100644 index 00000000..d8dc7806 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Header/index.vue @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Logo/index.vue b/Yi.Ai.Vue3/src/layouts/components0/Logo/index.vue new file mode 100644 index 00000000..a84746fd --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Logo/index.vue @@ -0,0 +1,8 @@ + + + + + Logo + + + diff --git a/Yi.Ai.Vue3/src/layouts/components0/Main/index.vue b/Yi.Ai.Vue3/src/layouts/components0/Main/index.vue new file mode 100644 index 00000000..59622a62 --- /dev/null +++ b/Yi.Ai.Vue3/src/layouts/components0/Main/index.vue @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/layouts/index.vue b/Yi.Ai.Vue3/src/layouts/index.vue index ed682c5e..c37bc699 100644 --- a/Yi.Ai.Vue3/src/layouts/index.vue +++ b/Yi.Ai.Vue3/src/layouts/index.vue @@ -1,21 +1,41 @@ + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/components/ConversationList.vue b/Yi.Ai.Vue3/src/pages/chat/components/ConversationList.vue new file mode 100644 index 00000000..7ca448cf --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/components/ConversationList.vue @@ -0,0 +1,226 @@ + + + + + + + + + 新建对话 + + + + + + + + + {{ session.title || '未命名对话' }} + + + {{ session.updateTime || session.createTime }} + + + + cmd === 'delete' ? handleDeleteSession(session.id, session.title) : handleRenameSession(session.id, session.title)"> + + + + + + + + 重命名 + + + + 删除 + + + + + + + + + + + 加载更多 + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/conversation/index.vue b/Yi.Ai.Vue3/src/pages/chat/conversation/index.vue new file mode 100644 index 00000000..815896e9 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/conversation/index.vue @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue b/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue new file mode 100644 index 00000000..03a82d35 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/image/components/ImageGenerator.vue @@ -0,0 +1,618 @@ + + + + + + + + + + 配置 + + + + + + + + + + + + + + + + {{ model.modelName }} + {{ model.modelDescribe }} + + + + + + + + + + 提示词 + + + + 清空 + + + + + + + + + + + + + + + 点击上传 + + + + 最多2张,< 5MB (支持 JPG/PNG/WEBP) + + + + + + + + + + {{ generating ? '生成中...' : '开始生成' }} + + + + + + + + + + + + + + + + + + + + + + + + + + 正在绘制您的想象... + + + 请稍候,这可能需要几秒钟 + + + + + + + + + + 生成失败 + + + + {{ currentTask.errorInfo || '请检查提示词或稍后重试' }} + + + + + 重试 + + + + + + + + 当前提示词 + + + {{ currentTask.prompt }} + + + + + + + + + + + + + 准备好开始了吗? + + + 在左侧配置您的创意参数,点击生成按钮,见证AI的奇迹。 + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/image/components/ImagePlaza.vue b/Yi.Ai.Vue3/src/pages/chat/image/components/ImagePlaza.vue new file mode 100644 index 00000000..920b6feb --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/image/components/ImagePlaza.vue @@ -0,0 +1,547 @@ + + + + + + + + 图片广场 + + + + + + 筛选 + + + + + + + + + 筛选条件 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + + + + 重置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + + + + 重置 + + + + + + + + + + + + + + + + + + 暂无图片,快去生成一张吧! + + + + + + + + + 加载中... + + + + + + - 到底了,没有更多图片了 - + + + + + + + + + + + + + + 生成中... + + + + + + 生成失败 + + + + + + + {{ currentTask.errorInfo || '未知错误,请稍后重试' }} + + + + + + + + + + + + + + + + + + 提示词 + + + {{ currentTask.prompt }} + + + + + + + 创建时间 + {{ formatTime(currentTask.creationTime) }} + + + 状态 + + 生成成功 + + + + + + 使用提示词 + + + 做参考图 + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/image/components/MyImages.vue b/Yi.Ai.Vue3/src/pages/chat/image/components/MyImages.vue new file mode 100644 index 00000000..b108efa7 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/image/components/MyImages.vue @@ -0,0 +1,699 @@ + + + + + + + + 我的图库 + + + + + + 筛选 + + + + + + + + + 筛选条件 + + + + + + + + + + + + + + + + + + + + + 进行中 + + + + + + + + 成功 + + + + + + + + 失败 + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + + + + 重置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + + + + 重置 + + + + + + + + + + + + + + + + + + 没有找到相关图片 + + + + + + + + + 加载中... + + + + + + - 到底了,没有更多图片了 - + + + + + + + + + + + + + + 生成中... + + + + + + 生成失败 + + + + + + + {{ currentTask.errorInfo || '未知错误,请稍后重试' }} + + + + + + + + + + + + + + + + + + 提示词 + + + {{ currentTask.prompt }} + + + + + + + 创建时间 + {{ formatTime(currentTask.creationTime) }} + + + 状态 + + + 成功 + + + 进行中 + + + 失败 + + + + 已发布 + + + + + + + + 使用提示词 + + + 做参考图 + + + + 发布到广场 + + + 已发布 + + + + + + + + + + + + + + {{ tag }} + + + + + New Tag + + + + + + + + + + 取消 + + 发布 + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/image/components/TaskCard.vue b/Yi.Ai.Vue3/src/pages/chat/image/components/TaskCard.vue new file mode 100644 index 00000000..29a76d57 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/image/components/TaskCard.vue @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + 加载失败 + + + + + + + + + + + + + + + + + + 生成中... + + + + + + 生成失败 + + + 等待中 + + + + + + + 查看详情 + + + + + + + + + + + + + + + + + + + + + + + + + 已发布 + + + + + + + + {{ task.prompt }} + + + + + + {{ tag }} + + + +{{ task.categories.length - 3 }} + + + + + + {{ formatTime(task.creationTime) }} + + {{ task.userName }} + + + 匿名用户 + + + + 完成 + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/image/index.vue b/Yi.Ai.Vue3/src/pages/chat/image/index.vue new file mode 100644 index 00000000..987f5bd4 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/image/index.vue @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/index.vue b/Yi.Ai.Vue3/src/pages/chat/index.vue index 933c9a65..f0e0c000 100644 --- a/Yi.Ai.Vue3/src/pages/chat/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/index.vue @@ -1,31 +1,305 @@ - - - - - + + + + + + AI聊天 + + + + + + + + + + + + + + + {{ item.label }} + + + + + + + + + + + + + + AI应用 + + + + + + + + - diff --git a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue index 44180051..09bd1c0f 100644 --- a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue @@ -1,168 +1,299 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue b/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue index 0ecb2ee1..2f7e0b97 100644 --- a/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/layouts/chatWithId/index.vue @@ -13,16 +13,26 @@ import { Sender } from 'vue-element-plus-x'; import { useRoute } from 'vue-router'; import { send } from '@/api'; import ModelSelect from '@/components/ModelSelect/index.vue'; -import { useGuideTourStore } from '@/stores'; +import Collapse from '@/layouts/components0/Header/components/Collapse.vue'; + +import CreateChat from '@/layouts/components0/Header/components/CreateChat.vue'; +import TitleEditing from '@/layouts/components0/Header/components/TitleEditing.vue'; +import { useDesignStore, useGuideTourStore } from '@/stores'; import { useChatStore } from '@/stores/modules/chat'; import { useFilesStore } from '@/stores/modules/files'; import { useModelStore } from '@/stores/modules/model'; +import { useSessionStore } from '@/stores/modules/session'; import { useUserStore } from '@/stores/modules/user'; import { getUserProfilePicture, systemProfilePicture } from '@/utils/user.ts'; import YMarkdown from '@/vue-element-plus-y/components/XMarkdown/index.vue'; import '@/styles/github-markdown.css'; import '@/styles/yixin-markdown.scss'; +// 新增的导入 +const designStore = useDesignStore(); +const sessionStore = useSessionStore(); +const currentSession = computed(() => sessionStore.currentSession); + type MessageItem = BubbleProps & { key: number; role: 'ai' | 'user' | 'assistant'; @@ -447,6 +457,31 @@ function handleImagePreview(url: string) { + + + + + + + + + + + + + + + + + + + + @@ -562,19 +597,30 @@ function handleImagePreview(url: string) { 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..318a79f2 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/console/channel/index.vue @@ -0,0 +1,550 @@ + + + + + + + + + 应用列表 + + 新建 + + + + + + + + {{ app.name }} + + + 详情 + + + 编辑 + + + 删除 + + + + + + + + + + + + + + 模型列表 + + + + 新建 + + + 刷新 + + + + + + + + + + + + + + + + + {{ row.isPremium ? '尊享' : '普通' }} + + + + + + + + + + 编辑 + + + 删除 + + + + + + + + + + + {{ appDetailData.name }} + {{ appDetailData.endpoint }} + {{ appDetailData.extraUrl || '-' }} + + + + {{ appDetailData.orderNum }} + {{ appDetailData.creationTime }} + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 保存 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + 保存 + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/console/index.vue b/Yi.Ai.Vue3/src/pages/console/index.vue new file mode 100644 index 00000000..5758085d --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/console/index.vue @@ -0,0 +1,332 @@ + + + + + + + + + 控制台 + + + + + + + + + + + + + + + {{ item.label }} + + + + + + + + + + + + + + 控制台 + + + + + + + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/modelLibrary/index.vue b/Yi.Ai.Vue3/src/pages/modelLibrary/index.vue index df8dc070..a7b76079 100644 --- a/Yi.Ai.Vue3/src/pages/modelLibrary/index.vue +++ b/Yi.Ai.Vue3/src/pages/modelLibrary/index.vue @@ -1,6 +1,6 @@
开启您的专属惊喜权益
完成每日任务,领取额外尊享包 Token 奖励,可累加重复
+ 完成每日任务,领取额外尊享包 Token 奖励,可累加重复 +
+ 正在绘制您的想象... +
+ 请稍候,这可能需要几秒钟 +
+ 生成失败 +
+ 当前提示词 +
+ {{ currentTask.prompt }} +
+ 在左侧配置您的创意参数,点击生成按钮,见证AI的奇迹。 +
暂无图片,快去生成一张吧!
没有找到相关图片
+ {{ task.prompt }} +