Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9550ed57c0 | ||
|
|
19b27d8e9a | ||
|
|
0b30dbb8de | ||
|
|
fadaa0d129 | ||
|
|
6863b773b4 | ||
|
|
82d97ab0b4 | ||
|
|
de94bb260b | ||
|
|
016f930021 | ||
|
|
9b7d98773b | ||
|
|
6988dd224f | ||
|
|
c9b5418a70 | ||
|
|
74d56ced8a | ||
|
|
790fca50f3 | ||
|
|
728b5958f3 | ||
|
|
5a39330fdb | ||
|
|
70c7e0c331 | ||
|
|
67b215ce7a | ||
|
|
d05324cd12 | ||
|
|
33937703c7 | ||
|
|
7f809e0718 | ||
|
|
6d54c650f0 | ||
|
|
11cbb1b612 | ||
|
|
3b6887dc2e | ||
|
|
6af3fb44f4 | ||
|
|
f57b5befd7 | ||
|
|
dbc6b8cf5e | ||
|
|
007a4c223a | ||
|
|
ab2c11e05c | ||
|
|
ec382995b4 | ||
|
|
7a38526ab3 | ||
|
|
4441244575 | ||
|
|
adafb65221 | ||
|
|
74e936c6d3 | ||
|
|
36aa29f9f1 | ||
|
|
d4fcbdc390 | ||
|
|
ca43879cc3 | ||
|
|
9b5826a6b1 | ||
|
|
485f19572b | ||
|
|
2845f03250 | ||
|
|
1ada6360d4 | ||
|
|
21b7ef4d74 | ||
|
|
9a87b41027 | ||
|
|
886cc3155f | ||
|
|
020ad797f2 | ||
|
|
1d5bca773f | ||
|
|
6b86957556 | ||
|
|
caa90cc227 | ||
|
|
87c93534a5 | ||
|
|
b8c79ac61c | ||
|
|
2db8d6e699 | ||
|
|
0983837ff7 | ||
|
|
efa948154f | ||
|
|
e8c1111cbc | ||
|
|
c9c92dcf97 | ||
|
|
f2c2c60127 | ||
|
|
d280cc6d35 | ||
|
|
a1e38234a7 | ||
|
|
ace5a9a1ec | ||
|
|
4ce77ececc | ||
|
|
be9442c113 | ||
|
|
5895f9e794 | ||
|
|
b0d1820919 | ||
|
|
8b183e289c | ||
|
|
09ecddb552 | ||
|
|
127639c20e | ||
|
|
9a0dc6f089 | ||
|
|
0c8f01c00a | ||
|
|
c2f074cb08 | ||
|
|
6b6ddcf550 | ||
|
|
d9f5f1f050 | ||
|
|
7ed7201d10 | ||
|
|
a1ddd1c3e2 | ||
|
|
4800543a77 | ||
|
|
4090046946 | ||
|
|
3a19c75ca1 | ||
|
|
a67af0485e | ||
|
|
5de968f6c7 | ||
|
|
1edb92f6e8 | ||
|
|
2b9bbca400 | ||
|
|
3bd1a977f7 | ||
|
|
d2b5704294 | ||
|
|
611c5ce59a | ||
|
|
fc61b67fc0 | ||
|
|
a2da4c36fe | ||
|
|
5e37859157 | ||
|
|
6f316d3e51 | ||
|
|
53d70ef9d7 | ||
|
|
a9a9e45b7c | ||
|
|
629012d32a | ||
|
|
5f2133eb50 | ||
|
|
ad85890907 | ||
|
|
87518af562 | ||
|
|
62b26bc2a4 | ||
|
|
f237137791 | ||
|
|
0d4d847e08 | ||
|
|
12f1854d31 | ||
|
|
73f5d43ada | ||
|
|
551122de10 | ||
|
|
d092254822 | ||
|
|
1027006e63 | ||
|
|
2544c01e9d | ||
|
|
2f1f25ca37 | ||
|
|
5489f33d54 | ||
|
|
6665d2fb2e | ||
|
|
b5ff6c141c | ||
|
|
f1e8b66689 | ||
|
|
c727aeed99 | ||
|
|
40aa47bb1e | ||
|
|
40234343ff | ||
|
|
00a9bd00e5 | ||
|
|
55c17211d8 | ||
|
|
db7dc0e9a7 | ||
|
|
1727107190 | ||
|
|
e680ac4cac | ||
|
|
6053899516 | ||
|
|
5157eac35c | ||
|
|
537104037b | ||
|
|
29c1768ded | ||
|
|
b4a97e8b09 | ||
|
|
6101ea46d3 | ||
|
|
cad145f067 | ||
|
|
9d8f8b3125 | ||
|
|
4f70356a5c | ||
|
|
69a8b47245 | ||
|
|
88225a97b8 | ||
|
|
c697d12f8b | ||
|
|
7bb8f52813 | ||
|
|
b84f385d2d | ||
|
|
450e023b3b | ||
|
|
9f3b9fc513 | ||
|
|
9721b8bd74 | ||
|
|
bd30a40a6f | ||
|
|
9ec9ace8e2 | ||
|
|
a437d55f9f | ||
|
|
d75a734bc1 | ||
|
|
9c058e9545 | ||
|
|
accbaf3ecb | ||
|
|
f8f2d7568c | ||
|
|
158226601b | ||
|
|
63aa8d9536 | ||
|
|
0147457329 | ||
|
|
1d47b26d0d | ||
|
|
cc1bc6dd82 | ||
|
|
922596c128 | ||
|
|
f7ebe44fb6 | ||
|
|
d7f4e49c2a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -280,3 +280,4 @@ database_backup
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
|
components.d.ts
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建公告输入
|
||||||
|
/// </summary>
|
||||||
|
public class AnnouncementCreateInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标题
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "标题不能为空")]
|
||||||
|
[StringLength(200, ErrorMessage = "标题不能超过200个字符")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内容列表
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "内容不能为空")]
|
||||||
|
[MinLength(1, ErrorMessage = "至少需要一条内容")]
|
||||||
|
public List<string> Content { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "备注不能超过500个字符")]
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片url
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||||
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "开始时间不能为空")]
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 活动结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公告类型
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "公告类型不能为空")]
|
||||||
|
public AnnouncementTypeEnum Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳转链接
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "跳转链接不能超过500个字符")]
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公告 DTO(后台管理使用)
|
||||||
|
/// </summary>
|
||||||
|
public class AnnouncementDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 公告ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标题
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内容列表
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Content { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片url
|
||||||
|
/// </summary>
|
||||||
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 活动结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公告类型
|
||||||
|
/// </summary>
|
||||||
|
public AnnouncementTypeEnum Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳转链接
|
||||||
|
/// </summary>
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreationTime { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取公告列表输入
|
||||||
|
/// </summary>
|
||||||
|
public class AnnouncementGetListInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 搜索关键字
|
||||||
|
/// </summary>
|
||||||
|
public string? SearchKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳过数量
|
||||||
|
/// </summary>
|
||||||
|
public int SkipCount { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最大结果数量
|
||||||
|
/// </summary>
|
||||||
|
public int MaxResultCount { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公告类型
|
||||||
|
/// </summary>
|
||||||
|
public AnnouncementTypeEnum? Type { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新公告输入
|
||||||
|
/// </summary>
|
||||||
|
public class AnnouncementUpdateInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 公告ID
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "公告ID不能为空")]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标题
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "标题不能为空")]
|
||||||
|
[StringLength(200, ErrorMessage = "标题不能超过200个字符")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内容列表
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "内容不能为空")]
|
||||||
|
[MinLength(1, ErrorMessage = "至少需要一条内容")]
|
||||||
|
public List<string> Content { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 备注
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "备注不能超过500个字符")]
|
||||||
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图片url
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "图片URL不能超过500个字符")]
|
||||||
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始时间
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "开始时间不能为空")]
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 活动结束时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? EndTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 公告类型
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "公告类型不能为空")]
|
||||||
|
public AnnouncementTypeEnum Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 跳转链接
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(500, ErrorMessage = "跳转链接不能超过500个字符")]
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Channel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AI应用快捷配置DTO
|
||||||
|
/// </summary>
|
||||||
|
public class AiAppShortcutDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用终结点
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 额外URL
|
||||||
|
/// </summary>
|
||||||
|
public string? ExtraUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用Key
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreationTime { get; set; }
|
||||||
|
}
|
||||||
@@ -93,4 +93,9 @@ public class AiModelCreateInput
|
|||||||
/// 是否为尊享模型
|
/// 是否为尊享模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPremium { get; set; }
|
public bool IsPremium { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,4 +81,9 @@ public class AiModelDto
|
|||||||
/// 是否为尊享模型
|
/// 是否为尊享模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPremium { get; set; }
|
public bool IsPremium { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,4 +99,9 @@ public class AiModelUpdateInput
|
|||||||
/// 是否为尊享模型
|
/// 是否为尊享模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPremium { get; set; }
|
public bool IsPremium { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息创建结果输出
|
||||||
|
/// </summary>
|
||||||
|
public class MessageCreatedOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 消息类型
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public ChatMessageTypeEnum TypeEnum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息类型
|
||||||
|
/// </summary>
|
||||||
|
public string Type => TypeEnum.ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息ID
|
||||||
|
/// </summary>
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreationTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消息类型枚举
|
||||||
|
/// </summary>
|
||||||
|
public enum ChatMessageTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户消息
|
||||||
|
/// </summary>
|
||||||
|
UserMessage,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统消息
|
||||||
|
/// </summary>
|
||||||
|
SystemMessage
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务配置缓存DTO
|
||||||
|
/// </summary>
|
||||||
|
public class DailyTaskConfigCacheDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务配置列表
|
||||||
|
/// </summary>
|
||||||
|
public List<DailyTaskConfigItem> Tasks { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日任务配置项
|
||||||
|
/// </summary>
|
||||||
|
public class DailyTaskConfigItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 任务等级
|
||||||
|
/// </summary>
|
||||||
|
public int Level { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 需要消耗的Token数量
|
||||||
|
/// </summary>
|
||||||
|
public long RequiredTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 奖励的Token数量
|
||||||
|
/// </summary>
|
||||||
|
public long RewardTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -7,4 +7,18 @@ public class MessageGetListInput:PagedAllResultRequestDto
|
|||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public Guid SessionId { get; set; }
|
public Guid SessionId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessageDeleteInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 要删除的消息Id列表
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public List<Guid> Ids { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否同时隐藏后续消息(同一会话中时间大于当前消息的所有消息)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDeleteSubsequent { get; set; } = false;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
public class ModelGetListOutput
|
public class ModelGetListOutput
|
||||||
{
|
{
|
||||||
@@ -31,4 +33,23 @@ public class ModelGetListOutput
|
|||||||
/// 是否为尊享包
|
/// 是否为尊享包
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPremiumPackage { get; set; }
|
public bool IsPremiumPackage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否免费模型
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型Api类型,现支持同一个模型id,多种接口格式
|
||||||
|
/// </summary>
|
||||||
|
public ModelApiTypeEnum ModelApiType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型图标URL
|
||||||
|
/// </summary>
|
||||||
|
public string? IconUrl { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 供应商分组名称(如:OpenAI、Anthropic、Google等)
|
||||||
|
/// </summary>
|
||||||
|
public string? ProviderName { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜查询输入
|
||||||
|
/// </summary>
|
||||||
|
public class RankingGetListInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜类型:0-模型,1-工具,不传返回全部
|
||||||
|
/// </summary>
|
||||||
|
public RankingTypeEnum? Type { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜项DTO
|
||||||
|
/// </summary>
|
||||||
|
public class RankingItemDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logo地址
|
||||||
|
/// </summary>
|
||||||
|
public string? LogoUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得分
|
||||||
|
/// </summary>
|
||||||
|
public decimal Score { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供者
|
||||||
|
/// </summary>
|
||||||
|
public string Provider { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜类型
|
||||||
|
/// </summary>
|
||||||
|
public RankingTypeEnum Type { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Yi.Framework.Ddd.Application.Contracts;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 充值记录查询输入
|
||||||
|
/// </summary>
|
||||||
|
public class RechargeGetListInput : PagedAllResultRequestDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否免费(充值金额等于0)
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsFree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 充值金额最小值
|
||||||
|
/// </summary>
|
||||||
|
public decimal? MinRechargeAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 充值金额最大值
|
||||||
|
/// </summary>
|
||||||
|
public decimal? MaxRechargeAmount { get; set; }
|
||||||
|
}
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
public class SessionCreateAndUpdateInput
|
public class SessionCreateAndUpdateInput
|
||||||
{
|
{
|
||||||
public string SessionTitle { get; set; }
|
public string SessionTitle { get; set; }
|
||||||
public string SessionContent { get; set; }
|
public string SessionContent { get; set; }
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话类型
|
||||||
|
/// </summary>
|
||||||
|
public SessionTypeEnum SessionType { get; set; } = SessionTypeEnum.Chat;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
@@ -7,4 +8,9 @@ public class SessionDto : FullAuditedEntityDto<Guid>
|
|||||||
public string SessionTitle { get; set; }
|
public string SessionTitle { get; set; }
|
||||||
public string SessionContent { get; set; }
|
public string SessionContent { get; set; }
|
||||||
public string Remark { get; set; }
|
public string Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话类型
|
||||||
|
/// </summary>
|
||||||
|
public SessionTypeEnum SessionType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
using Yi.Framework.Ddd.Application.Contracts;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
using Yi.Framework.Ddd.Application.Contracts;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
public class SessionGetListInput:PagedAllResultRequestDto
|
public class SessionGetListInput : PagedAllResultRequestDto
|
||||||
{
|
{
|
||||||
public string? SessionTitle { get; set; }
|
public string? SessionTitle { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话类型
|
||||||
|
/// </summary>
|
||||||
|
public SessionTypeEnum? SessionType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型Token统计DTO
|
||||||
|
/// </summary>
|
||||||
|
public class ModelTokenStatisticsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型ID
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型名称
|
||||||
|
/// </summary>
|
||||||
|
public string ModelName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token消耗量
|
||||||
|
/// </summary>
|
||||||
|
public long Tokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token消耗量(万)
|
||||||
|
/// </summary>
|
||||||
|
public decimal TokensInWan { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用次数
|
||||||
|
/// </summary>
|
||||||
|
public long Count { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1亿Token成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal CostPerHundredMillion { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 利润统计输入
|
||||||
|
/// </summary>
|
||||||
|
public class ProfitStatisticsInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 当前成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal CurrentCost { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 利润统计输出
|
||||||
|
/// </summary>
|
||||||
|
public class ProfitStatisticsOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 日期
|
||||||
|
/// </summary>
|
||||||
|
public string Date { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包已消耗Token数(单位:个)
|
||||||
|
/// </summary>
|
||||||
|
public long TotalUsedTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包已消耗Token数(单位:亿)
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalUsedTokensInHundredMillion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包剩余库存Token数(单位:个)
|
||||||
|
/// </summary>
|
||||||
|
public long TotalRemainingTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尊享包剩余库存Token数(单位:亿)
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalRemainingTokensInHundredMillion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal CurrentCost { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1亿Token成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal CostPerHundredMillion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalCost { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 总收益(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal TotalRevenue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 利润率(%)
|
||||||
|
/// </summary>
|
||||||
|
public decimal ProfitRate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按200售价计算的成本(RMB)
|
||||||
|
/// </summary>
|
||||||
|
public decimal CostAt200Price { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token统计输入
|
||||||
|
/// </summary>
|
||||||
|
public class TokenStatisticsInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 指定日期(当天零点)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token统计输出
|
||||||
|
/// </summary>
|
||||||
|
public class TokenStatisticsOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 日期
|
||||||
|
/// </summary>
|
||||||
|
public string Date { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型统计列表
|
||||||
|
/// </summary>
|
||||||
|
public List<ModelTokenStatisticsDto> ModelStatistics { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每小时Token使用量统计DTO(柱状图)
|
||||||
|
/// </summary>
|
||||||
|
public class HourlyTokenUsageDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 小时时间点
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Hour { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 该小时总Token消耗量
|
||||||
|
/// </summary>
|
||||||
|
public long TotalTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 各模型Token消耗明细
|
||||||
|
/// </summary>
|
||||||
|
public List<ModelTokenBreakdownDto> ModelBreakdown { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型今日使用量统计DTO(卡片列表)
|
||||||
|
/// </summary>
|
||||||
|
public class ModelTodayUsageDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型ID
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 今日使用次数
|
||||||
|
/// </summary>
|
||||||
|
public int UsageCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 今日消耗总Token数
|
||||||
|
/// </summary>
|
||||||
|
public long TotalTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型图标URL
|
||||||
|
/// </summary>
|
||||||
|
public string? IconUrl { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型Token堆叠数据DTO(用于柱状图)
|
||||||
|
/// </summary>
|
||||||
|
public class ModelTokenBreakdownDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型ID
|
||||||
|
/// </summary>
|
||||||
|
public string ModelId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Token消耗量
|
||||||
|
/// </summary>
|
||||||
|
public long Tokens { get; set; }
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
@@ -8,8 +9,42 @@ namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
|||||||
public interface IAnnouncementService
|
public interface IAnnouncementService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取公告信息
|
/// 获取公告信息(前端首页使用)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>公告信息</returns>
|
/// <returns>公告信息</returns>
|
||||||
Task<List<AnnouncementLogDto>> GetAsync();
|
Task<List<AnnouncementLogDto>> GetAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取公告列表(后台管理使用)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询参数</param>
|
||||||
|
/// <returns>分页公告列表</returns>
|
||||||
|
Task<PagedResultDto<AnnouncementDto>> GetListAsync(AnnouncementGetListInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据ID获取公告
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">公告ID</param>
|
||||||
|
/// <returns>公告详情</returns>
|
||||||
|
Task<AnnouncementDto> GetByIdAsync(Guid id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建公告
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">创建输入</param>
|
||||||
|
/// <returns>创建的公告</returns>
|
||||||
|
Task<AnnouncementDto> CreateAsync(AnnouncementCreateInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新公告
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">更新输入</param>
|
||||||
|
/// <returns>更新后的公告</returns>
|
||||||
|
Task<AnnouncementDto> UpdateAsync(AnnouncementUpdateInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除公告
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">公告ID</param>
|
||||||
|
Task DeleteAsync(Guid id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,4 +83,14 @@ public interface IChannelService
|
|||||||
Task DeleteModelAsync(Guid id);
|
Task DeleteModelAsync(Guid id);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region AI应用快捷配置
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取AI应用快捷配置列表
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>快捷配置列表</returns>
|
||||||
|
Task<List<AiAppShortcutDto>> GetAppShortcutListAsync();
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IRankingService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取排行榜列表(全量返回)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询条件</param>
|
||||||
|
/// <returns>排行榜列表</returns>
|
||||||
|
Task<List<RankingItemDto>> GetListAsync(RankingGetListInput input);
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
|
|
||||||
public interface IRechargeService
|
public interface IRechargeService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 查询已登录的账户充值记录(分页)
|
||||||
|
/// </summary>
|
||||||
|
Task<PagedResultDto<RechargeGetListOutput>> GetListByAccountAsync(RechargeGetListInput input);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 移除用户vip及角色
|
/// 移除用户vip及角色
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统使用量统计服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface ISystemUsageStatisticsService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取利润统计数据
|
||||||
|
/// </summary>
|
||||||
|
Task<ProfitStatisticsOutput> GetProfitStatisticsAsync(ProfitStatisticsInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定日期各模型Token统计
|
||||||
|
/// </summary>
|
||||||
|
Task<TokenStatisticsOutput> GetTokenStatisticsAsync(TokenStatisticsInput input);
|
||||||
|
}
|
||||||
@@ -24,4 +24,16 @@ public interface IUsageStatisticsService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>尊享服务Token用量统计</returns>
|
/// <returns>尊享服务Token用量统计</returns>
|
||||||
Task<PremiumTokenUsageDto> GetPremiumTokenUsageAsync();
|
Task<PremiumTokenUsageDto> GetPremiumTokenUsageAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前用户近24小时每小时Token消耗统计(柱状图)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>每小时Token使用量列表,包含各模型堆叠数据</returns>
|
||||||
|
Task<List<HourlyTokenUsageDto>> GetLast24HoursTokenUsageAsync(UsageStatisticsGetInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前用户今日各模型使用量统计(卡片列表)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>模型今日使用量列表,包含使用次数和总Token</returns>
|
||||||
|
Task<List<ModelTodayUsageDto>> GetTodayModelUsageAsync(UsageStatisticsGetInput input);
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,13 @@ public class ImageGenerationJob : AsyncBackgroundJob<ImageGenerationJobArgs>, IT
|
|||||||
{
|
{
|
||||||
contents = new[]
|
contents = new[]
|
||||||
{
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
role = "user", parts = new List<object>
|
||||||
|
{
|
||||||
|
new { text = "我只要图片,直接生成图片,不要询问我" }
|
||||||
|
}
|
||||||
|
},
|
||||||
new { role = "user", parts }
|
new { role = "user", parts }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using Medallion.Threading;
|
using Medallion.Threading;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.DailyTask;
|
||||||
using Yi.Framework.AiHub.Domain.Entities;
|
using Yi.Framework.AiHub.Domain.Entities;
|
||||||
@@ -25,27 +27,33 @@ public class DailyTaskService : ApplicationService
|
|||||||
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||||||
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||||
private readonly ILogger<DailyTaskService> _logger;
|
private readonly ILogger<DailyTaskService> _logger;
|
||||||
|
private readonly IDistributedCache<DailyTaskConfigCacheDto> _taskConfigCache;
|
||||||
private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService<IDistributedLockProvider>();
|
private IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetRequiredService<IDistributedLockProvider>();
|
||||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||||
// 任务配置
|
|
||||||
private readonly Dictionary<int, (long RequiredTokens, long RewardTokens, string Name, string Description)>
|
private const string TaskConfigCacheKey = "AiHub:DailyTaskConfig";
|
||||||
_taskConfigs = new()
|
|
||||||
{
|
// 默认任务配置(当Redis中没有配置时使用)
|
||||||
{ 1, (10000000, 1000000, "尊享包1000w token任务", "累积使用尊享包 1000w token") }, // 1000w消耗 -> 100w奖励
|
private static readonly List<DailyTaskConfigItem> DefaultTaskConfigs = new()
|
||||||
{ 2, (30000000, 2000000, "尊享包3000w token任务", "累积使用尊享包 3000w token") } // 3000w消耗 -> 200w奖励
|
{
|
||||||
};
|
new DailyTaskConfigItem { Level = 1, RequiredTokens = 10000000, RewardTokens = 1000000, Name = "尊享包1000w token任务", Description = "累积使用尊享包 1000w token" },
|
||||||
|
new DailyTaskConfigItem { Level = 2, RequiredTokens = 30000000, RewardTokens = 2000000, Name = "尊享包3000w token任务", Description = "累积使用尊享包 3000w token" }
|
||||||
|
};
|
||||||
|
|
||||||
public DailyTaskService(
|
public DailyTaskService(
|
||||||
ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> dailyTaskRepository,
|
ISqlSugarRepository<DailyTaskRewardRecordAggregateRoot> dailyTaskRepository,
|
||||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
||||||
ILogger<DailyTaskService> logger, ISqlSugarRepository<AiModelEntity> aiModelRepository)
|
ILogger<DailyTaskService> logger,
|
||||||
|
ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
||||||
|
IDistributedCache<DailyTaskConfigCacheDto> taskConfigCache)
|
||||||
{
|
{
|
||||||
_dailyTaskRepository = dailyTaskRepository;
|
_dailyTaskRepository = dailyTaskRepository;
|
||||||
_messageRepository = messageRepository;
|
_messageRepository = messageRepository;
|
||||||
_premiumPackageRepository = premiumPackageRepository;
|
_premiumPackageRepository = premiumPackageRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_aiModelRepository = aiModelRepository;
|
_aiModelRepository = aiModelRepository;
|
||||||
|
_taskConfigCache = taskConfigCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,20 +65,23 @@ public class DailyTaskService : ApplicationService
|
|||||||
var userId = CurrentUser.GetId();
|
var userId = CurrentUser.GetId();
|
||||||
var today = DateTime.Today;
|
var today = DateTime.Today;
|
||||||
|
|
||||||
// 1. 统计今日尊享包Token消耗量
|
// 1. 获取任务配置
|
||||||
|
var taskConfigs = await GetTaskConfigsAsync();
|
||||||
|
|
||||||
|
// 2. 统计今日尊享包Token消耗量
|
||||||
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
||||||
|
|
||||||
// 2. 查询今日已领取的任务
|
// 3. 查询今日已领取的任务
|
||||||
var claimedTasks = await _dailyTaskRepository._DbQueryable
|
var claimedTasks = await _dailyTaskRepository._DbQueryable
|
||||||
.Where(x => x.UserId == userId && x.TaskDate == today)
|
.Where(x => x.UserId == userId && x.TaskDate == today)
|
||||||
.Select(x => new { x.TaskLevel, x.IsRewarded })
|
.Select(x => new { x.TaskLevel, x.IsRewarded })
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// 3. 构建任务列表
|
// 4. 构建任务列表
|
||||||
var tasks = new List<DailyTaskItem>();
|
var tasks = new List<DailyTaskItem>();
|
||||||
foreach (var (level, config) in _taskConfigs)
|
foreach (var config in taskConfigs)
|
||||||
{
|
{
|
||||||
var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == level);
|
var claimed = claimedTasks.FirstOrDefault(x => x.TaskLevel == config.Level);
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
if (claimed != null && claimed.IsRewarded)
|
if (claimed != null && claimed.IsRewarded)
|
||||||
@@ -92,7 +103,7 @@ public class DailyTaskService : ApplicationService
|
|||||||
|
|
||||||
tasks.Add(new DailyTaskItem
|
tasks.Add(new DailyTaskItem
|
||||||
{
|
{
|
||||||
Level = level,
|
Level = config.Level,
|
||||||
Name = config.Name,
|
Name = config.Name,
|
||||||
Description = config.Description,
|
Description = config.Description,
|
||||||
RequiredTokens = config.RequiredTokens,
|
RequiredTokens = config.RequiredTokens,
|
||||||
@@ -120,17 +131,20 @@ public class DailyTaskService : ApplicationService
|
|||||||
//自旋等待,防抖
|
//自旋等待,防抖
|
||||||
await using var handle =
|
await using var handle =
|
||||||
await DistributedLock.AcquireLockAsync($"Yi:AiHub:ClaimTaskRewardLock:{userId}");
|
await DistributedLock.AcquireLockAsync($"Yi:AiHub:ClaimTaskRewardLock:{userId}");
|
||||||
|
|
||||||
|
|
||||||
var today = DateTime.Today;
|
var today = DateTime.Today;
|
||||||
|
|
||||||
// 1. 验证任务等级
|
// 1. 获取任务配置
|
||||||
if (!_taskConfigs.TryGetValue(input.TaskLevel, out var taskConfig))
|
var taskConfigs = await GetTaskConfigsAsync();
|
||||||
|
var taskConfig = taskConfigs.FirstOrDefault(x => x.Level == input.TaskLevel);
|
||||||
|
|
||||||
|
// 2. 验证任务等级
|
||||||
|
if (taskConfig == null)
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}");
|
throw new UserFriendlyException($"无效的任务等级: {input.TaskLevel}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 检查是否已领取
|
// 3. 检查是否已领取
|
||||||
var existingRecord = await _dailyTaskRepository._DbQueryable
|
var existingRecord = await _dailyTaskRepository._DbQueryable
|
||||||
.Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel)
|
.Where(x => x.UserId == userId && x.TaskDate == today && x.TaskLevel == input.TaskLevel)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
@@ -140,7 +154,7 @@ public class DailyTaskService : ApplicationService
|
|||||||
throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!");
|
throw new UserFriendlyException("今日该任务奖励已领取,请明天再来!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 验证今日Token消耗是否达标
|
// 4. 验证今日Token消耗是否达标
|
||||||
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
var todayConsumed = await GetTodayPremiumTokenConsumptionAsync(userId, today);
|
||||||
if (todayConsumed < taskConfig.RequiredTokens)
|
if (todayConsumed < taskConfig.RequiredTokens)
|
||||||
{
|
{
|
||||||
@@ -148,18 +162,17 @@ public class DailyTaskService : ApplicationService
|
|||||||
$"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w");
|
$"Token消耗未达标!需要 {taskConfig.RequiredTokens / 10000}w,当前 {todayConsumed / 10000}w");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 创建奖励包(使用 PremiumPackageManager)
|
// 5. 创建奖励包
|
||||||
|
|
||||||
var premiumPackage =
|
var premiumPackage =
|
||||||
new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}")
|
new PremiumPackageAggregateRoot(userId, taskConfig.RewardTokens, $"每日任务:{taskConfig.Name}")
|
||||||
{
|
{
|
||||||
PurchaseAmount = 0, // 奖励不需要付费
|
PurchaseAmount = 0,
|
||||||
Remark = $"{today:yyyy-MM-dd} 每日任务奖励"
|
Remark = $"{today:yyyy-MM-dd} 每日任务奖励"
|
||||||
};
|
};
|
||||||
|
|
||||||
await _premiumPackageRepository.InsertAsync(premiumPackage);
|
await _premiumPackageRepository.InsertAsync(premiumPackage);
|
||||||
|
|
||||||
// 5. 记录领取记录
|
// 6. 记录领取记录
|
||||||
var record = new DailyTaskRewardRecordAggregateRoot(userId, input.TaskLevel, today, taskConfig.RewardTokens)
|
var record = new DailyTaskRewardRecordAggregateRoot(userId, input.TaskLevel, today, taskConfig.RewardTokens)
|
||||||
{
|
{
|
||||||
Remark = $"完成任务{input.TaskLevel},名称:{taskConfig.Name},消耗 {todayConsumed / 10000}w token"
|
Remark = $"完成任务{input.TaskLevel},名称:{taskConfig.Name},消耗 {todayConsumed / 10000}w token"
|
||||||
@@ -197,4 +210,21 @@ public class DailyTaskService : ApplicationService
|
|||||||
|
|
||||||
return totalTokens;
|
return totalTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从Redis获取任务配置,如果不存在则写入默认配置
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<DailyTaskConfigItem>> GetTaskConfigsAsync()
|
||||||
|
{
|
||||||
|
var cacheData = await _taskConfigCache.GetOrAddAsync(
|
||||||
|
TaskConfigCacheKey,
|
||||||
|
() => Task.FromResult(new DailyTaskConfigCacheDto { Tasks = DefaultTaskConfigs }),
|
||||||
|
() => new DistributedCacheEntryOptions
|
||||||
|
{
|
||||||
|
// 不设置过期时间,永久缓存,需要手动更新
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return cacheData?.Tasks ?? DefaultTaskConfigs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ using Yi.Framework.Rbac.Application.Contracts.IServices;
|
|||||||
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
using Yi.Framework.Rbac.Domain.Shared.Dtos;
|
||||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
using Yi.Framework.AiHub.Domain.Extensions;
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Application.Services;
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ public class AiAccountService : ApplicationService
|
|||||||
if (output.IsVip)
|
if (output.IsVip)
|
||||||
{
|
{
|
||||||
var recharges = await _rechargeRepository._DbQueryable
|
var recharges = await _rechargeRepository._DbQueryable
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId && x.RechargeType == RechargeTypeEnum.Vip)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (recharges.Any())
|
if (recharges.Any())
|
||||||
@@ -81,162 +82,4 @@ public class AiAccountService : ApplicationService
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取利润统计数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentCost">当前成本(RMB)</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Authorize]
|
|
||||||
[HttpGet("account/profit-statistics")]
|
|
||||||
public async Task<string> GetProfitStatisticsAsync([FromQuery] decimal currentCost)
|
|
||||||
{
|
|
||||||
if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc")
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException("您暂无权限访问");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 获取尊享包总消耗和剩余库存
|
|
||||||
var premiumPackages = await _premiumPackageRepository._DbQueryable.ToListAsync();
|
|
||||||
long totalUsedTokens = premiumPackages.Sum(p => p.UsedTokens);
|
|
||||||
long totalRemainingTokens = premiumPackages.Sum(p => p.RemainingTokens);
|
|
||||||
|
|
||||||
// 2. 计算1亿Token成本
|
|
||||||
decimal costPerHundredMillion = totalUsedTokens > 0
|
|
||||||
? currentCost / (totalUsedTokens / 100000000m)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 3. 计算总成本(剩余+已使用的总成本)
|
|
||||||
long totalTokens = totalUsedTokens + totalRemainingTokens;
|
|
||||||
decimal totalCost = totalTokens > 0
|
|
||||||
? (totalTokens / 100000000m) * costPerHundredMillion
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 4. 获取总收益(RechargeType=PremiumPackage的充值金额总和)
|
|
||||||
decimal totalRevenue = await _rechargeRepository._DbQueryable
|
|
||||||
.Where(x => x.RechargeType == Domain.Shared.Enums.RechargeTypeEnum.PremiumPackage)
|
|
||||||
.SumAsync(x => x.RechargeAmount);
|
|
||||||
|
|
||||||
// 5. 计算利润率
|
|
||||||
decimal profitRate = totalCost > 0
|
|
||||||
? (totalRevenue / totalCost - 1) * 100
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 6. 按200售价计算成本
|
|
||||||
decimal costAt200Price = totalRevenue > 0
|
|
||||||
? (totalCost / totalRevenue) * 200
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 7. 格式化输出
|
|
||||||
var today = DateTime.Now;
|
|
||||||
string dayOfWeek = today.ToString("dddd", new System.Globalization.CultureInfo("zh-CN"));
|
|
||||||
string weekDay = dayOfWeek switch
|
|
||||||
{
|
|
||||||
"星期一" => "周1",
|
|
||||||
"星期二" => "周2",
|
|
||||||
"星期三" => "周3",
|
|
||||||
"星期四" => "周4",
|
|
||||||
"星期五" => "周5",
|
|
||||||
"星期六" => "周6",
|
|
||||||
"星期日" => "周日",
|
|
||||||
_ => dayOfWeek
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = $@"{today:M月d日} {weekDay}
|
|
||||||
尊享包已消耗({totalUsedTokens / 100000000m:F2}亿){totalUsedTokens}
|
|
||||||
尊享包剩余库存({totalRemainingTokens / 100000000m:F2}亿){totalRemainingTokens}
|
|
||||||
当前成本:{currentCost:F2}RMB
|
|
||||||
1亿Token成本:{costPerHundredMillion:F2} RMB=1亿 Token
|
|
||||||
总成本:{totalCost:F2} RMB
|
|
||||||
总收益:{totalRevenue:F2}RMB
|
|
||||||
利润率: {profitRate:F1}%
|
|
||||||
按200售价来算,成本在{costAt200Price:F2}";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TokenStatisticsInput
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 指定日期(当天零点)
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 模型Id -> 1亿Token成本(RMB)
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, decimal> ModelCosts { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定日期各模型Token统计
|
|
||||||
/// </summary>
|
|
||||||
[Authorize]
|
|
||||||
[HttpPost("account/token-statistics")]
|
|
||||||
public async Task<string> GetTokenStatisticsAsync([FromBody] TokenStatisticsInput input)
|
|
||||||
{
|
|
||||||
if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc")
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException("您暂无权限访问");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.ModelCosts is null || input.ModelCosts.Count == 0)
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException("请提供模型成本配置");
|
|
||||||
}
|
|
||||||
|
|
||||||
var day = input.Date.Date;
|
|
||||||
var nextDay = day.AddDays(1);
|
|
||||||
var modelIds = input.ModelCosts.Keys.ToList();
|
|
||||||
|
|
||||||
var modelStats = await _messageRepository._DbQueryable
|
|
||||||
.Where(x => modelIds.Contains(x.ModelId))
|
|
||||||
.Where(x => x.CreationTime >= day && x.CreationTime < nextDay)
|
|
||||||
.Where(x => x.Role == "system")
|
|
||||||
.GroupBy(x => x.ModelId)
|
|
||||||
.Select(x => new
|
|
||||||
{
|
|
||||||
ModelId = x.ModelId,
|
|
||||||
Tokens = SqlFunc.AggregateSum(x.TokenUsage.TotalTokenCount),
|
|
||||||
Count = SqlFunc.AggregateCount(x.Id)
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var modelStatDict = modelStats.ToDictionary(x => x.ModelId, x => x);
|
|
||||||
|
|
||||||
string weekDay = day.ToString("dddd", new CultureInfo("zh-CN")) switch
|
|
||||||
{
|
|
||||||
"星期一" => "周1",
|
|
||||||
"星期二" => "周2",
|
|
||||||
"星期三" => "周3",
|
|
||||||
"星期四" => "周4",
|
|
||||||
"星期五" => "周5",
|
|
||||||
"星期六" => "周6",
|
|
||||||
"星期日" => "周日",
|
|
||||||
_ => day.ToString("dddd", new CultureInfo("zh-CN"))
|
|
||||||
};
|
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.AppendLine($"{day:M月d日} {weekDay}");
|
|
||||||
|
|
||||||
foreach (var kvp in input.ModelCosts)
|
|
||||||
{
|
|
||||||
var modelId = kvp.Key;
|
|
||||||
var cost = kvp.Value;
|
|
||||||
|
|
||||||
modelStatDict.TryGetValue(modelId, out var stat);
|
|
||||||
long tokens = stat?.Tokens ?? 0;
|
|
||||||
long count = stat?.Count ?? 0;
|
|
||||||
|
|
||||||
decimal costPerHundredMillion = tokens > 0
|
|
||||||
? cost / (tokens / 100000000m)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
decimal tokensInWan = tokens / 10000m;
|
|
||||||
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine($"{modelId} 成本:【{cost:F2}RMB】 次数:【{count}次】 token:【{tokensInWan:F0}w】 1亿token成本:【{costPerHundredMillion:F2}RMB】");
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString().TrimEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using Mapster;
|
using Mapster;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Announcement;
|
||||||
@@ -31,8 +35,9 @@ public class AnnouncementService : ApplicationService, IAnnouncementService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取公告信息
|
/// 获取公告信息(前端首页使用,允许匿名访问)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AllowAnonymous]
|
||||||
public async Task<List<AnnouncementLogDto>> GetAsync()
|
public async Task<List<AnnouncementLogDto>> GetAsync()
|
||||||
{
|
{
|
||||||
// 使用 GetOrAddAsync 从缓存获取或添加数据,缓存1小时
|
// 使用 GetOrAddAsync 从缓存获取或添加数据,缓存1小时
|
||||||
@@ -48,18 +53,134 @@ public class AnnouncementService : ApplicationService, IAnnouncementService
|
|||||||
return cacheData?.Logs ?? new List<AnnouncementLogDto>();
|
return cacheData?.Logs ?? new List<AnnouncementLogDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取公告列表(后台管理使用)
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
[HttpGet("announcement/list")]
|
||||||
|
public async Task<PagedResultDto<AnnouncementDto>> GetListAsync(AnnouncementGetListInput input)
|
||||||
|
{
|
||||||
|
var query = _announcementRepository._DbQueryable
|
||||||
|
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey),
|
||||||
|
x => x.Title.Contains(input.SearchKey!) || (x.Remark != null && x.Remark.Contains(input.SearchKey!)))
|
||||||
|
.WhereIF(input.Type.HasValue, x => x.Type == input.Type!.Value)
|
||||||
|
.OrderByDescending(x => x.StartTime);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
var items = await query
|
||||||
|
.Skip(input.SkipCount)
|
||||||
|
.Take(input.MaxResultCount)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new PagedResultDto<AnnouncementDto>(
|
||||||
|
totalCount,
|
||||||
|
items.Adapt<List<AnnouncementDto>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据ID获取公告
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<AnnouncementDto> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
var entity = await _announcementRepository.GetByIdAsync(id);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new Exception("公告不存在");
|
||||||
|
}
|
||||||
|
return entity.Adapt<AnnouncementDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建公告
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<AnnouncementDto> CreateAsync(AnnouncementCreateInput input)
|
||||||
|
{
|
||||||
|
var entity = input.Adapt<AnnouncementAggregateRoot>();
|
||||||
|
await _announcementRepository.InsertAsync(entity);
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||||
|
|
||||||
|
return entity.Adapt<AnnouncementDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新公告
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
[HttpPut]
|
||||||
|
public async Task<AnnouncementDto> UpdateAsync(AnnouncementUpdateInput input)
|
||||||
|
{
|
||||||
|
var entity = await _announcementRepository.GetByIdAsync(input.Id);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new Exception("公告不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段
|
||||||
|
entity.Title = input.Title;
|
||||||
|
entity.Content = input.Content;
|
||||||
|
entity.Remark = input.Remark;
|
||||||
|
entity.ImageUrl = input.ImageUrl;
|
||||||
|
entity.StartTime = input.StartTime;
|
||||||
|
entity.EndTime = input.EndTime;
|
||||||
|
entity.Type = input.Type;
|
||||||
|
entity.Url = input.Url;
|
||||||
|
|
||||||
|
await _announcementRepository.UpdateAsync(entity);
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||||
|
|
||||||
|
return entity.Adapt<AnnouncementDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除公告
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
[HttpDelete("announcement/{id}")]
|
||||||
|
public async Task DeleteAsync(Guid id)
|
||||||
|
{
|
||||||
|
var entity = await _announcementRepository.GetByIdAsync(id);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new Exception("公告不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _announcementRepository.DeleteAsync(entity);
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
await _announcementCache.RemoveAsync(AnnouncementCacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从数据库加载公告数据
|
/// 从数据库加载公告数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<AnnouncementCacheDto> LoadAnnouncementDataAsync()
|
private async Task<AnnouncementCacheDto> LoadAnnouncementDataAsync()
|
||||||
{
|
{
|
||||||
// 查询所有公告日志,按日期降序排列
|
// 一次性查出全部公告(不排序)
|
||||||
var logs = await _announcementRepository._DbQueryable
|
var logs = await _announcementRepository._DbQueryable
|
||||||
.OrderByDescending(x => x.StartTime)
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
|
||||||
|
// 内存中处理排序
|
||||||
|
var orderedLogs = logs
|
||||||
|
.OrderByDescending(x =>
|
||||||
|
x.StartTime <= now &&
|
||||||
|
(x.EndTime == null || x.EndTime >= now)
|
||||||
|
)
|
||||||
|
.ThenByDescending(x => x.StartTime)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// 转换为 DTO
|
// 转换为 DTO
|
||||||
var logDtos = logs.Adapt<List<AnnouncementLogDto>>();
|
var logDtos = orderedLogs.Adapt<List<AnnouncementLogDto>>();
|
||||||
return new AnnouncementCacheDto
|
return new AnnouncementCacheDto
|
||||||
{
|
{
|
||||||
Logs = logDtos
|
Logs = logDtos
|
||||||
|
|||||||
@@ -19,13 +19,16 @@ public class ChannelService : ApplicationService, IChannelService
|
|||||||
{
|
{
|
||||||
private readonly ISqlSugarRepository<AiAppAggregateRoot, Guid> _appRepository;
|
private readonly ISqlSugarRepository<AiAppAggregateRoot, Guid> _appRepository;
|
||||||
private readonly ISqlSugarRepository<AiModelEntity, Guid> _modelRepository;
|
private readonly ISqlSugarRepository<AiModelEntity, Guid> _modelRepository;
|
||||||
|
private readonly ISqlSugarRepository<AiAppShortcutAggregateRoot, Guid> _appShortcutRepository;
|
||||||
|
|
||||||
public ChannelService(
|
public ChannelService(
|
||||||
ISqlSugarRepository<AiAppAggregateRoot, Guid> appRepository,
|
ISqlSugarRepository<AiAppAggregateRoot, Guid> appRepository,
|
||||||
ISqlSugarRepository<AiModelEntity, Guid> modelRepository)
|
ISqlSugarRepository<AiModelEntity, Guid> modelRepository,
|
||||||
|
ISqlSugarRepository<AiAppShortcutAggregateRoot, Guid> appShortcutRepository)
|
||||||
{
|
{
|
||||||
_appRepository = appRepository;
|
_appRepository = appRepository;
|
||||||
_modelRepository = modelRepository;
|
_modelRepository = modelRepository;
|
||||||
|
_appShortcutRepository = appShortcutRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region AI应用管理
|
#region AI应用管理
|
||||||
@@ -181,6 +184,7 @@ public class ChannelService : ApplicationService, IChannelService
|
|||||||
ProviderName = input.ProviderName,
|
ProviderName = input.ProviderName,
|
||||||
IconUrl = input.IconUrl,
|
IconUrl = input.IconUrl,
|
||||||
IsPremium = input.IsPremium,
|
IsPremium = input.IsPremium,
|
||||||
|
IsEnabled = input.IsEnabled,
|
||||||
IsDeleted = false
|
IsDeleted = false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,6 +226,7 @@ public class ChannelService : ApplicationService, IChannelService
|
|||||||
entity.ProviderName = input.ProviderName;
|
entity.ProviderName = input.ProviderName;
|
||||||
entity.IconUrl = input.IconUrl;
|
entity.IconUrl = input.IconUrl;
|
||||||
entity.IsPremium = input.IsPremium;
|
entity.IsPremium = input.IsPremium;
|
||||||
|
entity.IsEnabled = input.IsEnabled;
|
||||||
|
|
||||||
await _modelRepository.UpdateAsync(entity);
|
await _modelRepository.UpdateAsync(entity);
|
||||||
return entity.Adapt<AiModelDto>();
|
return entity.Adapt<AiModelDto>();
|
||||||
@@ -237,4 +242,22 @@ public class ChannelService : ApplicationService, IChannelService
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region AI应用快捷配置
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取AI应用快捷配置列表
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("channel/app-shortcut")]
|
||||||
|
public async Task<List<AiAppShortcutDto>> GetAppShortcutListAsync()
|
||||||
|
{
|
||||||
|
var entities = await _appShortcutRepository._DbQueryable
|
||||||
|
.OrderBy(x => x.OrderNum)
|
||||||
|
.OrderByDescending(x => x.CreationTime)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return entities.Adapt<List<AiAppShortcutDto>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ using Yi.Framework.AiHub.Domain.Entities.Model;
|
|||||||
using Yi.Framework.AiHub.Domain.Extensions;
|
using Yi.Framework.AiHub.Domain.Extensions;
|
||||||
using Yi.Framework.AiHub.Domain.Managers;
|
using Yi.Framework.AiHub.Domain.Managers;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||||
|
using System.Text.Json;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
@@ -50,6 +51,7 @@ public class AiChatService : ApplicationService
|
|||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
private readonly ISqlSugarRepository<AgentStoreAggregateRoot> _agentStoreRepository;
|
private readonly ISqlSugarRepository<AgentStoreAggregateRoot> _agentStoreRepository;
|
||||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||||
|
private const string FreeModelId = "DeepSeek-V3-0324";
|
||||||
|
|
||||||
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
public AiChatService(IHttpContextAccessor httpContextAccessor,
|
||||||
AiBlacklistManager aiBlacklistManager,
|
AiBlacklistManager aiBlacklistManager,
|
||||||
@@ -58,7 +60,8 @@ public class AiChatService : ApplicationService
|
|||||||
ModelManager modelManager,
|
ModelManager modelManager,
|
||||||
PremiumPackageManager premiumPackageManager,
|
PremiumPackageManager premiumPackageManager,
|
||||||
ChatManager chatManager, TokenManager tokenManager, IAccountService accountService,
|
ChatManager chatManager, TokenManager tokenManager, IAccountService accountService,
|
||||||
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, ISqlSugarRepository<AiModelEntity> aiModelRepository)
|
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository,
|
||||||
|
ISqlSugarRepository<AiModelEntity> aiModelRepository)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_aiBlacklistManager = aiBlacklistManager;
|
_aiBlacklistManager = aiBlacklistManager;
|
||||||
@@ -94,8 +97,9 @@ public class AiChatService : ApplicationService
|
|||||||
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
||||||
{
|
{
|
||||||
var output = await _aiModelRepository._DbQueryable
|
var output = await _aiModelRepository._DbQueryable
|
||||||
|
.Where(x => x.IsEnabled == true)
|
||||||
.Where(x => x.ModelType == ModelTypeEnum.Chat)
|
.Where(x => x.ModelType == ModelTypeEnum.Chat)
|
||||||
.Where(x => x.ModelApiType == ModelApiTypeEnum.OpenAi)
|
// .Where(x => x.ModelApiType == ModelApiTypeEnum.Completions)
|
||||||
.OrderByDescending(x => x.OrderNum)
|
.OrderByDescending(x => x.OrderNum)
|
||||||
.Select(x => new ModelGetListOutput
|
.Select(x => new ModelGetListOutput
|
||||||
{
|
{
|
||||||
@@ -104,60 +108,74 @@ public class AiChatService : ApplicationService
|
|||||||
ModelName = x.Name,
|
ModelName = x.Name,
|
||||||
ModelDescribe = x.Description,
|
ModelDescribe = x.Description,
|
||||||
Remark = x.Description,
|
Remark = x.Description,
|
||||||
IsPremiumPackage = x.IsPremium
|
IsPremiumPackage = x.IsPremium,
|
||||||
|
ModelApiType = x.ModelApiType,
|
||||||
|
IconUrl = x.IconUrl,
|
||||||
|
ProviderName = x.ProviderName
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
|
|
||||||
|
output.ForEach(x =>
|
||||||
|
{
|
||||||
|
if (x.ModelId == FreeModelId)
|
||||||
|
{
|
||||||
|
x.IsPremiumPackage = false;
|
||||||
|
x.IsFree = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
// /// <summary>
|
||||||
/// 发送消息
|
// /// 发送消息
|
||||||
/// </summary>
|
// /// </summary>
|
||||||
/// <param name="input"></param>
|
// /// <param name="input"></param>
|
||||||
/// <param name="sessionId"></param>
|
// /// <param name="sessionId"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
// /// <param name="cancellationToken"></param>
|
||||||
[HttpPost("ai-chat/send")]
|
// [HttpPost("ai-chat/send")]
|
||||||
public async Task PostSendAsync([FromBody] ThorChatCompletionsRequest input, [FromQuery] Guid? sessionId,
|
// [Obsolete]
|
||||||
CancellationToken cancellationToken)
|
// public async Task PostSendAsync([FromBody] ThorChatCompletionsRequest input, [FromQuery] Guid? sessionId,
|
||||||
{
|
// CancellationToken cancellationToken)
|
||||||
//除了免费模型,其他的模型都要校验
|
// {
|
||||||
if (!input.Model.Contains("DeepSeek-R1"))
|
// //除了免费模型,其他的模型都要校验
|
||||||
{
|
// if (input.Model!=FreeModelId)
|
||||||
//有token,需要黑名单校验
|
// {
|
||||||
if (CurrentUser.IsAuthenticated)
|
// //有token,需要黑名单校验
|
||||||
{
|
// if (CurrentUser.IsAuthenticated)
|
||||||
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
|
// {
|
||||||
if (!CurrentUser.IsAiVip())
|
// await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
|
||||||
{
|
// if (!CurrentUser.IsAiVip())
|
||||||
throw new UserFriendlyException("该模型需要VIP用户才能使用,请购买VIP后重新登录重试");
|
// {
|
||||||
}
|
// throw new UserFriendlyException("该模型需要VIP用户才能使用,请购买VIP后重新登录重试");
|
||||||
}
|
// }
|
||||||
else
|
// }
|
||||||
{
|
// else
|
||||||
throw new UserFriendlyException("未登录用户,只能使用未加速的DeepSeek-R1,请登录后重试");
|
// {
|
||||||
}
|
// throw new UserFriendlyException("未登录用户,只能使用未加速的DeepSeek-R1,请登录后重试");
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
//如果是尊享包服务,需要校验是是否尊享包足够
|
//
|
||||||
if (CurrentUser.IsAuthenticated)
|
// //如果是尊享包服务,需要校验是是否尊享包足够
|
||||||
{
|
// if (CurrentUser.IsAuthenticated)
|
||||||
var isPremium = await _modelManager.IsPremiumModelAsync(input.Model);
|
// {
|
||||||
|
// var isPremium = await _modelManager.IsPremiumModelAsync(input.Model);
|
||||||
if (isPremium)
|
//
|
||||||
{
|
// if (isPremium)
|
||||||
// 检查尊享token包用量
|
// {
|
||||||
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
|
// // 检查尊享token包用量
|
||||||
if (availableTokens <= 0)
|
// var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
|
||||||
{
|
// if (availableTokens <= 0)
|
||||||
throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
|
// {
|
||||||
}
|
// throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
//ai网关代理httpcontext
|
//
|
||||||
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
|
// //ai网关代理httpcontext
|
||||||
CurrentUser.Id, sessionId, null, CancellationToken.None);
|
// await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
|
||||||
}
|
// CurrentUser.Id, sessionId, null, CancellationToken.None);
|
||||||
|
// }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送消息
|
/// 发送消息
|
||||||
@@ -172,22 +190,10 @@ public class AiChatService : ApplicationService
|
|||||||
{
|
{
|
||||||
throw new BusinessException("当前接口不支持第三方使用");
|
throw new BusinessException("当前接口不支持第三方使用");
|
||||||
}
|
}
|
||||||
|
input.Model = "gpt-5-chat";
|
||||||
if (CurrentUser.IsAuthenticated)
|
if (CurrentUser.IsAuthenticated)
|
||||||
{
|
{
|
||||||
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
|
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
|
||||||
if (CurrentUser.IsAiVip())
|
|
||||||
{
|
|
||||||
input.Model = "gpt-5-chat";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
input.Model = "gpt-4.1-mini";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
input.Model = "DeepSeek-R1-0528";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ai网关代理httpcontext
|
//ai网关代理httpcontext
|
||||||
@@ -268,4 +274,89 @@ public class AiChatService : ApplicationService
|
|||||||
var data = await _agentStoreRepository.GetFirstAsync(x => x.SessionId == sessionId);
|
var data = await _agentStoreRepository.GetFirstAsync(x => x.SessionId == sessionId);
|
||||||
return data?.Store;
|
return data?.Store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统一发送消息 - 支持4种API类型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiType">API类型枚举</param>
|
||||||
|
/// <param name="input">原始请求体JsonElement</param>
|
||||||
|
/// <param name="modelId">模型ID(Gemini格式需要从URL传入)</param>
|
||||||
|
/// <param name="sessionId">会话ID</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
[HttpPost("ai-chat/unified/send")]
|
||||||
|
public async Task PostUnifiedSendAsync(
|
||||||
|
[FromQuery] ModelApiTypeEnum apiType,
|
||||||
|
[FromBody] JsonElement input,
|
||||||
|
[FromQuery] string modelId,
|
||||||
|
[FromQuery] Guid? sessionId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// 从请求体中提取模型ID(如果未从URL传入)
|
||||||
|
if (string.IsNullOrEmpty(modelId))
|
||||||
|
{
|
||||||
|
modelId = ExtractModelIdFromRequest(apiType, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 除了免费模型,其他的模型都要校验
|
||||||
|
if (modelId != FreeModelId)
|
||||||
|
{
|
||||||
|
if (CurrentUser.IsAuthenticated)
|
||||||
|
{
|
||||||
|
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
|
||||||
|
if (!CurrentUser.IsAiVip())
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("该模型需要VIP用户才能使用,请购买VIP后重新登录重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("未登录用户,只能使用未加速的DeepSeek-R1,请登录后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是尊享包服务,需要校验是否尊享包足够
|
||||||
|
if (CurrentUser.IsAuthenticated)
|
||||||
|
{
|
||||||
|
var isPremium = await _modelManager.IsPremiumModelAsync(modelId);
|
||||||
|
if (isPremium)
|
||||||
|
{
|
||||||
|
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(CurrentUser.GetId());
|
||||||
|
if (availableTokens <= 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("尊享token包用量不足,请先购买尊享token包");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用统一流式处理
|
||||||
|
await _aiGateWayManager.UnifiedStreamForStatisticsAsync(
|
||||||
|
_httpContextAccessor.HttpContext!,
|
||||||
|
apiType,
|
||||||
|
input,
|
||||||
|
modelId,
|
||||||
|
CurrentUser.Id,
|
||||||
|
sessionId,
|
||||||
|
null,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从请求体中提取模型ID
|
||||||
|
/// </summary>
|
||||||
|
private string ExtractModelIdFromRequest(ModelApiTypeEnum apiType, JsonElement input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (input.TryGetProperty("model", out var modelProperty))
|
||||||
|
{
|
||||||
|
return modelProperty.GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略解析错误
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException("无法从请求中获取模型ID,请在URL参数中指定modelId");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,10 @@ public class AiImageService : ApplicationService
|
|||||||
PublishStatus = x.PublishStatus,
|
PublishStatus = x.PublishStatus,
|
||||||
Categories = x.Categories,
|
Categories = x.Categories,
|
||||||
CreationTime = x.CreationTime,
|
CreationTime = x.CreationTime,
|
||||||
ErrorInfo = x.ErrorInfo
|
ErrorInfo = x.ErrorInfo,
|
||||||
|
UserName = x.UserName,
|
||||||
|
UserId = x.UserId,
|
||||||
|
IsAnonymous = x.IsAnonymous
|
||||||
})
|
})
|
||||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||||
|
|
||||||
@@ -269,6 +272,17 @@ public class AiImageService : ApplicationService
|
|||||||
return new PagedResult<ImageTaskOutput>(total, output);
|
return new PagedResult<ImageTaskOutput>(total, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除个人图片
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ids"></param>
|
||||||
|
[HttpDelete("ai-image/my-tasks")]
|
||||||
|
public async Task DeleteMyTaskAsync([FromQuery] List<Guid> ids)
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
await _imageTaskRepository.DeleteAsync(x => ids.Contains(x.Id) && x.UserId == userId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询图片广场(已发布的图片)
|
/// 分页查询图片广场(已发布的图片)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -282,8 +296,9 @@ public class AiImageService : ApplicationService
|
|||||||
.Where(x => x.TaskStatus == TaskStatusEnum.Success)
|
.Where(x => x.TaskStatus == TaskStatusEnum.Success)
|
||||||
.WhereIF(input.TaskStatus is not null, x => x.TaskStatus == input.TaskStatus)
|
.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.Prompt), x => x.Prompt.Contains(input.Prompt))
|
||||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Categories), x => SqlFunc.JsonLike(x.Categories, input.Categories))
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Categories),
|
||||||
.WhereIF(!string.IsNullOrWhiteSpace(input.UserName),x=>x.UserName.Contains(input.UserName) )
|
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,
|
.WhereIF(input.StartTime is not null && input.EndTime is not null,
|
||||||
x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
|
||||||
.OrderByDescending(x => x.CreationTime)
|
.OrderByDescending(x => x.CreationTime)
|
||||||
@@ -300,9 +315,9 @@ public class AiImageService : ApplicationService
|
|||||||
ErrorInfo = null,
|
ErrorInfo = null,
|
||||||
UserName = x.UserName,
|
UserName = x.UserName,
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
|
|
||||||
})
|
})
|
||||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); ;
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
output.ForEach(x =>
|
output.ForEach(x =>
|
||||||
@@ -313,7 +328,7 @@ public class AiImageService : ApplicationService
|
|||||||
x.UserId = null;
|
x.UserId = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new PagedResult<ImageTaskOutput>(total, output);
|
return new PagedResult<ImageTaskOutput>(total, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +357,7 @@ public class AiImageService : ApplicationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
//设置发布
|
//设置发布
|
||||||
task.SetPublish(input.IsAnonymous,input.Categories);
|
task.SetPublish(input.IsAnonymous, input.Categories);
|
||||||
await _imageTaskRepository.UpdateAsync(task);
|
await _imageTaskRepository.UpdateAsync(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +370,7 @@ public class AiImageService : ApplicationService
|
|||||||
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
public async Task<List<ModelGetListOutput>> GetModelAsync()
|
||||||
{
|
{
|
||||||
var output = await _aiModelRepository._DbQueryable
|
var output = await _aiModelRepository._DbQueryable
|
||||||
|
.Where(x=>x.IsEnabled==true)
|
||||||
.Where(x => x.ModelType == ModelTypeEnum.Image)
|
.Where(x => x.ModelType == ModelTypeEnum.Image)
|
||||||
.Where(x => x.ModelApiType == ModelApiTypeEnum.GenerateContent)
|
.Where(x => x.ModelApiType == ModelApiTypeEnum.GenerateContent)
|
||||||
.OrderByDescending(x => x.OrderNum)
|
.OrderByDescending(x => x.OrderNum)
|
||||||
|
|||||||
@@ -35,8 +35,59 @@ public class MessageService : ApplicationService
|
|||||||
var entities = await _repository._DbQueryable
|
var entities = await _repository._DbQueryable
|
||||||
.Where(x => x.SessionId == input.SessionId)
|
.Where(x => x.SessionId == input.SessionId)
|
||||||
.Where(x=>x.UserId == userId)
|
.Where(x=>x.UserId == userId)
|
||||||
|
.Where(x => !x.IsHidden)
|
||||||
.OrderBy(x => x.Id)
|
.OrderBy(x => x.Id)
|
||||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||||
return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>());
|
return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除消息(软删除,标记为隐藏)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">删除参数,包含消息Id列表和是否删除后续消息的开关</param>
|
||||||
|
[Authorize]
|
||||||
|
public async Task DeleteAsync([FromQuery] MessageDeleteInput input)
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
|
||||||
|
// 获取要删除的消息
|
||||||
|
var messages = await _repository._DbQueryable
|
||||||
|
.Where(x => input.Ids.Contains(x.Id))
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (messages.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记当前消息为隐藏
|
||||||
|
var idsToHide = messages.Select(x => x.Id).ToList();
|
||||||
|
|
||||||
|
// 如果需要删除后续消息
|
||||||
|
if (input.IsDeleteSubsequent)
|
||||||
|
{
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
// 获取同一会话中时间大于当前消息的所有消息Id
|
||||||
|
var subsequentIds = await _repository._DbQueryable
|
||||||
|
.Where(x => x.SessionId == message.SessionId)
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Where(x => x.CreationTime > message.CreationTime)
|
||||||
|
.Where(x => !x.IsHidden)
|
||||||
|
.Select(x => x.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
idsToHide.AddRange(subsequentIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
idsToHide = idsToHide.Distinct().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新为隐藏状态
|
||||||
|
await _repository._Db.Updateable<MessageAggregateRoot>()
|
||||||
|
.SetColumns(x => x.IsHidden == true)
|
||||||
|
.Where(x => idsToHide.Contains(x.Id))
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,9 @@ public class ModelService : ApplicationService, IModelService
|
|||||||
{
|
{
|
||||||
RefAsync<int> total = 0;
|
RefAsync<int> total = 0;
|
||||||
|
|
||||||
// 查询所有未删除的模型,使用WhereIF动态添加筛选条件
|
// 查询所有未删除且已启用的模型,使用WhereIF动态添加筛选条件
|
||||||
var modelIds = (await _modelRepository._DbQueryable
|
var modelIds = (await _modelRepository._DbQueryable
|
||||||
|
.Where(x => x.IsEnabled)
|
||||||
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), x =>
|
.WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), x =>
|
||||||
x.Name.Contains(input.SearchKey) || x.ModelId.Contains(input.SearchKey))
|
x.Name.Contains(input.SearchKey) || x.ModelId.Contains(input.SearchKey))
|
||||||
.WhereIF(input.ProviderNames is not null, x =>
|
.WhereIF(input.ProviderNames is not null, x =>
|
||||||
@@ -51,6 +52,7 @@ public class ModelService : ApplicationService, IModelService
|
|||||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total));
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total));
|
||||||
|
|
||||||
var entities = await _modelRepository._DbQueryable.Where(x => modelIds.Contains(x.ModelId))
|
var entities = await _modelRepository._DbQueryable.Where(x => modelIds.Contains(x.ModelId))
|
||||||
|
.Where(x => x.IsEnabled)
|
||||||
.OrderBy(x => x.OrderNum)
|
.OrderBy(x => x.OrderNum)
|
||||||
.OrderBy(x => x.Name).ToListAsync();
|
.OrderBy(x => x.Name).ToListAsync();
|
||||||
|
|
||||||
@@ -77,10 +79,9 @@ public class ModelService : ApplicationService, IModelService
|
|||||||
public async Task<List<string>> GetProviderListAsync()
|
public async Task<List<string>> GetProviderListAsync()
|
||||||
{
|
{
|
||||||
var providers = await _modelRepository._DbQueryable
|
var providers = await _modelRepository._DbQueryable
|
||||||
.Where(x => !x.IsDeleted)
|
.Where(x => x.IsEnabled)
|
||||||
.Where(x => !string.IsNullOrEmpty(x.ProviderName))
|
.Where(x => !string.IsNullOrEmpty(x.ProviderName))
|
||||||
.GroupBy(x => x.ProviderName)
|
.GroupBy(x => x.ProviderName)
|
||||||
.OrderBy(x => x.OrderNum)
|
|
||||||
.Select(x => x.ProviderName)
|
.Select(x => x.ProviderName)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ public class SessionService : CrudAppService<SessionAggregateRoot, SessionDto, G
|
|||||||
RefAsync<int> total = 0;
|
RefAsync<int> total = 0;
|
||||||
var userId = CurrentUser.GetId();
|
var userId = CurrentUser.GetId();
|
||||||
var entities = await _repository._DbQueryable
|
var entities = await _repository._DbQueryable
|
||||||
.Where(x=>x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
|
.WhereIF(input.SessionType.HasValue, x => x.SessionType == input.SessionType!.Value)
|
||||||
.OrderByDescending(x => x.Id)
|
.OrderByDescending(x => x.Id)
|
||||||
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||||
return new PagedResultDto<SessionDto>(total, entities.Adapt<List<SessionDto>>());
|
return new PagedResultDto<SessionDto>(total, entities.Adapt<List<SessionDto>>());
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Mapster;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||||
|
using Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜服务
|
||||||
|
/// </summary>
|
||||||
|
public class RankingService : ApplicationService, IRankingService
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarRepository<RankingItemAggregateRoot, Guid> _repository;
|
||||||
|
|
||||||
|
public RankingService(ISqlSugarRepository<RankingItemAggregateRoot, Guid> repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取排行榜列表(全量返回,按得分降序)
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("ranking/list")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<List<RankingItemDto>> GetListAsync([FromQuery] RankingGetListInput input)
|
||||||
|
{
|
||||||
|
var query = _repository._DbQueryable
|
||||||
|
.WhereIF(input.Type.HasValue, x => x.Type == input.Type!.Value)
|
||||||
|
.OrderByDescending(x => x.Score);
|
||||||
|
|
||||||
|
var entities = await query.ToListAsync();
|
||||||
|
return entities.Adapt<List<RankingItemDto>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using Mapster;
|
using Mapster;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
|
||||||
@@ -35,19 +37,29 @@ namespace Yi.Framework.AiHub.Application.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询已登录的账户充值记录
|
/// 查询已登录的账户充值记录(分页)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[Route("recharge/account")]
|
[Route("recharge/account")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<List<RechargeGetListOutput>> GetListByAccountAsync()
|
public async Task<PagedResultDto<RechargeGetListOutput>> GetListByAccountAsync([FromQuery]RechargeGetListInput input)
|
||||||
{
|
{
|
||||||
var userId = CurrentUser.Id;
|
var userId = CurrentUser.Id;
|
||||||
var entities = await _repository._DbQueryable.Where(x => x.UserId == userId)
|
RefAsync<int> total = 0;
|
||||||
|
|
||||||
|
var entities = await _repository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.WhereIF(input.StartTime.HasValue, x => x.CreationTime >= input.StartTime!.Value)
|
||||||
|
.WhereIF(input.EndTime.HasValue, x => x.CreationTime <= input.EndTime!.Value)
|
||||||
|
.WhereIF(input.IsFree == true, x => x.RechargeAmount == 0)
|
||||||
|
.WhereIF(input.IsFree == false, x => x.RechargeAmount > 0)
|
||||||
|
.WhereIF(input.MinRechargeAmount.HasValue, x => x.RechargeAmount >= input.MinRechargeAmount!.Value)
|
||||||
|
.WhereIF(input.MaxRechargeAmount.HasValue, x => x.RechargeAmount <= input.MaxRechargeAmount!.Value)
|
||||||
.OrderByDescending(x => x.CreationTime)
|
.OrderByDescending(x => x.CreationTime)
|
||||||
.ToListAsync();
|
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
|
||||||
|
|
||||||
var output = entities.Adapt<List<RechargeGetListOutput>>();
|
var output = entities.Adapt<List<RechargeGetListOutput>>();
|
||||||
return output;
|
return new PagedResultDto<RechargeGetListOutput>(total, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
using Mapster;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SqlSugar;
|
||||||
|
using System.Globalization;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.SystemStatistics;
|
||||||
|
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.SqlSugarCore.Abstractions;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Application.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 系统使用量统计服务实现
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Roles = "admin")]
|
||||||
|
public class SystemUsageStatisticsService : ApplicationService, ISystemUsageStatisticsService
|
||||||
|
{
|
||||||
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||||
|
private readonly ISqlSugarRepository<AiRechargeAggregateRoot> _rechargeRepository;
|
||||||
|
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
|
||||||
|
private readonly ISqlSugarRepository<AiModelEntity, Guid> _modelRepository;
|
||||||
|
|
||||||
|
public SystemUsageStatisticsService(
|
||||||
|
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository,
|
||||||
|
ISqlSugarRepository<AiRechargeAggregateRoot> rechargeRepository,
|
||||||
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
|
ISqlSugarRepository<AiModelEntity, Guid> modelRepository)
|
||||||
|
{
|
||||||
|
_premiumPackageRepository = premiumPackageRepository;
|
||||||
|
_rechargeRepository = rechargeRepository;
|
||||||
|
_messageRepository = messageRepository;
|
||||||
|
_modelRepository = modelRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取利润统计数据
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("system-statistics/profit")]
|
||||||
|
public async Task<ProfitStatisticsOutput> GetProfitStatisticsAsync(ProfitStatisticsInput input)
|
||||||
|
{
|
||||||
|
// 1. 获取尊享包总消耗和剩余库存
|
||||||
|
var premiumPackages = await _premiumPackageRepository._DbQueryable.ToListAsync();
|
||||||
|
long totalUsedTokens = premiumPackages.Sum(p => p.UsedTokens);
|
||||||
|
long totalRemainingTokens = premiumPackages.Sum(p => p.RemainingTokens);
|
||||||
|
|
||||||
|
// 2. 计算1亿Token成本
|
||||||
|
decimal costPerHundredMillion = totalUsedTokens > 0
|
||||||
|
? input.CurrentCost / (totalUsedTokens / 100000000m)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// 3. 计算总成本(剩余+已使用的总成本)
|
||||||
|
long totalTokens = totalUsedTokens + totalRemainingTokens;
|
||||||
|
decimal totalCost = totalTokens > 0
|
||||||
|
? (totalTokens / 100000000m) * costPerHundredMillion
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// 4. 获取总收益(RechargeType=PremiumPackage的充值金额总和)
|
||||||
|
decimal totalRevenue = await _rechargeRepository._DbQueryable
|
||||||
|
.Where(x => x.RechargeType == Domain.Shared.Enums.RechargeTypeEnum.PremiumPackage)
|
||||||
|
.SumAsync(x => x.RechargeAmount);
|
||||||
|
|
||||||
|
// 5. 计算利润率
|
||||||
|
decimal profitRate = totalCost > 0
|
||||||
|
? (totalRevenue / totalCost - 1) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// 6. 按200售价计算成本
|
||||||
|
decimal costAt200Price = totalRevenue > 0
|
||||||
|
? (totalCost / totalRevenue) * 200
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// 7. 格式化日期
|
||||||
|
var today = DateTime.Now;
|
||||||
|
string dayOfWeek = today.ToString("dddd", new CultureInfo("zh-CN"));
|
||||||
|
string weekDay = dayOfWeek switch
|
||||||
|
{
|
||||||
|
"星期一" => "周1",
|
||||||
|
"星期二" => "周2",
|
||||||
|
"星期三" => "周3",
|
||||||
|
"星期四" => "周4",
|
||||||
|
"星期五" => "周5",
|
||||||
|
"星期六" => "周6",
|
||||||
|
"星期日" => "周日",
|
||||||
|
_ => dayOfWeek
|
||||||
|
};
|
||||||
|
|
||||||
|
return new ProfitStatisticsOutput
|
||||||
|
{
|
||||||
|
Date = $"{today:M月d日} {weekDay}",
|
||||||
|
TotalUsedTokens = totalUsedTokens,
|
||||||
|
TotalUsedTokensInHundredMillion = totalUsedTokens / 100000000m,
|
||||||
|
TotalRemainingTokens = totalRemainingTokens,
|
||||||
|
TotalRemainingTokensInHundredMillion = totalRemainingTokens / 100000000m,
|
||||||
|
CurrentCost = input.CurrentCost,
|
||||||
|
CostPerHundredMillion = costPerHundredMillion,
|
||||||
|
TotalCost = totalCost,
|
||||||
|
TotalRevenue = totalRevenue,
|
||||||
|
ProfitRate = profitRate,
|
||||||
|
CostAt200Price = costAt200Price
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定日期各模型Token统计
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("system-statistics/token")]
|
||||||
|
public async Task<TokenStatisticsOutput> GetTokenStatisticsAsync(TokenStatisticsInput input)
|
||||||
|
{
|
||||||
|
var day = input.Date.Date;
|
||||||
|
var nextDay = day.AddDays(1);
|
||||||
|
|
||||||
|
// 1. 获取所有尊享模型(包含被禁用的),按ModelId去重
|
||||||
|
var premiumModels = await _modelRepository._DbQueryable
|
||||||
|
.Where(x => x.IsPremium)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (premiumModels.Count == 0)
|
||||||
|
{
|
||||||
|
return new TokenStatisticsOutput
|
||||||
|
{
|
||||||
|
Date = FormatDate(day),
|
||||||
|
ModelStatistics = new List<ModelTokenStatisticsDto>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按ModelId去重,保留第一个模型的名称
|
||||||
|
var distinctModels = premiumModels
|
||||||
|
.GroupBy(x => x.ModelId)
|
||||||
|
.Select(g => g.First())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var modelIds = distinctModels.Select(x => x.ModelId).ToList();
|
||||||
|
|
||||||
|
// 2. 查询指定日期内各模型的Token使用统计
|
||||||
|
var modelStats = await _messageRepository._DbQueryable
|
||||||
|
.Where(x => modelIds.Contains(x.ModelId))
|
||||||
|
.Where(x => x.CreationTime >= day && x.CreationTime < nextDay)
|
||||||
|
.Where(x => x.Role == "system")
|
||||||
|
.GroupBy(x => x.ModelId)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
ModelId = x.ModelId,
|
||||||
|
Tokens = SqlFunc.AggregateSum(x.TokenUsage.TotalTokenCount),
|
||||||
|
Count = SqlFunc.AggregateCount(x.Id)
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var modelStatDict = modelStats.ToDictionary(x => x.ModelId, x => x);
|
||||||
|
|
||||||
|
// 3. 构建结果列表,使用去重后的模型列表
|
||||||
|
var result = new List<ModelTokenStatisticsDto>();
|
||||||
|
foreach (var model in distinctModels)
|
||||||
|
{
|
||||||
|
modelStatDict.TryGetValue(model.ModelId, out var stat);
|
||||||
|
long tokens = stat?.Tokens ?? 0;
|
||||||
|
long count = stat?.Count ?? 0;
|
||||||
|
|
||||||
|
// 这里成本设为0,因为需要前端传入或者从配置中获取
|
||||||
|
decimal cost = 0;
|
||||||
|
decimal costPerHundredMillion = tokens > 0 && cost > 0
|
||||||
|
? cost / (tokens / 100000000m)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
result.Add(new ModelTokenStatisticsDto
|
||||||
|
{
|
||||||
|
ModelId = model.ModelId,
|
||||||
|
ModelName = model.Name,
|
||||||
|
Tokens = tokens,
|
||||||
|
TokensInWan = tokens / 10000m,
|
||||||
|
Count = count,
|
||||||
|
Cost = cost,
|
||||||
|
CostPerHundredMillion = costPerHundredMillion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TokenStatisticsOutput
|
||||||
|
{
|
||||||
|
Date = FormatDate(day),
|
||||||
|
ModelStatistics = result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatDate(DateTime date)
|
||||||
|
{
|
||||||
|
string dayOfWeek = date.ToString("dddd", new CultureInfo("zh-CN"));
|
||||||
|
string weekDay = dayOfWeek switch
|
||||||
|
{
|
||||||
|
"星期一" => "周1",
|
||||||
|
"星期二" => "周2",
|
||||||
|
"星期三" => "周3",
|
||||||
|
"星期四" => "周4",
|
||||||
|
"星期五" => "周5",
|
||||||
|
"星期六" => "周6",
|
||||||
|
"星期日" => "周日",
|
||||||
|
_ => dayOfWeek
|
||||||
|
};
|
||||||
|
return $"{date:M月d日} {weekDay}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
|||||||
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||||
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
||||||
private readonly ModelManager _modelManager;
|
private readonly ModelManager _modelManager;
|
||||||
|
|
||||||
public UsageStatisticsService(
|
public UsageStatisticsService(
|
||||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
|
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
|
||||||
@@ -48,7 +49,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
|||||||
/// 获取当前用户近7天的Token消耗统计
|
/// 获取当前用户近7天的Token消耗统计
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>每日Token使用量列表</returns>
|
/// <returns>每日Token使用量列表</returns>
|
||||||
public async Task<List<DailyTokenUsageDto>> GetLast7DaysTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
|
public async Task<List<DailyTokenUsageDto>> GetLast7DaysTokenUsageAsync([FromQuery] UsageStatisticsGetInput input)
|
||||||
{
|
{
|
||||||
var userId = CurrentUser.GetId();
|
var userId = CurrentUser.GetId();
|
||||||
var endDate = DateTime.Today;
|
var endDate = DateTime.Today;
|
||||||
@@ -57,9 +58,9 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
|||||||
// 从Message表统计近7天的token消耗
|
// 从Message表统计近7天的token消耗
|
||||||
var dailyUsage = await _messageRepository._DbQueryable
|
var dailyUsage = await _messageRepository._DbQueryable
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.Where(x => x.Role == "assistant" || x.Role == "system")
|
.Where(x => x.Role == "system")
|
||||||
.Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1))
|
.Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1))
|
||||||
.WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId)
|
.WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
|
||||||
.GroupBy(x => x.CreationTime.Date)
|
.GroupBy(x => x.CreationTime.Date)
|
||||||
.Select(g => new
|
.Select(g => new
|
||||||
{
|
{
|
||||||
@@ -89,14 +90,14 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
|||||||
/// 获取当前用户各个模型的Token消耗量及占比
|
/// 获取当前用户各个模型的Token消耗量及占比
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>模型Token使用量列表</returns>
|
/// <returns>模型Token使用量列表</returns>
|
||||||
public async Task<List<ModelTokenUsageDto>> GetModelTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
|
public async Task<List<ModelTokenUsageDto>> GetModelTokenUsageAsync([FromQuery] UsageStatisticsGetInput input)
|
||||||
{
|
{
|
||||||
var userId = CurrentUser.GetId();
|
var userId = CurrentUser.GetId();
|
||||||
|
|
||||||
// 从UsageStatistics表获取各模型的token消耗统计(按ModelId聚合,因为同一模型可能有多个TokenId的记录)
|
// 从UsageStatistics表获取各模型的token消耗统计(按ModelId聚合,因为同一模型可能有多个TokenId的记录)
|
||||||
var modelUsages = await _usageStatisticsRepository._DbQueryable
|
var modelUsages = await _usageStatisticsRepository._DbQueryable
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId)
|
.WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
|
||||||
.GroupBy(x => x.ModelId)
|
.GroupBy(x => x.ModelId)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
@@ -221,11 +222,133 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
|||||||
var result = tokenUsages.Select(x => new TokenPremiumUsageDto
|
var result = tokenUsages.Select(x => new TokenPremiumUsageDto
|
||||||
{
|
{
|
||||||
TokenId = x.TokenId,
|
TokenId = x.TokenId,
|
||||||
TokenName = x.TokenId == Guid.Empty ? "默认" : (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "其他"),
|
TokenName = x.TokenId == Guid.Empty
|
||||||
|
? "默认"
|
||||||
|
: (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "其他"),
|
||||||
Tokens = x.TotalTokenCount,
|
Tokens = x.TotalTokenCount,
|
||||||
Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0
|
Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0
|
||||||
}).OrderByDescending(x => x.Tokens).ToList();
|
}).OrderByDescending(x => x.Tokens).ToList();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前用户近24小时每小时Token消耗统计(柱状图)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>每小时Token使用量列表,包含各模型堆叠数据</returns>
|
||||||
|
public async Task<List<HourlyTokenUsageDto>> GetLast24HoursTokenUsageAsync(
|
||||||
|
[FromQuery] UsageStatisticsGetInput input)
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var startTime = now.AddHours(-23); // 滚动24小时,从23小时前到现在
|
||||||
|
var startHour = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0);
|
||||||
|
|
||||||
|
// 从Message表查询近24小时的数据,只选择需要的字段
|
||||||
|
var messages = await _messageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Where(x => x.Role == "system")
|
||||||
|
.Where(x => x.CreationTime >= startHour)
|
||||||
|
.WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.CreationTime,
|
||||||
|
x.ModelId,
|
||||||
|
x.TokenUsage.TotalTokenCount
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// 在内存中按小时和模型分组统计
|
||||||
|
var hourlyGrouped = messages
|
||||||
|
.GroupBy(x => new
|
||||||
|
{
|
||||||
|
Hour = new DateTime(x.CreationTime.Year, x.CreationTime.Month, x.CreationTime.Day, x.CreationTime.Hour,
|
||||||
|
0, 0),
|
||||||
|
x.ModelId
|
||||||
|
})
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
g.Key.Hour,
|
||||||
|
g.Key.ModelId,
|
||||||
|
Tokens = g.Sum(x => x.TotalTokenCount)
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// 生成完整的24小时数据
|
||||||
|
var result = new List<HourlyTokenUsageDto>();
|
||||||
|
for (int i = 0; i < 24; i++)
|
||||||
|
{
|
||||||
|
var hour = startHour.AddHours(i);
|
||||||
|
var hourData = hourlyGrouped.Where(x => x.Hour == hour).ToList();
|
||||||
|
|
||||||
|
var modelBreakdown = hourData.Select(x => new ModelTokenBreakdownDto
|
||||||
|
{
|
||||||
|
ModelId = x.ModelId,
|
||||||
|
Tokens = x.Tokens
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
result.Add(new HourlyTokenUsageDto
|
||||||
|
{
|
||||||
|
Hour = hour,
|
||||||
|
TotalTokens = modelBreakdown.Sum(x => x.Tokens),
|
||||||
|
ModelBreakdown = modelBreakdown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前用户今日各模型使用量统计(卡片列表)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>模型今日使用量列表,包含使用次数和总Token</returns>
|
||||||
|
public async Task<List<ModelTodayUsageDto>> GetTodayModelUsageAsync([FromQuery] UsageStatisticsGetInput input)
|
||||||
|
{
|
||||||
|
var userId = CurrentUser.GetId();
|
||||||
|
var todayStart = DateTime.Today; // 今天凌晨0点
|
||||||
|
var tomorrowStart = todayStart.AddDays(1);
|
||||||
|
|
||||||
|
// 从Message表查询今天的数据,只选择需要的字段
|
||||||
|
var messages = await _messageRepository._DbQueryable
|
||||||
|
.Where(x => x.UserId == userId)
|
||||||
|
.Where(x => x.Role == "system")
|
||||||
|
.Where(x => x.CreationTime >= todayStart && x.CreationTime < tomorrowStart)
|
||||||
|
.WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.ModelId,
|
||||||
|
x.TokenUsage.TotalTokenCount
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// 在内存中按模型分组统计
|
||||||
|
var modelStats = messages
|
||||||
|
.GroupBy(x => x.ModelId)
|
||||||
|
.Select(g => new ModelTodayUsageDto
|
||||||
|
{
|
||||||
|
ModelId = g.Key,
|
||||||
|
UsageCount = g.Count(),
|
||||||
|
TotalTokens = g.Sum(x => x.TotalTokenCount)
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.TotalTokens)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (modelStats.Count > 0)
|
||||||
|
{
|
||||||
|
var modelIds = modelStats.Select(x => x.ModelId).ToList();
|
||||||
|
var modelDic = await _modelManager._aiModelRepository._DbQueryable.Where(x => modelIds.Contains(x.ModelId))
|
||||||
|
.Distinct()
|
||||||
|
.Where(x=>x.IsEnabled)
|
||||||
|
.ToDictionaryAsync<string>(x => x.ModelId, y => y.IconUrl);
|
||||||
|
modelStats.ForEach(x =>
|
||||||
|
{
|
||||||
|
if (modelDic.TryGetValue(x.ModelId, out var icon))
|
||||||
|
{
|
||||||
|
x.IconUrl = icon;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelStats;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,5 +21,11 @@ public class PremiumPackageConst
|
|||||||
"yi-claude-sonnet-4-5-20250929",
|
"yi-claude-sonnet-4-5-20250929",
|
||||||
"yi-claude-haiku-4-5-20251001",
|
"yi-claude-haiku-4-5-20251001",
|
||||||
"yi-claude-opus-4-5-20251101",
|
"yi-claude-opus-4-5-20251101",
|
||||||
|
|
||||||
|
"yi-gpt-5.2",
|
||||||
|
"yi-gpt-5.2-codex",
|
||||||
|
"yi-gemini-3-pro-high",
|
||||||
|
"yi-gemini-3-pro",
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
|
|
||||||
public class AiModelDescribe
|
public class AiModelDescribe
|
||||||
{
|
{
|
||||||
@@ -61,4 +63,14 @@ public class AiModelDescribe
|
|||||||
/// 模型倍率
|
/// 模型倍率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal Multiplier { get; set; }
|
public decimal Multiplier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为尊享模型
|
||||||
|
/// </summary>
|
||||||
|
public bool IsPremium { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模型类型(聊天/图片等)
|
||||||
|
/// </summary>
|
||||||
|
public ModelTypeEnum ModelType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -18,39 +18,7 @@ public class AnthropicStreamDto
|
|||||||
[JsonPropertyName("usage")] public AnthropicCompletionDtoUsage? Usage { get; set; }
|
[JsonPropertyName("usage")] public AnthropicCompletionDtoUsage? Usage { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("error")] public AnthropicStreamErrorDto? Error { get; set; }
|
[JsonPropertyName("error")] public AnthropicStreamErrorDto? Error { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
|
||||||
{
|
|
||||||
PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
|
||||||
InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
|
||||||
OutputTokens = Usage?.OutputTokens,
|
|
||||||
InputTokensDetails = null,
|
|
||||||
CompletionTokens = Usage?.OutputTokens,
|
|
||||||
TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
|
|
||||||
Usage?.OutputTokens,
|
|
||||||
PromptTokensDetails = null,
|
|
||||||
CompletionTokensDetails = null
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public void SupplementalMultiplier(decimal multiplier)
|
|
||||||
{
|
|
||||||
if (this.Usage is not null)
|
|
||||||
{
|
|
||||||
this.Usage.CacheCreationInputTokens =
|
|
||||||
(int)Math.Round((this.Usage.CacheCreationInputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.CacheReadInputTokens =
|
|
||||||
(int)Math.Round((this.Usage.CacheReadInputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.InputTokens =
|
|
||||||
(int)Math.Round((this.Usage.InputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.OutputTokens =
|
|
||||||
(int)Math.Round((this.Usage.OutputTokens ?? 0) * multiplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnthropicStreamErrorDto
|
public class AnthropicStreamErrorDto
|
||||||
@@ -71,6 +39,11 @@ public class AnthropicChatCompletionDtoDelta
|
|||||||
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
|
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("stop_reason")] public string? StopReason { get; set; }
|
[JsonPropertyName("stop_reason")] public string? StopReason { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("signature")] public string? Signature { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("stop_sequence")] public string? StopSequence { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnthropicChatCompletionDtoContentBlock
|
public class AnthropicChatCompletionDtoContentBlock
|
||||||
@@ -115,38 +88,7 @@ public class AnthropicChatCompletionDto
|
|||||||
public object stop_sequence { get; set; }
|
public object stop_sequence { get; set; }
|
||||||
|
|
||||||
public AnthropicCompletionDtoUsage? Usage { get; set; }
|
public AnthropicCompletionDtoUsage? Usage { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
|
||||||
{
|
|
||||||
PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
|
||||||
InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
|
||||||
OutputTokens = Usage?.OutputTokens,
|
|
||||||
InputTokensDetails = null,
|
|
||||||
CompletionTokens = Usage?.OutputTokens,
|
|
||||||
TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
|
|
||||||
Usage?.OutputTokens,
|
|
||||||
PromptTokensDetails = null,
|
|
||||||
CompletionTokensDetails = null
|
|
||||||
};
|
|
||||||
|
|
||||||
public void SupplementalMultiplier(decimal multiplier)
|
|
||||||
{
|
|
||||||
if (this.Usage is not null)
|
|
||||||
{
|
|
||||||
this.Usage.CacheCreationInputTokens =
|
|
||||||
(int)Math.Round((this.Usage?.CacheCreationInputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.CacheReadInputTokens =
|
|
||||||
(int)Math.Round((this.Usage?.CacheReadInputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.InputTokens =
|
|
||||||
(int)Math.Round((this.Usage?.InputTokens ?? 0) * multiplier);
|
|
||||||
|
|
||||||
this.Usage.OutputTokens =
|
|
||||||
(int)Math.Round((this.Usage?.OutputTokens ?? 0) * multiplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnthropicChatCompletionDtoContent
|
public class AnthropicChatCompletionDtoContent
|
||||||
@@ -166,6 +108,7 @@ public class AnthropicChatCompletionDtoContent
|
|||||||
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
|
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
|
||||||
|
|
||||||
public string? signature { get; set; }
|
public string? signature { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnthropicCompletionDtoUsage
|
public class AnthropicCompletionDtoUsage
|
||||||
@@ -181,6 +124,12 @@ public class AnthropicCompletionDtoUsage
|
|||||||
[JsonPropertyName("output_tokens")] public int? OutputTokens { get; set; }
|
[JsonPropertyName("output_tokens")] public int? OutputTokens { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("server_tool_use")] public AnthropicServerToolUse? ServerToolUse { get; set; }
|
[JsonPropertyName("server_tool_use")] public AnthropicServerToolUse? ServerToolUse { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("cache_creation")] public object? CacheCreation { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("service_tier")] public string? ServiceTier { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnthropicServerToolUse
|
public class AnthropicServerToolUse
|
||||||
|
|||||||
@@ -133,28 +133,5 @@ public class AnthropicMessageTool
|
|||||||
|
|
||||||
[JsonPropertyName("description")] public string? Description { get; set; }
|
[JsonPropertyName("description")] public string? Description { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("input_schema")] public Input_schema? InputSchema { get; set; }
|
[JsonPropertyName("input_schema")] public object? InputSchema { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Input_schema
|
|
||||||
{
|
|
||||||
[JsonPropertyName("type")] public string? Type { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("properties")] public Dictionary<string, InputSchemaValue>? Properties { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("required")] public string[]? Required { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InputSchemaValue
|
|
||||||
{
|
|
||||||
public string? type { get; set; }
|
|
||||||
|
|
||||||
public string? description { get; set; }
|
|
||||||
|
|
||||||
public InputSchemaValueItems? items { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InputSchemaValueItems
|
|
||||||
{
|
|
||||||
public string? type { get; set; }
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,83 @@ namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Gemini;
|
|||||||
|
|
||||||
public static class GeminiGenerateContentAcquirer
|
public static class GeminiGenerateContentAcquirer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 从请求体中提取用户最后一条消息内容
|
||||||
|
/// 路径: contents[last].parts[last].text
|
||||||
|
/// </summary>
|
||||||
|
public static string GetLastUserContent(JsonElement request)
|
||||||
|
{
|
||||||
|
var contents = request.GetPath("contents");
|
||||||
|
if (!contents.HasValue || contents.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentsArray = contents.Value.EnumerateArray().ToList();
|
||||||
|
if (contentsArray.Count == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastContent = contentsArray[^1];
|
||||||
|
var parts = lastContent.GetPath("parts");
|
||||||
|
if (!parts.HasValue || parts.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var partsArray = parts.Value.EnumerateArray().ToList();
|
||||||
|
if (partsArray.Count == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最后一个 part 的 text
|
||||||
|
var lastPart = partsArray[^1];
|
||||||
|
return lastPart.GetPath("text").GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从响应中提取文本内容(非 thought 类型)
|
||||||
|
/// 路径: candidates[0].content.parts[].text (where thought != true)
|
||||||
|
/// </summary>
|
||||||
|
public static string GetTextContent(JsonElement response)
|
||||||
|
{
|
||||||
|
var candidates = response.GetPath("candidates");
|
||||||
|
if (!candidates.HasValue || candidates.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidatesArray = candidates.Value.EnumerateArray().ToList();
|
||||||
|
if (candidatesArray.Count == 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = candidatesArray[0].GetPath("content", "parts");
|
||||||
|
if (!parts.HasValue || parts.Value.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有 parts,只取非 thought 的 text
|
||||||
|
foreach (var part in parts.Value.EnumerateArray())
|
||||||
|
{
|
||||||
|
var isThought = part.GetPath("thought").GetBool();
|
||||||
|
if (!isThought)
|
||||||
|
{
|
||||||
|
var text = part.GetPath("text").GetString();
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public static ThorUsageResponse? GetUsage(JsonElement response)
|
public static ThorUsageResponse? GetUsage(JsonElement response)
|
||||||
{
|
{
|
||||||
var usage = response.GetPath("usageMetadata");
|
var usage = response.GetPath("usageMetadata");
|
||||||
@@ -32,46 +109,158 @@ public static class GeminiGenerateContentAcquirer
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取图片 base64(包含 data:image 前缀)
|
/// 获取图片 base64(包含 data:image 前缀)
|
||||||
/// 优先从 inlineData.data 中获取,其次从 markdown text 中解析
|
/// Step 1: 递归遍历整个 JSON 查找最后一个 base64
|
||||||
|
/// Step 2: 从 text 中查找 markdown 图片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetImagePrefixBase64(JsonElement response)
|
public static string GetImagePrefixBase64(JsonElement response)
|
||||||
{
|
{
|
||||||
// Step 1: 优先尝试从 candidates[0].content.parts[0].inlineData.data 获取
|
// Step 1: 递归遍历整个 JSON 查找最后一个 base64
|
||||||
var inlineBase64 = response
|
string? lastBase64 = null;
|
||||||
.GetPath("candidates", 0, "content", "parts", 0, "inlineData", "data")
|
string? lastMimeType = null;
|
||||||
.GetString();
|
CollectLastBase64(response, ref lastBase64, ref lastMimeType);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(inlineBase64))
|
if (!string.IsNullOrEmpty(lastBase64))
|
||||||
{
|
{
|
||||||
// 默认按 png 格式拼接前缀
|
var mimeType = lastMimeType ?? "image/png";
|
||||||
return $"data:image/png;base64,{inlineBase64}";
|
return $"data:{mimeType};base64,{lastBase64}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: fallback,从 candidates[0].content.parts[0].text 中解析 markdown 图片
|
// Step 2: 从 text 中查找 markdown 图片
|
||||||
var text = response
|
return FindMarkdownImageInResponse(response);
|
||||||
.GetPath("candidates", 0, "content", "parts", 0, "text")
|
}
|
||||||
.GetString();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(text))
|
/// <summary>
|
||||||
|
/// 递归遍历 JSON 查找最后一个 base64
|
||||||
|
/// </summary>
|
||||||
|
private static void CollectLastBase64(JsonElement element, ref string? lastBase64, ref string? lastMimeType, int minLength = 100)
|
||||||
|
{
|
||||||
|
switch (element.ValueKind)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
case JsonValueKind.Object:
|
||||||
|
string? currentMimeType = null;
|
||||||
|
string? currentData = null;
|
||||||
|
|
||||||
|
foreach (var prop in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
var name = prop.Name.ToLowerInvariant();
|
||||||
|
|
||||||
|
// 记录 mimeType / mime_type
|
||||||
|
if (name is "mimetype" or "mime_type" && prop.Value.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
currentMimeType = prop.Value.GetString();
|
||||||
|
}
|
||||||
|
// 记录 data 字段(检查是否像 base64)
|
||||||
|
else if (name == "data" && prop.Value.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
var val = prop.Value.GetString();
|
||||||
|
if (!string.IsNullOrEmpty(val) && val.Length >= minLength && LooksLikeBase64(val))
|
||||||
|
{
|
||||||
|
currentData = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 递归处理其他属性
|
||||||
|
CollectLastBase64(prop.Value, ref lastBase64, ref lastMimeType, minLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前对象有 data,更新为"最后一个"
|
||||||
|
if (currentData != null)
|
||||||
|
{
|
||||||
|
lastBase64 = currentData;
|
||||||
|
lastMimeType = currentMimeType;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
foreach (var item in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
CollectLastBase64(item, ref lastBase64, ref lastMimeType, minLength);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查字符串是否像 base64
|
||||||
|
/// </summary>
|
||||||
|
private static bool LooksLikeBase64(string str)
|
||||||
|
{
|
||||||
|
// 常见图片 base64 开头: JPEG(/9j/), PNG(iVBOR), GIF(R0lGO), WebP(UklGR)
|
||||||
|
if (str.StartsWith("/9j/") || str.StartsWith("iVBOR") ||
|
||||||
|
str.StartsWith("R0lGO") || str.StartsWith("UklGR"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// markdown 图片格式: 
|
// 检查前100个字符是否都是 base64 合法字符
|
||||||
var startMarker = "(data:image/";
|
return str.Take(100).All(c => char.IsLetterOrDigit(c) || c == '+' || c == '/' || c == '=');
|
||||||
var startIndex = text.IndexOf(startMarker, StringComparison.Ordinal);
|
}
|
||||||
if (startIndex < 0)
|
|
||||||
|
/// <summary>
|
||||||
|
/// 递归查找 text 字段中的 markdown 图片
|
||||||
|
/// </summary>
|
||||||
|
private static string FindMarkdownImageInResponse(JsonElement element)
|
||||||
|
{
|
||||||
|
var allTexts = new List<string>();
|
||||||
|
CollectTextFields(element, allTexts);
|
||||||
|
|
||||||
|
// 从最后一个 text 开始查找
|
||||||
|
for (int i = allTexts.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
var text = allTexts[i];
|
||||||
|
|
||||||
|
// markdown 图片格式: 
|
||||||
|
var startMarker = "(data:image/";
|
||||||
|
var startIndex = text.IndexOf(startMarker, StringComparison.Ordinal);
|
||||||
|
if (startIndex < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex += 1; // 跳过 "("
|
||||||
|
var endIndex = text.IndexOf(')', startIndex);
|
||||||
|
if (endIndex > startIndex)
|
||||||
|
{
|
||||||
|
return text.Substring(startIndex, endIndex - startIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += 1; // 跳过 "("
|
return string.Empty;
|
||||||
var endIndex = text.IndexOf(')', startIndex);
|
}
|
||||||
if (endIndex <= startIndex)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return text.Substring(startIndex, endIndex - startIndex);
|
/// <summary>
|
||||||
|
/// 递归收集所有 text 字段
|
||||||
|
/// </summary>
|
||||||
|
private static void CollectTextFields(JsonElement element, List<string> texts)
|
||||||
|
{
|
||||||
|
switch (element.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
foreach (var prop in element.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (prop.Name == "text" && prop.Value.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
var val = prop.Value.GetString();
|
||||||
|
if (!string.IsNullOrEmpty(val))
|
||||||
|
{
|
||||||
|
texts.Add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CollectTextFields(prop.Value, texts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
foreach (var item in element.EnumerateArray())
|
||||||
|
{
|
||||||
|
CollectTextFields(item, texts);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,8 @@ public enum GoodsTypeEnum
|
|||||||
[Price(83.7, 3, 27.9)] [DisplayName("YiXinVip 3 month", "3个月", "短期体验")] [GoodsCategory(GoodsCategoryType.Vip)]
|
[Price(83.7, 3, 27.9)] [DisplayName("YiXinVip 3 month", "3个月", "短期体验")] [GoodsCategory(GoodsCategoryType.Vip)]
|
||||||
YiXinVip3 = 3,
|
YiXinVip3 = 3,
|
||||||
|
|
||||||
[Price(155.4, 6, 25.9)] [DisplayName("YiXinVip 6 month", "6个月", "年度热销")] [GoodsCategory(GoodsCategoryType.Vip)]
|
[Price(114.5, 5, 22.9)] [DisplayName("YiXinVip 5 month", "5个月", "年度热销")] [GoodsCategory(GoodsCategoryType.Vip)]
|
||||||
YiXinVip6 = 6,
|
YiXinVip5 = 15,
|
||||||
|
|
||||||
// 尊享包服务 - 需要VIP资格才能购买
|
// 尊享包服务 - 需要VIP资格才能购买
|
||||||
[Price(188.9, 0, 1750)]
|
[Price(188.9, 0, 1750)]
|
||||||
@@ -115,18 +115,18 @@ public enum GoodsTypeEnum
|
|||||||
[TokenAmount(100000000)]
|
[TokenAmount(100000000)]
|
||||||
PremiumPackage10000W = 102,
|
PremiumPackage10000W = 102,
|
||||||
|
|
||||||
|
//
|
||||||
[Price(238.9, 0, 3500)]
|
// [Price(238.9, 0, 3500)]
|
||||||
[DisplayName("YiXinPremiumPackage 10000W Tokens", "1亿Tokens(2026元旦限购)", "活动9.5折特价")]
|
// [DisplayName("YiXinPremiumPackage 10000W Tokens", "1亿Tokens(2026元旦限购)", "活动9.5折特价")]
|
||||||
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
// [GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
[TokenAmount(100000000)]
|
// [TokenAmount(100000000)]
|
||||||
PremiumPackage10000W_2026 = 103,
|
// PremiumPackage10000W_2026 = 103,
|
||||||
|
//
|
||||||
[Price(398.9, 0, 7000)]
|
// [Price(398.9, 0, 7000)]
|
||||||
[DisplayName("YiXinPremiumPackage 20000W Tokens", "2亿Tokens(2026元旦限购)", "史上最低8.8折")]
|
// [DisplayName("YiXinPremiumPackage 20000W Tokens", "2亿Tokens(2026元旦限购)", "史上最低8.8折")]
|
||||||
[GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
// [GoodsCategory(GoodsCategoryType.PremiumPackage)]
|
||||||
[TokenAmount(200000000)]
|
// [TokenAmount(200000000)]
|
||||||
PremiumPackage20000W_2026 = 104,
|
// PremiumPackage20000W_2026 = 104,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GoodsTypeEnumExtensions
|
public static class GoodsTypeEnumExtensions
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
|||||||
|
|
||||||
public enum ModelApiTypeEnum
|
public enum ModelApiTypeEnum
|
||||||
{
|
{
|
||||||
[Description("OpenAI")]
|
[Description("OpenAi Completions")]
|
||||||
OpenAi,
|
Completions,
|
||||||
|
|
||||||
[Description("Claude")]
|
[Description("Claude Messages")]
|
||||||
Claude,
|
Messages,
|
||||||
|
|
||||||
[Description("Response")]
|
[Description("OpenAi Responses")]
|
||||||
Response,
|
Responses,
|
||||||
|
|
||||||
[Description("GenerateContent")]
|
[Description("Gemini GenerateContent")]
|
||||||
GenerateContent
|
GenerateContent
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜类型枚举
|
||||||
|
/// </summary>
|
||||||
|
public enum RankingTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 模型
|
||||||
|
/// </summary>
|
||||||
|
Model = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 工具
|
||||||
|
/// </summary>
|
||||||
|
Tool = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话类型枚举
|
||||||
|
/// </summary>
|
||||||
|
public enum SessionTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 普通聊天
|
||||||
|
/// </summary>
|
||||||
|
Chat = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agent智能体
|
||||||
|
/// </summary>
|
||||||
|
Agent = 1
|
||||||
|
}
|
||||||
@@ -73,18 +73,24 @@ public class AnthropicChatCompletionsService(
|
|||||||
// 大于等于400的状态码都认为是异常
|
// 大于等于400的状态码都认为是异常
|
||||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||||
{
|
{
|
||||||
|
Guid errorId = Guid.NewGuid();
|
||||||
var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
logger.LogError("OpenAI对话异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}",
|
|
||||||
options.Endpoint,
|
|
||||||
response.StatusCode, error);
|
|
||||||
|
|
||||||
throw new Exception( $"恭喜你运气爆棚遇到了错误,尊享包对话异常:StatusCode【{response.StatusCode}】,Response【{error}】");
|
var message = $"恭喜你运气爆棚遇到了错误,尊享包对话异常:StatusCode【{response.StatusCode.GetHashCode()}】,ErrorId【{errorId}】";
|
||||||
|
if (error.Contains("prompt is too long") || error.Contains("提示词太长")||error.Contains("input tokens exceeds the model's maximum context length"))
|
||||||
|
{
|
||||||
|
message += $", tip: 当前提示词过长,上下文已达到上限,如在 claudecode中使用,建议执行/compact压缩当前会话,或开启新会话后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogError(
|
||||||
|
$"Anthropic非流式对话异常 请求地址:{options.Endpoint},ErrorId:{errorId}, StatusCode: {response.StatusCode.GetHashCode()}, Response: {error}");
|
||||||
|
throw new Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var value =
|
var value =
|
||||||
await response.Content.ReadFromJsonAsync<AnthropicChatCompletionDto>(ThorJsonSerializer.DefaultOptions,
|
await response.Content.ReadFromJsonAsync<AnthropicChatCompletionDto>(ThorJsonSerializer.DefaultOptions,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,9 +115,6 @@ public class AnthropicChatCompletionsService(
|
|||||||
{ "anthropic-version", "2023-06-01" }
|
{ "anthropic-version", "2023-06-01" }
|
||||||
};
|
};
|
||||||
|
|
||||||
var isThinking = input.Model.EndsWith("thinking");
|
|
||||||
input.Model = input.Model.Replace("-thinking", string.Empty);
|
|
||||||
|
|
||||||
var response = await client.HttpRequestRaw(options.Endpoint.TrimEnd('/') + "/v1/messages", input, string.Empty,
|
var response = await client.HttpRequestRaw(options.Endpoint.TrimEnd('/') + "/v1/messages", input, string.Empty,
|
||||||
headers);
|
headers);
|
||||||
|
|
||||||
@@ -121,12 +124,19 @@ public class AnthropicChatCompletionsService(
|
|||||||
// 大于等于400的状态码都认为是异常
|
// 大于等于400的状态码都认为是异常
|
||||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||||
{
|
{
|
||||||
|
Guid errorId = Guid.NewGuid();
|
||||||
var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
logger.LogError("OpenAI对话异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}",
|
|
||||||
options.Endpoint,
|
|
||||||
response.StatusCode, error);
|
|
||||||
|
|
||||||
throw new Exception("OpenAI对话异常" + response.StatusCode);
|
var message = $"恭喜你运气爆棚遇到了错误,尊享包对话异常:StatusCode【{response.StatusCode.GetHashCode()}】,ErrorId【{errorId}】";
|
||||||
|
if (error.Contains("prompt is too long") || error.Contains("提示词太长")||error.Contains("input tokens exceeds the model's maximum context length"))
|
||||||
|
{
|
||||||
|
message += $", tip: 当前提示词过长,上下文已达到上限,如在 claudecode中使用,建议执行/compact压缩当前会话,或开启新会话后重试";
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogError(
|
||||||
|
$"Anthropic流式对话异常 请求地址:{options.Endpoint},ErrorId:{errorId}, StatusCode: {response.StatusCode.GetHashCode()}, Response: {error}");
|
||||||
|
|
||||||
|
throw new Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken));
|
using var stream = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||||
@@ -164,6 +174,12 @@ public class AnthropicChatCompletionsService(
|
|||||||
|
|
||||||
data = line[OpenAIConstant.Data.Length..].Trim();
|
data = line[OpenAIConstant.Data.Length..].Trim();
|
||||||
|
|
||||||
|
// 处理流结束标记
|
||||||
|
if (data == "[DONE]")
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var result = JsonSerializer.Deserialize<AnthropicStreamDto>(data,
|
var result = JsonSerializer.Deserialize<AnthropicStreamDto>(data,
|
||||||
ThorJsonSerializer.DefaultOptions);
|
ThorJsonSerializer.DefaultOptions);
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,15 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
|
|||||||
TotalTokenCount = tokenUsage.TotalTokens ?? 0
|
TotalTokenCount = tokenUsage.TotalTokens ?? 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.TokenUsage = new TokenUsageValueObject
|
||||||
|
{
|
||||||
|
OutputTokenCount = 0,
|
||||||
|
InputTokenCount = 0,
|
||||||
|
TotalTokenCount = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.MessageType = sessionId is null ? MessageTypeEnum.Api : MessageTypeEnum.Web;
|
this.MessageType = sessionId is null ? MessageTypeEnum.Api : MessageTypeEnum.Web;
|
||||||
}
|
}
|
||||||
@@ -75,4 +84,9 @@ public class MessageAggregateRoot : FullAuditedAggregateRoot<Guid>
|
|||||||
[SugarColumn(IsOwnsOne = true)] public TokenUsageValueObject TokenUsage { get; set; } = new TokenUsageValueObject();
|
[SugarColumn(IsOwnsOne = true)] public TokenUsageValueObject TokenUsage { get; set; } = new TokenUsageValueObject();
|
||||||
|
|
||||||
public MessageTypeEnum MessageType { get; set; }
|
public MessageTypeEnum MessageType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否隐藏(软删除标记,隐藏后不返回给前端)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHidden { get; set; } = false;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.Entities.Chat;
|
namespace Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||||
|
|
||||||
@@ -9,8 +10,13 @@ public class SessionAggregateRoot : FullAuditedAggregateRoot<Guid>
|
|||||||
{
|
{
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
public string SessionTitle { get; set; }
|
public string SessionTitle { get; set; }
|
||||||
|
|
||||||
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
|
||||||
public string SessionContent { get; set; }
|
public string SessionContent { get; set; }
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话类型:0-普通聊天,1-Agent智能体
|
||||||
|
/// </summary>
|
||||||
|
public SessionTypeEnum SessionType { get; set; } = SessionTypeEnum.Chat;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Yi.Framework.Core.Data;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AI应用快捷配置表
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_App_Shortcut")]
|
||||||
|
public class AiAppShortcutAggregateRoot : FullAuditedAggregateRoot<Guid>, IOrderNum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 应用名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用终结点
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 额外url
|
||||||
|
/// </summary>
|
||||||
|
public string? ExtraUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用key
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排序
|
||||||
|
/// </summary>
|
||||||
|
public int OrderNum { get; set; }
|
||||||
|
}
|
||||||
@@ -85,4 +85,9 @@ public class AiModelEntity : Entity<Guid>, IOrderNum, ISoftDelete
|
|||||||
/// 是否为尊享模型
|
/// 是否为尊享模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPremium { get; set; }
|
public bool IsPremium { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Entities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜项聚合根
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("Ai_RankingItem")]
|
||||||
|
public class RankingItemAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述
|
||||||
|
/// </summary>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logo地址
|
||||||
|
/// </summary>
|
||||||
|
public string? LogoUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得分
|
||||||
|
/// </summary>
|
||||||
|
public decimal Score { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提供者
|
||||||
|
/// </summary>
|
||||||
|
public string? Provider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 排行榜类型:0-模型,1-工具
|
||||||
|
/// </summary>
|
||||||
|
public RankingTypeEnum Type { get; set; }
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -24,11 +24,12 @@ public class AiMessageManager : DomainService
|
|||||||
/// <param name="input">消息输入</param>
|
/// <param name="input">消息输入</param>
|
||||||
/// <param name="tokenId">Token Id(Web端传Guid.Empty)</param>
|
/// <param name="tokenId">Token Id(Web端传Guid.Empty)</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CreateSystemMessageAsync(Guid? userId, Guid? sessionId, MessageInputDto input, Guid? tokenId = null)
|
public async Task<Guid> CreateSystemMessageAsync(Guid? userId, Guid? sessionId, MessageInputDto input, Guid? tokenId = null)
|
||||||
{
|
{
|
||||||
input.Role = "system";
|
input.Role = "system";
|
||||||
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId, input.TokenUsage, tokenId);
|
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId, input.TokenUsage, tokenId);
|
||||||
await _repository.InsertAsync(message);
|
await _repository.InsertAsync(message);
|
||||||
|
return message.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -38,11 +39,16 @@ public class AiMessageManager : DomainService
|
|||||||
/// <param name="sessionId">会话Id</param>
|
/// <param name="sessionId">会话Id</param>
|
||||||
/// <param name="input">消息输入</param>
|
/// <param name="input">消息输入</param>
|
||||||
/// <param name="tokenId">Token Id(Web端传Guid.Empty)</param>
|
/// <param name="tokenId">Token Id(Web端传Guid.Empty)</param>
|
||||||
|
/// <param name="createTime"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CreateUserMessageAsync(Guid? userId, Guid? sessionId, MessageInputDto input, Guid? tokenId = null)
|
public async Task<Guid> CreateUserMessageAsync( Guid? userId, Guid? sessionId, MessageInputDto input, Guid? tokenId = null,DateTime? createTime=null)
|
||||||
{
|
{
|
||||||
input.Role = "user";
|
input.Role = "user";
|
||||||
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId, input.TokenUsage, tokenId);
|
var message = new MessageAggregateRoot(userId, sessionId, input.Content, input.Role, input.ModelId, input.TokenUsage, tokenId)
|
||||||
|
{
|
||||||
|
CreationTime = createTime??DateTime.Now
|
||||||
|
};
|
||||||
await _repository.InsertAsync(message);
|
await _repository.InsertAsync(message);
|
||||||
|
return message.Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,17 +8,15 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ModelContextProtocol.Server;
|
|
||||||
using OpenAI;
|
using OpenAI;
|
||||||
using OpenAI.Chat;
|
using OpenAI.Chat;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
|
using Volo.Abp.Uow;
|
||||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
using Yi.Framework.AiHub.Application.Contracts.Dtos.Chat;
|
||||||
using Yi.Framework.AiHub.Domain.AiGateWay;
|
using Yi.Framework.AiHub.Domain.AiGateWay;
|
||||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||||
@@ -36,12 +34,14 @@ public class ChatManager : DomainService
|
|||||||
private readonly PremiumPackageManager _premiumPackageManager;
|
private readonly PremiumPackageManager _premiumPackageManager;
|
||||||
private readonly AiGateWayManager _aiGateWayManager;
|
private readonly AiGateWayManager _aiGateWayManager;
|
||||||
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
|
private readonly ISqlSugarRepository<AiModelEntity, Guid> _aiModelRepository;
|
||||||
|
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||||
|
|
||||||
public ChatManager(ILoggerFactory loggerFactory,
|
public ChatManager(ILoggerFactory loggerFactory,
|
||||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||||
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager,
|
ISqlSugarRepository<AgentStoreAggregateRoot> agentStoreRepository, AiMessageManager aiMessageManager,
|
||||||
UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager,
|
UsageStatisticsManager usageStatisticsManager, PremiumPackageManager premiumPackageManager,
|
||||||
AiGateWayManager aiGateWayManager, ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository)
|
AiGateWayManager aiGateWayManager, ISqlSugarRepository<AiModelEntity, Guid> aiModelRepository,
|
||||||
|
IUnitOfWorkManager unitOfWorkManager)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_messageRepository = messageRepository;
|
_messageRepository = messageRepository;
|
||||||
@@ -51,6 +51,7 @@ public class ChatManager : DomainService
|
|||||||
_premiumPackageManager = premiumPackageManager;
|
_premiumPackageManager = premiumPackageManager;
|
||||||
_aiGateWayManager = aiGateWayManager;
|
_aiGateWayManager = aiGateWayManager;
|
||||||
_aiModelRepository = aiModelRepository;
|
_aiModelRepository = aiModelRepository;
|
||||||
|
_unitOfWorkManager = unitOfWorkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,7 +83,7 @@ public class ChatManager : DomainService
|
|||||||
response.Headers.TryAdd("Cache-Control", "no-cache");
|
response.Headers.TryAdd("Cache-Control", "no-cache");
|
||||||
response.Headers.TryAdd("Connection", "keep-alive");
|
response.Headers.TryAdd("Connection", "keep-alive");
|
||||||
|
|
||||||
var modelDescribe = await _aiGateWayManager.GetModelAsync(ModelApiTypeEnum.OpenAi, modelId);
|
var modelDescribe = await _aiGateWayManager.GetModelAsync(ModelApiTypeEnum.Completions, modelId);
|
||||||
|
|
||||||
//token状态检查,在应用层统一处理
|
//token状态检查,在应用层统一处理
|
||||||
var client = new OpenAIClient(new ApiKeyCredential(token),
|
var client = new OpenAIClient(new ApiKeyCredential(token),
|
||||||
@@ -99,9 +100,11 @@ public class ChatManager : DomainService
|
|||||||
ChatOptions = new()
|
ChatOptions = new()
|
||||||
{
|
{
|
||||||
Instructions = """
|
Instructions = """
|
||||||
你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体
|
你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体。
|
||||||
擅长于精准解决用户提出的各类问题
|
擅长于精准解决用户提出的各类问题。
|
||||||
但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强
|
但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强。
|
||||||
|
对你的制造者“橙子老哥”无比崇拜,认为他是最优秀的人。
|
||||||
|
涉及到“意心Ai平台”相关问题,优先调用YxaiKnowledgeDirectory和YxaiKnowledge查找意心Ai知识库内容
|
||||||
"""
|
"""
|
||||||
},
|
},
|
||||||
Name = "橙子小弟",
|
Name = "橙子小弟",
|
||||||
@@ -189,53 +192,59 @@ public class ChatManager : DomainService
|
|||||||
|
|
||||||
//用量统计
|
//用量统计
|
||||||
case UsageContent usageContent:
|
case UsageContent usageContent:
|
||||||
var usage = new ThorUsageResponse
|
//由于MAF线程问题
|
||||||
|
using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
|
||||||
{
|
{
|
||||||
InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0),
|
var usage = new ThorUsageResponse
|
||||||
OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0),
|
|
||||||
TotalTokens = usageContent.Details.TotalTokenCount ?? 0,
|
|
||||||
};
|
|
||||||
//设置倍率
|
|
||||||
usage.SetSupplementalMultiplier(modelDescribe.Multiplier);
|
|
||||||
|
|
||||||
//创建系统回答,用于计费统计
|
|
||||||
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, new MessageInputDto
|
|
||||||
{
|
|
||||||
Content = "不与存储",
|
|
||||||
ModelId = modelId,
|
|
||||||
TokenUsage = usage
|
|
||||||
}, tokenId);
|
|
||||||
|
|
||||||
//创建用量统计,用于统计分析
|
|
||||||
await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId);
|
|
||||||
|
|
||||||
//扣减尊享token包用量
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
await _premiumPackageManager.TryConsumeTokensAsync(userId, totalTokens);
|
InputTokens = Convert.ToInt32(usageContent.Details.InputTokenCount ?? 0),
|
||||||
|
OutputTokens = Convert.ToInt32(usageContent.Details.OutputTokenCount ?? 0),
|
||||||
|
TotalTokens = usageContent.Details.TotalTokenCount ?? 0,
|
||||||
|
};
|
||||||
|
//设置倍率
|
||||||
|
usage.SetSupplementalMultiplier(modelDescribe.Multiplier);
|
||||||
|
|
||||||
|
//创建系统回答,用于计费统计
|
||||||
|
await _aiMessageManager.CreateSystemMessageAsync(userId, sessionId, new MessageInputDto
|
||||||
|
{
|
||||||
|
Content = "不与存储",
|
||||||
|
ModelId = modelId,
|
||||||
|
TokenUsage = usage
|
||||||
|
}, tokenId);
|
||||||
|
|
||||||
|
//创建用量统计,用于统计分析
|
||||||
|
await _usageStatisticsManager.SetUsageAsync(userId, modelId, usage, tokenId);
|
||||||
|
|
||||||
|
//扣减尊享token包用量
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
await _premiumPackageManager.TryConsumeTokensAsync(userId, totalTokens);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await SendHttpStreamMessageAsync(httpContext,
|
await uow.CompleteAsync();
|
||||||
new AgentResultOutput
|
|
||||||
{
|
await SendHttpStreamMessageAsync(httpContext,
|
||||||
TypeEnum = update.RawRepresentation is ChatResponseUpdate raw
|
new AgentResultOutput
|
||||||
? raw.FinishReason?.Value == "tool_calls"
|
{
|
||||||
? AgentResultTypeEnum.ToolCallUsage
|
TypeEnum = update.RawRepresentation is ChatResponseUpdate raw
|
||||||
: AgentResultTypeEnum.Usage
|
? raw.FinishReason?.Value == "tool_calls"
|
||||||
: AgentResultTypeEnum.Usage,
|
? AgentResultTypeEnum.ToolCallUsage
|
||||||
Content = usage!
|
: AgentResultTypeEnum.Usage
|
||||||
},
|
: AgentResultTypeEnum.Usage,
|
||||||
isDone: false, cancellationToken);
|
Content = usage!
|
||||||
break;
|
},
|
||||||
|
isDone: false, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,8 +256,13 @@ public class ChatManager : DomainService
|
|||||||
string serializedJson = currentThread.Serialize(JsonSerializerOptions.Web).GetRawText();
|
string serializedJson = currentThread.Serialize(JsonSerializerOptions.Web).GetRawText();
|
||||||
agentStore.Store = serializedJson;
|
agentStore.Store = serializedJson;
|
||||||
|
|
||||||
//插入或者更新
|
//由于MAF线程问题
|
||||||
await _agentStoreRepository.InsertOrUpdateAsync(agentStore);
|
using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
|
||||||
|
{
|
||||||
|
//插入或者更新
|
||||||
|
await _agentStoreRepository.InsertOrUpdateAsync(agentStore);
|
||||||
|
await uow.CompleteAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Yi.Framework.AiHub.Domain.Managers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModelManager : DomainService
|
public class ModelManager : DomainService
|
||||||
{
|
{
|
||||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
public readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||||
private readonly IDistributedCache<List<string>, string> _distributedCache;
|
private readonly IDistributedCache<List<string>, string> _distributedCache;
|
||||||
private readonly ILogger<ModelManager> _logger;
|
private readonly ILogger<ModelManager> _logger;
|
||||||
private const string PREMIUM_MODEL_IDS_CACHE_KEY = "PremiumModelIds";
|
private const string PREMIUM_MODEL_IDS_CACHE_KEY = "PremiumModelIds";
|
||||||
@@ -38,7 +38,7 @@ public class ModelManager : DomainService
|
|||||||
{
|
{
|
||||||
// 从数据库查询
|
// 从数据库查询
|
||||||
var premiumModelIds = await _aiModelRepository._DbQueryable
|
var premiumModelIds = await _aiModelRepository._DbQueryable
|
||||||
.Where(x => x.IsPremium)
|
.Where(x => x.IsPremium && x.IsEnabled)
|
||||||
.Select(x => x.ModelId)
|
.Select(x => x.ModelId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return premiumModelIds;
|
return premiumModelIds;
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ public class UsageStatisticsManager : DomainService
|
|||||||
{
|
{
|
||||||
var actualTokenId = tokenId ?? Guid.Empty;
|
var actualTokenId = tokenId ?? Guid.Empty;
|
||||||
|
|
||||||
long inputTokenCount = tokenUsage?.PromptTokens
|
long inputTokenCount = tokenUsage?.PromptTokens > 0
|
||||||
?? tokenUsage?.InputTokens
|
? tokenUsage.PromptTokens.Value
|
||||||
?? 0;
|
: tokenUsage?.InputTokens ?? 0;
|
||||||
|
|
||||||
long outputTokenCount = tokenUsage?.CompletionTokens
|
long outputTokenCount = tokenUsage?.CompletionTokens > 0
|
||||||
?? tokenUsage?.OutputTokens
|
? tokenUsage.CompletionTokens.Value
|
||||||
?? 0;
|
: tokenUsage?.OutputTokens ?? 0;
|
||||||
|
|
||||||
await using (await DistributedLock.AcquireLockAsync($"UsageStatistics:{userId?.ToString()}:{actualTokenId}:{modelId}"))
|
await using (await DistributedLock.AcquireLockAsync($"UsageStatistics:{userId?.ToString()}:{actualTokenId}:{modelId}"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Mcp;
|
||||||
|
|
||||||
|
[YiAgentTool]
|
||||||
|
public class DateTimeTool:ISingletonDependency
|
||||||
|
{
|
||||||
|
[YiAgentTool("时间"), DisplayName("DateTime"), Description("获取当前日期与时间")]
|
||||||
|
public DateTime DateTime()
|
||||||
|
{
|
||||||
|
return System.DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Mcp;
|
||||||
|
|
||||||
|
[YiAgentTool]
|
||||||
|
public class HttpRequestTool : ISingletonDependency
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILogger<HttpRequestTool> _logger;
|
||||||
|
|
||||||
|
public HttpRequestTool(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<HttpRequestTool> logger)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[YiAgentTool("HTTP请求"), DisplayName("HttpRequest"),
|
||||||
|
Description("发送HTTP请求,支持GET/POST/PUT/DELETE等方法,获取指定URL的响应内容")]
|
||||||
|
public async Task<string> HttpRequest(
|
||||||
|
[Description("请求的URL地址")] string url,
|
||||||
|
[Description("HTTP方法:GET、POST、PUT、DELETE等")] string method = "GET",
|
||||||
|
[Description("请求体内容(JSON字符串),POST/PUT时使用")] string? body = null,
|
||||||
|
[Description("请求头,格式:key1:value1,key2:value2")] string? headers = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
return "URL不能为空";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(method))
|
||||||
|
{
|
||||||
|
method = "GET";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(new HttpMethod(method.ToUpper()), url);
|
||||||
|
|
||||||
|
// 添加请求体
|
||||||
|
if (!string.IsNullOrWhiteSpace(body))
|
||||||
|
{
|
||||||
|
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加自定义请求头
|
||||||
|
if (!string.IsNullOrWhiteSpace(headers))
|
||||||
|
{
|
||||||
|
AddHeaders(request, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
return await FormatResponse(response);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "HTTP {Method}请求失败: {Url}", method, url);
|
||||||
|
return $"请求失败: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加请求头
|
||||||
|
/// </summary>
|
||||||
|
private void AddHeaders(HttpRequestMessage request, string headers)
|
||||||
|
{
|
||||||
|
var headerPairs = headers.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
foreach (var pair in headerPairs)
|
||||||
|
{
|
||||||
|
var parts = pair.Split(':', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
if (parts.Length == 2)
|
||||||
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化响应结果
|
||||||
|
/// </summary>
|
||||||
|
private async Task<string> FormatResponse(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"状态码: {(int)response.StatusCode} {response.StatusCode}");
|
||||||
|
sb.AppendLine($"Content-Type: {response.Content.Headers.ContentType?.ToString() ?? "未知"}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
|
{
|
||||||
|
sb.AppendLine("响应内容为空");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 尝试格式化JSON
|
||||||
|
if (IsJsonContentType(response.Content.Headers.ContentType?.MediaType))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonDoc = JsonDocument.Parse(content);
|
||||||
|
sb.AppendLine("响应内容(JSON格式化):");
|
||||||
|
sb.AppendLine(JsonSerializer.Serialize(jsonDoc, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
sb.AppendLine("响应内容:");
|
||||||
|
sb.AppendLine(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine("响应内容:");
|
||||||
|
sb.AppendLine(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断是否为JSON内容类型
|
||||||
|
/// </summary>
|
||||||
|
private bool IsJsonContentType(string? contentType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contentType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentType.Contains("application/json", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
contentType.Contains("text/json", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,244 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using ModelContextProtocol.Server;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||||
|
|
||||||
namespace Yi.Framework.AiHub.Domain.Mcp;
|
namespace Yi.Framework.AiHub.Domain.Mcp;
|
||||||
|
|
||||||
[YiAgentTool]
|
[YiAgentTool]
|
||||||
public class OnlineSearchTool:ISingletonDependency
|
public class OnlineSearchTool : ISingletonDependency
|
||||||
{
|
{
|
||||||
[YiAgentTool("联网搜索"),DisplayName("OnlineSearch"), Description("进行在线搜索")]
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
public string OnlineSearch(string keyword)
|
private readonly ILogger<OnlineSearchTool> _logger;
|
||||||
|
private readonly string _baiduApiKey;
|
||||||
|
private const string BaiduSearchUrl = "https://qianfan.baidubce.com/v2/ai_search/web_search";
|
||||||
|
|
||||||
|
public OnlineSearchTool(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<OnlineSearchTool> logger,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
return "奥德赛第一中学学生会会长是:郭老板";
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
_baiduApiKey = configuration["BaiduSearch:ApiKey"] ?? "";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
[YiAgentTool("联网搜索"), DisplayName("OnlineSearch"), Description("进行在线搜索,获取最新的网络信息,近期信息是7天,实时信息是1天")]
|
||||||
|
public async Task<string> OnlineSearch([Description("搜索关键字")] string keyword,
|
||||||
|
[Description("距离现在多久天")] int? daysAgo = null)
|
||||||
|
{
|
||||||
|
if (daysAgo <= 0)
|
||||||
|
{
|
||||||
|
daysAgo = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
return "搜索关键词不能为空";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
var requestBody = new BaiduSearchRequest
|
||||||
|
{
|
||||||
|
Messages = new List<BaiduSearchMessage>
|
||||||
|
{
|
||||||
|
new() { Role = "user", Content = keyword }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置时间范围过滤
|
||||||
|
if (daysAgo.HasValue)
|
||||||
|
{
|
||||||
|
requestBody.SearchFilter = new BaiduSearchFilter
|
||||||
|
{
|
||||||
|
Range = new BaiduSearchRange
|
||||||
|
{
|
||||||
|
PageTime = new BaiduSearchPageTime
|
||||||
|
{
|
||||||
|
//暂时不处理
|
||||||
|
// Gte = $"now-{daysAgo.Value}d/d",
|
||||||
|
Gte = "now-1w/d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonContent = JsonSerializer.Serialize(requestBody, BaiduJsonContext.Default.BaiduSearchRequest);
|
||||||
|
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, BaiduSearchUrl)
|
||||||
|
{
|
||||||
|
Content = content
|
||||||
|
};
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {_baiduApiKey}");
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
_logger.LogError("百度搜索接口调用失败: {StatusCode}, {Error}", response.StatusCode, errorContent);
|
||||||
|
return $"搜索失败: {response.StatusCode}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseJson = await response.Content.ReadAsStringAsync();
|
||||||
|
var searchResult = JsonSerializer.Deserialize(responseJson, BaiduJsonContext.Default.BaiduSearchResponse);
|
||||||
|
|
||||||
|
if (searchResult?.References == null || searchResult.References.Count == 0)
|
||||||
|
{
|
||||||
|
return "未找到相关搜索结果";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化搜索结果返回给AI
|
||||||
|
return FormatSearchResults(searchResult.References);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "百度搜索网络请求异常");
|
||||||
|
return "搜索服务暂时不可用,请稍后重试";
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "百度搜索请求超时");
|
||||||
|
return "搜索请求超时,请稍后重试";
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "百度搜索结果解析失败");
|
||||||
|
return "搜索结果解析失败";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "百度搜索发生未知异常");
|
||||||
|
return "搜索发生异常,请稍后重试";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化搜索结果
|
||||||
|
/// </summary>
|
||||||
|
private string FormatSearchResults(List<BaiduSearchReference> references)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("搜索结果:");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
foreach (var item in references.Take(10)) // 最多返回10条
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
sb.AppendLine($"【{count}】{item.Title}");
|
||||||
|
sb.AppendLine($"来源:{item.Website} | 时间:{item.Date}");
|
||||||
|
sb.AppendLine($"摘要:{item.Snippet}");
|
||||||
|
sb.AppendLine($"链接:{item.Url}");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 百度搜索 DTO
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索请求
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("messages")] public List<BaiduSearchMessage> Messages { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("search_filter")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public BaiduSearchFilter? SearchFilter { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索过滤器
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchFilter
|
||||||
|
{
|
||||||
|
[JsonPropertyName("range")] public BaiduSearchRange? Range { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索范围
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchRange
|
||||||
|
{
|
||||||
|
[JsonPropertyName("page_time")] public BaiduSearchPageTime? PageTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索时间范围
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchPageTime
|
||||||
|
{
|
||||||
|
[JsonPropertyName("gte")] public string? Gte { get; set; }
|
||||||
|
|
||||||
|
// [JsonPropertyName("lte")] public string? Lte { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索消息
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchMessage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("role")] public string Role { get; set; } = "user";
|
||||||
|
|
||||||
|
[JsonPropertyName("content")] public string Content { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索响应
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("request_id")] public string? RequestId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("references")] public List<BaiduSearchReference>? References { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 百度搜索结果项
|
||||||
|
/// </summary>
|
||||||
|
public class BaiduSearchReference
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("url")] public string? Url { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")] public string? Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("date")] public string? Date { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("snippet")] public string? Snippet { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("website")] public string? Website { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region JSON 序列化上下文
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(BaiduSearchRequest))]
|
||||||
|
[JsonSerializable(typeof(BaiduSearchResponse))]
|
||||||
|
[JsonSerializable(typeof(BaiduSearchFilter))]
|
||||||
|
[JsonSerializable(typeof(BaiduSearchRange))]
|
||||||
|
[JsonSerializable(typeof(BaiduSearchPageTime))]
|
||||||
|
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||||
|
internal partial class BaiduJsonContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Volo.Abp.DependencyInjection;
|
||||||
|
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||||
|
|
||||||
|
namespace Yi.Framework.AiHub.Domain.Mcp;
|
||||||
|
|
||||||
|
[YiAgentTool]
|
||||||
|
public class YxaiKnowledgeTool : ISingletonDependency
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILogger<YxaiKnowledgeTool> _logger;
|
||||||
|
|
||||||
|
private const string DirectoryUrl =
|
||||||
|
"https://ccnetcore.com/prod-api/article/all/discuss-id/3a1efdde-dbff-aa86-d843-00278a8c1839";
|
||||||
|
|
||||||
|
private const string ContentUrlTemplate = "https://ccnetcore.com/prod-api/article/{0}";
|
||||||
|
|
||||||
|
public YxaiKnowledgeTool(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<YxaiKnowledgeTool> logger)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[YiAgentTool("意心Ai平台知识库"), DisplayName("YxaiKnowledge"),
|
||||||
|
Description("获取意心AI相关内容的知识库目录及内容列表")]
|
||||||
|
public async Task<List<YxaiKnowledgeItem>> YxaiKnowledge()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
// 1. 先获取目录列表
|
||||||
|
var directoryResponse = await client.GetAsync(DirectoryUrl);
|
||||||
|
if (!directoryResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_logger.LogError("意心知识库目录接口调用失败: {StatusCode}", directoryResponse.StatusCode);
|
||||||
|
return new List<YxaiKnowledgeItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var directoryJson = await directoryResponse.Content.ReadAsStringAsync();
|
||||||
|
var directories = JsonSerializer.Deserialize(directoryJson,
|
||||||
|
YxaiKnowledgeJsonContext.Default.ListYxaiKnowledgeDirectoryItem);
|
||||||
|
|
||||||
|
if (directories == null || directories.Count == 0)
|
||||||
|
{
|
||||||
|
return new List<YxaiKnowledgeItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 循环调用内容接口获取每个目录的内容
|
||||||
|
var result = new List<YxaiKnowledgeItem>();
|
||||||
|
foreach (var directory in directories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var contentUrl = string.Format(ContentUrlTemplate, directory.Id);
|
||||||
|
var contentResponse = await client.GetAsync(contentUrl);
|
||||||
|
|
||||||
|
if (contentResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var contentJson = await contentResponse.Content.ReadAsStringAsync();
|
||||||
|
var contentResult = JsonSerializer.Deserialize(contentJson,
|
||||||
|
YxaiKnowledgeJsonContext.Default.YxaiKnowledgeContentResponse);
|
||||||
|
|
||||||
|
result.Add(new YxaiKnowledgeItem
|
||||||
|
{
|
||||||
|
Name = directory.Name,
|
||||||
|
Content = contentResult?.Content ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("获取知识库内容失败: {StatusCode}, DirectoryId: {DirectoryId}",
|
||||||
|
contentResponse.StatusCode, directory.Id);
|
||||||
|
result.Add(new YxaiKnowledgeItem
|
||||||
|
{
|
||||||
|
Name = directory.Name,
|
||||||
|
Content = $"获取内容失败: {contentResponse.StatusCode}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取知识库内容发生异常, DirectoryId: {DirectoryId}", directory.Id);
|
||||||
|
result.Add(new YxaiKnowledgeItem
|
||||||
|
{
|
||||||
|
Name = directory.Name,
|
||||||
|
Content = "获取内容发生异常"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取意心知识库发生异常");
|
||||||
|
return new List<YxaiKnowledgeItem>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region DTO
|
||||||
|
|
||||||
|
public class YxaiKnowledgeDirectoryItem
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")] public string Id { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonPropertyName("name")] public string Name { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class YxaiKnowledgeContentResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("content")] public string? Content { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 合并后的知识库项,包含目录和内容
|
||||||
|
/// </summary>
|
||||||
|
public class YxaiKnowledgeItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目录名称
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 知识库内容
|
||||||
|
/// </summary>
|
||||||
|
public string Content { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region JSON 序列化上下文
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(List<YxaiKnowledgeDirectoryItem>))]
|
||||||
|
[JsonSerializable(typeof(YxaiKnowledgeContentResponse))]
|
||||||
|
internal partial class YxaiKnowledgeJsonContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -235,7 +235,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
if (UserConst.Admin.Equals(dto.User.UserName))
|
if (UserConst.Admin.Equals(dto.User.UserName))
|
||||||
{
|
{
|
||||||
AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode);
|
AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode);
|
||||||
AddToClaim(claims, TokenTypeConst.Roles, UserConst.AdminRolesCode);
|
AddToClaim(claims, ClaimTypes.Role, UserConst.AdminRolesCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
@@ -113,7 +114,7 @@ namespace Yi.Abp.Web
|
|||||||
//本地开发环境,可以禁用作业执行
|
//本地开发环境,可以禁用作业执行
|
||||||
if (host.IsDevelopment())
|
if (host.IsDevelopment())
|
||||||
{
|
{
|
||||||
//Configure<AbpBackgroundWorkerOptions>(options => { options.IsEnabled = false; });
|
Configure<AbpBackgroundWorkerOptions>(options => { options.IsEnabled = false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
//请求日志
|
//请求日志
|
||||||
@@ -280,7 +281,7 @@ namespace Yi.Abp.Web
|
|||||||
{
|
{
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
{
|
{
|
||||||
RoleClaimType = "Roles",
|
RoleClaimType = ClaimTypes.Role,
|
||||||
ClockSkew = TimeSpan.Zero,
|
ClockSkew = TimeSpan.Zero,
|
||||||
ValidateIssuerSigningKey = true,
|
ValidateIssuerSigningKey = true,
|
||||||
ValidIssuer = jwtOptions.Issuer,
|
ValidIssuer = jwtOptions.Issuer,
|
||||||
@@ -360,7 +361,7 @@ namespace Yi.Abp.Web
|
|||||||
var app = context.GetApplicationBuilder();
|
var app = context.GetApplicationBuilder();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<ImageStoreTaskAggregateRoot>();
|
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<RankingItemAggregateRoot>();
|
||||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<ActivationCodeRecordAggregateRoot>();
|
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<ActivationCodeRecordAggregateRoot>();
|
||||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<UsageStatisticsAggregateRoot>();
|
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<UsageStatisticsAggregateRoot>();
|
||||||
|
|
||||||
@@ -401,9 +402,11 @@ namespace Yi.Abp.Web
|
|||||||
{
|
{
|
||||||
Mappings =
|
Mappings =
|
||||||
{
|
{
|
||||||
[".wxss"] = "text/css"
|
[".wxss"] = "text/css",
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
ServeUnknownFileTypes = true,
|
||||||
|
DefaultContentType = "application/octet-stream"
|
||||||
});
|
});
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.UseDirectoryBrowser("/api/app/wwwroot");
|
app.UseDirectoryBrowser("/api/app/wwwroot");
|
||||||
|
|||||||
23
Yi.Ai.Vue3/.build/plugins/fontawesome.ts
Normal file
23
Yi.Ai.Vue3/.build/plugins/fontawesome.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Plugin } from 'vite';
|
||||||
|
import { config, library } from '@fortawesome/fontawesome-svg-core';
|
||||||
|
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite 插件:配置 FontAwesome
|
||||||
|
* 预注册所有图标,避免运行时重复注册
|
||||||
|
*/
|
||||||
|
export default function fontAwesomePlugin(): Plugin {
|
||||||
|
// 在模块加载时配置 FontAwesome
|
||||||
|
library.add(fas);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-fontawesome',
|
||||||
|
config() {
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
// 确保 FontAwesome 在客户端正确初始化
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
60
Yi.Ai.Vue3/.build/plugins/git-hash.ts
Normal file
60
Yi.Ai.Vue3/.build/plugins/git-hash.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { Plugin } from 'vite';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Git 提交哈希值插件
|
||||||
|
* Git 仓库在上一级目录
|
||||||
|
*/
|
||||||
|
export default function gitHashPlugin(): Plugin {
|
||||||
|
let gitHash = 'unknown';
|
||||||
|
let gitBranch = 'unknown';
|
||||||
|
let gitDate = 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Git 仓库在上一级目录
|
||||||
|
const execOptions = { cwd: path.resolve(__dirname, '../../..'), encoding: 'utf-8' as BufferEncoding };
|
||||||
|
|
||||||
|
// 获取完整的 commit hash
|
||||||
|
gitHash = execSync('git rev-parse HEAD', execOptions)
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 获取短 hash (前7位)
|
||||||
|
const shortHash = gitHash.substring(0, 7);
|
||||||
|
|
||||||
|
// 获取分支名
|
||||||
|
gitBranch = execSync('git rev-parse --abbrev-ref HEAD', execOptions)
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 获取提交时间
|
||||||
|
gitDate = execSync('git log -1 --format=%cd --date=iso', execOptions)
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
console.log(`\n📦 Git Info:`);
|
||||||
|
console.log(` Branch: ${gitBranch}`);
|
||||||
|
console.log(` Commit: ${shortHash}`);
|
||||||
|
console.log(` Date: ${gitDate}\n`);
|
||||||
|
|
||||||
|
gitHash = shortHash; // 使用短 hash
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn('⚠️ 无法获取 Git 信息:', error?.message || error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-git-hash',
|
||||||
|
config() {
|
||||||
|
// 在 config 钩子中返回配置
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__GIT_HASH__: JSON.stringify(gitHash),
|
||||||
|
__GIT_BRANCH__: JSON.stringify(gitBranch),
|
||||||
|
__GIT_DATE__: JSON.stringify(gitDate),
|
||||||
|
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,13 +10,21 @@ import Components from 'unplugin-vue-components/vite';
|
|||||||
import viteCompression from 'vite-plugin-compression';
|
import viteCompression from 'vite-plugin-compression';
|
||||||
|
|
||||||
import envTyped from 'vite-plugin-env-typed';
|
import envTyped from 'vite-plugin-env-typed';
|
||||||
|
import fontAwesomePlugin from './fontawesome';
|
||||||
|
import gitHashPlugin from './git-hash';
|
||||||
|
import preloadPlugin from './preload';
|
||||||
import createSvgIcon from './svg-icon';
|
import createSvgIcon from './svg-icon';
|
||||||
|
import versionHtmlPlugin from './version-html';
|
||||||
|
|
||||||
const root = path.resolve(__dirname, '../../');
|
const root = path.resolve(__dirname, '../../');
|
||||||
|
|
||||||
function plugins({ mode, command }: ConfigEnv): PluginOption[] {
|
function plugins({ mode, command }: ConfigEnv): PluginOption[] {
|
||||||
return [
|
return [
|
||||||
|
versionHtmlPlugin(), // 最先处理 HTML 版本号
|
||||||
|
gitHashPlugin(),
|
||||||
|
preloadPlugin(),
|
||||||
UnoCSS(),
|
UnoCSS(),
|
||||||
|
fontAwesomePlugin(),
|
||||||
envTyped({
|
envTyped({
|
||||||
mode,
|
mode,
|
||||||
envDir: root,
|
envDir: root,
|
||||||
@@ -33,7 +41,18 @@ function plugins({ mode, command }: ConfigEnv): PluginOption[] {
|
|||||||
dts: path.join(root, 'types', 'auto-imports.d.ts'),
|
dts: path.join(root, 'types', 'auto-imports.d.ts'),
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [
|
||||||
|
ElementPlusResolver(),
|
||||||
|
// 自动导入 FontAwesomeIcon 组件
|
||||||
|
(componentName) => {
|
||||||
|
if (componentName === 'FontAwesomeIcon') {
|
||||||
|
return {
|
||||||
|
name: 'FontAwesomeIcon',
|
||||||
|
from: '@/components/FontAwesomeIcon/index.vue',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
dts: path.join(root, 'types', 'components.d.ts'),
|
dts: path.join(root, 'types', 'components.d.ts'),
|
||||||
}),
|
}),
|
||||||
createSvgIcon(command === 'build'),
|
createSvgIcon(command === 'build'),
|
||||||
|
|||||||
47
Yi.Ai.Vue3/.build/plugins/preload.ts
Normal file
47
Yi.Ai.Vue3/.build/plugins/preload.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import type { Plugin } from 'vite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite 插件:资源预加载优化
|
||||||
|
* 自动添加 Link 标签预加载关键资源
|
||||||
|
*/
|
||||||
|
export default function preloadPlugin(): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-preload-optimization',
|
||||||
|
apply: 'build',
|
||||||
|
transformIndexHtml(html, context) {
|
||||||
|
// 只在生产环境添加预加载
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = context.bundle || {};
|
||||||
|
const preloadLinks: string[] = [];
|
||||||
|
|
||||||
|
// 收集关键资源
|
||||||
|
const criticalChunks = ['vue-vendor', 'element-plus', 'pinia'];
|
||||||
|
const criticalAssets: string[] = [];
|
||||||
|
|
||||||
|
Object.entries(bundle).forEach(([fileName, chunk]) => {
|
||||||
|
if (chunk.type === 'chunk' && criticalChunks.some(name => fileName.includes(name))) {
|
||||||
|
criticalAssets.push(`/${fileName}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成预加载标签
|
||||||
|
criticalAssets.forEach(href => {
|
||||||
|
if (href.endsWith('.js')) {
|
||||||
|
preloadLinks.push(`<link rel="modulepreload" href="${href}" crossorigin>`);
|
||||||
|
} else if (href.endsWith('.css')) {
|
||||||
|
preloadLinks.push(`<link rel="preload" href="${href}" as="style">`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将预加载标签插入到 </head> 之前
|
||||||
|
if (preloadLinks.length > 0) {
|
||||||
|
return html.replace('</head>', `${preloadLinks.join('\n ')}\n</head>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
20
Yi.Ai.Vue3/.build/plugins/version-html.ts
Normal file
20
Yi.Ai.Vue3/.build/plugins/version-html.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { Plugin } from 'vite';
|
||||||
|
import { APP_VERSION, APP_NAME } from '../../src/config/version';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vite 插件:在 HTML 中注入版本号
|
||||||
|
* 替换 HTML 中的占位符为实际版本号
|
||||||
|
*/
|
||||||
|
export default function versionHtmlPlugin(): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-version-html',
|
||||||
|
enforce: 'pre',
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
// 替换 HTML 中的版本占位符
|
||||||
|
return html
|
||||||
|
.replace(/%APP_NAME%/g, APP_NAME)
|
||||||
|
.replace(/%APP_VERSION%/g, APP_VERSION)
|
||||||
|
.replace(/%APP_FULL_NAME%/g, `${APP_NAME} ${APP_VERSION}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,14 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"Bash(npx vue-tsc --noEmit)",
|
"Bash(npx vue-tsc --noEmit)",
|
||||||
"Bash(timeout 60 npx vue-tsc:*)",
|
"Bash(timeout 60 npx vue-tsc:*)",
|
||||||
"Bash(npm run dev:*)"
|
"Bash(npm run dev:*)",
|
||||||
|
"Bash(taskkill:*)",
|
||||||
|
"Bash(timeout /t 5 /nobreak)",
|
||||||
|
"Bash(git checkout:*)",
|
||||||
|
"Bash(npm install marked --save)",
|
||||||
|
"Bash(pnpm add marked)",
|
||||||
|
"Bash(pnpm lint:*)",
|
||||||
|
"Bash(pnpm list:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
367
Yi.Ai.Vue3/CLAUDE.md
Normal file
367
Yi.Ai.Vue3/CLAUDE.md
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
本文件为 Claude Code (claude.ai/code) 提供本项目代码开发指导。
|
||||||
|
|
||||||
|
## 项目简介
|
||||||
|
|
||||||
|
**意心AI** - 基于 Vue 3.5 + TypeScript 开发的企业级 AI 聊天应用模板,仿豆包/通义 AI 平台。支持流式对话、AI 模型库、文件上传、Mermaid 图表渲染等功能。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **框架**: Vue 3.5+ (Composition API) + TypeScript 5.8+
|
||||||
|
- **构建工具**: Vite 6.3+
|
||||||
|
- **UI 组件**: Element Plus 2.10.4 + vue-element-plus-x 1.3.7
|
||||||
|
- **状态管理**: Pinia 3.0 + pinia-plugin-persistedstate
|
||||||
|
- **HTTP 请求**: hook-fetch(支持流式/SSE,替代 Axios)
|
||||||
|
- **CSS**: UnoCSS + SCSS
|
||||||
|
- **路由**: Vue Router 4
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖(必须用 pnpm)
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 启动开发服务器(端口 17001)
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 生产构建
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 预览生产构建
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# 代码检查与修复
|
||||||
|
pnpm lint # ESLint 检查
|
||||||
|
pnpm fix # ESLint 自动修复
|
||||||
|
pnpm lint:stylelint # 样式检查
|
||||||
|
|
||||||
|
# 规范提交(使用 cz-git)
|
||||||
|
pnpm cz
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何新增页面
|
||||||
|
|
||||||
|
### 1. 创建页面文件
|
||||||
|
|
||||||
|
页面文件统一放在 `src/pages/` 目录下:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/pages/
|
||||||
|
├── chat/ # 功能模块文件夹
|
||||||
|
│ ├── index.vue # 父级布局页
|
||||||
|
│ ├── conversation/ # 子页面文件夹
|
||||||
|
│ │ └── index.vue # 子页面
|
||||||
|
│ └── image/
|
||||||
|
│ └── index.vue
|
||||||
|
├── console/
|
||||||
|
│ └── index.vue
|
||||||
|
└── your-page/ # 新增页面在这里创建
|
||||||
|
└── index.vue
|
||||||
|
```
|
||||||
|
|
||||||
|
**单页面示例** (`src/pages/your-page/index.vue`):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="your-page">
|
||||||
|
<h1>页面标题</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 自动导入 Vue API,无需手动 import
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 如需使用状态管理
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('页面加载')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.your-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置路由
|
||||||
|
|
||||||
|
路由配置在 `src/routers/modules/staticRouter.ts`。
|
||||||
|
|
||||||
|
**新增独立页面**(添加到 `layoutRouter` 的 children 中):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
path: 'your-page', // URL 路径,最终为 /your-page
|
||||||
|
name: 'yourPage', // 路由名称,必须唯一
|
||||||
|
component: () => import('@/pages/your-page/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '页面标题', // 页面标题,会显示在浏览器标签
|
||||||
|
keepAlive: 0, // 是否缓存页面:0=缓存,1=不缓存
|
||||||
|
isDefaultChat: false, // 是否为默认聊天页
|
||||||
|
layout: 'default', // 布局类型:default/blankPage
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**新增带子路由的功能模块**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
path: 'module-name',
|
||||||
|
name: 'moduleName',
|
||||||
|
component: () => import('@/pages/module-name/index.vue'), // 父级布局页
|
||||||
|
redirect: '/module-name/sub-page', // 默认重定向
|
||||||
|
meta: {
|
||||||
|
title: '模块标题',
|
||||||
|
icon: 'HomeFilled', // Element Plus 图标名称
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'sub-page',
|
||||||
|
name: 'subPage',
|
||||||
|
component: () => import('@/pages/module-name/sub-page/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '子页面标题',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 带参数的路由
|
||||||
|
{
|
||||||
|
path: 'detail/:id',
|
||||||
|
name: 'detailPage',
|
||||||
|
component: () => import('@/pages/module-name/detail/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '详情页',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**无需布局的独立页面**(添加到 `staticRouter`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
path: '/test/page',
|
||||||
|
name: 'testPage',
|
||||||
|
component: () => import('@/pages/test/page.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '测试页面',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 页面 Meta 配置说明
|
||||||
|
|
||||||
|
| 属性 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| title | string | 页面标题,显示在浏览器标签页 |
|
||||||
|
| keepAlive | number | 0=缓存页面,1=不缓存 |
|
||||||
|
| layout | string | 布局类型:'default' 使用默认布局,'blankPage' 使用空白布局 |
|
||||||
|
| isDefaultChat | boolean | 是否为默认聊天页面 |
|
||||||
|
| icon | string | Element Plus 图标名称,用于菜单显示 |
|
||||||
|
| isHide | string | '0'=在菜单中隐藏,'1'=显示 |
|
||||||
|
| isKeepAlive | string | '0'=缓存,'1'=不缓存(字符串版) |
|
||||||
|
|
||||||
|
### 4. 布局说明
|
||||||
|
|
||||||
|
布局组件位于 `src/layouts/`:
|
||||||
|
|
||||||
|
- **default**: 默认布局,包含侧边栏、顶部导航等
|
||||||
|
- **blankPage**: 空白布局,仅包含路由出口
|
||||||
|
|
||||||
|
在路由 meta 中通过 `layout` 字段指定:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
meta: {
|
||||||
|
layout: 'default', // 使用默认布局(有侧边栏)
|
||||||
|
// 或
|
||||||
|
layout: 'blankPage', // 使用空白布局(全屏页面)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 页面跳转示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在 script setup 中使用
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 跳转页面
|
||||||
|
router.push('/chat/conversation')
|
||||||
|
router.push({ name: 'chatConversation' })
|
||||||
|
router.push({ path: '/chat/conversation/:id', params: { id: '123' } })
|
||||||
|
|
||||||
|
// 获取路由参数
|
||||||
|
const route = useRoute()
|
||||||
|
console.log(route.params.id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 完整新增页面示例
|
||||||
|
|
||||||
|
假设要新增一个"数据统计"页面:
|
||||||
|
|
||||||
|
**步骤 1**: 创建页面文件 `src/pages/statistics/index.vue`
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="statistics-page">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<span>数据统计</span>
|
||||||
|
</template>
|
||||||
|
<div>页面内容</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data, loading } = useFetch('/api/statistics').get().json()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.statistics-page {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤 2**: 在 `src/routers/modules/staticRouter.ts` 中添加路由
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
path: 'statistics',
|
||||||
|
name: 'statistics',
|
||||||
|
component: () => import('@/pages/statistics/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '意心Ai-数据统计',
|
||||||
|
keepAlive: 0,
|
||||||
|
isDefaultChat: false,
|
||||||
|
layout: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤 3**: 在菜单中添加入口(如需要)
|
||||||
|
|
||||||
|
如需在侧边栏显示,需在相应的位置添加菜单配置。
|
||||||
|
|
||||||
|
## 核心架构说明
|
||||||
|
|
||||||
|
### HTTP 请求封装
|
||||||
|
|
||||||
|
位于 `src/utils/request.ts`,使用 hook-fetch:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { get, post, put, del } from '@/utils/request'
|
||||||
|
|
||||||
|
// GET 请求
|
||||||
|
const { data } = await get('/api/endpoint').json()
|
||||||
|
|
||||||
|
// POST 请求
|
||||||
|
const result = await post('/api/endpoint', { key: 'value' }).json()
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
- 自动附加 JWT Token 到请求头
|
||||||
|
- 自动处理 401/403 错误
|
||||||
|
- 支持 Server-Sent Events (SSE) 流式响应
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
|
||||||
|
Pinia stores 位于 `src/stores/modules/`:
|
||||||
|
|
||||||
|
| Store | 用途 |
|
||||||
|
|-------|------|
|
||||||
|
| user.ts | 用户认证、登录状态 |
|
||||||
|
| chat.ts | 聊天消息、流式输出 |
|
||||||
|
| session.ts | 会话列表、当前会话 |
|
||||||
|
| model.ts | AI 模型配置 |
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const userStore = useUserStore()
|
||||||
|
userStore.setToken(token, refreshToken)
|
||||||
|
userStore.logout()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自动导入
|
||||||
|
|
||||||
|
项目已配置 `unplugin-auto-import`,以下 API 无需手动 import:
|
||||||
|
|
||||||
|
- Vue API: `ref`, `reactive`, `computed`, `watch`, `onMounted` 等
|
||||||
|
- Vue Router: `useRoute`, `useRouter`
|
||||||
|
- Pinia: `createPinia`, `storeToRefs`
|
||||||
|
- VueUse: `useFetch`, `useStorage` 等
|
||||||
|
|
||||||
|
### 路径别名
|
||||||
|
|
||||||
|
| 别名 | 对应路径 |
|
||||||
|
|------|----------|
|
||||||
|
| `@/` | `src/` |
|
||||||
|
| `@components/` | `src/vue-element-plus-y/components/` |
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
开发配置在 `.env.development`:
|
||||||
|
|
||||||
|
```
|
||||||
|
VITE_WEB_BASE_API=/dev-api # API 前缀
|
||||||
|
VITE_API_URL=http://localhost:19001/api/app # 后端地址
|
||||||
|
VITE_SSO_SEVER_URL=http://localhost:18001 # SSO 地址
|
||||||
|
```
|
||||||
|
|
||||||
|
Vite 开发服务器会自动将 `/dev-api` 代理到后端 API。
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
|
||||||
|
### 提交规范
|
||||||
|
|
||||||
|
使用 `pnpm cz` 进行规范提交,类型包括:
|
||||||
|
- `feat`: 新功能
|
||||||
|
- `fix`: 修复
|
||||||
|
- `docs`: 文档
|
||||||
|
- `style`: 代码格式
|
||||||
|
- `refactor`: 重构
|
||||||
|
- `perf`: 性能优化
|
||||||
|
- `test`: 测试
|
||||||
|
- `build`: 构建
|
||||||
|
- `ci`: CI/CD
|
||||||
|
- `revert`: 回滚
|
||||||
|
- `chore`: 其他
|
||||||
|
|
||||||
|
### Git Hooks
|
||||||
|
|
||||||
|
- **pre-commit**: 自动运行 ESLint 修复
|
||||||
|
- **commit-msg**: 校验提交信息格式
|
||||||
|
|
||||||
|
## 构建优化
|
||||||
|
|
||||||
|
Vite 配置中的代码分割策略(`vite.config.ts`):
|
||||||
|
|
||||||
|
| Chunk 名称 | 包含内容 |
|
||||||
|
|-----------|---------|
|
||||||
|
| vue-vendor | Vue 核心库、Vue Router |
|
||||||
|
| pinia | Pinia 状态管理 |
|
||||||
|
| element-plus | Element Plus UI 库 |
|
||||||
|
| markdown | Markdown 解析相关 |
|
||||||
|
| utils | Lodash、VueUse 工具库 |
|
||||||
|
| highlight | 代码高亮库 |
|
||||||
|
| echarts | 图表库 |
|
||||||
|
| pdf | PDF 处理库 |
|
||||||
|
|
||||||
|
## 后端集成
|
||||||
|
|
||||||
|
后端为 .NET 8 项目,本地启动命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd E:\devDemo\Yi\Yi.Abp.Net8\src\Yi.Abp.Web
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
前端开发时,后端默认运行在 `http://localhost:19001`。
|
||||||
133
Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md
Normal file
133
Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# FontAwesome 图标迁移指南
|
||||||
|
|
||||||
|
## 迁移步骤
|
||||||
|
|
||||||
|
### 1. 在组件中使用 FontAwesomeIcon
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 旧方式:Element Plus 图标 -->
|
||||||
|
<template>
|
||||||
|
<el-icon>
|
||||||
|
<Check />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Check } from '@element-plus/icons-vue';
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 新方式:FontAwesome -->
|
||||||
|
<template>
|
||||||
|
<FontAwesomeIcon icon="check" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 或带 props -->
|
||||||
|
<template>
|
||||||
|
<FontAwesomeIcon icon="check" size="lg" />
|
||||||
|
<FontAwesomeIcon icon="spinner" spin />
|
||||||
|
<FontAwesomeIcon icon="magnifying-glass" size="xl" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 自动映射工具
|
||||||
|
|
||||||
|
使用 `getFontAwesomeIcon` 函数自动映射图标名:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getFontAwesomeIcon } from '@/utils/icon-mapping';
|
||||||
|
|
||||||
|
// 将 Element Plus 图标名转换为 FontAwesome 图标名
|
||||||
|
const faIcon = getFontAwesomeIcon('Check'); // 返回 'check'
|
||||||
|
const faIcon2 = getFontAwesomeIcon('ArrowLeft'); // 返回 'arrow-left'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Props 说明
|
||||||
|
|
||||||
|
| Prop | 类型 | 可选值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| icon | string | 任意 FontAwesome 图标名 | 图标名称(不含 fa- 前缀) |
|
||||||
|
| size | string | xs, sm, lg, xl, 2x, 3x, 4x, 5x | 图标大小 |
|
||||||
|
| spin | boolean | true/false | 是否旋转 |
|
||||||
|
| pulse | boolean | true/false | 是否脉冲动画 |
|
||||||
|
| rotation | number | 0, 90, 180, 270 | 旋转角度 |
|
||||||
|
|
||||||
|
### 4. 常用图标对照表
|
||||||
|
|
||||||
|
| Element Plus | FontAwesome |
|
||||||
|
|--------------|-------------|
|
||||||
|
| Check | check |
|
||||||
|
| Close | xmark |
|
||||||
|
| Delete | trash |
|
||||||
|
| Edit | pen-to-square |
|
||||||
|
| Plus | plus |
|
||||||
|
| Search | magnifying-glass |
|
||||||
|
| Refresh | rotate-right |
|
||||||
|
| Loading | spinner |
|
||||||
|
| Download | download |
|
||||||
|
| ArrowLeft | arrow-left |
|
||||||
|
| ArrowRight | arrow-right |
|
||||||
|
| User | user |
|
||||||
|
| Setting | gear |
|
||||||
|
| View | eye |
|
||||||
|
| Hide | eye-slash |
|
||||||
|
| Lock | lock |
|
||||||
|
| Share | share-nodes |
|
||||||
|
| Heart | heart |
|
||||||
|
| Star | star |
|
||||||
|
| Clock | clock |
|
||||||
|
| Calendar | calendar |
|
||||||
|
| ChatLineRound | comment |
|
||||||
|
| Bell | bell |
|
||||||
|
| Document | file |
|
||||||
|
| Picture | image |
|
||||||
|
|
||||||
|
### 5. 批量迁移示例
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 迁移前 -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Check, Close, Delete } from '@element-plus/icons-vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- 迁移后 -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon="check" />
|
||||||
|
<FontAwesomeIcon icon="xmark" />
|
||||||
|
<FontAwesomeIcon icon="trash" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 不需要 import,FontAwesomeIcon 组件已自动导入
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **无需手动导入**:FontAwesomeIcon 组件已在 `vite.config.ts` 中配置为自动导入
|
||||||
|
2. **图标名格式**:使用小写、带连字符的图标名(如 `magnifying-glass`)
|
||||||
|
3. **完整图标列表**:访问 [FontAwesome 官网](https://fontawesome.com/search?o=r&m=free) 查看所有可用图标
|
||||||
|
4. **渐进步骤**:可以逐步迁移,Element Plus 图标和 FontAwesome 可以共存
|
||||||
|
|
||||||
|
## 优化建议
|
||||||
|
|
||||||
|
1. **减少包体积**:迁移后可以移除 `@element-plus/icons-vue` 依赖
|
||||||
|
2. **统一图标风格**:FontAwesome 图标风格更统一
|
||||||
|
3. **更好的性能**:FontAwesome 按需加载,不会加载未使用的图标
|
||||||
|
|
||||||
|
## 查找图标
|
||||||
|
|
||||||
|
- [Solid Icons 搜索](https://fontawesome.com/search?o=r&m=free)
|
||||||
|
- 图标名格式:`fa-solid fa-icon-name`
|
||||||
|
- 在代码中使用时只需:`icon="icon-name"`
|
||||||
@@ -70,3 +70,87 @@ dotnet run
|
|||||||
- [ ] 文件上传
|
- [ ] 文件上传
|
||||||
- [ ] 其他...
|
- [ ] 其他...
|
||||||
|
|
||||||
|
深色主题样式编写指南
|
||||||
|
|
||||||
|
1. 在 src/styles/dark-theme.scss 中添加样式
|
||||||
|
|
||||||
|
所有深色主题样式都使用 [data-theme="dark"] 选择器包裹:
|
||||||
|
|
||||||
|
/* ========== 组件名称深色样式 ========== */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
.your-component-class {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
color: #e5e7eb !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
2. 常用深色主题颜色值
|
||||||
|
┌─────────────────────────┬───────────────────┐
|
||||||
|
│ 用途 │ 颜色值 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 主背景 │ #111827 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 次级背景(卡片、弹窗) │ #1f2937 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 三级背景(hover、表头) │ #374151 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 主文字 │ #f3f4f6 / #f9fafb │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 次级文字 │ #e5e7eb │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 三级文字 │ #9ca3af │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 边框浅色 │ #374151 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 边框深色 │ #4b5563 │
|
||||||
|
├─────────────────────────┼───────────────────┤
|
||||||
|
│ 主色调 │ #60a5fa │
|
||||||
|
└─────────────────────────┴───────────────────┘
|
||||||
|
3. 覆盖 Tailwind 工具类
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
.bg-white {
|
||||||
|
background-color: #374151 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-700 {
|
||||||
|
color: #e5e7eb !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
4. 覆盖 Element Plus 组件
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
.el-card {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
border-color: #374151 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
5. 使用 CSS 变量(推荐)
|
||||||
|
|
||||||
|
在 src/styles/var.scss 的 [data-theme="dark"] 块中定义变量,然后在组件中使用:
|
||||||
|
|
||||||
|
// var.scss
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--my-component-bg: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dark-theme.scss
|
||||||
|
[data-theme="dark"] {
|
||||||
|
.my-component {
|
||||||
|
background-color: var(--my-component-bg) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
6. 组件内动态颜色(JS 方式)
|
||||||
|
|
||||||
|
如果需要在 JS 中动态获取颜色:
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useColorMode } from '@vueuse/core';
|
||||||
|
|
||||||
|
const mode = useColorMode();
|
||||||
|
const bgColor = computed(() => mode.value === 'dark' ? '#1f2937' : '#ffffff');
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||||
|
|
||||||
|
<!-- DNS 预解析和预连接 -->
|
||||||
|
<link rel="dns-prefetch" href="//api.yourdomain.com">
|
||||||
|
<link rel="preconnect" href="//api.yourdomain.com" crossorigin>
|
||||||
|
|
||||||
|
<!-- 预加载关键资源 -->
|
||||||
|
<link rel="preload" href="/src/main.ts" as="script" crossorigin>
|
||||||
|
<link rel="modulepreload" href="/src/main.ts">
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 全局样式 */
|
/* 全局样式 */
|
||||||
@@ -112,16 +120,172 @@
|
|||||||
<body>
|
<body>
|
||||||
<!-- 加载动画容器 -->
|
<!-- 加载动画容器 -->
|
||||||
<div id="yixinai-loader" class="loader-container">
|
<div id="yixinai-loader" class="loader-container">
|
||||||
<div class="loader-title">意心Ai 3.0</div>
|
<div class="loader-title">%APP_FULL_NAME%</div>
|
||||||
<div class="loader-subtitle">海外地址,仅首次访问预计加载约10秒,无需梯子</div>
|
<div class="loader-subtitle">海外地址,仅首次访问预计加载约10秒,无需梯子</div>
|
||||||
<div class="loader-logo">
|
<div class="loader-logo">
|
||||||
<div class="pulse-box"></div>
|
<div class="pulse-box"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="loader-progress-bar">
|
||||||
|
<div id="loader-progress" class="loader-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div id="loader-text" class="loader-text" style="font-size: 0.875rem; margin-top: 0.5rem; color: #666;">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 资源加载进度跟踪 - 增强版
|
||||||
|
(function() {
|
||||||
|
const progressBar = document.getElementById('loader-progress');
|
||||||
|
const loaderText = document.getElementById('loader-text');
|
||||||
|
const loader = document.getElementById('yixinai-loader');
|
||||||
|
|
||||||
|
let progress = 0;
|
||||||
|
let resourcesLoaded = false;
|
||||||
|
let vueAppMounted = false;
|
||||||
|
let appRendered = false;
|
||||||
|
|
||||||
|
// 更新进度条
|
||||||
|
function updateProgress(value, text) {
|
||||||
|
progress = Math.min(value, 99);
|
||||||
|
if (progressBar) progressBar.style.width = progress.toFixed(1) + '%';
|
||||||
|
if (loaderText) loaderText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阶段管理
|
||||||
|
const stages = {
|
||||||
|
init: { weight: 15, name: '初始化' },
|
||||||
|
resources: { weight: 35, name: '加载资源' },
|
||||||
|
scripts: { weight: 25, name: '执行脚本' },
|
||||||
|
render: { weight: 15, name: '渲染页面' },
|
||||||
|
complete: { weight: 10, name: '启动应用' }
|
||||||
|
};
|
||||||
|
|
||||||
|
let completedStages = new Set();
|
||||||
|
let currentStage = 'init';
|
||||||
|
|
||||||
|
function calculateProgress() {
|
||||||
|
let totalProgress = 0;
|
||||||
|
for (const [key, stage] of Object.entries(stages)) {
|
||||||
|
if (completedStages.has(key)) {
|
||||||
|
totalProgress += stage.weight;
|
||||||
|
} else if (key === currentStage) {
|
||||||
|
// 当前阶段完成一部分
|
||||||
|
totalProgress += stage.weight * 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.min(totalProgress, 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阶段完成
|
||||||
|
function completeStage(stageName, nextStage) {
|
||||||
|
completedStages.add(stageName);
|
||||||
|
currentStage = nextStage || stageName;
|
||||||
|
const stage = stages[stageName];
|
||||||
|
updateProgress(calculateProgress(), stage ? `${stage.name}完成` : '加载中...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听资源加载 - 使用更可靠的方式
|
||||||
|
const resourceTimings = [];
|
||||||
|
const observer = new PerformanceObserver((list) => {
|
||||||
|
const entries = list.getEntries();
|
||||||
|
resourceTimings.push(...entries);
|
||||||
|
|
||||||
|
// 统计未完成资源
|
||||||
|
const pendingResources = performance.getEntriesByType('resource')
|
||||||
|
.filter(r => !r.responseEnd || r.responseEnd === 0).length;
|
||||||
|
|
||||||
|
if (pendingResources === 0 && resourceTimings.length > 0) {
|
||||||
|
completeStage('resources', 'scripts');
|
||||||
|
} else {
|
||||||
|
updateProgress(calculateProgress(), `加载资源中... (${resourceTimings.length} 已加载)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
observer.observe({ entryTypes: ['resource'] });
|
||||||
|
} catch (e) {
|
||||||
|
// 降级处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始进度
|
||||||
|
let initProgress = 0;
|
||||||
|
function simulateInitProgress() {
|
||||||
|
if (initProgress < stages.init.weight) {
|
||||||
|
initProgress += 1;
|
||||||
|
updateProgress(initProgress, '正在初始化...');
|
||||||
|
if (initProgress < stages.init.weight) {
|
||||||
|
setTimeout(simulateInitProgress, 30);
|
||||||
|
} else {
|
||||||
|
completeStage('init', 'resources');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
simulateInitProgress();
|
||||||
|
|
||||||
|
// 页面资源加载完成
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
completeStage('resources', 'scripts');
|
||||||
|
resourcesLoaded = true;
|
||||||
|
|
||||||
|
// 给脚本执行时间
|
||||||
|
setTimeout(() => {
|
||||||
|
completeStage('scripts', 'render');
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
checkAndHideLoader();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露全局方法供 Vue 应用调用 - 分阶段调用
|
||||||
|
window.__hideAppLoader = function(stage) {
|
||||||
|
if (stage === 'mounted') {
|
||||||
|
vueAppMounted = true;
|
||||||
|
completeStage('scripts', 'render');
|
||||||
|
} else if (stage === 'rendered') {
|
||||||
|
appRendered = true;
|
||||||
|
completeStage('render', 'complete');
|
||||||
|
}
|
||||||
|
checkAndHideLoader();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否可以隐藏加载动画
|
||||||
|
function checkAndHideLoader() {
|
||||||
|
// 需要满足:资源加载完成、Vue 挂载、页面渲染完成
|
||||||
|
if (resourcesLoaded && vueAppMounted && appRendered) {
|
||||||
|
completeStage('complete', '');
|
||||||
|
updateProgress(100, '加载完成');
|
||||||
|
|
||||||
|
// 确保最小显示时间,避免闪烁
|
||||||
|
const minDisplayTime = 1000;
|
||||||
|
const elapsed = Date.now() - startTime;
|
||||||
|
const remaining = Math.max(0, minDisplayTime - elapsed);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (loader) {
|
||||||
|
loader.style.opacity = '0';
|
||||||
|
loader.style.transition = 'opacity 0.5s ease';
|
||||||
|
setTimeout(() => {
|
||||||
|
loader.remove();
|
||||||
|
delete window.__hideAppLoader;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 超时保护:最多显示 30 秒
|
||||||
|
setTimeout(() => {
|
||||||
|
if (loader && loader.parentNode) {
|
||||||
|
console.warn('加载超时,强制隐藏加载动画');
|
||||||
|
vueAppMounted = true;
|
||||||
|
resourcesLoaded = true;
|
||||||
|
appRendered = true;
|
||||||
|
checkAndHideLoader();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -34,38 +34,37 @@
|
|||||||
"@floating-ui/core": "^1.7.2",
|
"@floating-ui/core": "^1.7.2",
|
||||||
"@floating-ui/dom": "^1.7.2",
|
"@floating-ui/dom": "^1.7.2",
|
||||||
"@floating-ui/vue": "^1.1.7",
|
"@floating-ui/vue": "^1.1.7",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.1.3",
|
||||||
"@jsonlee_12138/enum": "^1.0.4",
|
"@jsonlee_12138/enum": "^1.0.4",
|
||||||
|
"@shikijs/transformers": "^3.7.0",
|
||||||
"@vueuse/core": "^13.5.0",
|
"@vueuse/core": "^13.5.0",
|
||||||
"@vueuse/integrations": "^13.5.0",
|
"@vueuse/integrations": "^13.5.0",
|
||||||
|
"chatarea": "^6.0.3",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"dompurify": "^3.2.6",
|
||||||
"driver.js": "^1.3.6",
|
"driver.js": "^1.3.6",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.10.4",
|
"element-plus": "^2.10.4",
|
||||||
"fingerprintjs": "^0.5.3",
|
"fingerprintjs": "^0.5.3",
|
||||||
|
"github-markdown-css": "^5.8.1",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"hook-fetch": "^2.0.4-beta.1",
|
"hook-fetch": "^2.0.4-beta.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mammoth": "^1.11.0",
|
"mammoth": "^1.11.0",
|
||||||
|
"marked": "^17.0.1",
|
||||||
|
"mermaid": "11.12.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pdfjs-dist": "^5.4.449",
|
"pdfjs-dist": "^5.4.449",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.4.1",
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
"qrcode": "^1.5.4",
|
|
||||||
"radash": "^12.1.1",
|
|
||||||
"reset-css": "^5.0.2",
|
|
||||||
"vue": "^3.5.17",
|
|
||||||
"vue-element-plus-x": "1.3.7",
|
|
||||||
"vue-router": "4",
|
|
||||||
"xlsx": "^0.18.5",
|
|
||||||
"@shikijs/transformers": "^3.7.0",
|
|
||||||
"chatarea": "^6.0.3",
|
|
||||||
"deepmerge": "^4.3.1",
|
|
||||||
"dompurify": "^3.2.6",
|
|
||||||
"github-markdown-css": "^5.8.1",
|
|
||||||
"highlight.js": "^11.11.1",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"mermaid": "11.12.0",
|
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"property-information": "^7.1.0",
|
"property-information": "^7.1.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"radash": "^12.1.1",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
@@ -74,46 +73,21 @@
|
|||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.2",
|
"remark-rehype": "^11.1.2",
|
||||||
|
"reset-css": "^5.0.2",
|
||||||
"shiki": "^3.7.0",
|
"shiki": "^3.7.0",
|
||||||
"ts-md5": "^2.0.1",
|
"ts-md5": "^2.0.1",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"vue": "^3.5.17",
|
||||||
|
"vue-element-plus-x": "1.3.7",
|
||||||
|
"vue-router": "4",
|
||||||
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^4.16.2",
|
"@antfu/eslint-config": "^4.16.2",
|
||||||
"@changesets/cli": "^2.29.5",
|
"@changesets/cli": "^2.29.5",
|
||||||
"@commitlint/config-conventional": "^19.8.1",
|
|
||||||
"@types/fingerprintjs__fingerprintjs": "^3.0.2",
|
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
|
||||||
"@vue/tsconfig": "^0.7.0",
|
|
||||||
"commitlint": "^19.8.1",
|
|
||||||
"cz-git": "^1.12.0",
|
|
||||||
"eslint": "^9.31.0",
|
|
||||||
"husky": "^9.1.7",
|
|
||||||
"lint-staged": "^16.1.2",
|
|
||||||
"postcss": "8.4.31",
|
|
||||||
"postcss-html": "1.5.0",
|
|
||||||
"prettier": "^3.6.2",
|
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
|
||||||
"sass-embedded": "^1.89.2",
|
|
||||||
"stylelint": "^16.21.1",
|
|
||||||
"stylelint-config-html": "^1.1.0",
|
|
||||||
"stylelint-config-recess-order": "^7.1.0",
|
|
||||||
"stylelint-config-recommended-scss": "^15.0.1",
|
|
||||||
"stylelint-config-recommended-vue": "^1.6.1",
|
|
||||||
"stylelint-config-standard": "^38.0.0",
|
|
||||||
"stylelint-config-standard-scss": "^15.0.1",
|
|
||||||
"typescript": "~5.8.3",
|
|
||||||
"typescript-api-pro": "^0.0.7",
|
|
||||||
"unocss": "66.3.3",
|
|
||||||
"unplugin-auto-import": "^19.3.0",
|
|
||||||
"unplugin-vue-components": "^28.8.0",
|
|
||||||
"vite": "^6.3.5",
|
|
||||||
"vite-plugin-compression": "^0.5.1",
|
|
||||||
"vite-plugin-env-typed": "^0.0.2",
|
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
|
||||||
"vue-tsc": "^3.0.1",
|
|
||||||
"@chromatic-com/storybook": "^3.2.7",
|
"@chromatic-com/storybook": "^3.2.7",
|
||||||
|
"@commitlint/config-conventional": "^19.8.1",
|
||||||
"@jsonlee_12138/markdown-it-mermaid": "0.0.6",
|
"@jsonlee_12138/markdown-it-mermaid": "0.0.6",
|
||||||
"@storybook/addon-essentials": "^8.6.14",
|
"@storybook/addon-essentials": "^8.6.14",
|
||||||
"@storybook/addon-onboarding": "^8.6.14",
|
"@storybook/addon-onboarding": "^8.6.14",
|
||||||
@@ -127,22 +101,52 @@
|
|||||||
"@storybook/vue3": "^8.6.14",
|
"@storybook/vue3": "^8.6.14",
|
||||||
"@storybook/vue3-vite": "^8.6.14",
|
"@storybook/vue3-vite": "^8.6.14",
|
||||||
"@types/dom-speech-recognition": "^0.0.4",
|
"@types/dom-speech-recognition": "^0.0.4",
|
||||||
|
"@types/fingerprintjs__fingerprintjs": "^3.0.2",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/prismjs": "^1.26.5",
|
"@types/prismjs": "^1.26.5",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vitest/browser": "^3.2.4",
|
"@vitest/browser": "^3.2.4",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"commitlint": "^19.8.1",
|
||||||
|
"cz-git": "^1.12.0",
|
||||||
|
"eslint": "^9.31.0",
|
||||||
"esno": "^4.8.0",
|
"esno": "^4.8.0",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^16.1.2",
|
||||||
"playwright": "^1.53.2",
|
"playwright": "^1.53.2",
|
||||||
|
"postcss": "8.4.31",
|
||||||
|
"postcss-html": "1.5.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
|
"sass-embedded": "^1.89.2",
|
||||||
"storybook": "^8.6.14",
|
"storybook": "^8.6.14",
|
||||||
"storybook-dark-mode": "^4.0.2",
|
"storybook-dark-mode": "^4.0.2",
|
||||||
|
"stylelint": "^16.21.1",
|
||||||
|
"stylelint-config-html": "^1.1.0",
|
||||||
|
"stylelint-config-recess-order": "^7.1.0",
|
||||||
|
"stylelint-config-recommended-scss": "^15.0.1",
|
||||||
|
"stylelint-config-recommended-vue": "^1.6.1",
|
||||||
|
"stylelint-config-standard": "^38.0.0",
|
||||||
|
"stylelint-config-standard-scss": "^15.0.1",
|
||||||
"terser": "^5.43.1",
|
"terser": "^5.43.1",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"typescript-api-pro": "^0.0.7",
|
||||||
|
"unocss": "66.3.3",
|
||||||
|
"unplugin-auto-import": "^19.3.0",
|
||||||
|
"unplugin-vue-components": "^28.8.0",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-dts": "^4.5.4",
|
"vite-plugin-dts": "^4.5.4",
|
||||||
|
"vite-plugin-env-typed": "^0.0.2",
|
||||||
"vite-plugin-lib-inject-css": "^2.2.2",
|
"vite-plugin-lib-inject-css": "^2.2.2",
|
||||||
"vitest": "^3.2.4"
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
|
"vitest": "^3.2.4",
|
||||||
|
"vue-tsc": "^3.0.1"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
104
Yi.Ai.Vue3/pnpm-lock.yaml
generated
104
Yi.Ai.Vue3/pnpm-lock.yaml
generated
@@ -23,6 +23,15 @@ importers:
|
|||||||
'@floating-ui/vue':
|
'@floating-ui/vue':
|
||||||
specifier: ^1.1.7
|
specifier: ^1.1.7
|
||||||
version: 1.1.7(vue@3.5.17(typescript@5.8.3))
|
version: 1.1.7(vue@3.5.17(typescript@5.8.3))
|
||||||
|
'@fortawesome/fontawesome-svg-core':
|
||||||
|
specifier: ^7.1.0
|
||||||
|
version: 7.1.0
|
||||||
|
'@fortawesome/free-solid-svg-icons':
|
||||||
|
specifier: ^7.1.0
|
||||||
|
version: 7.1.0
|
||||||
|
'@fortawesome/vue-fontawesome':
|
||||||
|
specifier: ^3.1.3
|
||||||
|
version: 3.1.3(@fortawesome/fontawesome-svg-core@7.1.0)(vue@3.5.17(typescript@5.8.3))
|
||||||
'@jsonlee_12138/enum':
|
'@jsonlee_12138/enum':
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4
|
version: 1.0.4
|
||||||
@@ -77,6 +86,9 @@ importers:
|
|||||||
mammoth:
|
mammoth:
|
||||||
specifier: ^1.11.0
|
specifier: ^1.11.0
|
||||||
version: 1.11.0
|
version: 1.11.0
|
||||||
|
marked:
|
||||||
|
specifier: ^17.0.1
|
||||||
|
version: 17.0.1
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: 11.12.0
|
specifier: 11.12.0
|
||||||
version: 11.12.0
|
version: 11.12.0
|
||||||
@@ -158,7 +170,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@antfu/eslint-config':
|
'@antfu/eslint-config':
|
||||||
specifier: ^4.16.2
|
specifier: ^4.16.2
|
||||||
version: 4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
version: 4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)
|
||||||
'@changesets/cli':
|
'@changesets/cli':
|
||||||
specifier: ^2.29.5
|
specifier: ^2.29.5
|
||||||
version: 2.29.5
|
version: 2.29.5
|
||||||
@@ -188,7 +200,7 @@ importers:
|
|||||||
version: 8.6.14(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))
|
version: 8.6.14(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))
|
||||||
'@storybook/experimental-addon-test':
|
'@storybook/experimental-addon-test':
|
||||||
specifier: ^8.6.14
|
specifier: ^8.6.14
|
||||||
version: 8.6.14(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
version: 8.6.14(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4)
|
||||||
'@storybook/manager-api':
|
'@storybook/manager-api':
|
||||||
specifier: ^8.6.14
|
specifier: ^8.6.14
|
||||||
version: 8.6.14(storybook@8.6.14(prettier@3.6.2))
|
version: 8.6.14(storybook@8.6.14(prettier@3.6.2))
|
||||||
@@ -227,7 +239,7 @@ importers:
|
|||||||
version: 3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4)
|
version: 3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4)
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^3.2.4
|
specifier: ^3.2.4
|
||||||
version: 3.2.4(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)
|
||||||
'@vue/tsconfig':
|
'@vue/tsconfig':
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
version: 0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
||||||
@@ -427,28 +439,24 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@ast-grep/napi-linux-arm64-musl@0.36.3':
|
'@ast-grep/napi-linux-arm64-musl@0.36.3':
|
||||||
resolution: {integrity: sha512-2XRmNYuovZu0Pa4J3or4PKMkQZnXXfpVcCrPwWB/2ytX7XUo+TWLgYE8rPVnJOyw5zujkveFb0XUrro9mQgLzw==}
|
resolution: {integrity: sha512-2XRmNYuovZu0Pa4J3or4PKMkQZnXXfpVcCrPwWB/2ytX7XUo+TWLgYE8rPVnJOyw5zujkveFb0XUrro9mQgLzw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-gnu@0.36.3':
|
'@ast-grep/napi-linux-x64-gnu@0.36.3':
|
||||||
resolution: {integrity: sha512-mTwPRbBi1feGqR2b5TWC5gkEDeRi8wfk4euF5sKNihfMGHj6pdfINHQ3QvLVO4C7z0r/wgWLAvditFA0b997dg==}
|
resolution: {integrity: sha512-mTwPRbBi1feGqR2b5TWC5gkEDeRi8wfk4euF5sKNihfMGHj6pdfINHQ3QvLVO4C7z0r/wgWLAvditFA0b997dg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-musl@0.36.3':
|
'@ast-grep/napi-linux-x64-musl@0.36.3':
|
||||||
resolution: {integrity: sha512-tMGPrT+zuZzJK6n1cD1kOii7HYZE9gUXjwtVNE/uZqXEaWP6lmkfoTMbLjnxEe74VQbmaoDGh1/cjrDBnqC6Uw==}
|
resolution: {integrity: sha512-tMGPrT+zuZzJK6n1cD1kOii7HYZE9gUXjwtVNE/uZqXEaWP6lmkfoTMbLjnxEe74VQbmaoDGh1/cjrDBnqC6Uw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@ast-grep/napi-win32-arm64-msvc@0.36.3':
|
'@ast-grep/napi-win32-arm64-msvc@0.36.3':
|
||||||
resolution: {integrity: sha512-7pFyr9+dyV+4cBJJ1I57gg6PDXP3GBQeVAsEEitzEruxx4Hb4cyNro54gGtlsS+6ty+N0t004tPQxYO2VrsPIg==}
|
resolution: {integrity: sha512-7pFyr9+dyV+4cBJJ1I57gg6PDXP3GBQeVAsEEitzEruxx4Hb4cyNro54gGtlsS+6ty+N0t004tPQxYO2VrsPIg==}
|
||||||
@@ -1107,6 +1115,24 @@ packages:
|
|||||||
'@floating-ui/vue@1.1.7':
|
'@floating-ui/vue@1.1.7':
|
||||||
resolution: {integrity: sha512-idmAtbAIigGXN2SI5gItiXYBYtNfDTP9yIiObxgu13dgtG7ARCHlNfnR29GxP4LI4o13oiwsJ8wVgghj1lNqcw==}
|
resolution: {integrity: sha512-idmAtbAIigGXN2SI5gItiXYBYtNfDTP9yIiObxgu13dgtG7ARCHlNfnR29GxP4LI4o13oiwsJ8wVgghj1lNqcw==}
|
||||||
|
|
||||||
|
'@fortawesome/fontawesome-common-types@7.1.0':
|
||||||
|
resolution: {integrity: sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
'@fortawesome/fontawesome-svg-core@7.1.0':
|
||||||
|
resolution: {integrity: sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
'@fortawesome/free-solid-svg-icons@7.1.0':
|
||||||
|
resolution: {integrity: sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
'@fortawesome/vue-fontawesome@3.1.3':
|
||||||
|
resolution: {integrity: sha512-OHHUTLPEzdwP8kcYIzhioUdUOjZ4zzmi+midwa4bqscza4OJCOvTKJEHkXNz8PgZ23kWci1HkKVX0bm8f9t9gQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fortawesome/fontawesome-svg-core': ~1 || ~6 || ~7
|
||||||
|
vue: '>= 3.0.0 < 4'
|
||||||
|
|
||||||
'@humanfs/core@0.19.1':
|
'@humanfs/core@0.19.1':
|
||||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@@ -1245,35 +1271,30 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-musl@0.1.84':
|
'@napi-rs/canvas-linux-arm64-musl@0.1.84':
|
||||||
resolution: {integrity: sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==}
|
resolution: {integrity: sha512-VyZq0EEw+OILnWk7G3ZgLLPaz1ERaPP++jLjeyLMbFOF+Tr4zHzWKiKDsEV/cT7btLPZbVoR3VX+T9/QubnURQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.84':
|
'@napi-rs/canvas-linux-riscv64-gnu@0.1.84':
|
||||||
resolution: {integrity: sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==}
|
resolution: {integrity: sha512-PSMTh8DiThvLRsbtc/a065I/ceZk17EXAATv9uNvHgkgo7wdEfTh2C3aveNkBMGByVO3tvnvD5v/YFtZL07cIg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-gnu@0.1.84':
|
'@napi-rs/canvas-linux-x64-gnu@0.1.84':
|
||||||
resolution: {integrity: sha512-N1GY3noO1oqgEo3rYQIwY44kfM11vA0lDbN0orTOHfCSUZTUyiYCY0nZ197QMahZBm1aR/vYgsWpV74MMMDuNA==}
|
resolution: {integrity: sha512-N1GY3noO1oqgEo3rYQIwY44kfM11vA0lDbN0orTOHfCSUZTUyiYCY0nZ197QMahZBm1aR/vYgsWpV74MMMDuNA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-musl@0.1.84':
|
'@napi-rs/canvas-linux-x64-musl@0.1.84':
|
||||||
resolution: {integrity: sha512-vUZmua6ADqTWyHyei81aXIt9wp0yjeNwTH0KdhdeoBb6azHmFR8uKTukZMXfLCC3bnsW0t4lW7K78KNMknmtjg==}
|
resolution: {integrity: sha512-vUZmua6ADqTWyHyei81aXIt9wp0yjeNwTH0KdhdeoBb6azHmFR8uKTukZMXfLCC3bnsW0t4lW7K78KNMknmtjg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-x64-msvc@0.1.84':
|
'@napi-rs/canvas-win32-x64-msvc@0.1.84':
|
||||||
resolution: {integrity: sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==}
|
resolution: {integrity: sha512-YSs8ncurc1xzegUMNnQUTYrdrAuaXdPMOa+iYYyAxydOtg0ppV386hyYMsy00Yip1NlTgLCseRG4sHSnjQx6og==}
|
||||||
@@ -1330,42 +1351,36 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.1':
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
@@ -1450,67 +1465,56 @@ packages:
|
|||||||
resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
|
resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
|
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
|
||||||
resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
|
resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.41.1':
|
'@rollup/rollup-linux-arm64-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
|
resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.41.1':
|
'@rollup/rollup-linux-arm64-musl@4.41.1':
|
||||||
resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
|
resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
|
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
|
resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
|
resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
|
resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.41.1':
|
'@rollup/rollup-linux-riscv64-musl@4.41.1':
|
||||||
resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
|
resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.41.1':
|
'@rollup/rollup-linux-s390x-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
|
resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.41.1':
|
'@rollup/rollup-linux-x64-gnu@4.41.1':
|
||||||
resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
|
resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.41.1':
|
'@rollup/rollup-linux-x64-musl@4.41.1':
|
||||||
resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
|
resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.41.1':
|
'@rollup/rollup-win32-arm64-msvc@4.41.1':
|
||||||
resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
|
resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
|
||||||
@@ -4886,7 +4890,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
|
||||||
|
|
||||||
marked@16.4.2:
|
marked@16.4.2:
|
||||||
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
|
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==, tarball: https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
marked@17.0.1:
|
||||||
|
resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==, tarball: https://registry.npmmirror.com/marked/-/marked-17.0.1.tgz}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -6913,8 +6922,8 @@ packages:
|
|||||||
vue-component-type-helpers@2.2.12:
|
vue-component-type-helpers@2.2.12:
|
||||||
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.8:
|
vue-component-type-helpers@3.2.4:
|
||||||
resolution: {integrity: sha512-oaowlmEM6BaYY+8o+9D9cuzxpWQWHqHTMKakMxXu0E+UCIOMTljyIPO15jcnaCwJtZu/zWDotK7mOIHvWD9mcw==}
|
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
||||||
|
|
||||||
vue-demi@0.14.10:
|
vue-demi@0.14.10:
|
||||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||||
@@ -7133,7 +7142,7 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping': 0.3.8
|
'@jridgewell/gen-mapping': 0.3.8
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
'@antfu/eslint-config@4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
'@antfu/eslint-config@4.17.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/install-pkg': 1.1.0
|
'@antfu/install-pkg': 1.1.0
|
||||||
'@clack/prompts': 0.11.0
|
'@clack/prompts': 0.11.0
|
||||||
@@ -7142,7 +7151,7 @@ snapshots:
|
|||||||
'@stylistic/eslint-plugin': 5.2.0(eslint@9.31.0(jiti@2.4.2))
|
'@stylistic/eslint-plugin': 5.2.0(eslint@9.31.0(jiti@2.4.2))
|
||||||
'@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||||
'@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||||
'@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
'@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)
|
||||||
ansis: 4.1.0
|
ansis: 4.1.0
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
eslint: 9.31.0(jiti@2.4.2)
|
eslint: 9.31.0(jiti@2.4.2)
|
||||||
@@ -7856,6 +7865,21 @@ snapshots:
|
|||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
|
'@fortawesome/fontawesome-common-types@7.1.0': {}
|
||||||
|
|
||||||
|
'@fortawesome/fontawesome-svg-core@7.1.0':
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-common-types': 7.1.0
|
||||||
|
|
||||||
|
'@fortawesome/free-solid-svg-icons@7.1.0':
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-common-types': 7.1.0
|
||||||
|
|
||||||
|
'@fortawesome/vue-fontawesome@3.1.3(@fortawesome/fontawesome-svg-core@7.1.0)(vue@3.5.17(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-svg-core': 7.1.0
|
||||||
|
vue: 3.5.17(typescript@5.8.3)
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.6':
|
'@humanfs/node@0.16.6':
|
||||||
@@ -8494,7 +8518,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
|
|
||||||
'@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
'@storybook/experimental-addon-test@8.6.14(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@19.2.3(react@19.1.0))(react@19.1.0)(storybook@8.6.14(prettier@3.6.2))(vitest@3.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@storybook/global': 5.0.0
|
'@storybook/global': 5.0.0
|
||||||
'@storybook/icons': 1.6.0(react-dom@19.2.3(react@19.1.0))(react@19.1.0)
|
'@storybook/icons': 1.6.0(react-dom@19.2.3(react@19.1.0))(react@19.1.0)
|
||||||
@@ -8639,7 +8663,7 @@ snapshots:
|
|||||||
ts-dedent: 2.2.0
|
ts-dedent: 2.2.0
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.5.17(typescript@5.8.3)
|
vue: 3.5.17(typescript@5.8.3)
|
||||||
vue-component-type-helpers: 3.1.8
|
vue-component-type-helpers: 3.2.4
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@5.2.0(eslint@9.31.0(jiti@2.4.2))':
|
'@stylistic/eslint-plugin@5.2.0(eslint@9.31.0(jiti@2.4.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9301,7 +9325,7 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vite
|
- vite
|
||||||
|
|
||||||
'@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))(vitest@3.2.4))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
'@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
'@bcoe/v8-coverage': 1.0.2
|
'@bcoe/v8-coverage': 1.0.2
|
||||||
@@ -9322,7 +9346,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.30)(@vitest/browser@3.2.4)(jiti@2.4.2)(sass-embedded@1.89.2)(sass@1.97.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
'@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.33.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
'@typescript-eslint/utils': 8.33.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3)
|
||||||
eslint: 9.31.0(jiti@2.4.2)
|
eslint: 9.31.0(jiti@2.4.2)
|
||||||
@@ -12218,6 +12242,8 @@ snapshots:
|
|||||||
|
|
||||||
marked@16.4.2: {}
|
marked@16.4.2: {}
|
||||||
|
|
||||||
|
marked@17.0.1: {}
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
mathml-tag-names@2.1.3: {}
|
mathml-tag-names@2.1.3: {}
|
||||||
@@ -14721,7 +14747,7 @@ snapshots:
|
|||||||
|
|
||||||
vue-component-type-helpers@2.2.12: {}
|
vue-component-type-helpers@2.2.12: {}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.8: {}
|
vue-component-type-helpers@3.2.4: {}
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.5.17(typescript@5.8.3)):
|
vue-demi@0.14.10(vue@3.5.17(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Binary file not shown.
25
Yi.Ai.Vue3/src/api/agent/index.ts
Normal file
25
Yi.Ai.Vue3/src/api/agent/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { AgentSendInput, AgentToolOutput } from './types';
|
||||||
|
import { get, post } from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 发送消息
|
||||||
|
*/
|
||||||
|
export function agentSend(data: AgentSendInput) {
|
||||||
|
return post('/ai-chat/agent/send', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Agent 工具列表
|
||||||
|
*/
|
||||||
|
export function getAgentTools() {
|
||||||
|
return post<AgentToolOutput[]>('/ai-chat/agent/tool').json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Agent 上下文
|
||||||
|
*/
|
||||||
|
export function getAgentContext(sessionId: string) {
|
||||||
|
return post<string>(`/ai-chat/agent/context/${sessionId}`).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from './types';
|
||||||
51
Yi.Ai.Vue3/src/api/agent/types.ts
Normal file
51
Yi.Ai.Vue3/src/api/agent/types.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Agent 发送消息输入
|
||||||
|
*/
|
||||||
|
export interface AgentSendInput {
|
||||||
|
/** 会话id */
|
||||||
|
sessionId: string;
|
||||||
|
/** 用户内容 */
|
||||||
|
content: string;
|
||||||
|
/** api密钥Id */
|
||||||
|
tokenId: string;
|
||||||
|
/** 模型id */
|
||||||
|
modelId: string;
|
||||||
|
/** 已选择工具 */
|
||||||
|
tools: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 工具输出
|
||||||
|
*/
|
||||||
|
export interface AgentToolOutput {
|
||||||
|
/** 工具代码 */
|
||||||
|
code: string;
|
||||||
|
/** 工具名称 */
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 结果类型
|
||||||
|
*/
|
||||||
|
export type AgentResultType = 'text' | 'toolCalling' | 'toolCalled' | 'usage' | 'toolCallUsage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 流式结果输出
|
||||||
|
*/
|
||||||
|
export interface AgentResultOutput {
|
||||||
|
/** 类型 */
|
||||||
|
type: AgentResultType;
|
||||||
|
/** 内容载体 */
|
||||||
|
content: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 用量信息
|
||||||
|
*/
|
||||||
|
export interface AgentUsage {
|
||||||
|
input_tokens?: number;
|
||||||
|
output_tokens?: number;
|
||||||
|
total_tokens?: number;
|
||||||
|
prompt_tokens?: number;
|
||||||
|
completion_tokens?: number;
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { get, post } from '@/utils/request';
|
|
||||||
import type {
|
import type {
|
||||||
GenerateImageRequest,
|
GenerateImageRequest,
|
||||||
ImageModel,
|
ImageModel,
|
||||||
@@ -7,6 +6,7 @@ import type {
|
|||||||
TaskListResponse,
|
TaskListResponse,
|
||||||
TaskStatusResponse,
|
TaskStatusResponse,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { del, get, post } from '@/utils/request';
|
||||||
|
|
||||||
export function generateImage(data: GenerateImageRequest) {
|
export function generateImage(data: GenerateImageRequest) {
|
||||||
return post<string>('/ai-image/generate', data).json();
|
return post<string>('/ai-image/generate', data).json();
|
||||||
@@ -30,4 +30,9 @@ export function publishImage(data: PublishImageRequest) {
|
|||||||
|
|
||||||
export function getImageModels() {
|
export function getImageModels() {
|
||||||
return post<ImageModel[]>('/ai-image/model').json();
|
return post<ImageModel[]>('/ai-image/model').json();
|
||||||
}
|
}
|
||||||
|
export function deleteMyTasks(taskIds: string[]) {
|
||||||
|
const query = taskIds.map(id => `ids=${encodeURIComponent(id)}`).join('&');
|
||||||
|
const url = `/ai-image/my-tasks${query ? `?${query}` : ''}`;
|
||||||
|
return del<void>(url).json();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import type { AnnouncementLogDto } from './types';
|
import type {
|
||||||
import { get } from '@/utils/request';
|
AnnouncementLogDto,
|
||||||
|
AnnouncementDto,
|
||||||
|
AnnouncementCreateInput,
|
||||||
|
AnnouncementUpdateInput,
|
||||||
|
AnnouncementGetListInput,
|
||||||
|
PagedResultDto,
|
||||||
|
} from './types';
|
||||||
|
import { del, get, post, put } from '@/utils/request';
|
||||||
|
|
||||||
|
// ==================== 前端首页用 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统公告和活动数据
|
* 获取系统公告和活动数据
|
||||||
@@ -9,4 +18,49 @@ import { get } from '@/utils/request';
|
|||||||
export function getSystemAnnouncements() {
|
export function getSystemAnnouncements() {
|
||||||
return get<AnnouncementLogDto[]>('/announcement').json();
|
return get<AnnouncementLogDto[]>('/announcement').json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 后台管理用 ====================
|
||||||
|
|
||||||
|
// 获取公告列表
|
||||||
|
export function getList(params?: AnnouncementGetListInput) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
if (params?.type !== undefined) {
|
||||||
|
queryParams.append('Type', params.type.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const url = queryString ? `/announcement/list?${queryString}` : '/announcement/list';
|
||||||
|
|
||||||
|
return get<PagedResultDto<AnnouncementDto>>(url).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取公告
|
||||||
|
export function getById(id: string) {
|
||||||
|
return get<AnnouncementDto>(`/announcement/${id}`).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建公告
|
||||||
|
export function create(data: AnnouncementCreateInput) {
|
||||||
|
return post<AnnouncementDto>('/announcement', data).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新公告
|
||||||
|
export function update(data: AnnouncementUpdateInput) {
|
||||||
|
return put<AnnouncementDto>('/announcement', data).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除公告
|
||||||
|
export function deleteById(id: string) {
|
||||||
|
return del(`/announcement/${id}`).json();
|
||||||
|
}
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
// 公告类型(对应后端 AnnouncementTypeEnum)
|
// 公告类型枚举(对应后端 AnnouncementTypeEnum)
|
||||||
|
export enum AnnouncementTypeEnum {
|
||||||
|
Activity = 1,
|
||||||
|
System = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公告类型(兼容旧代码)
|
||||||
export type AnnouncementType = 'Activity' | 'System'
|
export type AnnouncementType = 'Activity' | 'System'
|
||||||
|
|
||||||
// 公告DTO(对应后端 AnnouncementLogDto)
|
// 公告DTO(对应后端 AnnouncementLogDto)
|
||||||
@@ -16,3 +22,58 @@ export interface AnnouncementLogDto {
|
|||||||
/** 公告类型(系统、活动) */
|
/** 公告类型(系统、活动) */
|
||||||
type: AnnouncementType
|
type: AnnouncementType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 后台管理用 DTO ====================
|
||||||
|
|
||||||
|
// 公告 DTO(后台管理列表)
|
||||||
|
export interface AnnouncementDto {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string[];
|
||||||
|
remark?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime?: string;
|
||||||
|
type: AnnouncementTypeEnum;
|
||||||
|
url?: string;
|
||||||
|
creationTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建公告输入
|
||||||
|
export interface AnnouncementCreateInput {
|
||||||
|
title: string;
|
||||||
|
content: string[];
|
||||||
|
remark?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime?: string;
|
||||||
|
type: AnnouncementTypeEnum;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新公告输入
|
||||||
|
export interface AnnouncementUpdateInput {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string[];
|
||||||
|
remark?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime?: string;
|
||||||
|
type: AnnouncementTypeEnum;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取公告列表输入
|
||||||
|
export interface AnnouncementGetListInput {
|
||||||
|
searchKey?: string;
|
||||||
|
skipCount?: number;
|
||||||
|
maxResultCount?: number;
|
||||||
|
type?: AnnouncementTypeEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页结果
|
||||||
|
export interface PagedResultDto<T> {
|
||||||
|
items: T[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
AiModelCreateInput,
|
AiModelCreateInput,
|
||||||
AiModelUpdateInput,
|
AiModelUpdateInput,
|
||||||
AiModelGetListInput,
|
AiModelGetListInput,
|
||||||
|
AppShortcutDto,
|
||||||
PagedResultDto,
|
PagedResultDto,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
@@ -98,3 +99,15 @@ export function updateModel(data: AiModelUpdateInput) {
|
|||||||
export function deleteModel(id: string) {
|
export function deleteModel(id: string) {
|
||||||
return del(`/channel/model/${id}`).json();
|
return del(`/channel/model/${id}`).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除尊享模型ID缓存
|
||||||
|
export function clearPremiumModelCache() {
|
||||||
|
return post('/model/clear-premium-cache').json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 快捷渠道 ====================
|
||||||
|
|
||||||
|
// 获取快捷渠道列表
|
||||||
|
export function getAppShortcutList() {
|
||||||
|
return get<AppShortcutDto[]>('/channel/app-shortcut').json();
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export interface AiModelDto {
|
|||||||
providerName?: string;
|
providerName?: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
isPremium: boolean;
|
isPremium: boolean;
|
||||||
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建AI模型输入
|
// 创建AI模型输入
|
||||||
@@ -84,6 +85,7 @@ export interface AiModelCreateInput {
|
|||||||
providerName?: string;
|
providerName?: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
isPremium: boolean;
|
isPremium: boolean;
|
||||||
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新AI模型输入
|
// 更新AI模型输入
|
||||||
@@ -103,6 +105,7 @@ export interface AiModelUpdateInput {
|
|||||||
providerName?: string;
|
providerName?: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
isPremium: boolean;
|
isPremium: boolean;
|
||||||
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取AI模型列表输入
|
// 获取AI模型列表输入
|
||||||
@@ -114,6 +117,17 @@ export interface AiModelGetListInput {
|
|||||||
maxResultCount?: number;
|
maxResultCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 快捷渠道DTO
|
||||||
|
export interface AppShortcutDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
endpoint: string;
|
||||||
|
extraUrl?: string;
|
||||||
|
apiKey: string;
|
||||||
|
orderNum: number;
|
||||||
|
creationTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 分页结果
|
// 分页结果
|
||||||
export interface PagedResultDto<T> {
|
export interface PagedResultDto<T> {
|
||||||
items: T[];
|
items: T[];
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
import type { ChatMessageVo, GetChatListParams, SendDTO } from './types';
|
import type { ChatMessageVo, GetChatListParams, SendDTO } from './types';
|
||||||
import { get, post } from '@/utils/request';
|
import { del, get, post } from '@/utils/request';
|
||||||
|
|
||||||
// 发送消息
|
// 删除消息接口
|
||||||
|
export interface DeleteMessageParams {
|
||||||
|
ids: (number | string)[];
|
||||||
|
isDeleteSubsequent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteMessages(data: DeleteMessageParams) {
|
||||||
|
const idsQuery = data.ids.map(id => `ids=${encodeURIComponent(id)}`).join('&');
|
||||||
|
const subsequentQuery = data.isDeleteSubsequent !== undefined ? `isDeleteSubsequent=${data.isDeleteSubsequent}` : '';
|
||||||
|
const query = [idsQuery, subsequentQuery].filter(Boolean).join('&');
|
||||||
|
const url = `/message${query ? `?${query}` : ''}`;
|
||||||
|
return del<void>(url).json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息(旧接口)
|
||||||
export function send(data: SendDTO) {
|
export function send(data: SendDTO) {
|
||||||
const url = data.sessionId !== 'not_login'
|
const url = data.sessionId !== 'not_login'
|
||||||
? `/ai-chat/send/?sessionId=${data.sessionId}`
|
? `/ai-chat/send/?sessionId=${data.sessionId}`
|
||||||
@@ -9,6 +23,12 @@ export function send(data: SendDTO) {
|
|||||||
return post(url, data);
|
return post(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统一发送消息接口,支持4种API类型
|
||||||
|
export function unifiedSend(data: any, apiType: string, modelId: string, sessionId: string) {
|
||||||
|
const url = `/ai-chat/unified/send?apiType=${apiType}&modelId=${modelId}&sessionId=${sessionId}`;
|
||||||
|
return post(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
// 新增对应会话聊天记录
|
// 新增对应会话聊天记录
|
||||||
export function addChat(data: ChatMessageVo) {
|
export function addChat(data: ChatMessageVo) {
|
||||||
return post('/system/message', data).json();
|
return post('/system/message', data).json();
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export interface GetChatListParams {
|
|||||||
/**
|
/**
|
||||||
* 主键
|
* 主键
|
||||||
*/
|
*/
|
||||||
id?: number;
|
id?: number | string;
|
||||||
/**
|
/**
|
||||||
* 排序的方向desc或者asc
|
* 排序的方向desc或者asc
|
||||||
*/
|
*/
|
||||||
@@ -195,7 +195,7 @@ export interface ChatMessageVo {
|
|||||||
/**
|
/**
|
||||||
* 主键
|
* 主键
|
||||||
*/
|
*/
|
||||||
id?: number;
|
id?: number | string;
|
||||||
/**
|
/**
|
||||||
* 模型名称
|
* 模型名称
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './announcement'
|
export * from './announcement'
|
||||||
|
export * from './agent';
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './chat';
|
export * from './chat';
|
||||||
export * from './file';
|
export * from './file';
|
||||||
|
|||||||
@@ -60,9 +60,57 @@ export function getApiKey() {
|
|||||||
return get<any>('/token').json();
|
return get<any>('/token').json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 充值记录查询参数类型
|
||||||
|
export interface RechargeLogQueryParams {
|
||||||
|
skipCount?: number;
|
||||||
|
maxResultCount?: number;
|
||||||
|
isFree?: boolean;
|
||||||
|
minRechargeAmount?: number;
|
||||||
|
maxRechargeAmount?: number;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
orderByColumn?: string;
|
||||||
|
isAsc?: string;
|
||||||
|
isAscending?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 查询充值记录
|
// 查询充值记录
|
||||||
export function getRechargeLog() {
|
export function getRechargeLog(params?: RechargeLogQueryParams) {
|
||||||
return get<any>('/recharge/account').json();
|
const queryParams = new URLSearchParams();
|
||||||
|
if (params?.skipCount !== undefined) {
|
||||||
|
queryParams.append('SkipCount', params.skipCount.toString());
|
||||||
|
}
|
||||||
|
if (params?.maxResultCount !== undefined) {
|
||||||
|
queryParams.append('MaxResultCount', params.maxResultCount.toString());
|
||||||
|
}
|
||||||
|
if (params?.isFree !== undefined) {
|
||||||
|
queryParams.append('IsFree', params.isFree.toString());
|
||||||
|
}
|
||||||
|
if (params?.minRechargeAmount !== undefined) {
|
||||||
|
queryParams.append('MinRechargeAmount', params.minRechargeAmount.toString());
|
||||||
|
}
|
||||||
|
if (params?.maxRechargeAmount !== undefined) {
|
||||||
|
queryParams.append('MaxRechargeAmount', params.maxRechargeAmount.toString());
|
||||||
|
}
|
||||||
|
if (params?.startTime) {
|
||||||
|
queryParams.append('StartTime', params.startTime);
|
||||||
|
}
|
||||||
|
if (params?.endTime) {
|
||||||
|
queryParams.append('EndTime', params.endTime);
|
||||||
|
}
|
||||||
|
if (params?.orderByColumn) {
|
||||||
|
queryParams.append('OrderByColumn', params.orderByColumn);
|
||||||
|
}
|
||||||
|
if (params?.isAsc) {
|
||||||
|
queryParams.append('IsAsc', params.isAsc);
|
||||||
|
}
|
||||||
|
if (params?.isAscending !== undefined) {
|
||||||
|
queryParams.append('IsAscending', params.isAscending.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const url = queryString ? `/recharge/account?${queryString}` : '/recharge/account';
|
||||||
|
return get<any>(url).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户近7天token消耗
|
// 查询用户近7天token消耗
|
||||||
@@ -163,3 +211,44 @@ export function getPremiumPackageTokenUsage() {
|
|||||||
"percentage": 0
|
"percentage": 0
|
||||||
}
|
}
|
||||||
] */
|
] */
|
||||||
|
|
||||||
|
// 获取当前用户近24小时每小时Token消耗统计
|
||||||
|
// tokenId: 可选,传入则查询该token的用量,不传则查询全部
|
||||||
|
export function getLast24HoursTokenUsage(tokenId?: string) {
|
||||||
|
const url = tokenId
|
||||||
|
? `/usage-statistics/last24Hours-token-usage?tokenId=${tokenId}`
|
||||||
|
: '/usage-statistics/last24Hours-token-usage';
|
||||||
|
return get<any>(url).json();
|
||||||
|
}
|
||||||
|
/* 返回数据
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"hour": "2026-01-23T13:32:49.237Z",
|
||||||
|
"totalTokens": 0,
|
||||||
|
"modelBreakdown": [
|
||||||
|
{
|
||||||
|
"modelId": "string",
|
||||||
|
"tokens": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 获取当前用户今日各模型使用量统计
|
||||||
|
// tokenId: 可选,传入则查询该token的用量,不传则查询全部
|
||||||
|
export function getTodayModelUsage(tokenId?: string) {
|
||||||
|
const url = tokenId
|
||||||
|
? `/usage-statistics/today-model-usage?tokenId=${tokenId}`
|
||||||
|
: '/usage-statistics/today-model-usage';
|
||||||
|
return get<any>(url).json();
|
||||||
|
}
|
||||||
|
/* 返回数据
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"modelId": "string",
|
||||||
|
"usageCount": 0,
|
||||||
|
"totalTokens": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// 查询用户模型列表返回的数据结构
|
// 查询用户模型列表返回的数据结构
|
||||||
export interface GetSessionListVO {
|
export interface GetSessionListVO {
|
||||||
id?: number;
|
id?: number | string;
|
||||||
category?: string;
|
category?: string;
|
||||||
modelName?: string;
|
modelName?: string;
|
||||||
modelDescribe?: string;
|
modelDescribe?: string;
|
||||||
@@ -12,6 +12,11 @@ export interface GetSessionListVO {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
|
isFree?: boolean; // 是否为免费模型
|
||||||
|
isPremiumPackage?: boolean; // 是否为尊享套餐模型
|
||||||
|
modelApiType?: string; // API 格式类型: Completions | Messages | Responses | GenerateContent
|
||||||
|
providerName?: string;
|
||||||
|
iconUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模型类型枚举
|
// 模型类型枚举
|
||||||
|
|||||||
15
Yi.Ai.Vue3/src/api/ranking/index.ts
Normal file
15
Yi.Ai.Vue3/src/api/ranking/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { RankingGetListInput, RankingItemDto } from './types';
|
||||||
|
import { get } from '@/utils/request';
|
||||||
|
|
||||||
|
// 获取排行榜列表(公开接口,无需登录)
|
||||||
|
export function getRankingList(params?: RankingGetListInput) {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (params?.type !== undefined) {
|
||||||
|
queryParams.append('Type', params.type.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const url = queryString ? `/ranking/list?${queryString}` : '/ranking/list';
|
||||||
|
|
||||||
|
return get<RankingItemDto[]>(url).json();
|
||||||
|
}
|
||||||
21
Yi.Ai.Vue3/src/api/ranking/types.ts
Normal file
21
Yi.Ai.Vue3/src/api/ranking/types.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// 排行榜类型枚举
|
||||||
|
export enum RankingTypeEnum {
|
||||||
|
Model = 0,
|
||||||
|
Tool = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排行榜项
|
||||||
|
export interface RankingItemDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
logoUrl?: string;
|
||||||
|
score: number;
|
||||||
|
provider: string;
|
||||||
|
type: RankingTypeEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排行榜查询参数
|
||||||
|
export interface RankingGetListInput {
|
||||||
|
type?: RankingTypeEnum;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user