Compare commits

..

277 Commits

Author SHA1 Message Date
ccnetcore
a9e8b2b01f feat: 增加尊享包商品及折扣逻辑,完善VIP与尊享包相关接口和数据返回
- 新增尊享包商品类型,支持 5000W 和 10000W Tokens
- 增加尊享包折扣计算与折扣后价格获取方法
- PayService 新增获取商品列表接口,支持尊享包折扣展示
- PayManager 支持尊享包订单金额按折扣计算,并新增获取用户累计充值金额方法
- OpenApiService Anthropic接口增加VIP与尊享包用量校验
- AiGateWayManager 增加尊享包Token扣减逻辑
- AiAccountService 返回用户VIP状态、到期时间及尊享包Token统计信息
2025-10-12 20:07:58 +08:00
ccnetcore
4d09243efd feat: 完成尊享服务 2025-10-12 16:42:26 +08:00
ccnetcore
5934056fe6 fix: 修复Anthropic接口TokenUsage序列化及HttpClient创建方式问题 2025-10-12 14:38:26 +08:00
chenchun
2a81062fa3 perf: 优化 HttpClient 配置,增加 10 分钟超时设置 2025-10-12 00:02:34 +08:00
chenchun
fdc868323f fix: 修正 TokenUsage 计算逻辑,使用 CacheReadInputTokens 替代重复的 CacheCreationInputTokens 2025-10-11 23:36:26 +08:00
chenchun
593b3a4cdd fix: 修正消息与Anthropic返回的Token统计逻辑,避免零值覆盖并支持缓存Token计算 2025-10-11 23:27:46 +08:00
chenchun
2b12e18e6c fix: 为AnthropicHandles添加MaxTokens有效性校验 2025-10-11 20:32:41 +08:00
chenchun
345ed80ec8 feat: 新增claude接口转换支持 2025-10-11 15:25:43 +08:00
chenchun
29dc1ae250 fix: 限制 Azure OpenAI 请求最大 tokens 并优化响应处理空行格式 2025-10-10 15:16:16 +08:00
ccnetcore
9fdd41b134 fix: 更新会员套餐为8个月并调整价格信息 2025-10-08 21:24:29 +08:00
ccnetcore
31ee5e8ffb feat: 新增 YiXinVip 8 个月商品类型并移除 9 个月配置 2025-10-08 21:18:42 +08:00
ccnetcore
d7922bb71d fix: 修复 tokenUsage 为空时的空引用问题 2025-09-27 17:40:31 +08:00
chenchun
fa3ac91ba4 fix: 修正文件整理大师Vip数量限制提示错误 2025-09-18 16:17:10 +08:00
ccnetcore
e7c152e955 fix: 修复文件整理大师文件数量限制及模型名称错误 2025-09-13 21:19:54 +08:00
ccnetcore
0223b5c104 fix: 更新客服联系方式和产品价格信息
- 统一修改客服支持提示信息为"备注ai获取专属客服支持"
- 更新会员套餐价格和描述信息
- 替换模型排行榜iframe为openrouter链接
- 调整内容截断长度从2000到10000字符
2025-09-07 18:49:58 +08:00
ccnetcore
9e41a7c446 fix: 调整YiXinVip商品价格及有效期配置 2025-09-07 18:29:41 +08:00
ccnetcore
2ba0ffc1b7 Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-09-07 17:16:13 +08:00
ccnetcore
7d038e1266 style: 优化样式1.3 2025-09-07 17:16:07 +08:00
Gsh
b98285f314 fix: 处理二维码接口过多调用问题 2025-09-07 13:30:03 +08:00
ccnetcore
73438da666 fix: 将 VerifyNext 接口由 GET 改为 POST 请求 2025-09-07 01:34:52 +08:00
ccnetcore
85f2e1b579 feat: 新增文件整理大师服务及校验与对话接口 2025-09-07 01:34:25 +08:00
ccnetcore
ece89ebad0 refactor: 移除自定义 HttpClientFactory,改用依赖注入的 IHttpClientFactory 2025-09-04 22:26:06 +08:00
Gsh
6e2dd39246 fix: 未登录支付按钮改登录 2025-09-03 11:43:40 +08:00
Gsh
a61286e534 fix: 非会员模型选择跳转修改 2025-09-03 10:56:44 +08:00
Gsh
4f944a5466 fix: 优化二维码加载错误显示 2025-08-31 23:56:42 +08:00
ccnetcore
d29aac088a feat: 全面优化ai-hub前端细节 2025-08-31 01:37:36 +08:00
Gsh
8abd122773 feat: 重新登录逻辑更改 2025-08-30 23:58:57 +08:00
Gsh
08084aa0bc feat: 默认展示二维码登录 2025-08-30 23:13:46 +08:00
Gsh
e69cd5a73c feat: 用户头像路径固定 2025-08-30 23:05:14 +08:00
Gsh
76aa3bdc64 feat: 用户头像路径固定 2025-08-30 22:39:27 +08:00
Gsh
93251104af Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-08-30 22:29:21 +08:00
Gsh
3cae477f3e feat: 增加扫码登录功能 2025-08-30 22:28:38 +08:00
ccnetcore
25c736dc0a fix: 修复扫码回调在非等待状态下仍被处理的问题 2025-08-30 22:07:09 +08:00
ccnetcore
96a09d8980 fix: 修复绑定用户时返回值未包含用户ID的问题 2025-08-30 21:58:43 +08:00
ccnetcore
72387235a0 refactor: 移除分布式锁获取时的超时参数 2025-08-30 21:17:25 +08:00
ccnetcore
1b00e505b7 fix: 修复绑定微信二维码生成时未登录用户的异常提示 2025-08-30 18:02:46 +08:00
ccnetcore
1c54e47b9e feat: 新增AI账户服务及扩展用户信息获取功能,支持通过userId查询用户信息 2025-08-30 17:55:13 +08:00
ccnetcore
ba07e2c905 feat: 新增服务号注册授权页面并优化注册提示信息 2025-08-30 00:02:27 +08:00
ccnetcore
bde4611a50 fix: 修复服务号注册缓存时间及锁定范围问题 2025-08-29 23:32:40 +08:00
ccnetcore
e7326fea7b Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-08-29 22:21:11 +08:00
ccnetcore
d13b23ad2e fix: 修复服务号扫码场景缓存未保存用户ID且场景结果错误的问题 2025-08-29 22:20:52 +08:00
Gsh
8b1830a711 Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-08-29 22:02:14 +08:00
Gsh
b70c530754 fix: 增加扫码 2025-08-29 22:01:44 +08:00
ccnetcore
d90e24f9ed fix: 修复服务号回调处理返回用户ID及缓存更新逻辑 2025-08-29 21:55:07 +08:00
chenchun
1fbd521d1a fix: 注册接口加分布式锁防止并发重复注册 2025-08-29 14:33:35 +08:00
chenchun
2ae6183e7f fix: 防止重复注册意社区账号 2025-08-29 14:31:04 +08:00
chenchun
7905911624 feat: 注册用户时支持传入头像参数 2025-08-29 14:11:50 +08:00
chenchun
c5b6b33d8e feat: 注册公众号用户时保存额外用户信息到数据库 2025-08-29 13:54:02 +08:00
chenchun
5d29fd6d3b feat: 为微信服务号用户信息响应模型添加JsonPropertyName映射并移除无用字段 2025-08-29 13:41:50 +08:00
chenchun
ad8f48f36b feat: 服务号用户信息获取增加日志记录 2025-08-29 11:53:36 +08:00
chenchun
f9843c13d4 fix: 修复场景缓存为空时的处理逻辑并调整注册成功缓存写入方式 2025-08-29 11:34:57 +08:00
chenchun
6bd561b094 feat: 新增微信公众号扫码注册功能及幂等处理
- 新增 `FuwuhaoConst` 常量类,统一缓存 Key 前缀管理
- `FuwuhaoOptions` 增加 FromUser、RedirectUri、PicUrl 配置项
- `FuwuhaoManager` 新增 `BuildRegisterMessage` 方法,构建注册引导图文消息
- `FuwuhaoService`
  - 增加 OpenId 与 Scene 绑定缓存,支持扫码注册有效期管理
  - 回调处理支持注册场景,返回图文消息引导用户注册
  - 新增注册接口 `RegisterByCodeAsync`,根据微信授权信息自动注册账号并更新场景状态
- `AccountManager` 注册方法增加分布式锁,防止重复注册,并校验用户名唯一性
2025-08-29 11:01:09 +08:00
chenchun
d2c6238df1 feat: 启用AI股票生成与新闻生成任务并切换至OpenAI接口配置 2025-08-28 15:40:59 +08:00
chenchun
1d108983e8 feat: 增加服务号回调签名校验及扫码回调幂等处理
- `FuwuhaoManager` 新增 `ValidateCallback` 方法,用于校验微信回调签名
- `FuwuhaoOptions` 增加 `CallbackToken` 配置项
- `QrCodeResponse` 属性添加 `JsonPropertyName` 标注,支持 JSON 序列化映射
- `FuwuhaoService` 在回调接口中增加签名校验,并通过分布式锁实现幂等处理
- 调整场景值解析逻辑,过滤非扫码/关注事件
- 优化缓存过期时间设置
2025-08-28 15:20:15 +08:00
ccnetcore
b768bca638 feat: 完成支持微信扫码功能 2025-08-27 23:42:46 +08:00
chenchun
28fcd6c9ce feat: 新增服务号回调处理服务及数据模型 2025-08-27 17:46:39 +08:00
ccnetcore
10559a925c style: 优化样式1.2 2025-08-25 00:39:16 +08:00
ccnetcore
942e218a9e feat: 新增 FileMaster 发送消息接口 2025-08-23 13:15:28 +08:00
ccnetcore
f6af9edc38 feat: 删除文件 2025-08-23 12:23:17 +08:00
ccnetcore
e0f6331ec3 feat:构建 2025-08-23 12:21:16 +08:00
ccnetcore
06f0c6caa7 style: 整体调整 2025-08-23 12:14:09 +08:00
ccnetcore
56ec260e3a perf: 优化已完成状态下的输出等待时间,加快响应速度 2025-08-21 21:50:06 +08:00
ccnetcore
176cf84369 fix: 修复 AiGateWayManager 中可能的空引用异常 2025-08-21 01:16:57 +08:00
Gsh
6de3b722ed fix: 新增文件助手目录 2025-08-18 23:07:34 +08:00
Gsh
2cf6326764 fix: 更新组件库 2025-08-18 21:02:55 +08:00
Gsh
ec27ee58b4 fix: 充值成功与记录页面增加联系客服,apikey教程更改 2025-08-17 22:08:03 +08:00
ccnetcore
4e42e2202e refactor: 移除代码补全兼容逻辑并优化 DTO 可空类型处理 2025-08-16 19:20:58 +08:00
ccnetcore
e60d8eceb7 feat: 新增 YiXinVip 商品价格配置及前端构建压缩环境变量定义 2025-08-16 00:08:03 +08:00
Gsh
08fb939b38 fix: 动态支付响应页面 2025-08-15 23:49:18 +08:00
ccnetcore
482dd73afd feat: 支持创建订单时自定义支付宝回调地址 2025-08-15 23:41:01 +08:00
Gsh
f09a9fee75 fix: 产品相关页面样式优化 2025-08-15 23:28:09 +08:00
chenchun
9a31d14b41 feat: 优化CORS配置支持通配符和代码格式化
- 支持CORS配置使用通配符"*"允许所有来源访问
- 优化CORS策略配置逻辑,区分通配符和具体域名处理
- 格式化代码,移除多余空行和统一代码风格
- 修复Hangfire配置中的变量赋值格式
- 更新默认CORS配置为通配符模式
2025-08-14 17:04:27 +08:00
chenchun
2fd7f88f04 feat: 新增海外站点流量限制和CORS配置优化
- 新增yxai.chat域名到CORS白名单
- 为海外站点yxai.chat添加大流量接口访问限制
- 修复Azure OpenAI图像生成服务默认尺寸设置
2025-08-14 15:14:30 +08:00
chenchun
9d4cc802e9 fix: 添加CodeGeeX跨域支持
在CORS配置中新增http://codegeex域名,支持CodeGeeX工具的跨域访问请求
2025-08-14 14:24:03 +08:00
Gsh
ee6b4827fa feat: 增加支付宝在线支付、套餐订购弹窗、会员权益、支持模型展示等 2025-08-14 00:26:39 +08:00
ccnetcore
48d8c528f6 fix: 调整YiXinVip各档价格为0.01方便测试 2025-08-13 22:21:03 +08:00
ccnetcore
40c0a5ac64 feat: 支付完成后自动为用户充值VIP并支持按商品类型计算有效期 2025-08-13 22:19:31 +08:00
chenchun
3a60bcc174 refactor: 优化交易状态枚举处理方式
- 为TradeStatusEnum枚举添加Description特性标注
- 重构GetTradeStatusDescription方法,使用反射获取Description特性值
- 简化ParseTradeStatus方法,使用Enum.TryParse替代switch表达式
- 提高代码可维护性,避免硬编码状态描述
2025-08-13 18:30:56 +08:00
chenchun
2b3fad16fd feat: 优化支付宝回调通知记录功能
- 新增SignStr字段记录支付宝回调的原始签名字符串
- 修改日志记录格式,使用键值对形式记录回调通知数据
- 更新PayManager.RecordPayNoticeAsync方法支持记录原始签名字符串
- 移除AlipayManager中冗余的注释说明
2025-08-13 18:21:05 +08:00
chenchun
f0cf6bf5c8 fix: 修复支付宝支付功能相关问题
- 修复支付接口参数顺序错误,调整商品名称和订单号参数位置
- 修复支付页面HTML返回格式,直接返回Body内容而非序列化字符串
- 添加支付相关接口的权限控制,支付回调接口允许匿名访问
- 优化支付宝回调验签逻辑,保持原始参数顺序避免验签失败
- 增加回调格式错误的异常处理
- 修复商品类型枚举显示名称为英文,新增测试商品类型
- 修正Token服务提示文案中的错别字
- 移除订单更新时不必要的时间字段设置
2025-08-13 17:42:13 +08:00
chenchun
0ba4e3240b feat: 完成支付宝接入 2025-08-13 12:07:35 +08:00
ccnetcore
9332b17fc1 feat: 集成支付宝支付SDK并添加当面付测试调用,更新CORS配置支持capacitor 2025-08-13 08:26:45 +08:00
ccnetcore
4ec4023f40 feat: 增加EmbeddingResponse的object字段并完善AiGateWayManager的Usage统计,更新CORS配置 2025-08-11 20:24:48 +08:00
chenchun
d9971541f2 feat: 支持字符串类型的embedding输入参数
在AiGateWayManager中新增对JsonElement字符串类型的处理,确保embedding请求能够正确处理单个字符串输入参数。
2025-08-11 18:10:11 +08:00
chenchun
7b0e4fcc73 fix: 修复Embedding输入处理逻辑和字段可空性
- 优化Embedding输入类型判断逻辑,支持string和JsonElement数组类型
- 将EncodingFormat字段设置为可空类型,提高兼容性
- 注释知识库场景下的消息统计功能,避免不必要的数据记录
2025-08-11 18:05:33 +08:00
chenchun
cfde73d13a fix: 修复输出为空问题 2025-08-11 16:53:33 +08:00
chenchun
c17c9000a8 refactor: 移除AiHub Domain层对Application.Contracts的循环依赖
移除Yi.Framework.AiHub.Domain项目中对Yi.Framework.AiHub.Application.Contracts的项目引用,解决领域层和应用层之间的循环依赖问题,符合DDD架构分层原则。
2025-08-11 15:51:59 +08:00
chenchun
42d537a68b style: 调整架构引用 2025-08-11 15:31:11 +08:00
chenchun
25eebec8f7 feat: 新增向量嵌入服务支持
新增SiliconFlow向量嵌入服务实现,支持文本向量化功能:
- 新增ITextEmbeddingService接口和SiliconFlowTextEmbeddingService实现
- 新增EmbeddingCreateRequest/Response等向量相关DTO
- 在AiGateWayManager中新增EmbeddingForStatisticsAsync方法
- 在OpenApiService中新增向量生成API接口
- 扩展ModelTypeEnum枚举支持Embedding类型
- 优化ThorChatMessage的Content属性处理逻辑
2025-08-11 15:29:24 +08:00
Gsh
bbe5b01872 fix: 优化token图表,增加全屏显示 2025-08-10 15:34:53 +08:00
ccnetcore
6b31536de5 fix: 修复用户过期判断逻辑,按日期比较避免当天误判 2025-08-10 12:07:09 +08:00
ccnetcore
2e5db5500f Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-08-10 11:53:52 +08:00
ccnetcore
7038d31c53 feat: 新增VIP充值接口并支持通过角色代码为用户分配角色 2025-08-10 11:53:28 +08:00
Gsh
3eb27c3d35 fix: 增加对话token显示,token消耗统计 2025-08-10 00:56:44 +08:00
ccnetcore
a9c3a1bcec fix: 修复统计中 Token 数量计算错误,将计数改为求和 2025-08-09 23:38:56 +08:00
ccnetcore
384926e73a feat: 新增用户数据导出功能 2025-08-09 22:55:26 +08:00
ccnetcore
4335c12659 chore: 注释掉生成新闻和股票价格的异步调用 2025-08-09 13:58:26 +08:00
ccnetcore
e6e4829164 feat: 新增VIP过期自动卸载功能
- 新增`AiRechargeManager`类,实现VIP过期用户的自动卸载逻辑。
- 新增`AiHubConst`常量类,统一管理角色名称。
- 在`IRoleService`中添加`RemoveUserRoleByRoleCodeAsync`方法,用于移除指定用户的角色。
- 在`RoleManager`中实现`RemoveUserRoleByRoleCodeAsync`方法。
- 优化`CurrentExtensions`中VIP角色判断逻辑,使用常量替代硬编码。
- 调整`YiAbpWebModule`中部分代码格式,提升可读性。
2025-08-09 13:14:15 +08:00
ccnetcore
f3c67cf598 fix: 修复统计数量偶发问题 2025-08-09 12:20:28 +08:00
ccnetcore
4681d468ce style: 优化验证码样式 2025-08-05 22:41:20 +08:00
chenchun
63e7d3d5f5 style: 更新主题2.2 2025-08-05 18:23:33 +08:00
chenchun
f47d8c8ce3 style: 优化2.1样式 2025-08-05 17:19:03 +08:00
chenchun
6f69f45ddc Merge branch 'bbs-sharpdance' into ai-hub 2025-08-05 14:11:16 +08:00
chenchun
e73678c788 style: 全部样式更新2.0 2025-08-05 14:09:39 +08:00
ccnetcore
09a2f91cbf style: 优化样式1.1 2025-08-04 23:55:48 +08:00
ccnetcore
29da7499a4 Merge branch 'bbs-sharpdance' into ai-hub 2025-08-04 23:37:11 +08:00
ccnetcore
5b024e9443 style: 重写ele 2025-08-04 23:34:13 +08:00
ccnetcore
225932eff1 style: 上线全局样式 2025-08-04 23:29:25 +08:00
Gsh
65d5f5ae86 fix: 加载优化、vip状态优化、apikey优化 2025-08-04 23:11:42 +08:00
ccnetcore
3e647ef14d style: 全局修改样式主题 2025-08-04 22:35:45 +08:00
chenchun
7cb3aea2e6 style: 调整样式 2025-08-04 18:27:18 +08:00
chenchun
7f4b8f1c8a feat: 添加暗色主题支持
- 在HTML根元素添加dark类名以启用暗色模式
- 引入Element Plus暗色主题CSS变量文件
- 格式化代码缩进和结构,提升代码可读性
2025-08-04 17:07:01 +08:00
ccnetcore
0a2710b865 feat: 支持图片生成 2025-08-04 01:03:47 +08:00
ccnetcore
2a301c4983 feat: 支持图片生成 2025-08-03 23:23:32 +08:00
Gsh
faa8131a1b fix: 未登录对话id逻辑玩优化 2025-08-03 21:56:51 +08:00
ccnetcore
71bd885bd0 fix: 支持router参数 2025-08-03 21:47:22 +08:00
ccnetcore
691a1e50f0 feat: 支持未登录用户统计 2025-08-03 21:32:54 +08:00
ccnetcore
ef6e9fd16d style: 优化提示词 2025-08-02 22:04:22 +08:00
chenchun
17f9ac6d54 style: 优化防抖样式 2025-08-01 17:58:07 +08:00
chenchun
3f8e6e48c0 Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-28 14:39:09 +08:00
chenchun
bda4fdf69d feat: 兼容代码补全功能 2025-07-28 14:39:02 +08:00
Gsh
5c85ed13fd fix: 加载进度优化与登录弹窗优化 2025-07-28 13:43:46 +08:00
chenchun
1986901031 Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-28 13:15:49 +08:00
chenchun
e1d3ec21e5 feat: 支持错误处理 2025-07-28 13:15:42 +08:00
Gsh
f45283dade Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-28 12:59:42 +08:00
Gsh
31c44d8df7 fix: 登录弹窗超时功能取消 2025-07-28 12:59:07 +08:00
chenchun
bf443963c8 fix: 修复ThorChatCompletionsRequest中Messages属性的可空类型问题 2025-07-28 12:50:48 +08:00
chenchun
a0eb234539 feat: 兼容了用量使用显示 2025-07-22 10:40:23 +08:00
ccnetcore
b6d670c240 perf: 兼容deepseek格式 2025-07-21 22:03:55 +08:00
ccnetcore
b5fb2c42c6 feat: 兼容deepseek协议 2025-07-21 21:57:14 +08:00
ccnetcore
d72cc529ba perf: 优化流式输出 2025-07-21 21:15:02 +08:00
Gsh
660bd00cae fix: apikey加载状态 2025-07-20 22:12:48 +08:00
Gsh
b5489711ec fix: 加载优化 2025-07-20 21:01:41 +08:00
Gsh
76717c4f8a Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-20 17:23:33 +08:00
ccnetcore
3d53d0bcd6 style: 完成进度条加载 2025-07-20 17:14:05 +08:00
ccnetcore
c7c9428b68 style: 完成进度条加载 2025-07-20 17:01:17 +08:00
ccnetcore
991a970d6a style: 完成进度条加载 2025-07-20 16:40:54 +08:00
ccnetcore
cbe93b9f7e style: 完成进度条加载 2025-07-20 15:15:05 +08:00
ccnetcore
5d7217b775 feat: 完成支持functioncall功能 2025-07-18 23:12:20 +08:00
ccnetcore
d6836b8bcf feat: 提交cicd产物 2025-07-18 21:08:51 +08:00
ccnetcore
c367651c78 chorm: 构建 2025-07-18 21:00:23 +08:00
ccnetcore
77123fd971 cicd: 提交流水线 2025-07-18 21:00:06 +08:00
ccnetcore
9d73a6837b Merge branch 'ai-hub' into ai-hub-dev 2025-07-18 20:46:36 +08:00
ccnetcore
651f0157dc feat: 完成兼容处理 2025-07-18 20:46:30 +08:00
Gsh
90e8dbe449 fix: 对话参数修改 2025-07-18 20:33:51 +08:00
ccnetcore
ccba2667bc Merge branch 'ai-hub' into ai-hub-dev 2025-07-18 19:51:15 +08:00
Gsh
3ce9fc9790 fix: 对话参数修改 2025-07-18 19:49:11 +08:00
ccnetcore
2f24dd77bf feat: 完成对接 2025-07-18 00:27:59 +08:00
ccnetcore
2bc07cb3df feat: 完成错误信息展示 2025-07-18 00:14:19 +08:00
ccnetcore
30678dbbb4 feat: 完成功能 2025-07-17 23:52:00 +08:00
ccnetcore
c5b0f69b51 feat: 重构完成 2025-07-17 23:16:16 +08:00
ccnetcore
e593f2cba4 feat: Thor搭建 2025-07-17 23:10:26 +08:00
ccnetcore
10f7499066 feat: 完成cicd搭建 2025-07-16 23:34:52 +08:00
Gsh
36b7e495f7 update: md渲染优化与依赖更新(0715 02:07) 2025-07-16 00:12:00 +08:00
Gsh
94b96e3c19 fix: 更新md 2025-07-15 16:42:47 +08:00
Gsh
0d1ee18da0 fix: 增加重新登录意社区 2025-07-15 00:54:34 +08:00
Gsh
cab0b61ee0 fix: 对话md渲染优化 2025-07-14 22:21:24 +08:00
Gsh
8e6611d76d Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-14 22:20:55 +08:00
Gsh
1ef82e5f93 fix: 对话md渲染优化 2025-07-14 22:20:32 +08:00
ccnetcore
43dc962606 feat: 支持邮箱注册功能 2025-07-13 21:26:46 +08:00
ccnetcore
020d674ca2 style: 调整header 2025-07-12 18:42:26 +08:00
ccnetcore
bb0e1081cc style: 调整header 2025-07-12 18:41:26 +08:00
Gsh
5162f9ce3b fix: 对话创建防抖 2025-07-12 00:36:11 +08:00
ccnetcore
57fae7fe4b fix: 知识库访问 2025-07-09 23:26:32 +08:00
ccnetcore
17412d7de7 feat: 新增支持Prompt 2025-07-09 23:11:57 +08:00
ccnetcore
d59f40dfba Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-07-09 22:44:36 +08:00
ccnetcore
5953be63cb feat: 兼容cline 2025-07-09 22:44:24 +08:00
Gsh
58a4311947 fix: 更新消息暂停 2025-07-09 22:31:52 +08:00
ccnetcore
c5a9b9a15f feat: 支持非流式传输 2025-07-09 21:52:00 +08:00
chenchun
716c344780 feat: 支持非流式传输功能 2025-07-09 19:12:53 +08:00
Gsh
9af8c4897b fix: 增加消息复制、消息时间 2025-07-09 00:08:30 +08:00
Gsh
ca72024a68 feat: 增加用户充值记录查询 2025-07-08 22:59:24 +08:00
chenchun
0d2bc585a9 feat: 提高模型输出速度 2025-07-08 18:24:21 +08:00
chenchun
4e3edefb35 feat: 整体调节 2025-07-08 15:47:51 +08:00
Gsh
9408242726 fix: 禁止移动端缩放、对话头像更改 2025-07-08 00:29:41 +08:00
Gsh
4710208e81 fix: 隐藏文件上传按钮,去除不必要的log打印 2025-07-07 23:29:39 +08:00
Gsh
4fc6a1e818 fix: 隐藏文件上传按钮,去除不必要的log打印 2025-07-07 23:27:55 +08:00
ccnetcore
c9b79a074b style: 增加markdown样式优化 2025-07-07 22:34:19 +08:00
Gsh
2e79eb346f fix: 添加微信群二维码 2025-07-07 21:47:21 +08:00
Gsh
3f88bd4158 fix:增加md样式重写文件 2025-07-07 21:21:55 +08:00
Gsh
f58e079741 fix:增加md样式文件 2025-07-07 21:12:35 +08:00
Gsh
6f1eb1f4b9 feat: 新增markdown渲染 2025-07-07 21:01:59 +08:00
ccnetcore
826d529997 fix: 修复接口名称 2025-07-05 17:47:44 +08:00
ccnetcore
43e60eab4a feat: 完成充值记录 2025-07-05 17:43:48 +08:00
Gsh
6c33024790 fix:百度seo添加与对话错误处理 2025-07-05 17:25:14 +08:00
Gsh
d27e625fde fix:前端模型主键换位modelId 2025-07-05 15:59:22 +08:00
Gsh
23cecb9360 fix:401、403错误提示,对话角色更改assistant,模型选择持久化 2025-07-05 15:49:29 +08:00
ccnetcore
7e4c835ced fix: 修复nugetapi 2025-07-05 15:31:18 +08:00
ccnetcore
aff460f555 feat: 升级yi.abp.tool 2025-07-05 15:23:08 +08:00
ccnetcore
52961b459e feat: 优化整体aihub架构 2025-07-05 15:11:56 +08:00
ccnetcore
0af2f867fc feat: 提交构建 2025-07-05 01:06:19 +08:00
chenchun
85e291e0b8 chorm: 修改构建域名 2025-07-04 19:17:45 +08:00
chenchun
6d8a859b20 feat: 关闭前端动画 2025-07-04 19:13:21 +08:00
ccnetcore
a70dfb0769 feat: 完成跨域处理 2025-07-04 00:16:58 +08:00
Gsh
c637d412e6 fix:增加用户中心,完成Apikey功能页,增加角色工具方法 2025-07-04 00:12:26 +08:00
ccnetcore
e996bc2d7f feat: 完成token模块 2025-07-03 22:44:52 +08:00
ccnetcore
15be047371 feat: 完成openapi改造 2025-07-03 22:31:39 +08:00
ccnetcore
0a0e0bca10 feat: 完成上下文功能 2025-07-03 21:28:40 +08:00
Gsh
9a8f3bd161 fix:增加seo优化 2025-07-03 17:13:21 +08:00
ccnetcore
7e2c035692 feat: 完成api接口搭建 2025-07-02 23:30:29 +08:00
ccnetcore
44b2ade9bc feat: 完成错误信息输出 2025-07-02 00:28:44 +08:00
Gsh
1200d02fbf fix:对话时只提供最近6条记录 2025-07-02 00:11:43 +08:00
chenchun
b020f48325 style: 调整配置jwt文件 2025-07-01 16:41:58 +08:00
chenchun
917857f1ff feat: 修改超时,改成10分钟 2025-07-01 16:11:41 +08:00
ccnetcore
69d8ff1034 fix: 修改deepseek校验 2025-06-30 22:23:37 +08:00
ccnetcore
9a334101ca fix: 修复ai模型问题 2025-06-30 21:58:34 +08:00
ccnetcore
ee53b3d9c4 feat: 完成细节调整 2025-06-30 21:08:32 +08:00
Gsh
01a5ad5302 fix:模型选择限制 2025-06-30 17:53:59 +08:00
Gsh
f12f0e1f84 fix:双token更新 2025-06-30 16:59:20 +08:00
Gsh
6aefcdbed8 fix:登录判断优化 2025-06-30 16:02:39 +08:00
ccnetcore
3d22a2ef65 feat: 完成支持鉴权刷新功能 2025-06-29 19:34:09 +08:00
Gsh
a33c6dbf1a fix: 增加用户角色标识与优化产品页 2025-06-29 17:47:07 +08:00
ccnetcore
2b7c779e14 style: 新增样式 2025-06-29 16:36:34 +08:00
ccnetcore
0e36f7c0b3 feat: 完成登录校验拦截 2025-06-29 15:41:49 +08:00
ccnetcore
228a309545 Revert "fix: 修复token样式"
This reverts commit b15ad8eb5e.
2025-06-29 15:22:57 +08:00
ccnetcore
b15ad8eb5e fix: 修复token样式 2025-06-29 15:22:04 +08:00
ccnetcore
a525735b0b Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-06-29 15:18:45 +08:00
ccnetcore
6a58af8dfb feat: 完成双token刷新 2025-06-29 15:18:30 +08:00
Gsh
0089e63832 feat: 产品订阅页面优化 2025-06-29 14:42:10 +08:00
ccnetcore
d4f00eb89f style: 修改Ai_Session表类型 2025-06-29 12:21:28 +08:00
Gsh
d15e6e395b fix: 修复拦截器报错 2025-06-29 12:09:34 +08:00
Gsh
39eb4bef07 fix: bbs与ai存储refreshToken 2025-06-29 00:57:57 +08:00
ccnetcore
03de576d8c fix: 修复值对象报错问题 2025-06-28 23:39:15 +08:00
ccnetcore
216b57a4c7 feat: 更新hook fetch 库 2025-06-28 23:07:32 +08:00
Gsh
5383d2d40e fix: 前端请求头增加浏览器指纹 2025-06-28 18:44:10 +08:00
Gsh
1d7a2013e3 fix: 单点登录优化与环境变量完善 2025-06-28 18:14:12 +08:00
Gsh
24d2908cca update: 修复样式规则报错 2025-06-28 14:33:13 +08:00
ccnetcore
330845a387 style: 调整下拉框样式 2025-06-27 22:50:51 +08:00
ccnetcore
bbedd01a72 fix: 兼容claude ai 2025-06-27 22:49:08 +08:00
ccnetcore
2b07061c18 feat: 完成个个模型ai统计 2025-06-27 22:21:44 +08:00
ccnetcore
01a3c81359 feat: 完成用量统计功能模块 2025-06-27 22:13:26 +08:00
Gsh
96e275efa6 fix: 单点登录优化 2025-06-27 14:23:06 +08:00
chenchun
12eb6c73c3 feat: 完成接入claude 2025-06-26 17:54:52 +08:00
ccnetcore
4166eddd28 Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-06-26 00:38:53 +08:00
ccnetcore
6ea1592c19 style: 调整标题样式 2025-06-26 00:38:36 +08:00
Gsh
f8799a073c Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-06-26 00:37:04 +08:00
Gsh
0eb83fc930 fix: 产品页面完善,增加空白布局与布局切换 2025-06-26 00:35:13 +08:00
ccnetcore
a5dd3946f8 fix: 修复模型接口错误 2025-06-25 23:15:31 +08:00
ccnetcore
2732df24af fix: 修复模型接口错误 2025-06-25 23:05:20 +08:00
ccnetcore
f0ae27a50b fix: 修复模型接口错误 2025-06-25 22:45:57 +08:00
ccnetcore
c5037ea397 feat: 完成ai网关改造 2025-06-25 22:41:32 +08:00
chenchun
695aaedfba feat: 完成ai-hub第一期功能 2025-06-25 17:12:09 +08:00
ccnetcore
4f71d874bd style: 调整速度 2025-06-25 00:35:25 +08:00
ccnetcore
c69729fadd feat: 提交队列 2025-06-25 00:30:01 +08:00
ccnetcore
64d04996af perf: 优化sse流式传输 2025-06-25 00:23:00 +08:00
ccnetcore
8eea510583 feat: ai完成接入deepseek 2025-06-25 00:05:00 +08:00
Gsh
04c2b246f6 fix: 放开产品页面 2025-06-23 23:36:59 +08:00
ccnetcore
a46eb176d7 feat: 还原 2025-06-23 23:04:22 +08:00
ccnetcore
2bea88f1a3 feat: 新增pro打包 2025-06-23 23:03:54 +08:00
ccnetcore
06617de984 feat: 完成对接接口 2025-06-22 19:09:13 +08:00
Gsh
6459d7c024 fix: ai-hub接口替换 2025-06-21 22:12:21 +08:00
Gsh
bd4af8039f fix: 用户信息接口替换 2025-06-21 21:57:07 +08:00
Gsh
8aaa22cea3 feat: ai-hub与bbs单点登录联通 2025-06-21 21:52:44 +08:00
ccnetcore
7d902682f8 feat: 完成账户信息转发 2025-06-21 21:40:51 +08:00
ccnetcore
a81be99100 style: 调整前端样式 2025-06-21 13:34:56 +08:00
ccnetcore
b6dfe93d2c Merge remote-tracking branch 'origin/ai-hub' into ai-hub 2025-06-21 13:30:21 +08:00
ccnetcore
35aa022984 fix: 优化用户更新,超管问题 2025-06-21 13:30:12 +08:00
ccnetcore
dfe2d4cc37 fix: 优化用户更新,超管问题 2025-06-21 13:29:41 +08:00
ccnetcore
1d16502d32 feat: 完成dto搭建 2025-06-21 13:20:13 +08:00
ccnetcore
25c88187a3 feat: 改造接口 2025-06-21 13:15:14 +08:00
ccnetcore
ac04e846fa feat: 完成ai message、session搭建 2025-06-21 13:02:38 +08:00
ccnetcore
29985e2118 feat: 完成ai网关搭建 2025-06-21 01:41:05 +08:00
ccnetcore
3b74dfd49a feat:完成ai网关搭建 2025-06-21 01:08:14 +08:00
cc
6abcc49ed4 feat: 提交 2025-06-20 18:06:33 +08:00
Gsh
f16e1cd7a6 fix: 关闭打包检查 2025-06-20 01:19:15 +08:00
ccnetcore
4341b8a24b style: 设置前端logo样式 2025-06-20 00:06:10 +08:00
Gsh
a89e11d132 feat: 前端接口代理 2025-06-19 23:45:22 +08:00
ccnetcore
bc91a8cff2 feat: 新增取消功能 2025-06-19 22:24:21 +08:00
ccnetcore
8040010b98 feat: 完成ai接口 2025-06-19 21:24:13 +08:00
ccnetcore
b39f15c798 feat: 新增文件夹 2025-06-19 19:13:43 +08:00
ccnetcore
c3cf49c63e feat: 完成节点改造 2025-06-19 14:17:24 +08:00
ccnetcore
899bd7e316 feat: 完成cicd流水线 2025-06-19 01:02:08 +08:00
Gsh
890727d495 feat: 产品页面搭建 2025-06-18 23:28:27 +08:00
ccnetcore
8a8e69596a feat: 完成ai改造 2025-06-17 23:38:20 +08:00
ccnetcore
58fcc92e4d feat: 完成AzureOpenAI改造 2025-06-17 23:25:55 +08:00
Gsh
0cd795f57a feat: 前端搭建 2025-06-17 22:37:37 +08:00
ccnetcore
4830be6388 feat: 搭建ai 2025-06-16 22:39:09 +08:00
3717 changed files with 60189 additions and 137327 deletions

6
.gitignore vendored
View File

@@ -154,10 +154,6 @@ PublishScripts/
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# 把 Yi.Vben5.Vue3 下的 packages 目录重新放出来
!**/Yi.Vben5.Vue3/packages/
!**/Yi.Vben5.Vue3/packages/**
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
@@ -269,8 +265,6 @@ src/Acme.BookStore.Blazor.Server.Tiered/Logs/*
**/wwwroot/libs/*
public
dist
# 把 Yi.Vben5.Vue3 下的 public 目录重新放出来
!**/Yi.Vben5.Vue3/apps/web-antd/public/
.vscode
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Development.json
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json

View File

@@ -1,18 +1,12 @@
<h1 align="center"><img align="left" height="150px" src="https://ccnetcore.com/prod-api/wwwroot/logo.png"> Yi框架</h1>
<h4 align="center">一套以用户体验出发的.Net8 Web开源框架</h4>
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本前端接入Vben/Ruoyi/Pure Vue</h5>
<h5 align="center">支持Abp.vNext 版本原生版本、Furion版本前端接入Ruoyi/Pure Vue</h5>
<h2 align="center">集大成者,终究轮子</h2>
[![star](https://gitee.com/ccnetcore/yi/badge/star.svg?theme=dark)](https://gitee.com/ccnetcore/Yi)
[![fork](https://gitee.com/ccnetcore/yi/badge/fork.svg?theme=dark)](https://gitee.com/ccnetcore/Yi)
[![license](https://img.shields.io/badge/license-MIT-yellow)](https://gitee.com/ccnetcore/Yi)
本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
[亚洲最佳CDN、边缘和安全解决方案 - Tencent EdgeOne](https://edgeone.ai/zh?from=github)
<img src="readme/edgeone.png"/>
[English](README-en.md) | 简体中文
****
## 🍍 简介:
@@ -39,17 +33,14 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
**分支目录:**
- 分支**main**: 基于Abp.vNext分支默认分支只使用Rbac权限管理后台
- 分支**abp**: 基于Abp.vNext分支完整分支具备超多内置模块
- 分支**Abp**: 基于Abp.vNext分支DDD领域驱动设计,回归开发本质,极度简单,一个后台支持以下多个前端
- Yi.Abp.Net8后端
- Yi.Bbs.Vue3Bbs社区 前端
- Yi.Doc.Md: 开源文档教程
- Yi.Vben5.Vue3Vben ts后台前端
- Yi.Pure.Vue3Pure ts后台前端
- Yi.RuoYi.Vue3RuoYi js后台前端
****
## 🍉 docker 一键启动
@@ -59,7 +50,7 @@ Yi框架-一套与SqlSugar一样爽的.Net8开源框架。
bbs前端`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/yi-bbs.conf:/etc/nginx/conf.d/yi-bbs.conf jiftcc/yi.bbs:last`
> 另外我们提供docker的build操作我们更希望你能通过此种方式二开构建属于自己的镜像,因为上面镜像更新不及时
> 另外我们提供docker的build操作我们更希望你能通过此种方式二开构建属于自己的镜像
****
@@ -67,24 +58,11 @@ bbs前端`docker run -d --name yi.bbs -p 18001:18001 -v /home/Yi/Yi.Bbs.Vue3/
废话少说直接上地址
- Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
Yi社区官网网址Bbs社区正式[ccnetcore.com](https://ccnetcore.com) (已上线,欢迎加入)
DotNet后端主要由SharpDance 意框架官方团队 及 数百名 开源用户 共同维护
Rbac后台演示地址https://ccnetcore.com:1000 用户cc、密码123456
- Vben后台演示地址https://data.ccnetcore.com:2000 用户cc、密码123456
Vben前端主要由【https://gitee.com/vichen2021】贡献维护
- Ruoyi后台演示地址https://data.ccnetcore.com:1000 用户cc、密码123456
Ruoyi前端主要由30多名开源用户共同贡献维护
- Pure后台演示地址https://data.ccnetcore.com:1001 用户cc、密码123456
Pure前端主要由3名开源用户共同贡献维护
Pure后台演示地址https://ccnetcore.com:1001 用户cc、密码123456
## 🍏 支持:
@@ -134,13 +112,8 @@ Pure前端主要由3名开源用户共同贡献维护
- [SqlSugar官网](https://www.donet5.com/home/doc)
## 🍅 内置模块简介
- rbac 权限管理系统已上线支持vben、pure、ruoyi前端
- bbs 论坛社区系统(已上线)
- ai-stock Ai模拟炒股系统已上线
- chat-hub Ai在线聊天室系统已上线
- code-gen 代码生成器工具系统(已上线)
- digital-collectibles 数字藏品小程序系统(已上线)
- Rbac权限管理系统已上线支持pure、ruoyi前端
- Bbs论坛社区系统已上线
> 重复的东西,无需再写一遍,这也是优雅的体现之一
@@ -161,6 +134,14 @@ C# Asp.NetCore 8.0
- [x] 分布式缓存Abp.vNext
- [x] 事件总线Abp.vNext
#### 前端
js Vue3
- [x] 异步请求axios
- [x] 图表echarts
- [x] uielement-plus
- [x] 存储pinia
- [x] 路由vue-router
- [x] 打包vite
#### 运维
- [x] 部署nginx
@@ -187,16 +168,24 @@ C# Asp.NetCore 8.0
- 缓存列表
- 服务监控
#### 🍐 BBS社区论坛系统持续更新
采用vue3前端
- 文章功能
- 板块功能
- 主题功能
- 个人中心
- 授权中心
- 权限管理
#### 🍉 演示截图:
<table>
<tr>
<td><img src="readme/303.png"/></td>
<td><img src="readme/304.png"/></td>
<td><img src="readme/101.png"/></td>
<td><img src="readme/102.png"/></td>
</tr>
<tr>
<td><img src="readme/301.png"/></td>
<td><img src="readme/302.png"/></td>
<td><img src="readme/103.png"/></td>
<td><img src="readme/104.png"/></td>
</tr>
</table>
@@ -267,8 +256,6 @@ C# Asp.NetCore 8.0
[Furion百小僧]https://furion.baiqian.ltd/
[du白]https://gitee.com/vichen2021
****
## 🌽 联系我们:
@@ -276,7 +263,7 @@ C# Asp.NetCore 8.0
QQ交流群官方一群已满、官方二群已满、官方三群`786308927`(已满)、官方四群:`498310311`(已满)、官方五群:`981136525`
微信交流群:官方微信一群(已满)、官方微信二群(已满)、官方微信三群(已满)、官方微信四群(即满)
微信交流群:官方微信一群(已满)、官方微信二群(已满)、官方微信三群
微信交流群:加作者微信 chengzilaoge520 橙子老哥520备注拉群
@@ -287,12 +274,6 @@ QQ交流群官方一群已满、官方二群已满、官方三群
****
## 🍄 FQA:
如何修改仓库代码请直接pr即可官方团队会第一时间处理目前前端都是pr来的
gitee仓库官方地址https://gitee.com/ccnetcore/Yi
github仓库官方地址https://github.com/ccnetcore/Yi.Abp.Admin
前往官网查看留言区:
前往官网查看留言区
[留言区](https://ccnetcore.com/discuss/1641030787056930818)

View File

@@ -64,6 +64,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Domain.Sh
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.SqlSugarCore", "module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj", "{4503A2F9-139D-4CBC-AF11-689C34F0D77B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bbs", "bbs", "{E902A945-4F41-4E96-A0DA-9F66CDA22261}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain.Shared", "module\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj", "{EB9349E2-256D-41EB-A345-21635A1361B3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Domain", "module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj", "{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application.Contracts", "module\bbs\Yi.Framework.Bbs.Application.Contracts\Yi.Framework.Bbs.Application.Contracts.csproj", "{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.Application", "module\bbs\Yi.Framework.Bbs.Application\Yi.Framework.Bbs.Application.csproj", "{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Bbs.SqlSugarCore", "module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj", "{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{73CCF2C4-B9FD-44AB-8D4B-0A421805B094}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.SqlSugarCore", "module\audit-logging\Yi.Framework.AuditLogging.SqlSugarCore\Yi.Framework.AuditLogging.SqlSugarCore.csproj", "{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}"
@@ -86,8 +98,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManageme
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.TenantManagement.Application.Contracts", "module\tenant-management\Yi.Framework.TenantManagement.Application.Contracts\Yi.Framework.TenantManagement.Application.Contracts.csproj", "{FA735055-CBDD-4EFD-B84B-85810DA1425E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "code-gen", "code-gen", "{4FFE7212-21F2-476D-B628-3C65E6C5075E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application", "module\code-gen\Yi.Framework.CodeGen.Application\Yi.Framework.CodeGen.Application.csproj", "{97EC40D7-DBFA-467A-98CB-221AF27B14F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Application.Contracts", "module\code-gen\Yi.Framework.CodeGen.Application.Contracts\Yi.Framework.CodeGen.Application.Contracts.csproj", "{882BC563-2F75-4B95-AC96-F4BF23F5E69D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain", "module\code-gen\Yi.Framework.CodeGen.Domain\Yi.Framework.CodeGen.Domain.csproj", "{85CB8517-2B80-42D8-B954-081079AC9BA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.Domain.Shared", "module\code-gen\Yi.Framework.CodeGen.Domain.Shared\Yi.Framework.CodeGen.Domain.Shared.csproj", "{EEFF0F05-2709-4151-A8CE-667935CEAE0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Caching.FreeRedis", "framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj", "{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.CodeGen.SqlSugarCore", "module\code-gen\Yi.Framework.CodeGen.SqlSugarCore\Yi.Framework.CodeGen.SqlSugarCore.csproj", "{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client", "client", "{8B27846A-043D-4F2F-8140-5CEC9D1863B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.HttpApi.Client", "client\Yi.Abp.HttpApi.Client\Yi.Abp.HttpApi.Client.csproj", "{6B554DCC-3A81-4624-9141-4E39365ADA35}"
@@ -102,6 +126,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagem
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.SettingManagement.SqlSugarCore", "module\setting-management\Yi.Framework.SettingManagement.SqlSugarCore\Yi.Framework.SettingManagement.SqlSugarCore.csproj", "{495C4643-39D4-46E7-BDC8-237589627BE4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "chat-hub", "chat-hub", "{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application.Contracts", "module\chat-hub\Yi.Framework.ChatHub.Application.Contracts\Yi.Framework.ChatHub.Application.Contracts.csproj", "{65D4D033-5504-44B9-B152-0172ACD64CE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain.Shared", "module\chat-hub\Yi.Framework.ChatHub.Domain.Shared\Yi.Framework.ChatHub.Domain.Shared.csproj", "{DEEC0B15-190C-4464-B469-C45C6563C592}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.SqlSugarCore", "module\chat-hub\Yi.Framework.ChatHub.SqlSugarCore\Yi.Framework.ChatHub.SqlSugarCore.csproj", "{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Domain", "module\chat-hub\Yi.Framework.ChatHub.Domain\Yi.Framework.ChatHub.Domain.csproj", "{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.ChatHub.Application", "module\chat-hub\Yi.Framework.ChatHub.Application\Yi.Framework.ChatHub.Application.csproj", "{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.Rbac.Test", "test\Yi.Framework.Rbac.Test\Yi.Framework.Rbac.Test.csproj", "{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{084CBEEC-5D37-4716-B9C7-D80D6960DFF4}"
@@ -122,10 +158,46 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.SettingManagement.Application", "module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj", "{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "digital-collectibles", "digital-collectibles", "{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application\Yi.Framework.DigitalCollectibles.Application.csproj", "{236B88D4-F018-4A5F-A506-7458F2308C70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Application.Contracts", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Application.Contracts\Yi.Framework.DigitalCollectibles.Application.Contracts.csproj", "{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain\Yi.Framework.DigitalCollectibles.Domain.csproj", "{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.Domain.Shared", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.Domain.Shared\Yi.Framework.DigitalCollectibles.Domain.Shared.csproj", "{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.DigitalCollectibles.SqlSugarCore", "module\digital-collectibles\Yi.Framework.DigitalCollectibles.SqlSugarCore\Yi.Framework.DigitalCollectibles.SqlSugarCore.csproj", "{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.WeChat.MiniProgram", "framework\Yi.Framework.WeChat.MiniProgram\Yi.Framework.WeChat.MiniProgram.csproj", "{81CEA2ED-917B-41D8-BE0D-39A785B050C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.BackgroundWorkers.Hangfire", "framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj", "{862CA181-BEE6-4870-82D2-B662E527ED8C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai-stock", "ai-stock", "{DB46873F-981A-43D8-91B0-D464CCB65943}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application", "module\ai-stock\Yi.Framework.Stock.Application\Yi.Framework.Stock.Application.csproj", "{B79CE23C-10F8-48A5-A039-5940A188CF5A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Application.Contracts", "module\ai-stock\Yi.Framework.Stock.Application.Contracts\Yi.Framework.Stock.Application.Contracts.csproj", "{846B781A-B77E-4F86-A31F-0B5B57AB0775}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain", "module\ai-stock\Yi.Framework.Stock.Domain\Yi.Framework.Stock.Domain.csproj", "{162821E4-8FE0-4A68-B3C0-49BD6596446F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain.Shared", "module\ai-stock\Yi.Framework.Stock.Domain.Shared\Yi.Framework.Stock.Domain.Shared.csproj", "{10273544-715D-4BB3-893C-6F010D947BDD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.SqlSugarCore", "module\ai-stock\Yi.Framework.Stock.SqlSugarCore\Yi.Framework.Stock.SqlSugarCore.csproj", "{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ai-hub", "ai-hub", "{7AD5DBAE-44F9-474B-8F7B-837EDE908934}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Application", "module\ai-hub\Yi.Framework.AiHub.Application\Yi.Framework.AiHub.Application.csproj", "{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Application.Contracts", "module\ai-hub\Yi.Framework.AiHub.Application.Contracts\Yi.Framework.AiHub.Application.Contracts.csproj", "{123D1C81-D667-4060-8E85-FFE7FB4584AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Domain", "module\ai-hub\Yi.Framework.AiHub.Domain\Yi.Framework.AiHub.Domain.csproj", "{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.Domain.Shared", "module\ai-hub\Yi.Framework.AiHub.Domain.Shared\Yi.Framework.AiHub.Domain.Shared.csproj", "{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.AiHub.SqlSugarCore", "module\ai-hub\Yi.Framework.AiHub.SqlSugarCore\Yi.Framework.AiHub.SqlSugarCore.csproj", "{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -204,6 +276,26 @@ Global
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4503A2F9-139D-4CBC-AF11-689C34F0D77B}.Release|Any CPU.Build.0 = Release|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB9349E2-256D-41EB-A345-21635A1361B3}.Release|Any CPU.Build.0 = Release|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7}.Release|Any CPU.Build.0 = Release|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20}.Release|Any CPU.Build.0 = Release|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B}.Release|Any CPU.Build.0 = Release|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA}.Release|Any CPU.Build.0 = Release|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -240,10 +332,30 @@ Global
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA735055-CBDD-4EFD-B84B-85810DA1425E}.Release|Any CPU.Build.0 = Release|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97EC40D7-DBFA-467A-98CB-221AF27B14F2}.Release|Any CPU.Build.0 = Release|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{882BC563-2F75-4B95-AC96-F4BF23F5E69D}.Release|Any CPU.Build.0 = Release|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85CB8517-2B80-42D8-B954-081079AC9BA0}.Release|Any CPU.Build.0 = Release|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEFF0F05-2709-4151-A8CE-667935CEAE0B}.Release|Any CPU.Build.0 = Release|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3}.Release|Any CPU.Build.0 = Release|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04}.Release|Any CPU.Build.0 = Release|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B554DCC-3A81-4624-9141-4E39365ADA35}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -264,6 +376,26 @@ Global
{495C4643-39D4-46E7-BDC8-237589627BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{495C4643-39D4-46E7-BDC8-237589627BE4}.Release|Any CPU.Build.0 = Release|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65D4D033-5504-44B9-B152-0172ACD64CE6}.Release|Any CPU.Build.0 = Release|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEEC0B15-190C-4464-B469-C45C6563C592}.Release|Any CPU.Build.0 = Release|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E}.Release|Any CPU.Build.0 = Release|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47}.Release|Any CPU.Build.0 = Release|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D}.Release|Any CPU.Build.0 = Release|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -300,6 +432,26 @@ Global
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{236B88D4-F018-4A5F-A506-7458F2308C70}.Release|Any CPU.Build.0 = Release|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B}.Release|Any CPU.Build.0 = Release|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C}.Release|Any CPU.Build.0 = Release|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061}.Release|Any CPU.Build.0 = Release|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F}.Release|Any CPU.Build.0 = Release|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81CEA2ED-917B-41D8-BE0D-39A785B050C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -308,6 +460,46 @@ Global
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.Build.0 = Release|Any CPU
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B79CE23C-10F8-48A5-A039-5940A188CF5A}.Release|Any CPU.Build.0 = Release|Any CPU
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Debug|Any CPU.Build.0 = Debug|Any CPU
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Release|Any CPU.ActiveCfg = Release|Any CPU
{846B781A-B77E-4F86-A31F-0B5B57AB0775}.Release|Any CPU.Build.0 = Release|Any CPU
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{162821E4-8FE0-4A68-B3C0-49BD6596446F}.Release|Any CPU.Build.0 = Release|Any CPU
{10273544-715D-4BB3-893C-6F010D947BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10273544-715D-4BB3-893C-6F010D947BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10273544-715D-4BB3-893C-6F010D947BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10273544-715D-4BB3-893C-6F010D947BDD}.Release|Any CPU.Build.0 = Release|Any CPU
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983}.Release|Any CPU.Build.0 = Release|Any CPU
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895}.Release|Any CPU.Build.0 = Release|Any CPU
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{123D1C81-D667-4060-8E85-FFE7FB4584AD}.Release|Any CPU.Build.0 = Release|Any CPU
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D}.Release|Any CPU.Build.0 = Release|Any CPU
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48}.Release|Any CPU.Build.0 = Release|Any CPU
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -332,6 +524,12 @@ Global
{C04D3F71-1557-46D0-B810-97B1FBB6AB73} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{A2BB899D-4F9A-4184-81BD-94B938E2AB03} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{4503A2F9-139D-4CBC-AF11-689C34F0D77B} = {9CC7A457-1236-40BA-B47B-E7B710A3F061}
{E902A945-4F41-4E96-A0DA-9F66CDA22261} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{EB9349E2-256D-41EB-A345-21635A1361B3} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{4EABBC84-BCED-46C1-8CF1-62A7B8081ED7} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{7E569FD9-B1AB-4848-8AB7-FD9EFA1DBA20} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{AD4EE9E6-F4A3-4139-AF05-71388167DE5B} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{6C86BA71-9F87-4E2C-B467-2950D77DCDFA} = {E902A945-4F41-4E96-A0DA-9F66CDA22261}
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
@@ -343,13 +541,25 @@ Global
{9C8C3C53-3DCE-4516-867E-228858E61B26} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
{17816837-E53B-486B-B796-53C601FE6CD9} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
{FA735055-CBDD-4EFD-B84B-85810DA1425E} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
{4FFE7212-21F2-476D-B628-3C65E6C5075E} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{97EC40D7-DBFA-467A-98CB-221AF27B14F2} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{882BC563-2F75-4B95-AC96-F4BF23F5E69D} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{85CB8517-2B80-42D8-B954-081079AC9BA0} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{EEFF0F05-2709-4151-A8CE-667935CEAE0B} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{862BB0EF-3D4E-44FF-AB15-0EB74CE553D3} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{FB09ACC2-A27D-4D87-8D85-1435FDED4D04} = {4FFE7212-21F2-476D-B628-3C65E6C5075E}
{6B554DCC-3A81-4624-9141-4E39365ADA35} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{2D23B44A-DFA3-4C36-8516-4F5AE442403C} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{00E49781-C6A0-491C-86A1-46F685C90915} = {8B27846A-043D-4F2F-8140-5CEC9D1863B5}
{8C68059E-F3B1-4D28-A1C9-A5830F53E5D3} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{6FEE0EB3-EAD2-47F8-B6FC-3D0FD3CCABFF} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{495C4643-39D4-46E7-BDC8-237589627BE4} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{65D4D033-5504-44B9-B152-0172ACD64CE6} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{DEEC0B15-190C-4464-B469-C45C6563C592} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{E476D266-8FB2-4D6B-AE2B-F0D279D4264E} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{C2DCA2FD-BFB4-4E76-967B-0AF8CC4F4D47} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{B7A1A8F3-CFA6-4ECF-A707-0F33FE0A6F1D} = {D8CDDE99-3684-4EED-A5E5-87F2AF4C78AB}
{9ECF0841-53BE-4FD8-95D1-A7223C7F3A07} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
{4FEBBDD9-E4F4-4BAF-8599-E2D57C08A74F} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{2CE51D4C-1EF9-462B-BA14-7EA01A7E4AF1} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
@@ -359,8 +569,26 @@ Global
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{B8F76A6B-2EEB-4E64-9F26-D84584E16B9C} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{236B88D4-F018-4A5F-A506-7458F2308C70} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{4FE7AC0E-91CC-4DF1-ACA7-ED83483C3F3B} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{9B5CAE1A-E062-4C9B-8121-E58FBF69309C} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{FFEC9DA6-1A13-480A-AE9E-2BF8763D3061} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{4CE6E4AE-0BA4-4984-A4F1-A9A414B1BB8F} = {B8F76A6B-2EEB-4E64-9F26-D84584E16B9C}
{81CEA2ED-917B-41D8-BE0D-39A785B050C0} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{DB46873F-981A-43D8-91B0-D464CCB65943} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{B79CE23C-10F8-48A5-A039-5940A188CF5A} = {DB46873F-981A-43D8-91B0-D464CCB65943}
{846B781A-B77E-4F86-A31F-0B5B57AB0775} = {DB46873F-981A-43D8-91B0-D464CCB65943}
{162821E4-8FE0-4A68-B3C0-49BD6596446F} = {DB46873F-981A-43D8-91B0-D464CCB65943}
{10273544-715D-4BB3-893C-6F010D947BDD} = {DB46873F-981A-43D8-91B0-D464CCB65943}
{5F49318F-E6C7-4194-BAE0-83D4FB8D1983} = {DB46873F-981A-43D8-91B0-D464CCB65943}
{7AD5DBAE-44F9-474B-8F7B-837EDE908934} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{1AD10DD2-535E-4EAB-A8A4-EC3FCA206895} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
{123D1C81-D667-4060-8E85-FFE7FB4584AD} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
{8EB4C8BB-6B21-4811-9FAB-B98FA5CA754D} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
{5FC6CA90-D5B4-433E-9B2C-94330FFB4C48} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
{8698C812-4DDC-4E80-BCD6-24C5D56AEDB1} = {7AD5DBAE-44F9-474B-8F7B-837EDE908934}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}

View File

@@ -12,7 +12,6 @@
</PropertyGroup>
<PropertyGroup>
<SatelliteResourceLanguages>en;zh-CN</SatelliteResourceLanguages>
<LangVersion>latest</LangVersion>
<Version>1.0.0</Version>
<NoWarn>$(NoWarn);CS1591;CS8618;CS1998;CS8604;CS8620;CS8600;CS8602</NoWarn>

View File

@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Core.Authentication;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Authentication;
/// <summary>
/// 可刷新的鉴权提供者
/// </summary>
public class RefreshAuthenticationHandlerProvider : IRefreshAuthenticationHandlerProvider
{
private Dictionary<string, IAuthenticationHandler> _handlerMap =
new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>)StringComparer.Ordinal);
/// <summary>Constructor.</summary>
/// <param name="schemes">The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.</param>
public RefreshAuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
{
this.Schemes = schemes;
}
/// <summary>
/// The <see cref="T:Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider" />.
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; }
/// <summary>Returns the handler instance that will be used.</summary>
/// <param name="context">The context.</param>
/// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
/// <returns>The handler instance.</returns>
public async Task<IAuthenticationHandler?> GetHandlerAsync(
HttpContext context,
string authenticationScheme)
{
IAuthenticationHandler handlerAsync;
if (this._handlerMap.TryGetValue(authenticationScheme, out handlerAsync))
return handlerAsync;
AuthenticationScheme schemeAsync = await this.Schemes.GetSchemeAsync(authenticationScheme);
if (schemeAsync == null)
return (IAuthenticationHandler)null;
if ((context.RequestServices.GetService(schemeAsync.HandlerType) ??
ActivatorUtilities.CreateInstance(context.RequestServices, schemeAsync.HandlerType)) is
IAuthenticationHandler handler)
{
handlerAsync = handler;
await handler.InitializeAsync(schemeAsync, context);
this._handlerMap[authenticationScheme] = handler;
}
return handlerAsync;
}
/// <summary>
/// 刷新鉴权
/// </summary>
public void RefreshAuthentication()
{
_handlerMap = new Dictionary<string, IAuthenticationHandler>((IEqualityComparer<string>)StringComparer.Ordinal);
}
}

View File

@@ -20,12 +20,12 @@ namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
/// <returns>异步任务</returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// // 在响应开始时处理文件下载相关的响应头
// context.Response.OnStarting(() =>
// {
// HandleFileDownloadResponse(context);
// return Task.CompletedTask;
// });
// 在响应开始时处理文件下载相关的响应头
context.Response.OnStarting(() =>
{
HandleFileDownloadResponse(context);
return Task.CompletedTask;
});
// 继续处理管道中的下一个中间件
await next(context);

View File

@@ -1,17 +1,12 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{

View File

@@ -1,4 +1,4 @@
// MIT 许可证
// MIT 许可证
//
// 版权 © 2020-present 百小僧, 百签科技(广东)有限公司 和所有贡献者
//
@@ -17,25 +17,25 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Validation;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Core.Extensions;
namespace Yi.Framework.AspNetCore.UnifyResult.Fiters;
/// <summary>
/// 友好异常拦截器
/// 友好异常拦截器
/// </summary>
public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
{
/// <summary>
/// 异常拦截
/// 异常拦截
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
// 排除 WebSocket 请求处理
if (context.HttpContext.IsWebSocketRequest()) return;
@@ -44,23 +44,20 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
// 解析异常信息
var exceptionMetadata = GetExceptionMetadata(context);
var unifyResult = context.GetRequiredService<IUnifyResultProvider>();
IUnifyResultProvider unifyResult = context.GetRequiredService<IUnifyResultProvider>();
// 执行规范化异常处理
context.Result = unifyResult.OnException(context, exceptionMetadata);
// 创建日志记录器
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<FriendlyExceptionFilter>>();
var errorMsg = "";
if (exceptionMetadata.Errors != null) errorMsg = "\n" + JsonConvert.SerializeObject(exceptionMetadata.Errors);
// 记录拦截日常
logger.LogError(context.Exception, context.Exception.Message + errorMsg);
logger.LogError(context.Exception, context.Exception.Message);
}
/// <summary>
/// 获取异常元数据
/// 获取异常元数据
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
@@ -77,25 +74,22 @@ public sealed class FriendlyExceptionFilter : IAsyncExceptionFilter
// 判断是否是 ExceptionContext 或者 ActionExecutedContext
var exception = context is ExceptionContext exContext
? exContext.Exception
: context is ActionExecutedContext edContext
? edContext.Exception
: default;
if (exception is AbpValidationException validationException)
{
errors = validationException.ValidationErrors;
isValidationException = true;
}
: (
context is ActionExecutedContext edContext
? edContext.Exception
: default
);
// 判断是否是友好异常
if (exception is UserFriendlyException friendlyException)
{
var statusCode2 = 500;
int statusCode2 = 500;
int.TryParse(friendlyException.Code, out statusCode2);
isFriendlyException = true;
errorCode = friendlyException.Code;
originErrorCode = friendlyException.Code;
statusCode = statusCode2 == 0 ? 403 : statusCode2;
statusCode = statusCode2==0?403:statusCode2;
isValidationException = false;
errors = friendlyException.Message;
data = friendlyException.Data;
}

View File

@@ -2,7 +2,6 @@
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Response;
using Yi.Framework.AspNetCore.UnifyResult.Fiters;
namespace Yi.Framework.AspNetCore.UnifyResult;
@@ -21,10 +20,9 @@ public static class UnifyResultExtensions
services.AddTransient<FriendlyExceptionFilter>();
services.AddMvc(options =>
{
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpNoContentActionFilter));
options.Filters.AddService<SucceededUnifyResultFilter>(99);
options.Filters.AddService<FriendlyExceptionFilter>(100);
options.Filters.RemoveAll(x => (x as ServiceFilterAttribute)?.ServiceType == typeof(AbpExceptionFilter));
});
return services;
}

View File

@@ -1,21 +1,10 @@
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Yi.Framework.AspNetCore.Mvc;
using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Authentication;
using Yi.Framework.Core;
using Yi.Framework.Core.Authentication;
namespace Yi.Framework.AspNetCore
{
@@ -35,8 +24,14 @@ namespace Yi.Framework.AspNetCore
// 替换默认的WebClientInfoProvider为支持代理的实现
services.Replace(new ServiceDescriptor(
typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider),
ServiceLifetime.Transient));
// 替换默认的AuthenticationHandlerProvider为支持刷新鉴权
services.Replace(new ServiceDescriptor(
typeof(IAuthenticationHandlerProvider),
typeof(RefreshAuthenticationHandlerProvider),
ServiceLifetime.Scoped));
}
}
}

View File

@@ -2,6 +2,7 @@
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
@@ -32,13 +33,19 @@ public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
/// <param name="context">应用程序初始化上下文</param>
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
if (!context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value.IsEnabled)
{
return;
}
// 获取后台任务管理器和所有 Hangfire 后台任务
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var workers = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
// 获取配置
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
// 检查是否启用 Redis
var isRedisEnabled = configuration.GetValue<bool>("Redis:IsEnabled");
@@ -56,11 +63,11 @@ public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{
// 内存模式:直接使用 Hangfire
var unProxyWorker = ProxyHelper.UnProxy(worker);
// 添加或更新循环任务
RecurringJob.AddOrUpdate(
worker.RecurringJobId,
(Expression<Func<Task>>)(() =>
(Expression<Func<Task>>)(() =>
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)),
worker.CronExpression,
new RecurringJobOptions

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Yi.Framework.Core.Authentication;
public static class AuthenticationExtensions
{
public static void RefreshAuthentication(this HttpContext context)
{
var currentAuthenticationHandler =
context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
if (currentAuthenticationHandler is IRefreshAuthenticationHandlerProvider refreshAuthenticationHandler)
{
refreshAuthenticationHandler.RefreshAuthentication();
}
}
}

View File

@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Authentication;
namespace Yi.Framework.Core.Authentication;
public interface IRefreshAuthenticationHandlerProvider: IAuthenticationHandlerProvider
{
/// <summary>
/// 刷新鉴权
/// </summary>
void RefreshAuthentication();
}

View File

@@ -185,13 +185,15 @@ namespace Yi.Framework.Ddd.Application
/// </summary>
/// <param name="keywords">查询关键字</param>
/// <returns></returns>
public virtual async Task<List<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
public virtual async Task<PagedResultDto<TGetListOutputDto>> GetSelectDataListAsync(string? keywords = null)
{
List<TEntity> entities = await Repository.GetListAsync();
// 获取总数并映射结果
var totalCount = entities.Count;
var dtos = await MapToGetListOutputDtosAsync(entities);
return dtos;
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
}
/// <summary>

View File

@@ -1,8 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Yi.Framework.Core;
using Mapster;
namespace Yi.Framework.Mapster
{
@@ -23,8 +22,7 @@ namespace Yi.Framework.Mapster
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
// 扫描并注册所有映射配置
TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
// 注册Mapster相关服务
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
services.AddTransient<IObjectMapper, MapsterObjectMapper>();

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="1.57.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Yi.Framework.Core.Options;
namespace Yi.Framework.SemanticKernel;
public class YiFrameworkSemanticKernelModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var services = context.Services;
}
}

View File

@@ -60,8 +60,8 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
public bool EnabledSaasMultiTenancy { get; set; } = false;
/// <summary>
/// 是否开启更新并发乐观锁
/// 并发乐观锁异常,否则不处理
/// </summary>
public bool EnabledConcurrencyException { get;set; } = false;
public bool EnabledConcurrencyException { get; set; } = true;
}
}

View File

@@ -1,7 +1,12 @@
using System.Linq.Expressions;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@@ -12,19 +17,18 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore.Repositories
{
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity> where TEntity : class, IEntity, new()
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity>
where TEntity : class, IEntity, new()
{
[Obsolete("使用GetDbContextAsync()")]
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
[Obsolete("使用AsQueryable()")]
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 异步查询执行器
/// </summary>
@@ -60,22 +64,26 @@ namespace Yi.Framework.SqlSugarCore.Repositories
#region Abp模块
public virtual async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
public virtual async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true, CancellationToken cancellationToken = default)
{
return await GetFirstAsync(predicate);
}
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true, CancellationToken cancellationToken = default)
{
return await GetFirstAsync(predicate);
}
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await this.DeleteAsync(predicate);
}
public virtual async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
public virtual async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate,
CancellationToken cancellationToken = default)
{
await this.DeleteAsync(predicate);
}
@@ -105,60 +113,71 @@ namespace Yi.Framework.SqlSugarCore.Repositories
throw new NotImplementedException();
}
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate,
bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetListAsync(predicate);
}
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
return await InsertReturnEntityAsync(entity);
}
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await InsertRangeAsync(entities.ToList());
}
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await UpdateAsync(entity);
return entity;
}
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await UpdateRangeAsync(entities.ToList());
}
public virtual async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task DeleteAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteAsync(entity);
}
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteAsync(entities.ToList());
}
public virtual async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
public virtual async Task<List<TEntity>> GetListAsync(bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetListAsync();
}
public virtual async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{
return await this.CountAsync(_=>true);
return await this.CountAsync(_ => true);
}
public virtual async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default)
public virtual async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting,
bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetPageListAsync(_ => true, skipCount, maxResultCount);
}
#endregion
#region DB快捷操作
public virtual async Task<IDeleteable<TEntity>> AsDeleteable()
{
return (await GetDbSimpleClientAsync()).AsDeleteable();
@@ -173,7 +192,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObj);
}
public virtual async Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs)
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObjs);
@@ -213,9 +232,11 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return (await GetDbSimpleClientAsync()).AsUpdateable(updateObjs);
}
#endregion
#region SimpleClient模块
public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).CountAsync(whereExpression);
@@ -232,7 +253,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return await (await GetDbSimpleClientAsync()).DeleteAsync(deleteObj);
}
}
public virtual async Task<bool> DeleteAsync(List<TEntity> deleteObjs)
@@ -252,13 +272,13 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
return await (await GetDbSimpleClientAsync()).AsUpdateable()
.SetColumns(nameof(ISoftDelete.IsDeleted), true).Where(whereExpression).ExecuteCommandAsync() > 0;
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteAsync(whereExpression);
}
}
public virtual async Task<bool> DeleteByIdAsync(dynamic id)
@@ -266,7 +286,11 @@ namespace Yi.Framework.SqlSugarCore.Repositories
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
var entity = await GetByIdAsync(id);
if (entity == null) return false;
if (entity is null)
{
return false;
}
//反射赋值
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
return await UpdateAsync(entity);
@@ -287,6 +311,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return false;
}
//反射赋值
entities.ForEach(e => ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, e));
return await UpdateRangeAsync(entities);
@@ -295,7 +320,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
{
return await (await GetDbSimpleClientAsync()).DeleteByIdAsync(ids);
}
}
public virtual async Task<TEntity> GetByIdAsync(dynamic id)
@@ -304,7 +328,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
}
public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).GetFirstAsync(whereExpression);
@@ -320,14 +343,19 @@ namespace Yi.Framework.SqlSugarCore.Repositories
return await (await GetDbSimpleClientAsync()).GetListAsync(whereExpression);
}
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize)
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression,
int pageNum, int pageSize)
{
return await (await AsQueryable()).Where(whereExpression).ToPageListAsync(pageNum, pageSize);
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
new PageModel() { PageIndex = pageNum, PageSize = pageSize });
}
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression,
int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null,
OrderByType orderByType = OrderByType.Asc)
{
return await (await AsQueryable()).Where(whereExpression) .OrderBy( orderByExpression,orderByType).ToPageListAsync(pageNum, pageSize);
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
}
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
@@ -382,9 +410,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
{
if (Options is not null && Options.EnabledConcurrencyException)
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
{
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
if (Options is not null && Options.EnabledConcurrencyException)
{
try
{
@@ -398,18 +426,24 @@ namespace Yi.Framework.SqlSugarCore.Repositories
$"{ex.Message}[更新失败ConcurrencyStamp不是最新版本],entityInfo{updateObj}", ex);
}
}
else
{
int num = await (await GetDbSimpleClientAsync())
.Context.Updateable(updateObj).ExecuteCommandAsync();
return num > 0;
}
}
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
}
public virtual async Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression)
public virtual async Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns,
Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).UpdateAsync(columns, whereExpression);
}
public virtual async Task<bool> UpdateRangeAsync(List<TEntity> updateObjs)
{
return await (await GetDbSimpleClientAsync()).UpdateRangeAsync(updateObjs);
@@ -418,30 +452,36 @@ namespace Yi.Framework.SqlSugarCore.Repositories
#endregion
}
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>, IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(sugarDbContextProvider)
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider) : base(
dbContextProvider)
{
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task DeleteAsync(TKey id, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteByIdAsync(id);
}
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteByIdsAsync(ids.Select(x => (object)x).ToArray());
}
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetByIdAsync(id);
}
public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetByIdAsync(id);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using Yi.Framework.Rbac.Domain.Shared.Dtos;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class AiUserRoleMenuDto:UserRoleMenuDto
{
/// <summary>
/// 是否绑定服务号
/// </summary>
public bool IsBindFuwuhao { get; set; }
/// <summary>
/// 是否为VIP用户
/// </summary>
public bool IsVip { get; set; }
/// <summary>
/// VIP到期时间
/// </summary>
public DateTime? VipExpireTime { get; set; }
/// <summary>
/// 尊享包总Token数
/// </summary>
public long PremiumTotalTokens { get; set; }
/// <summary>
/// 尊享包已使用Token数
/// </summary>
public long PremiumUsedTokens { get; set; }
/// <summary>
/// 尊享包剩余Token数
/// </summary>
public long PremiumRemainingTokens { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.FileMaster;
public class VerifyNextInput
{
/// <summary>
/// 文件数
/// </summary>
public int FileCount { get; set; }
/// <summary>
/// 文件夹数
/// </summary>
public int DirectoryCount { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Fuwuhao;
public class QrCodeOutput
{
/// <summary>
/// Qrcode url
/// </summary>
public string QrCodeUrl { get; set; }
/// <summary>
/// 场景值
/// </summary>
public string Scene { get; set; }
}

View File

@@ -0,0 +1,21 @@
using Yi.Framework.AiHub.Domain.Shared.Enums.Fuwuhao;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Fuwuhao;
public class QrCodeResultOutput
{
/// <summary>
/// 返回状态
/// </summary>
public SceneResultEnum SceneResult { get; set; } = SceneResultEnum.Wait;
/// <summary>
/// 如果是已登录返回token
/// </summary>
public string? Token { get; set; }
/// <summary>
/// 刷新token
/// </summary>
public string? RefreshToken { get; set; }
}

View File

@@ -0,0 +1,16 @@
using Yi.Framework.AiHub.Domain.Shared.Enums.Fuwuhao;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Fuwuhao;
public class SceneCacheDto
{
public SceneResultEnum SceneResult { get; set; } = SceneResultEnum.Wait;
public SceneTypeEnum SceneType { get; set; }
/// <summary>
/// 如果是绑定类型需要用户id
/// </summary>
public Guid? UserId { get; set; }
}

View File

@@ -0,0 +1,13 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class MessageDto : FullAuditedEntityDto<Guid>
{
public Guid UserId { get; set; }
public Guid SessionId { get; set; }
public string Content { get; set; }
public string Role { get; set; }
public string ModelId { get; set; }
public string Remark { get; set; }
}

View File

@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
using Yi.Framework.Ddd.Application.Contracts;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class MessageGetListInput:PagedAllResultRequestDto
{
[Required]
public Guid SessionId { get; set; }
}

View File

@@ -0,0 +1,65 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class ModelGetListOutput
{
/// <summary>
/// 模型ID
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// 模型分类
/// </summary>
public string Category { get; set; }
/// <summary>
/// 模型id
/// </summary>
public string ModelId { get; set; }
/// <summary>
/// 模型名称
/// </summary>
public string ModelName { get; set; }
/// <summary>
/// 模型描述
/// </summary>
public string? ModelDescribe { get; set; }
/// <summary>
/// 模型价格
/// </summary>
public double ModelPrice { get; set; }
/// <summary>
/// 模型类型
/// </summary>
public string ModelType { get; set; }
/// <summary>
/// 模型展示状态
/// </summary>
public string ModelShow { get; set; }
/// <summary>
/// 系统提示
/// </summary>
public string SystemPrompt { get; set; }
/// <summary>
/// API 主机地址
/// </summary>
public string ApiHost { get; set; }
/// <summary>
/// API 密钥
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// 备注信息
/// </summary>
public string? Remark { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
/// <summary>
/// 创建订单输入DTO
/// </summary>
public class CreateOrderInput
{
/// <summary>
/// 商品类型
/// </summary>
[Required]
public GoodsTypeEnum GoodsType { get; set; }
public string? ReturnUrl{ get; set; }
}

View File

@@ -0,0 +1,22 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
/// <summary>
/// 创建订单输出DTO
/// </summary>
public class CreateOrderOutput
{
/// <summary>
/// 订单ID
/// </summary>
public Guid OrderId { get; set; }
/// <summary>
/// 商家订单号
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 支付页面HTML内容
/// </summary>
public object PaymentPageHtml { get; set; }
}

View File

@@ -0,0 +1,44 @@
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
/// <summary>
/// 商品列表输出DTO
/// </summary>
public class GoodsListOutput
{
/// <summary>
/// 商品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 商品原价
/// </summary>
public decimal OriginalPrice { get; set; }
/// <summary>
/// 商品实际价格(折扣后的价格)
/// </summary>
public decimal GoodsPrice { get; set; }
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType { get; set; }
/// <summary>
/// 商品备注
/// </summary>
public string Remark { get; set; }
/// <summary>
/// 折扣金额(仅尊享包)
/// </summary>
public decimal? DiscountAmount { get; set; }
/// <summary>
/// 折扣说明(仅尊享包)
/// </summary>
public string? DiscountDescription { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
/// <summary>
/// 查询订单状态输入DTO
/// </summary>
public class QueryOrderStatusInput
{
/// <summary>
/// 商家订单号
/// </summary>
public string OutTradeNo { get; set; }
}

View File

@@ -0,0 +1,59 @@
using Yi.Framework.AiHub.Domain.Shared.Enums;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
/// <summary>
/// 查询订单状态输出DTO
/// </summary>
public class QueryOrderStatusOutput
{
/// <summary>
/// 订单ID
/// </summary>
public Guid OrderId { get; set; }
/// <summary>
/// 商家订单号
/// </summary>
public string OutTradeNo { get; set; }
/// <summary>
/// 支付宝交易号
/// </summary>
public string? TradeNo { get; set; }
/// <summary>
/// 交易状态
/// </summary>
public TradeStatusEnum TradeStatus { get; set; }
/// <summary>
/// 交易状态描述
/// </summary>
public string TradeStatusDescription { get; set; }
/// <summary>
/// 订单金额
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
public DateTime? LastModificationTime { get; set; }
}

View File

@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
public class RechargeCreateInput
{
/// <summary>
/// 用户ID
/// </summary>
[Required]
public Guid UserId { get; set; }
/// <summary>
/// 充值金额
/// </summary>
[Required]
[Range(0.01, double.MaxValue, ErrorMessage = "充值金额必须大于0")]
public decimal RechargeAmount { get; set; }
/// <summary>
/// 充值内容
/// </summary>
[Required]
[StringLength(500, ErrorMessage = "充值内容不能超过500个字符")]
public string Content { get; set; } = string.Empty;
/// <summary>
/// VIP月数为空或0表示永久VIP
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "月数必须大于等于0")]
public int? Months { get; set; }
/// <summary>
/// 备注
/// </summary>
[StringLength(1000, ErrorMessage = "备注不能超过1000个字符")]
public string? Remark { get; set; }
/// <summary>
/// 联系方式
/// </summary>
[StringLength(200, ErrorMessage = "联系方式不能超过200个字符")]
public string? ContactInfo { get; set; }
}

View File

@@ -0,0 +1,45 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
public class RechargeGetListOutput
{
/// <summary>
/// 充值金额
/// </summary>
public decimal RechargeAmount { get; set; }
/// <summary>
/// ID
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// 用户
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 充值内容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 到期时间
/// </summary>
public DateTime? ExpireDateTime { get; set; }
/// <summary>
/// 联系方式
/// </summary>
public string? ContactInfo { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SendMessageInput
{
public List<Message> Messages { get; set; }
public string Model { get; set; }
public Guid? SessionId{ get; set; }
}
public class Message
{
public string Role { get; set; }
public string Content { get; set; }
}

View File

@@ -0,0 +1,164 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SendMessageStreamOutputDto
{
/// <summary>
/// 唯一标识符
/// </summary>
public string Id { get; set; }
/// <summary>
/// 对象类型
/// </summary>
public string Object { get; set; }
/// <summary>
/// 创建时间Unix时间戳格式
/// </summary>
public long Created { get; set; }
/// <summary>
/// 模型名称
/// </summary>
public string Model { get; set; }
/// <summary>
/// 选择项列表
/// </summary>
public List<Choice> Choices { get; set; }
/// <summary>
/// 系统指纹(可能为空)
/// </summary>
public string SystemFingerprint { get; set; }
/// <summary>
/// 使用情况信息
/// </summary>
public Usage Usage { get; set; }
}
/// <summary>
/// 选择项类,表示模型返回的一个选择
/// </summary>
public class Choice
{
/// <summary>
/// 选择索引
/// </summary>
public int Index { get; set; }
/// <summary>
/// 变化内容,包括内容字符串和角色
/// </summary>
public Delta Delta { get; set; }
/// <summary>
/// 结束原因,可能为空
/// </summary>
public string? FinishReason { get; set; }
/// <summary>
/// 内容过滤结果
/// </summary>
public ContentFilterResults ContentFilterResults { get; set; }
}
/// <summary>
/// 变化内容
/// </summary>
public class Delta
{
/// <summary>
/// 内容文本
/// </summary>
public string Content { get; set; }
/// <summary>
/// 角色,例如"assistant"
/// </summary>
public string Role { get; set; }
}
/// <summary>
/// 内容过滤结果
/// </summary>
public class ContentFilterResults
{
public FilterStatus Hate { get; set; }
public FilterStatus SelfHarm { get; set; }
public FilterStatus Sexual { get; set; }
public FilterStatus Violence { get; set; }
public FilterStatus Jailbreak { get; set; }
public FilterStatus Profanity { get; set; }
}
/// <summary>
/// 过滤状态,表示是否经过过滤以及检测是否命中
/// </summary>
public class FilterStatus
{
/// <summary>
/// 是否被过滤
/// </summary>
public bool Filtered { get; set; }
/// <summary>
/// 是否检测到该类型(例如 Jailbreak 中存在此字段)
/// </summary>
public bool? Detected { get; set; }
}
/// <summary>
/// 使用情况,记录 token 数量等信息
/// </summary>
public class Usage
{
/// <summary>
/// 提示词数量
/// </summary>
public int PromptTokens { get; set; }
/// <summary>
/// 补全词数量
/// </summary>
public int CompletionTokens { get; set; }
/// <summary>
/// 总的 Token 数量
/// </summary>
public int TotalTokens { get; set; }
/// <summary>
/// 提示词详细信息
/// </summary>
public PromptTokensDetails PromptTokensDetails { get; set; }
/// <summary>
/// 补全文字详细信息
/// </summary>
public CompletionTokensDetails CompletionTokensDetails { get; set; }
}
/// <summary>
/// 提示词相关 token 详细信息
/// </summary>
public class PromptTokensDetails
{
public int AudioTokens { get; set; }
public int CachedTokens { get; set; }
}
/// <summary>
/// 补全相关 token 详细信息
/// </summary>
public class CompletionTokensDetails
{
public int AudioTokens { get; set; }
public int ReasoningTokens { get; set; }
public int AcceptedPredictionTokens { get; set; }
public int RejectedPredictionTokens { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SessionCreateAndUpdateInput
{
public string SessionTitle { get; set; }
public string SessionContent { get; set; }
public string? Remark { get; set; }
}

View File

@@ -0,0 +1,10 @@
using Volo.Abp.Application.Dtos;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SessionDto : FullAuditedEntityDto<Guid>
{
public string SessionTitle { get; set; }
public string SessionContent { get; set; }
public string Remark { get; set; }
}

View File

@@ -0,0 +1,8 @@
using Yi.Framework.Ddd.Application.Contracts;
namespace Yi.Framework.AiHub.Application.Contracts.Dtos;
public class SessionGetListInput:PagedAllResultRequestDto
{
public string? SessionTitle { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Token;
public class TokenOutput
{
public string? ApiKey { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
/// <summary>
/// 每日Token使用量统计DTO
/// </summary>
public class DailyTokenUsageDto
{
/// <summary>
/// 日期
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Token消耗量
/// </summary>
public long Tokens { get; set; }
}

View File

@@ -0,0 +1,22 @@
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
/// <summary>
/// 模型Token使用量统计DTO
/// </summary>
public class ModelTokenUsageDto
{
/// <summary>
/// 模型ID
/// </summary>
public string Model { get; set; }
/// <summary>
/// 总消耗量
/// </summary>
public long Tokens { get; set; }
/// <summary>
/// 占比(百分比)
/// </summary>
public decimal Percentage { get; set; }
}

View File

@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
/// <summary>
/// 支付服务接口
/// </summary>
public interface IPayService : IApplicationService
{
/// <summary>
/// 创建订单并发起支付
/// </summary>
/// <param name="input">创建订单输入</param>
/// <returns>订单创建结果</returns>
Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input);
/// <summary>
/// 支付宝异步通知处理
/// </summary>
/// <param name="form">表单数据</param>
/// <returns></returns>
Task<string> AlipayNotifyAsync([FromForm] IFormCollection form);
/// <summary>
/// 查询订单状态
/// </summary>
/// <param name="input">查询订单状态输入</param>
/// <returns>订单状态信息</returns>
Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input);
/// <summary>
/// 获取商品列表
/// </summary>
/// <returns>商品列表</returns>
Task<List<GoodsListOutput>> GetGoodsListAsync();
}

View File

@@ -0,0 +1,18 @@
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
public interface IRechargeService
{
/// <summary>
/// 移除用户vip及角色
/// </summary>
Task RemoveVipRoleByExpireAsync();
/// <summary>
/// 给用户充值VIP
/// </summary>
/// <param name="input">充值输入参数</param>
/// <returns></returns>
Task RechargeVipAsync(RechargeCreateInput input);
}

View File

@@ -0,0 +1,21 @@
using Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
/// <summary>
/// 使用量统计服务接口
/// </summary>
public interface IUsageStatisticsService
{
/// <summary>
/// 获取当前用户近7天的Token消耗统计
/// </summary>
/// <returns>每日Token使用量列表</returns>
Task<List<DailyTokenUsageDto>> GetLast7DaysTokenUsageAsync();
/// <summary>
/// 获取当前用户各个模型的Token消耗量及占比
/// </summary>
/// <returns>模型Token使用量列表</returns>
Task<List<ModelTokenUsageDto>> GetModelTokenUsageAsync();
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application.Contracts\Yi.Framework.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\..\rbac\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.AiHub.Domain.Shared\Yi.Framework.AiHub.Domain.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Yi.Framework.AiHub.Domain.Shared;
using Yi.Framework.Ddd.Application.Contracts;
using Yi.Framework.Rbac.Application.Contracts;
namespace Yi.Framework.AiHub.Application.Contracts
{
[DependsOn(
typeof(YiFrameworkAiHubDomainSharedModule),
typeof(YiFrameworkDddApplicationContractsModule),
typeof(YiFrameworkRbacApplicationContractsModule)
)]
public class YiFrameworkAiHubApplicationContractsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var build = context.Services.GetConfiguration();
}
}
}

View File

@@ -0,0 +1,96 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Shared.Consts;
namespace Yi.Framework.AiHub.Application.Services;
public class AiAccountService : ApplicationService
{
private IAccountService _accountService;
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
private ISqlSugarRepository<AiRechargeAggregateRoot> _rechargeRepository;
private ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
public AiAccountService(
IAccountService accountService,
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
ISqlSugarRepository<AiRechargeAggregateRoot> rechargeRepository,
ISqlSugarRepository<PremiumPackageAggregateRoot> premiumPackageRepository)
{
_accountService = accountService;
_userRepository = userRepository;
_rechargeRepository = rechargeRepository;
_premiumPackageRepository = premiumPackageRepository;
}
/// <summary>
/// 获取ai用户信息
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet("account/ai")]
public async Task<AiUserRoleMenuDto> GetAsync()
{
var userId = CurrentUser.GetId();
var userAccount = await _accountService.GetAsync(null, null, userId: CurrentUser.GetId());
var output = userAccount.Adapt<AiUserRoleMenuDto>();
// 是否绑定服务号
output.IsBindFuwuhao = await _userRepository.IsAnyAsync(x => userId == x.UserId);
// 是否为VIP用户
output.IsVip = CurrentUser.IsAiVip();
// 获取VIP到期时间
if (output.IsVip)
{
var recharges = await _rechargeRepository._DbQueryable
.Where(x => x.UserId == userId)
.ToListAsync();
if (recharges.Any())
{
// 如果有任何一个充值记录的过期时间为null说明是永久VIP
if (recharges.Any(x => !x.ExpireDateTime.HasValue))
{
output.VipExpireTime = null; // 永久VIP
}
else
{
// 取最大的过期时间
output.VipExpireTime = recharges
.Where(x => x.ExpireDateTime.HasValue)
.Max(x => x.ExpireDateTime);
}
}
}
// 获取尊享包Token信息
var premiumPackages = await _premiumPackageRepository._DbQueryable
.Where(x => x.UserId == userId && x.IsActive)
.ToListAsync();
if (premiumPackages.Any())
{
// 过滤掉已过期的包
var validPackages = premiumPackages
.Where(p => p.IsAvailable())
.ToList();
output.PremiumTotalTokens = validPackages.Sum(x => x.TotalTokens);
output.PremiumUsedTokens = validPackages.Sum(x => x.UsedTokens);
output.PremiumRemainingTokens = validPackages.Sum(x => x.RemainingTokens);
}
return output;
}
}

View File

@@ -0,0 +1,162 @@
using System.Collections.Concurrent;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenAI.Chat;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Dtos;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Dtos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// ai服务
/// </summary>
public class AiChatService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
private readonly AiBlacklistManager _aiBlacklistManager;
private readonly ILogger<AiChatService> _logger;
private readonly AiGateWayManager _aiGateWayManager;
public AiChatService(IHttpContextAccessor httpContextAccessor,
AiBlacklistManager aiBlacklistManager,
ISqlSugarRepository<AiModelEntity> aiModelRepository,
ILogger<AiChatService> logger, AiGateWayManager aiGateWayManager)
{
_httpContextAccessor = httpContextAccessor;
_aiBlacklistManager = aiBlacklistManager;
_aiModelRepository = aiModelRepository;
_logger = logger;
_aiGateWayManager = aiGateWayManager;
}
/// <summary>
/// 查询已登录的账户信息
/// </summary>
/// <returns></returns>
[Route("ai-chat/account")]
[Authorize]
public async Task<UserRoleMenuDto> GetAsync()
{
var accountService = LazyServiceProvider.GetRequiredService<IAccountService>();
var output = await accountService.GetAsync();
return output;
}
/// <summary>
/// 获取模型列表
/// </summary>
/// <returns></returns>
public async Task<List<ModelGetListOutput>> GetModelAsync()
{
var output = await _aiModelRepository._DbQueryable
.Where(x => x.ModelType == ModelTypeEnum.Chat)
.OrderByDescending(x => x.OrderNum)
.Select(x => new ModelGetListOutput
{
Id = x.Id,
Category = "chat",
ModelId = x.ModelId,
ModelName = x.Name,
ModelDescribe = x.Description,
ModelPrice = 0,
ModelType = "1",
ModelShow = "0",
SystemPrompt = null,
ApiHost = null,
ApiKey = null,
Remark = x.Description
}).ToListAsync();
return output;
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="input"></param>
/// <param name="sessionId"></param>
/// <param name="cancellationToken"></param>
[HttpPost("ai-chat/send")]
public async Task PostSendAsync([FromBody] ThorChatCompletionsRequest input, [FromQuery] Guid? sessionId,
CancellationToken cancellationToken)
{
//除了免费模型,其他的模型都要校验
if (!input.Model.Contains("DeepSeek-R1"))
{
//有token需要黑名单校验
if (CurrentUser.IsAuthenticated)
{
await _aiBlacklistManager.VerifiyAiBlacklist(CurrentUser.GetId());
if (!CurrentUser.IsAiVip())
{
throw new UserFriendlyException("该模型需要VIP用户才能使用请购买VIP后重新登录重试");
}
}
else
{
throw new UserFriendlyException("未登录用户只能使用未加速的DeepSeek-R1请登录后重试");
}
}
//ai网关代理httpcontext
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
CurrentUser.Id, sessionId, cancellationToken);
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("ai-chat/FileMaster/send")]
public async Task PostFileMasterSendAsync([FromBody] ThorChatCompletionsRequest input,
CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(input.Model))
{
throw new BusinessException("当前接口不支持第三方使用");
}
if (CurrentUser.IsAuthenticated)
{
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
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
CurrentUser.Id, null, cancellationToken);
}
}

View File

@@ -0,0 +1,88 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.FileMaster;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
namespace Yi.Framework.AiHub.Application.Services.FileMaster;
public class FileMasterService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly AiGateWayManager _aiGateWayManager;
private readonly AiBlacklistManager _aiBlacklistManager;
public FileMasterService(IHttpContextAccessor httpContextAccessor, AiGateWayManager aiGateWayManager,
AiBlacklistManager aiBlacklistManager)
{
_httpContextAccessor = httpContextAccessor;
_aiGateWayManager = aiGateWayManager;
_aiBlacklistManager = aiBlacklistManager;
}
/// <summary>
/// 校验下一步
/// </summary>
/// <returns></returns>
[HttpPost("FileMaster/VerifyNext")]
public Task<string> VerifyNextAsync(VerifyNextInput input)
{
if (!CurrentUser.IsAuthenticated)
{
if (input.DirectoryCount + input.FileCount >= 20)
{
throw new UserFriendlyException("未登录用户文件夹与文件数量不能大于20个请登录后解锁全部功能");
}
}
else
{
if (input.DirectoryCount + input.FileCount >= 100)
{
throw new UserFriendlyException("为防止无限制暴力使用当前文件整理大师Vip最多支持100文件与文件夹数量");
}
}
return Task.FromResult("success");
}
/// <summary>
/// 对话
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("FileMaster/chat/completions")]
public async Task ChatCompletionsAsync([FromBody] ThorChatCompletionsRequest input,
CancellationToken cancellationToken)
{
if (CurrentUser.IsAuthenticated)
{
input.Model = "gpt-5-chat";
}
else
{
input.Model = "gpt-5-chat";
}
Guid? userId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : null;
if (userId is not null)
{
await _aiBlacklistManager.VerifiyAiBlacklist(userId.Value);
}
//ai网关代理httpcontext
if (input.Stream == true)
{
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
}
else
{
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
cancellationToken);
}
}
}

View File

@@ -0,0 +1,309 @@
using System.Text;
using System.Text.Json;
using System.Xml.Serialization;
using Medallion.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Fuwuhao;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Managers.Fuwuhao;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.AiHub.Domain.Shared.Enums.Fuwuhao;
using Yi.Framework.Rbac.Application.Contracts.Dtos.Account;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// 服务号服务
/// </summary>
public class FuwuhaoService : ApplicationService
{
private readonly ILogger<FuwuhaoService> _logger;
private readonly IHttpContextAccessor _accessor;
private readonly FuwuhaoManager _fuwuhaoManager;
private IDistributedCache<SceneCacheDto> _sceneCache;
private IDistributedCache<string> _openIdToSceneCache;
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
private IAccountService _accountService;
private IFileService _fileService;
public IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetService<IDistributedLockProvider>();
public FuwuhaoService(ILogger<FuwuhaoService> logger, IHttpContextAccessor accessor, FuwuhaoManager fuwuhaoManager,
IDistributedCache<SceneCacheDto> sceneCache, IAccountService accountService, IFileService fileService,
IDistributedCache<string> openIdToSceneCache, ISqlSugarRepository<AiUserExtraInfoEntity> userRepositroy)
{
_logger = logger;
_accessor = accessor;
_fuwuhaoManager = fuwuhaoManager;
_sceneCache = sceneCache;
_accountService = accountService;
_fileService = fileService;
_openIdToSceneCache = openIdToSceneCache;
_userRepository = userRepositroy;
}
/// <summary>
/// 服务器号测试回调
/// </summary>
/// <returns></returns>
[HttpGet("fuwuhao/callback")]
public async Task<string> GetCallbackAsync([FromQuery] string signature, [FromQuery] string timestamp,
[FromQuery] string nonce, [FromQuery] string echostr)
{
return echostr;
}
/// <summary>
/// 服务号关注回调
/// </summary>
/// <param name="signature"></param>
/// <param name="timestamp"></param>
/// <param name="nonce"></param>
/// <returns></returns>
[HttpPost("fuwuhao/callback")]
public async Task<string> PostCallbackAsync([FromQuery] string signature, [FromQuery] string timestamp,
[FromQuery] string nonce)
{
_fuwuhaoManager.ValidateCallback(signature, timestamp, nonce);
var request = _accessor.HttpContext.Request;
// 1. 读取原始 XML 内容
using var reader = new StreamReader(request.Body, Encoding.UTF8);
var xmlString = await reader.ReadToEndAsync();
var serializer = new XmlSerializer(typeof(FuwuhaoCallModel));
using var stringReader = new StringReader(xmlString);
var body = (FuwuhaoCallModel)serializer.Deserialize(stringReader);
//获取场景值,后续通过场景值设置缓存状态,前端轮询这个场景值用户是否已操作即可
var scene = body.EventKey.Replace("qrscene_", "");
if (!(body.Event is "SCAN" or "subscribe"))
{
throw new UserFriendlyException("当前回调只处理扫码 与 关注");
}
if (scene is null)
{
throw new UserFriendlyException("服务号返回无场景值");
}
//制作幂等
await using (var handle =
await DistributedLock.TryAcquireLockAsync($"Yi:fuwuhao:callbacklock:{scene}"))
{
if (handle == null)
{
return "success"; // 跳过直接返回成功
}
var cache = await _sceneCache.GetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}");
if (cache == null)
{
return "success"; // 跳过直接返回成功
}
if (cache.SceneResult != SceneResultEnum.Wait)
{
return "success"; // 跳过直接返回成功
}
//根据操作类型,进行业务处理,返回处理结果,再写入缓存,10s过去相当于用户10s扫完app后轮询要在10秒内完成
var scenResult =
await _fuwuhaoManager.CallBackHandlerAsync(cache.SceneType, body.FromUserName, cache.UserId);
cache.SceneResult = scenResult.SceneResult;
cache.UserId = scenResult.UserId;
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}", cache,
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(120) });
//如果是注册将OpenId与Scene进行绑定代表用户有30分钟进行注册
if (scenResult.SceneResult == SceneResultEnum.Register)
{
await _openIdToSceneCache.SetAsync($"{FuwuhaoConst.OpenIdToSceneCacheKey}{body.FromUserName}", scene,
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
var replyMessage =
_fuwuhaoManager.BuildRegisterMessage(body.FromUserName);
return replyMessage;
}
}
return "success";
}
/// <summary>
/// 创建带参数的二维码
/// </summary>
/// <returns>二维码URL</returns>
[HttpPost("fuwuhao/qrcode")]
public async Task<QrCodeOutput> GetQrCodeAsync([FromQuery] SceneTypeEnum sceneType)
{
if (sceneType == SceneTypeEnum.Bind && CurrentUser.Id is null)
{
throw new UserFriendlyException("绑定微信,需登录用户,请重新登录后重试");
}
//生成一个随机场景值
var scene = Guid.NewGuid().ToString("N");
var qrCodeUrl = await _fuwuhaoManager.CreateQrCodeAsync(scene);
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}", new SceneCacheDto()
{
UserId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : null,
SceneType = sceneType
}, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
return new QrCodeOutput()
{
QrCodeUrl = qrCodeUrl,
Scene = scene
};
}
/// <summary>
/// 扫码登录/注册/绑定,轮询接口
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
[HttpGet("fuwuhao/qrcode/result")]
public async Task<QrCodeResultOutput> GetQrCodeResultAsync([FromQuery] string scene)
{
var output = new QrCodeResultOutput();
var cache = await _sceneCache.GetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}");
if (cache is null)
{
output.SceneResult = SceneResultEnum.Expired;
return output;
}
output.SceneResult = cache.SceneResult;
switch (output.SceneResult)
{
case SceneResultEnum.Login:
if (cache.UserId is null)
{
throw new ApplicationException("获取用户id异常请重试");
}
var loginInfo = await _accountService.PostLoginAsync(cache.UserId!.Value);
output.Token = loginInfo.Token;
output.RefreshToken = loginInfo.RefreshToken;
break;
}
return output;
}
/// <summary>
/// 注册账号需要微信code
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
[HttpGet("fuwuhao/register")]
public async Task<IActionResult> RegisterByCodeAsync([FromQuery] string code)
{
var message = await RegisterByCodeForMessageAsync(code);
//var message = "恭喜注册";
var filePath = Path.Combine("wwwroot", "aihub", "auth.html");
var html = await File.ReadAllTextAsync(filePath);
var result = new ContentResult
{
Content = html.Replace("{{message}}", message),
ContentType = "text/html",
StatusCode = 200
};
return result;
}
private async Task<string> RegisterByCodeForMessageAsync(string code)
{
//根据code获取到openid、微信用户昵称、头像
var userInfo = await _fuwuhaoManager.GetUserInfoByCodeAsync(code);
if (userInfo is null)
{
return "当前注册已经失效,请重新扫码注册!";
}
var scene = await _openIdToSceneCache.GetAsync($"{FuwuhaoConst.OpenIdToSceneCacheKey}{userInfo.OpenId}");
if (scene is null)
{
return "当前注册已经过期,请重新扫码注册!";
}
var files = new FormFileCollection();
// 下载头像并添加到系统文件中
if (!string.IsNullOrEmpty(userInfo.HeadImgUrl))
{
using var httpClient = new HttpClient();
var imageBytes = await httpClient.GetByteArrayAsync(userInfo.HeadImgUrl);
var imageStream = new MemoryStream(imageBytes);
// 从URL中提取文件扩展名默认为png
var fileName = $"avatar_{userInfo.OpenId}.png";
var formFile = new FormFile(imageStream, 0, imageBytes.Length, "avatar", fileName)
{
Headers = new HeaderDictionary(),
ContentType = "image/png"
};
files.Add(formFile);
}
var result = await _fileService.Post(files);
//由于存在查询/编辑在同一个事务操作,上锁防止并发
await using (await DistributedLock.AcquireLockAsync($"fuwuhao:RegisterLock:{userInfo.OpenId}",
TimeSpan.FromMinutes(1)))
{
if (await _userRepository.IsAnyAsync(x => x.FuwuhaoOpenId == userInfo.OpenId))
{
return "你已注册过意社区账号!";
}
Guid userId;
try
{
userId = await _accountService.PostSystemRegisterAsync(new RegisterDto
{
UserName = $"wx{Random.Shared.Next(100000, 999999)}",
Password = Guid.NewGuid().ToString("N"),
Phone = null,
Email = null,
Nick = userInfo.Nickname,
Icon = result.FirstOrDefault()?.Id.ToString()
});
}
catch (UserFriendlyException e)
{
return e.Message;
}
await _userRepository.InsertAsync(new AiUserExtraInfoEntity(userId, userInfo.OpenId));
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}", new SceneCacheDto
{
UserId = userId,
SceneResult = SceneResultEnum.Login
},
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(120) });
}
return "恭喜你成功注册意社区账号!";
}
}
[XmlRoot("xml")]
public class FuwuhaoCallModel
{
[XmlElement("ToUserName")] public string ToUserName { get; set; }
[XmlElement("FromUserName")] public string FromUserName { get; set; }
[XmlElement("CreateTime")] public string CreateTime { get; set; }
[XmlElement("MsgType")] public string MsgType { get; set; }
[XmlElement("Event")] public string Event { get; set; }
[XmlElement("EventKey")] public string EventKey { get; set; }
[XmlElement("Ticket")] public string Ticket { get; set; }
}

View File

@@ -0,0 +1,42 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
public class MessageService : ApplicationService
{
private readonly ISqlSugarRepository<MessageAggregateRoot> _repository;
public MessageService(ISqlSugarRepository<MessageAggregateRoot> repository)
{
_repository = repository;
}
/// <summary>
/// 查询消息
/// 需要会话id
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[Authorize]
public async Task<PagedResultDto<MessageDto>> GetListAsync([FromQuery]MessageGetListInput input)
{
RefAsync<int> total = 0;
var userId = CurrentUser.GetId();
var entities = await _repository._DbQueryable
.Where(x => x.SessionId == input.SessionId)
.Where(x=>x.UserId == userId)
.OrderBy(x => x.Id)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<MessageDto>(total, entities.Adapt<List<MessageDto>>());
}
}

View File

@@ -0,0 +1,206 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Model;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
public class OpenApiService : ApplicationService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<OpenApiService> _logger;
private readonly TokenManager _tokenManager;
private readonly AiGateWayManager _aiGateWayManager;
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
private readonly AiBlacklistManager _aiBlacklistManager;
private readonly IAccountService _accountService;
private readonly PremiumPackageManager _premiumPackageManager;
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager,
IAccountService accountService, PremiumPackageManager premiumPackageManager)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_tokenManager = tokenManager;
_aiGateWayManager = aiGateWayManager;
_aiModelRepository = aiModelRepository;
_aiBlacklistManager = aiBlacklistManager;
_accountService = accountService;
_premiumPackageManager = premiumPackageManager;
}
/// <summary>
/// 对话
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("openApi/v1/chat/completions")]
public async Task ChatCompletionsAsync([FromBody] ThorChatCompletionsRequest input,
CancellationToken cancellationToken)
{
//前面都是校验,后面才是真正的调用
var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
//ai网关代理httpcontext
if (input.Stream == true)
{
await _aiGateWayManager.CompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
}
else
{
await _aiGateWayManager.CompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
cancellationToken);
}
}
/// <summary>
/// 图片生成
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("openApi/v1/images/generations")]
public async Task ImagesGenerationsAsync([FromBody] ImageCreateRequest input, CancellationToken cancellationToken)
{
var httpContext = this._httpContextAccessor.HttpContext;
Intercept(httpContext);
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
await _aiGateWayManager.CreateImageForStatisticsAsync(httpContext, userId, null, input);
}
/// <summary>
/// 向量生成
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("openApi/v1/embeddings")]
public async Task EmbeddingAsync([FromBody] ThorEmbeddingInput input, CancellationToken cancellationToken)
{
var httpContext = this._httpContextAccessor.HttpContext;
Intercept(httpContext);
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
await _aiGateWayManager.EmbeddingForStatisticsAsync(httpContext, userId, null, input);
}
/// <summary>
/// 获取模型列表
/// </summary>
/// <returns></returns>
[HttpGet("openApi/v1/models")]
public async Task<ModelsListDto> ModelsAsync()
{
var data = await _aiModelRepository._DbQueryable
.Where(x => x.ModelType == ModelTypeEnum.Chat)
.OrderByDescending(x => x.OrderNum)
.Select(x => new ModelsDataDto
{
Id = x.ModelId,
@object = "model",
Created = DateTime.Now.ToUnixTimeSeconds(),
OwnedBy = "organization-owner",
Type = x.ModelId
}).ToListAsync();
return new ModelsListDto()
{
Data = data
};
}
/// <summary>
/// Anthropic对话尊享服务专用
/// </summary>
/// <param name="input"></param>
/// <param name="cancellationToken"></param>
[HttpPost("openApi/v1/messages")]
public async Task MessagesAsync([FromBody] AnthropicInput input,
CancellationToken cancellationToken)
{
//前面都是校验,后面才是真正的调用
var httpContext = this._httpContextAccessor.HttpContext;
var userId = await _tokenManager.GetUserIdAsync(GetTokenByHttpContext(httpContext));
await _aiBlacklistManager.VerifiyAiBlacklist(userId);
// 验证用户是否为VIP
var userInfo = await _accountService.GetAsync(null, null, userId);
if (userInfo == null)
{
throw new UserFriendlyException("用户信息不存在");
}
// 检查是否为VIP使用RoleCodes判断
if (!userInfo.RoleCodes.Contains(AiHubConst.VipRole) && userInfo.User.UserName != "cc")
{
throw new UserFriendlyException("该接口为尊享服务专用需要VIP权限才能使用");
}
// 检查尊享token包用量
var availableTokens = await _premiumPackageManager.GetAvailableTokensAsync(userId);
if (availableTokens <= 0)
{
throw new UserFriendlyException("尊享token包用量不足请先购买尊享token包");
}
//ai网关代理httpcontext
if (input.Stream)
{
await _aiGateWayManager.AnthropicCompleteChatStreamForStatisticsAsync(_httpContextAccessor.HttpContext, input,
userId, null, cancellationToken);
}
else
{
await _aiGateWayManager.AnthropicCompleteChatForStatisticsAsync(_httpContextAccessor.HttpContext, input, userId,
null,
cancellationToken);
}
}
#region
private string? GetTokenByHttpContext(HttpContext httpContext)
{
// 获取Authorization头
string authHeader = httpContext.Request.Headers["Authorization"];
// 检查是否有Bearer token
if (authHeader != null && authHeader.StartsWith("Bearer "))
{
return authHeader.Substring("Bearer ".Length).Trim();
}
return null;
}
private void Intercept(HttpContext httpContext)
{
if (httpContext.Request.Host.Value == "yxai.chat")
{
throw new UserFriendlyException("当前海外站点不支持大流量接口请使用转发站点https://ai.ccnetcore.com");
}
}
#endregion
}

View File

@@ -0,0 +1,293 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Yi.Framework.AiHub.Domain.Alipay;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
using Yi.Framework.AiHub.Domain.Shared.Enums;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Yi.Framework.AiHub.Application.Contracts.IServices;
using Volo.Abp;
using Yi.Framework.AiHub.Domain.Entities.Pay;
using Yi.Framework.SqlSugarCore.Abstractions;
using System.ComponentModel;
using System.Reflection;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// 支付服务
/// </summary>
public class PayService : ApplicationService, IPayService
{
private readonly AlipayManager _alipayManager;
private readonly PayManager _payManager;
private readonly ILogger<PayService> _logger;
private readonly ISqlSugarRepository<PayOrderAggregateRoot, Guid> _payOrderRepository;
private readonly IRechargeService _rechargeService;
private readonly PremiumPackageManager _premiumPackageManager;
public PayService(
AlipayManager alipayManager,
PayManager payManager,
ILogger<PayService> logger,
ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository,
IRechargeService rechargeService,
PremiumPackageManager premiumPackageManager)
{
_alipayManager = alipayManager;
_payManager = payManager;
_logger = logger;
_payOrderRepository = payOrderRepository;
_rechargeService = rechargeService;
_premiumPackageManager = premiumPackageManager;
}
/// <summary>
/// 创建订单并发起支付
/// </summary>
/// <param name="input">创建订单输入</param>
/// <returns>订单创建结果</returns>
[Authorize]
[HttpPost("pay/Order")]
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
{
// 1. 通过PayManager创建订单内部会验证VIP资格
var order = await _payManager.CreateOrderAsync(input.GoodsType);
// 2. 通过AlipayManager发起页面支付
var paymentPageHtml = await _alipayManager.PaymentPageAsync(
order.GoodsName,
order.OutTradeNo,
order.TotalAmount,
input.ReturnUrl);
// 3. 返回结果
return new CreateOrderOutput
{
OrderId = order.Id,
OutTradeNo = order.OutTradeNo,
PaymentPageHtml = paymentPageHtml.Body
};
}
/// <summary>
/// 支付宝异步通知处理
/// </summary>
/// <param name="form">表单数据</param>
/// <returns></returns>
[HttpPost("pay/AlipayNotify")]
[AllowAnonymous]
public async Task<string> AlipayNotifyAsync([FromForm] IFormCollection form)
{
// 1. 将表单数据转换为字典,保持原始顺序
var notifyData = new Dictionary<string, string>();
foreach (var item in form)
{
notifyData[item.Key] = item.Value.ToString();
}
var signStr = string.Join("&", notifyData.Select(kv => $"{kv.Key}={kv.Value}"));
_logger.LogInformation($"收到支付宝回调通知:{signStr}");
// 2. 验证签名
await _alipayManager.VerifyNotifyAsync(notifyData);
// 3. 记录支付通知
await _payManager.RecordPayNoticeAsync(notifyData, signStr);
// 4. 更新订单状态
var outTradeNo = notifyData.GetValueOrDefault("out_trade_no", string.Empty);
var tradeStatus = notifyData.GetValueOrDefault("trade_status", string.Empty);
var tradeNo = notifyData.GetValueOrDefault("trade_no", string.Empty);
if (!string.IsNullOrEmpty(outTradeNo) && !string.IsNullOrEmpty(tradeStatus))
{
var status = ParseTradeStatus(tradeStatus);
var order = await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo);
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
// 5. 根据商品类型进行不同的处理
if (order.GoodsType.IsPremiumPackage())
{
// 处理尊享包商品:创建尊享包记录
await _premiumPackageManager.CreatePremiumPackageAsync(
order.UserId,
order.GoodsType,
order.TotalAmount,
expireMonths: null // 尊享包不设置过期时间,或者可以根据需求设置
);
_logger.LogInformation(
$"用户 {order.UserId} 购买尊享包成功,订单号:{outTradeNo},商品:{order.GoodsName}");
}
else if (order.GoodsType.IsVipService())
{
// 处理VIP服务商品充值VIP
await _rechargeService.RechargeVipAsync(new RechargeCreateInput
{
UserId = order.UserId,
RechargeAmount = order.TotalAmount,
Content = order.GoodsName,
Months = order.GoodsType.GetValidMonths(),
Remark = "自助充值",
ContactInfo = null
});
_logger.LogInformation(
$"用户 {order.UserId} 充值VIP成功订单号{outTradeNo},月数:{order.GoodsType.GetValidMonths()}");
}
else
{
_logger.LogWarning($"未知的商品类型:{order.GoodsType},订单号:{outTradeNo}");
}
}
else
{
throw new AlipayException($"回调格式错误");
}
return "success";
}
/// <summary>
/// 查询订单状态
/// </summary>
/// <param name="input">查询订单状态输入</param>
/// <returns>订单状态信息</returns>
[HttpGet("pay/OrderStatus")]
[Authorize]
public async Task<QueryOrderStatusOutput> QueryOrderStatusAsync([FromQuery] QueryOrderStatusInput input)
{
// 通过PayManager查询订单
var order = await _payOrderRepository.GetFirstAsync(x => x.OutTradeNo == input.OutTradeNo);
if (order == null)
{
throw new UserFriendlyException($"订单不存在:{input.OutTradeNo}");
}
return new QueryOrderStatusOutput
{
OrderId = order.Id,
OutTradeNo = order.OutTradeNo,
TradeNo = order.TradeNo,
TradeStatus = order.TradeStatus,
TradeStatusDescription = GetTradeStatusDescription(order.TradeStatus),
TotalAmount = order.TotalAmount,
GoodsName = order.GoodsName,
GoodsType = order.GoodsType,
CreationTime = order.CreationTime,
LastModificationTime = order.LastModificationTime
};
}
/// <summary>
/// 获取商品列表
/// </summary>
/// <returns>商品列表</returns>
[HttpGet("pay/GoodsList")]
public async Task<List<GoodsListOutput>> GetGoodsListAsync()
{
var goodsList = new List<GoodsListOutput>();
// 获取当前用户的累加充值金额(仅已登录用户)
decimal totalRechargeAmount = 0m;
if (CurrentUser.IsAuthenticated)
{
totalRechargeAmount = await _payManager.GetUserTotalRechargeAmountAsync(CurrentUser.GetId());
}
// 遍历所有商品枚举
foreach (GoodsTypeEnum goodsType in Enum.GetValues(typeof(GoodsTypeEnum)))
{
var originalPrice = goodsType.GetTotalAmount();
decimal actualPrice = originalPrice;
decimal? discountAmount = null;
string? discountDescription = null;
// 如果是尊享包商品,计算折扣
if (goodsType.IsPremiumPackage() && CurrentUser.IsAuthenticated)
{
discountAmount = goodsType.CalculateDiscount(totalRechargeAmount);
actualPrice = goodsType.GetDiscountedPrice(totalRechargeAmount);
if (discountAmount > 0)
{
discountDescription = $"已优惠 ¥{discountAmount:F2}累计充值每10元减1元最多减20元";
}
else
{
discountDescription = "累计充值每10元可减1元最多减20元";
}
}
var goodsItem = new GoodsListOutput
{
GoodsName = goodsType.GetDisplayName(),
OriginalPrice = originalPrice,
GoodsPrice = actualPrice,
GoodsType = goodsType,
Remark = GetGoodsRemark(goodsType),
DiscountAmount = discountAmount,
DiscountDescription = discountDescription
};
goodsList.Add(goodsItem);
}
return goodsList;
}
/// <summary>
/// 获取商品备注信息
/// </summary>
/// <param name="goodsType">商品类型</param>
/// <returns>商品备注</returns>
private string GetGoodsRemark(GoodsTypeEnum goodsType)
{
if (goodsType.IsPremiumPackage())
{
var tokenAmount = goodsType.GetTokenAmount();
return $"尊享包服务,提供 {tokenAmount:N0} Tokens需要VIP资格";
}
else if (goodsType.IsVipService())
{
var validMonths = goodsType.GetValidMonths();
var monthlyPrice = goodsType.GetMonthlyPrice();
return $"VIP服务有效期 {validMonths} 个月,月均价 ¥{monthlyPrice:F2}";
}
return "未知商品类型";
}
/// <summary>
/// 获取交易状态描述
/// </summary>
/// <param name="tradeStatus">交易状态</param>
/// <returns>状态描述</returns>
private string GetTradeStatusDescription(TradeStatusEnum tradeStatus)
{
var fieldInfo = tradeStatus.GetType().GetField(tradeStatus.ToString());
var descriptionAttribute = fieldInfo?.GetCustomAttribute<DescriptionAttribute>();
return descriptionAttribute?.Description ?? "未知状态";
}
/// <summary>
/// 解析交易状态
/// </summary>
/// <param name="tradeStatus">状态字符串</param>
/// <returns></returns>
private TradeStatusEnum ParseTradeStatus(string tradeStatus)
{
if (Enum.TryParse<TradeStatusEnum>(tradeStatus, out var result))
{
return result;
}
return TradeStatusEnum.WAIT_TRADE;
}
}

View File

@@ -0,0 +1,112 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Recharge;
using Yi.Framework.AiHub.Application.Contracts.IServices;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.AiHub.Domain.Shared.Consts;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services
{
public class RechargeService : ApplicationService, IRechargeService
{
private readonly ISqlSugarRepository<AiRechargeAggregateRoot> _repository;
private readonly ICurrentUser _currentUser;
private readonly IUserService _userService;
private readonly IRoleService _roleService;
private readonly AiRechargeManager _aiMessageManager;
public RechargeService(
ISqlSugarRepository<AiRechargeAggregateRoot> repository,
ICurrentUser currentUser,
IUserService userService, IRoleService roleService, AiRechargeManager aiMessageManager)
{
_repository = repository;
_currentUser = currentUser;
_userService = userService;
_roleService = roleService;
_aiMessageManager = aiMessageManager;
}
/// <summary>
/// 查询已登录的账户充值记录
/// </summary>
/// <returns></returns>
[Route("recharge/account")]
[Authorize]
public async Task<List<RechargeGetListOutput>> GetListByAccountAsync()
{
var userId = CurrentUser.Id;
var entities = await _repository._DbQueryable.Where(x => x.UserId == userId)
.OrderByDescending(x => x.CreationTime)
.ToListAsync();
var output = entities.Adapt<List<RechargeGetListOutput>>();
return output;
}
/// <summary>
/// 给用户充值VIP
/// </summary>
/// <param name="input">充值输入参数</param>
/// <returns></returns>
[HttpPost("recharge/vip")]
public async Task RechargeVipAsync(RechargeCreateInput input)
{
DateTime? expireDateTime = null;
// 如果传入了月数,计算过期时间
if (input.Months.HasValue && input.Months.Value > 0)
{
// 直接查询该用户最大的过期时间
var maxExpireTime = await _repository._DbQueryable
.Where(x => x.UserId == input.UserId && x.ExpireDateTime.HasValue)
.MaxAsync(x => x.ExpireDateTime);
// 如果最大过期时间大于现在时间,从最大过期时间开始计算
// 否则从当天开始计算
DateTime baseDateTime = maxExpireTime.HasValue && maxExpireTime.Value > DateTime.Now
? maxExpireTime.Value
: DateTime.Now;
// 计算新的过期时间
expireDateTime = baseDateTime.AddMonths(input.Months.Value);
}
// 如果月数为空或0表示永久VIPExpireDateTime保持为null
// 创建充值记录
var rechargeRecord = new AiRechargeAggregateRoot
{
UserId = input.UserId,
RechargeAmount = input.RechargeAmount,
Content = input.Content,
ExpireDateTime = expireDateTime,
Remark = input.Remark,
ContactInfo = input.ContactInfo
};
// 保存充值记录到数据库
await _repository.InsertAsync(rechargeRecord);
// 使用UserService给用户添加VIP角色
await _userService.AddUserRoleByRoleCodeAsync(input.UserId,
new List<string>() { AiHubConst.VipRole, "default" });
}
/// <summary>
/// 移除用户vip及角色
/// </summary>
[RemoteService(isEnabled: false)]
public async Task RemoveVipRoleByExpireAsync()
{
var expiredUserIds = await _aiMessageManager.RemoveVipByExpireAsync();
if (expiredUserIds is not null)
{
await _roleService.RemoveUserRoleByRoleCodeAsync(expiredUserIds, AiHubConst.VipRole);
}
}
}
}

View File

@@ -0,0 +1,92 @@
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
public class SessionService : CrudAppService<SessionAggregateRoot, SessionDto, Guid,SessionGetListInput,SessionCreateAndUpdateInput>
{
private readonly ISqlSugarRepository<SessionAggregateRoot, Guid> _repository;
public readonly ISqlSugarRepository<MessageAggregateRoot, Guid> _messageRepository;
public SessionService(ISqlSugarRepository<SessionAggregateRoot, Guid> repository, ISqlSugarRepository<MessageAggregateRoot, Guid> messageRepository) : base(repository)
{
_repository = repository;
_messageRepository = messageRepository;
}
/// <summary>
/// 创建会话
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[Authorize]
public override async Task<SessionDto> CreateAsync(SessionCreateAndUpdateInput input)
{
var entity = await MapToEntityAsync(input);
entity.UserId = CurrentUser.GetId();
await _repository.InsertAsync(entity);
return entity.Adapt<SessionDto>();
}
/// <summary>
/// 详情会话
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[Authorize]
public override Task<SessionDto> GetAsync(Guid id)
{
return base.GetAsync(id);
}
/// <summary>
/// 编辑会话
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
[Authorize]
public override Task<SessionDto> UpdateAsync(Guid id, SessionCreateAndUpdateInput input)
{
return base.UpdateAsync(id, input);
}
/// <summary>
/// 删除会话
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[Authorize]
public override async Task DeleteAsync(Guid id)
{
await base.DeleteAsync(id);
//对应的消息一起删除
await _messageRepository.DeleteAsync(x => x.SessionId == id);
}
/// <summary>
/// 查询会话
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[Authorize]
public override async Task<PagedResultDto<SessionDto>> GetListAsync(SessionGetListInput input)
{
RefAsync<int> total = 0;
var userId = CurrentUser.GetId();
var entities = await _repository._DbQueryable
.Where(x=>x.UserId == userId)
.OrderByDescending(x => x.Id)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<SessionDto>(total, entities.Adapt<List<SessionDto>>());
}
}

View File

@@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.Token;
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
using Yi.Framework.AiHub.Domain.Extensions;
using Yi.Framework.AiHub.Domain.Managers;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
public class TokenService : ApplicationService
{
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
private readonly TokenManager _tokenManager;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tokenRepository"></param>
/// <param name="tokenManager"></param>
public TokenService(ISqlSugarRepository<TokenAggregateRoot> tokenRepository, TokenManager tokenManager)
{
_tokenRepository = tokenRepository;
_tokenManager = tokenManager;
}
/// <summary>
/// 获取token
/// </summary>
/// <returns></returns>
[Authorize]
public async Task<TokenOutput> GetAsync()
{
return new TokenOutput
{
ApiKey = await _tokenManager.GetAsync(CurrentUser.GetId())
};
}
/// <summary>
/// 创建token
/// </summary>
/// <exception cref="UserFriendlyException"></exception>
[Authorize]
public async Task CreateAsync()
{
if (!CurrentUser.IsAiVip())
{
throw new UserFriendlyException("充值成为Vip畅享第三方token服务");
}
await _tokenManager.CreateAsync(CurrentUser.GetId());
}
}

View File

@@ -0,0 +1,105 @@
using Microsoft.AspNetCore.Authorization;
using SqlSugar;
using Volo.Abp.Application.Services;
using Volo.Abp.Users;
using Yi.Framework.AiHub.Application.Contracts.Dtos.UsageStatistics;
using Yi.Framework.AiHub.Application.Contracts.IServices;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Entities.Chat;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Application.Services;
/// <summary>
/// 使用量统计服务
/// </summary>
[Authorize]
public class UsageStatisticsService : ApplicationService, IUsageStatisticsService
{
private readonly ISqlSugarRepository<MessageAggregateRoot> _messageRepository;
private readonly ISqlSugarRepository<UsageStatisticsAggregateRoot> _usageStatisticsRepository;
public UsageStatisticsService(
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository)
{
_messageRepository = messageRepository;
_usageStatisticsRepository = usageStatisticsRepository;
}
/// <summary>
/// 获取当前用户近7天的Token消耗统计
/// </summary>
/// <returns>每日Token使用量列表</returns>
public async Task<List<DailyTokenUsageDto>> GetLast7DaysTokenUsageAsync()
{
var userId = CurrentUser.GetId();
var endDate = DateTime.Today;
var startDate = endDate.AddDays(-6); // 近7天
// 从Message表统计近7天的token消耗
var dailyUsage = await _messageRepository._DbQueryable
.Where(x => x.UserId == userId)
.Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1))
.GroupBy(x => x.CreationTime.Date)
.Select(g => new
{
Date = g.CreationTime.Date,
Tokens = SqlFunc.AggregateSum(g.TokenUsage.TotalTokenCount)
})
.ToListAsync();
// 生成完整的7天数据包括没有使用记录的日期
var result = new List<DailyTokenUsageDto>();
for (int i = 0; i < 7; i++)
{
var date = startDate.AddDays(i);
var usage = dailyUsage.FirstOrDefault(x => x.Date == date);
result.Add(new DailyTokenUsageDto
{
Date = date,
Tokens = usage?.Tokens ?? 0
});
}
return result.OrderBy(x => x.Date).ToList();
}
/// <summary>
/// 获取当前用户各个模型的Token消耗量及占比
/// </summary>
/// <returns>模型Token使用量列表</returns>
public async Task<List<ModelTokenUsageDto>> GetModelTokenUsageAsync()
{
var userId = CurrentUser.GetId();
// 从UsageStatistics表获取各模型的token消耗统计
var modelUsages = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId)
.Select(x => new
{
x.ModelId,
x.TotalTokenCount
})
.ToListAsync();
if (!modelUsages.Any())
{
return new List<ModelTokenUsageDto>();
}
// 计算总token数
var totalTokens = modelUsages.Sum(x => x.TotalTokenCount);
// 计算各模型占比
var result = modelUsages.Select(x => new ModelTokenUsageDto
{
Model = x.ModelId,
Tokens = x.TotalTokenCount,
Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0
}).OrderByDescending(x => x.Tokens).ToList();
return result;
}
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" />
<ProjectReference Include="..\Yi.Framework.AiHub.Application.Contracts\Yi.Framework.AiHub.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.AiHub.Domain\Yi.Framework.AiHub.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using Yi.Framework.AiHub.Application.Contracts;
using Yi.Framework.AiHub.Domain;
using Yi.Framework.Ddd.Application;
namespace Yi.Framework.AiHub.Application
{
[DependsOn(
typeof(YiFrameworkAiHubApplicationContractsModule),
typeof(YiFrameworkAiHubDomainModule),
typeof(YiFrameworkDddApplicationModule)
)]
public class YiFrameworkAiHubApplicationModule : AbpModule
{
}
}

View File

@@ -0,0 +1,6 @@
namespace Yi.Framework.AiHub.Domain.Shared.Consts;
public class AiHubConst
{
public const string VipRole = "YiXinAi-Vip";
}

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.AiHub.Domain.Shared.Consts;
public class FuwuhaoConst
{
public const string SceneCacheKey = "fuwuhao:scene:";
public const string OpenIdToSceneCacheKey = "fuwuhao:OpenIdToScene:";
}

View File

@@ -0,0 +1,59 @@
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
public class AiModelDescribe
{
/// <summary>
/// 应用id
/// </summary>
public Guid AppId { get; set; }
/// <summary>
/// 应用名称
/// </summary>
public string AppName { get; set; }
/// <summary>
/// 应用终结点
/// </summary>
public string Endpoint { get; set; }
/// <summary>
/// 应用key
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// 排序
/// </summary>
public int OrderNum { get; set; }
/// <summary>
/// 处理名
/// </summary>
public string HandlerName { get; set; }
/// <summary>
/// 模型id
/// </summary>
public string ModelId { get; set; }
/// <summary>
/// 模型名称
/// </summary>
public string ModelName { get; set; }
/// <summary>
/// 模型描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 额外url
/// </summary>
public string? AppExtraUrl { get; set; }
/// <summary>
/// 模型额外信息
/// </summary>
public string? ModelExtraInfo { get; set; }
}

View File

@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public sealed class AnthropicCacheControl
{
[JsonPropertyName("type")]
public string Type { get; set; }
}

View File

@@ -0,0 +1,151 @@
using System.Text.Json.Serialization;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public class AnthropicStreamDto
{
[JsonPropertyName("type")] public string? Type { get; set; }
[JsonPropertyName("index")] public int? Index { get; set; }
[JsonPropertyName("content_block")] public AnthropicChatCompletionDtoContentBlock? ContentBlock { get; set; }
[JsonPropertyName("delta")] public AnthropicChatCompletionDtoDelta? Delta { get; set; }
[JsonPropertyName("message")] public AnthropicChatCompletionDto? Message { get; set; }
[JsonPropertyName("usage")] public AnthropicCompletionDtoUsage? Usage { 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 class AnthropicStreamErrorDto
{
[JsonPropertyName("type")] public string? Type { get; set; }
[JsonPropertyName("message")] public string? Message { get; set; }
}
public class AnthropicChatCompletionDtoDelta
{
[JsonPropertyName("type")] public string? Type { get; set; }
[JsonPropertyName("text")] public string? Text { get; set; }
[JsonPropertyName("thinking")] public string? Thinking { get; set; }
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
[JsonPropertyName("stop_reason")] public string? StopReason { get; set; }
}
public class AnthropicChatCompletionDtoContentBlock
{
[JsonPropertyName("type")] public string? Type { get; set; }
[JsonPropertyName("thinking")] public string? Thinking { get; set; }
[JsonPropertyName("signature")] public string? Signature { get; set; }
[JsonPropertyName("id")] public string? Id { get; set; }
[JsonPropertyName("name")] public string? Name { get; set; }
[JsonPropertyName("input")] public object? Input { get; set; }
[JsonPropertyName("server_name")] public string? ServerName { get; set; }
[JsonPropertyName("is_error")] public bool? IsError { get; set; }
[JsonPropertyName("tool_use_id")] public string? ToolUseId { get; set; }
[JsonPropertyName("content")] public object? Content { get; set; }
}
public class AnthropicChatCompletionDto
{
public string id { get; set; }
public string type { get; set; }
public string role { get; set; }
public AnthropicChatCompletionDtoContent[] content { get; set; }
public string model { get; set; }
public string stop_reason { get; set; }
public object stop_sequence { 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 class AnthropicChatCompletionDtoContent
{
public string type { get; set; }
public string? text { get; set; }
public string? id { get; set; }
public string? name { get; set; }
public object? input { get; set; }
[JsonPropertyName("thinking")] public string? Thinking { get; set; }
[JsonPropertyName("partial_json")] public string? PartialJson { get; set; }
public string? signature { get; set; }
}
public class AnthropicCompletionDtoUsage
{
[JsonPropertyName("input_tokens")] public int? InputTokens { get; set; }
[JsonPropertyName("cache_creation_input_tokens")]
public int? CacheCreationInputTokens { get; set; }
[JsonPropertyName("cache_read_input_tokens")]
public int? CacheReadInputTokens { get; set; }
[JsonPropertyName("output_tokens")] public int? OutputTokens { get; set; }
[JsonPropertyName("server_tool_use")] public AnthropicServerToolUse? ServerToolUse { get; set; }
}
public class AnthropicServerToolUse
{
[JsonPropertyName("web_search_requests")]
public int? WebSearchRequests { get; set; }
}

View File

@@ -0,0 +1,153 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public sealed class AnthropicInput
{
[JsonPropertyName("stream")] public bool Stream { get; set; }
[JsonPropertyName("model")] public string Model { get; set; }
[JsonPropertyName("max_tokens")] public int? MaxTokens { get; set; }
[JsonPropertyName("messages")] public IList<AnthropicMessageInput> Messages { get; set; }
[JsonPropertyName("tools")] public IList<AnthropicMessageTool>? Tools { get; set; }
[JsonPropertyName("tool_choice")]
public object? ToolChoiceCalculated
{
get
{
if (string.IsNullOrEmpty(ToolChoiceString))
{
return ToolChoiceString;
}
if (ToolChoice?.Type == "function")
{
return ToolChoice;
}
return ToolChoice?.Type;
}
set
{
if (value is JsonElement jsonElement)
{
if (jsonElement.ValueKind == JsonValueKind.String)
{
ToolChoiceString = jsonElement.GetString();
}
else if (jsonElement.ValueKind == JsonValueKind.Object)
{
ToolChoice = jsonElement.Deserialize<AnthropicTooChoiceInput>(ThorJsonSerializer.DefaultOptions);
}
}
else
{
ToolChoice = (AnthropicTooChoiceInput)value;
}
}
}
[JsonIgnore] public string? ToolChoiceString { get; set; }
[JsonIgnore] public AnthropicTooChoiceInput? ToolChoice { get; set; }
[JsonIgnore] public IList<AnthropicMessageContent>? Systems { get; set; }
[JsonIgnore] public string? System { get; set; }
[JsonPropertyName("system")]
public object? SystemCalculated
{
get
{
if (System is not null && Systems is not null)
{
throw new ValidationException("System 和 Systems 字段不能同时有值");
}
if (System is not null)
{
return System;
}
return Systems!;
}
set
{
if (value is JsonElement str)
{
if (str.ValueKind == JsonValueKind.String)
{
System = value?.ToString();
}
else if (str.ValueKind == JsonValueKind.Array)
{
Systems = JsonSerializer.Deserialize<IList<AnthropicMessageContent>>(value?.ToString(),
ThorJsonSerializer.DefaultOptions);
}
}
else
{
System = value?.ToString();
}
}
}
[JsonPropertyName("thinking")] public AnthropicThinkingInput? Thinking { get; set; }
[JsonPropertyName("temperature")] public double? Temperature { get; set; }
[JsonPropertyName("metadata")] public Dictionary<string, object>? Metadata { get; set; }
}
public class AnthropicThinkingInput
{
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("budget_tokens")] public int BudgetTokens { get; set; }
}
public class AnthropicTooChoiceInput
{
[JsonPropertyName("type")] public string? Type { get; set; }
[JsonPropertyName("name")] public string? Name { get; set; }
}
public class AnthropicMessageTool
{
[JsonPropertyName("name")] public string name { get; set; }
[JsonPropertyName("description")] public string? Description { get; set; }
[JsonPropertyName("input_schema")] public Input_schema 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; }
}

View File

@@ -0,0 +1,76 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public class AnthropicMessageContent
{
[JsonPropertyName("cache_control")] public AnthropicCacheControl? CacheControl { get; set; }
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("text")] public string? Text { get; set; }
[JsonPropertyName("tool_use_id")] public string? ToolUseId { get; set; }
[JsonPropertyName("name")] public string? Name { get; set; }
[JsonPropertyName("id")] public string? Id { get; set; }
[JsonPropertyName("thinking")] public string? Thinking { get; set; }
[JsonPropertyName("input")] public object? Input { get; set; }
[JsonPropertyName("content")]
public object? Content
{
get
{
if (_content is not null && _contents is not null)
{
throw new ValidationException("Messages 中 Content 和 Contents 字段不能同时有值");
}
if (_content is not null)
{
return _content;
}
return _contents;
}
set
{
if (value is JsonElement str)
{
if (str.ValueKind == JsonValueKind.String)
{
_content = value?.ToString();
}
else if (str.ValueKind == JsonValueKind.Array)
{
_contents = JsonSerializer.Deserialize<List<AnthropicMessageContent>>(value?.ToString());
}
}
else
{
_content = value?.ToString();
}
}
}
private string? _content;
private List<AnthropicMessageContent>? _contents;
public class AnthropicMessageContentSource
{
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("media_type")] public string? MediaType { get; set; }
[JsonPropertyName("data")] public string? Data { get; set; }
}
[JsonPropertyName("source")] public AnthropicMessageContentSource? Source { get; set; }
}

View File

@@ -0,0 +1,54 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public class AnthropicMessageInput
{
[JsonPropertyName("role")]
public string Role { get; set; }
[JsonIgnore]
public string? Content;
[JsonPropertyName("content")]
public object? ContentCalculated
{
get
{
if (Content is not null && Contents is not null)
{
throw new ValidationException("Messages 中 Content 和 Contents 字段不能同时有值");
}
if (Content is not null)
{
return Content;
}
return Contents!;
}
set
{
if (value is JsonElement str)
{
if (str.ValueKind == JsonValueKind.String)
{
Content = value?.ToString();
}
else if (str.ValueKind == JsonValueKind.Array)
{
Contents = JsonSerializer.Deserialize<IList<AnthropicMessageContent>>(value?.ToString(),ThorJsonSerializer.DefaultOptions);
}
}
else
{
Content = value?.ToString();
}
}
}
[JsonIgnore]
public IList<AnthropicMessageContent>? Contents;
}

View File

@@ -0,0 +1,648 @@
using System.Text.Json;
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public static class AnthropicToOpenAi
{
/// <summary>
/// 将AnthropicInput转换为ThorChatCompletionsRequest
/// </summary>
public static ThorChatCompletionsRequest ConvertAnthropicToOpenAi(AnthropicInput anthropicInput)
{
var openAiRequest = new ThorChatCompletionsRequest
{
Model = anthropicInput.Model,
MaxTokens = anthropicInput.MaxTokens,
Stream = anthropicInput.Stream,
Messages = new List<ThorChatMessage>(anthropicInput.Messages.Count)
};
// high medium minimal low
if (openAiRequest.Model.EndsWith("-high") ||
openAiRequest.Model.EndsWith("-medium") ||
openAiRequest.Model.EndsWith("-minimal") ||
openAiRequest.Model.EndsWith("-low"))
{
openAiRequest.ReasoningEffort = openAiRequest.Model switch
{
var model when model.EndsWith("-high") => "high",
var model when model.EndsWith("-medium") => "medium",
var model when model.EndsWith("-minimal") => "minimal",
var model when model.EndsWith("-low") => "low",
_ => "medium"
};
openAiRequest.Model = openAiRequest.Model.Replace("-high", "")
.Replace("-medium", "")
.Replace("-minimal", "")
.Replace("-low", "");
}
if (anthropicInput.Thinking != null &&
anthropicInput.Thinking.Type.Equals("enabled", StringComparison.OrdinalIgnoreCase))
{
openAiRequest.Thinking = new ThorChatClaudeThinking()
{
BudgetToken = anthropicInput.Thinking.BudgetTokens,
Type = "enabled",
};
openAiRequest.EnableThinking = true;
}
if (openAiRequest.Model.EndsWith("-thinking"))
{
openAiRequest.EnableThinking = true;
openAiRequest.Model = openAiRequest.Model.Replace("-thinking", "");
}
if (openAiRequest.Stream == true)
{
openAiRequest.StreamOptions = new ThorStreamOptions()
{
IncludeUsage = true,
};
}
if (!string.IsNullOrEmpty(anthropicInput.System))
{
openAiRequest.Messages.Add(ThorChatMessage.CreateSystemMessage(anthropicInput.System));
}
if (anthropicInput.Systems?.Count > 0)
{
foreach (var systemContent in anthropicInput.Systems)
{
openAiRequest.Messages.Add(ThorChatMessage.CreateSystemMessage(systemContent.Text ?? string.Empty));
}
}
// 处理messages
if (anthropicInput.Messages != null)
{
foreach (var message in anthropicInput.Messages)
{
var thorMessages = ConvertAnthropicMessageToThor(message);
// 需要过滤 空消息
if (thorMessages.Count == 0)
{
continue;
}
openAiRequest.Messages.AddRange(thorMessages);
}
openAiRequest.Messages = openAiRequest.Messages
.Where(m => !string.IsNullOrEmpty(m.Content) || m.Contents?.Count > 0 || m.ToolCalls?.Count > 0 ||
!string.IsNullOrEmpty(m.ToolCallId))
.ToList();
}
// 处理tools
if (anthropicInput.Tools is { Count: > 0 })
{
openAiRequest.Tools = anthropicInput.Tools.Where(x => x.name != "web_search")
.Select(ConvertAnthropicToolToThor).ToList();
}
// 判断是否存在web_search
if (anthropicInput.Tools?.Any(x => x.name == "web_search") == true)
{
openAiRequest.WebSearchOptions = new ThorChatWebSearchOptions()
{
};
}
// 处理tool_choice
if (anthropicInput.ToolChoice != null)
{
openAiRequest.ToolChoice = ConvertAnthropicToolChoiceToThor(anthropicInput.ToolChoice);
}
return openAiRequest;
}
/// <summary>
/// 根据最后的内容块类型和OpenAI的完成原因确定Claude的停止原因
/// </summary>
public static string GetStopReasonByLastContentType(string? openAiFinishReason, string lastContentBlockType)
{
// 如果最后一个内容块是工具调用优先返回tool_use
if (lastContentBlockType == "tool_use")
{
return "tool_use";
}
// 否则使用标准的转换逻辑
return GetClaudeStopReason(openAiFinishReason);
}
/// <summary>
/// 创建message_start事件
/// </summary>
public static AnthropicStreamDto CreateMessageStartEvent(string messageId, string model)
{
return new AnthropicStreamDto
{
Type = "message_start",
Message = new AnthropicChatCompletionDto
{
id = messageId,
type = "message",
role = "assistant",
model = model,
content = new AnthropicChatCompletionDtoContent[0],
Usage = new AnthropicCompletionDtoUsage
{
InputTokens = 0,
OutputTokens = 0,
CacheCreationInputTokens = 0,
CacheReadInputTokens = 0
}
}
};
}
/// <summary>
/// 创建content_block_start事件
/// </summary>
public static AnthropicStreamDto CreateContentBlockStartEvent()
{
return new AnthropicStreamDto
{
Type = "content_block_start",
Index = 0,
ContentBlock = new AnthropicChatCompletionDtoContentBlock
{
Type = "text",
Id = null,
Name = null
}
};
}
/// <summary>
/// 创建thinking block start事件
/// </summary>
public static AnthropicStreamDto CreateThinkingBlockStartEvent()
{
return new AnthropicStreamDto
{
Type = "content_block_start",
Index = 0,
ContentBlock = new AnthropicChatCompletionDtoContentBlock
{
Type = "thinking",
Id = null,
Name = null
}
};
}
/// <summary>
/// 创建content_block_delta事件
/// </summary>
public static AnthropicStreamDto CreateContentBlockDeltaEvent(string text)
{
return new AnthropicStreamDto
{
Type = "content_block_delta",
Index = 0,
Delta = new AnthropicChatCompletionDtoDelta
{
Type = "text_delta",
Text = text
}
};
}
/// <summary>
/// 创建thinking delta事件
/// </summary>
public static AnthropicStreamDto CreateThinkingBlockDeltaEvent(string thinking)
{
return new AnthropicStreamDto
{
Type = "content_block_delta",
Index = 0,
Delta = new AnthropicChatCompletionDtoDelta
{
Type = "thinking",
Thinking = thinking
}
};
}
/// <summary>
/// 创建content_block_stop事件
/// </summary>
public static AnthropicStreamDto CreateContentBlockStopEvent()
{
return new AnthropicStreamDto
{
Type = "content_block_stop",
Index = 0
};
}
/// <summary>
/// 创建message_delta事件
/// </summary>
public static AnthropicStreamDto CreateMessageDeltaEvent(string finishReason, AnthropicCompletionDtoUsage usage)
{
return new AnthropicStreamDto
{
Type = "message_delta",
Usage = usage,
Delta = new AnthropicChatCompletionDtoDelta
{
StopReason = finishReason
}
};
}
/// <summary>
/// 创建message_stop事件
/// </summary>
public static AnthropicStreamDto CreateMessageStopEvent()
{
return new AnthropicStreamDto
{
Type = "message_stop"
};
}
/// <summary>
/// 创建tool block start事件
/// </summary>
public static AnthropicStreamDto CreateToolBlockStartEvent(string? toolId, string? toolName)
{
return new AnthropicStreamDto
{
Type = "content_block_start",
Index = 0,
ContentBlock = new AnthropicChatCompletionDtoContentBlock
{
Type = "tool_use",
Id = toolId,
Name = toolName
}
};
}
/// <summary>
/// 创建tool delta事件
/// </summary>
public static AnthropicStreamDto CreateToolBlockDeltaEvent(string partialJson)
{
return new AnthropicStreamDto
{
Type = "content_block_delta",
Index = 0,
Delta = new AnthropicChatCompletionDtoDelta
{
Type = "input_json_delta",
PartialJson = partialJson
}
};
}
/// <summary>
/// 转换Anthropic消息为Thor消息列表
/// </summary>
public static List<ThorChatMessage> ConvertAnthropicMessageToThor(AnthropicMessageInput anthropicMessage)
{
var results = new List<ThorChatMessage>();
// 处理简单的字符串内容
if (anthropicMessage.Content != null)
{
var thorMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
Content = anthropicMessage.Content
};
results.Add(thorMessage);
return results;
}
// 处理多模态内容
if (anthropicMessage.Contents is { Count: > 0 })
{
var currentContents = new List<ThorChatMessageContent>();
var currentToolCalls = new List<ThorToolCall>();
foreach (var content in anthropicMessage.Contents)
{
switch (content.Type)
{
case "text":
currentContents.Add(ThorChatMessageContent.CreateTextContent(content.Text ?? string.Empty));
break;
case "thinking" when !string.IsNullOrEmpty(content.Thinking):
results.Add(new ThorChatMessage()
{
ReasoningContent = content.Thinking
});
break;
case "image":
{
if (content.Source != null)
{
var imageUrl = content.Source.Type == "base64"
? $"data:{content.Source.MediaType};base64,{content.Source.Data}"
: content.Source.Data;
currentContents.Add(ThorChatMessageContent.CreateImageUrlContent(imageUrl ?? string.Empty));
}
break;
}
case "tool_use":
{
// 如果有普通内容,先创建内容消息
if (currentContents.Count > 0)
{
if (currentContents.Count == 1 && currentContents.Any(x => x.Type == "text"))
{
var contentMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
ContentCalculated = currentContents.FirstOrDefault()?.Text ?? string.Empty
};
results.Add(contentMessage);
}
else
{
var contentMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
Contents = currentContents
};
results.Add(contentMessage);
}
currentContents = new List<ThorChatMessageContent>();
}
// 收集工具调用
var toolCall = new ThorToolCall
{
Id = content.Id,
Type = "function",
Function = new ThorChatMessageFunction
{
Name = content.Name,
Arguments = JsonSerializer.Serialize(content.Input)
}
};
currentToolCalls.Add(toolCall);
break;
}
case "tool_result":
{
// 如果有普通内容,先创建内容消息
if (currentContents.Count > 0)
{
var contentMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
Contents = currentContents
};
results.Add(contentMessage);
currentContents = [];
}
// 如果有工具调用,先创建工具调用消息
if (currentToolCalls.Count > 0)
{
var toolCallMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
ToolCalls = currentToolCalls
};
results.Add(toolCallMessage);
currentToolCalls = new List<ThorToolCall>();
}
// 创建工具结果消息
var toolMessage = new ThorChatMessage
{
Role = "tool",
ToolCallId = content.ToolUseId,
Content = content.Content?.ToString() ?? string.Empty
};
results.Add(toolMessage);
break;
}
}
}
// 处理剩余的内容
if (currentContents.Count > 0)
{
var contentMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
Contents = currentContents
};
results.Add(contentMessage);
}
// 处理剩余的工具调用
if (currentToolCalls.Count > 0)
{
var toolCallMessage = new ThorChatMessage
{
Role = anthropicMessage.Role,
ToolCalls = currentToolCalls
};
results.Add(toolCallMessage);
}
}
// 如果没有任何内容,返回一个空的消息
if (results.Count == 0)
{
results.Add(new ThorChatMessage
{
Role = anthropicMessage.Role,
Content = string.Empty
});
}
// 如果只有一个text则使用content字段
if (results is [{ Contents.Count: 1 }] &&
results.FirstOrDefault()?.Contents?.FirstOrDefault()?.Type == "text" &&
!string.IsNullOrEmpty(results.FirstOrDefault()?.Contents?.FirstOrDefault()?.Text))
{
return
[
new ThorChatMessage
{
Role = results[0].Role,
Content = results.FirstOrDefault()?.Contents?.FirstOrDefault()?.Text ?? string.Empty
}
];
}
return results;
}
/// <summary>
/// 转换Anthropic工具为Thor工具
/// </summary>
public static ThorToolDefinition ConvertAnthropicToolToThor(AnthropicMessageTool anthropicTool)
{
IDictionary<string, ThorToolFunctionPropertyDefinition> values =
new Dictionary<string, ThorToolFunctionPropertyDefinition>();
if (anthropicTool.InputSchema?.Properties != null)
{
foreach (var property in anthropicTool.InputSchema.Properties)
{
if (property.Value?.description != null)
{
var definitionType = new ThorToolFunctionPropertyDefinition()
{
Description = property.Value.description,
Type = property.Value.type
};
if (property.Value?.items?.type != null)
{
definitionType.Items = new ThorToolFunctionPropertyDefinition()
{
Type = property.Value.items.type
};
}
values.Add(property.Key, definitionType);
}
}
}
return new ThorToolDefinition
{
Type = "function",
Function = new ThorToolFunctionDefinition
{
Name = anthropicTool.name,
Description = anthropicTool.Description,
Parameters = new ThorToolFunctionPropertyDefinition
{
Type = anthropicTool.InputSchema?.Type ?? "object",
Properties = values,
Required = anthropicTool.InputSchema?.Required
}
}
};
}
/// <summary>
/// 将OpenAI的完成原因转换为Claude的停止原因
/// </summary>
public static string GetClaudeStopReason(string? openAIFinishReason)
{
return openAIFinishReason switch
{
"stop" => "end_turn",
"length" => "max_tokens",
"tool_calls" => "tool_use",
"content_filter" => "stop_sequence",
_ => "end_turn"
};
}
/// <summary>
/// 将OpenAI响应转换为Claude响应格式
/// </summary>
public static AnthropicChatCompletionDto ConvertOpenAIToClaude(ThorChatCompletionsResponse openAIResponse,
AnthropicInput originalRequest)
{
var claudeResponse = new AnthropicChatCompletionDto
{
id = openAIResponse.Id,
type = "message",
role = "assistant",
model = openAIResponse.Model ?? originalRequest.Model,
stop_reason = GetClaudeStopReason(openAIResponse.Choices?.FirstOrDefault()?.FinishReason),
stop_sequence = "",
content = []
};
if (openAIResponse.Choices is { Count: > 0 })
{
var choice = openAIResponse.Choices.First();
var contents = new List<AnthropicChatCompletionDtoContent>();
if (!string.IsNullOrEmpty(choice.Message.Content) && !string.IsNullOrEmpty(choice.Message.ReasoningContent))
{
contents.Add(new AnthropicChatCompletionDtoContent
{
type = "thinking",
Thinking = choice.Message.ReasoningContent
});
contents.Add(new AnthropicChatCompletionDtoContent
{
type = "text",
text = choice.Message.Content
});
}
else
{
// 处理思维内容
if (!string.IsNullOrEmpty(choice.Message.ReasoningContent))
contents.Add(new AnthropicChatCompletionDtoContent
{
type = "thinking",
Thinking = choice.Message.ReasoningContent
});
// 处理文本内容
if (!string.IsNullOrEmpty(choice.Message.Content))
contents.Add(new AnthropicChatCompletionDtoContent
{
type = "text",
text = choice.Message.Content
});
}
// 处理工具调用
if (choice.Message.ToolCalls is { Count: > 0 })
contents.AddRange(choice.Message.ToolCalls.Select(toolCall => new AnthropicChatCompletionDtoContent
{
type = "tool_use", id = toolCall.Id, name = toolCall.Function?.Name,
input = JsonSerializer.Deserialize<object>(toolCall.Function?.Arguments ?? "{}")
}));
claudeResponse.content = contents.ToArray();
}
// 处理使用情况统计 - 确保始终提供Usage信息
claudeResponse.Usage = new AnthropicCompletionDtoUsage
{
InputTokens = openAIResponse.Usage?.PromptTokens ?? 0,
OutputTokens = (int?)(openAIResponse.Usage?.CompletionTokens ?? 0),
CacheCreationInputTokens = openAIResponse.Usage?.PromptTokensDetails?.CachedTokens ?? 0,
CacheReadInputTokens = openAIResponse.Usage?.PromptTokensDetails?.CachedTokens ?? 0
};
return claudeResponse;
}
/// <summary>
/// 转换Anthropic工具选择为Thor工具选择
/// </summary>
public static ThorToolChoice ConvertAnthropicToolChoiceToThor(AnthropicTooChoiceInput anthropicToolChoice)
{
return new ThorToolChoice
{
Type = anthropicToolChoice.Type ?? "auto",
Function = anthropicToolChoice.Name != null
? new ThorToolChoiceFunctionTool { Name = anthropicToolChoice.Name }
: null
};
}
}

View File

@@ -0,0 +1,14 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
public static class ThorJsonSerializer
{
public static JsonSerializerOptions DefaultOptions => new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
}

View File

@@ -0,0 +1,13 @@
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
public class MessageInputDto
{
public string? Content { get; set; }
public string Role { get; set; }
public string ModelId { get; set; }
public string? Remark { get; set; }
public ThorUsageResponse? TokenUsage { get; set; }
}

View File

@@ -0,0 +1,79 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
//TODO add model validation
//TODO check what is string or array for prompt,..
public record EmbeddingCreateRequest
{
/// <summary>
/// Input text to get embeddings for, encoded as a string or array of tokens. To get embeddings for multiple inputs
/// in a single request, pass an array of strings or array of token arrays. Each input must not exceed 2048 tokens in
/// length.
/// Unless your are embedding code, we suggest replacing newlines (`\n`) in your input with a single space, as we have
/// observed inferior results when newlines are present.
/// </summary>
/// <see href="https://platform.openai.com/docs/api-reference/embeddings/create#embeddings/create-input" />
[JsonIgnore]
public List<string>? InputAsList { get; set; }
/// <summary>
/// Input text to get embeddings for, encoded as a string or array of tokens. To get embeddings for multiple inputs
/// in a single request, pass an array of strings or array of token arrays. Each input must not exceed 2048 tokens in
/// length.
/// Unless your are embedding code, we suggest replacing newlines (`\n`) in your input with a single space, as we have
/// observed inferior results when newlines are present.
/// </summary>
/// <see href="https://platform.openai.com/docs/api-reference/embeddings/create#embeddings/create-input" />
[JsonIgnore]
public string? Input { get; set; }
[JsonPropertyName("input")]
public IList<string>? InputCalculated
{
get
{
if (Input != null && InputAsList != null)
{
throw new ValidationException(
"Input and InputAsList can not be assigned at the same time. One of them is should be null.");
}
if (Input != null)
{
return new List<string> { Input };
}
return InputAsList;
}
}
/// <summary>
/// ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your
/// available models, or see our [Model overview](/docs/models/overview) for descriptions of them.
/// </summary>
/// <see href="https://platform.openai.com/docs/api-reference/embeddings/create#embeddings/create-model" />
[JsonPropertyName("model")]
public string? Model { get; set; }
/// <summary>
/// The number of dimensions the resulting output embeddings should have. Only supported in text-embedding-3 and later models.
/// </summary>
/// <see href="https://platform.openai.com/docs/api-reference/embeddings/create#embeddings-create-dimensions" />
[JsonPropertyName("dimensions")]
public int? Dimensions { get; set; }
/// <summary>
/// The format to return the embeddings in. Can be either float or base64.
/// </summary>
/// <returns></returns>
[JsonPropertyName("encoding_format")]
public string? EncodingFormat { get; set; }
public IEnumerable<ValidationResult> Validate()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,113 @@
using System.Buffers;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
public record EmbeddingCreateResponse : ThorBaseResponse
{
[JsonPropertyName("model")] public string Model { get; set; }
[JsonPropertyName("data")] public List<EmbeddingResponse> Data { get; set; } = [];
/// <summary>
/// 类型转换如果类型是base64,则将float[]转换为base64,如果是空或是float和原始类型一样则不转换
/// </summary>
public void ConvertEmbeddingData(string? encodingFormat)
{
if (Data.Count == 0)
{
return;
}
switch (encodingFormat)
{
// 判断第一个是否是float[],如果是则不转换
case null or "float" when Data[0].Embedding is float[]:
return;
// 否则转换成float[]
case null or "float":
{
foreach (var embeddingResponse in Data)
{
if (embeddingResponse.Embedding is string base64)
{
embeddingResponse.Embedding = Convert.FromBase64String(base64);
}
}
return;
}
// 判断第一个是否是string如果是则不转换
case "base64" when Data[0].Embedding is string:
return;
// 否则转换成base64
case "base64":
{
foreach (var embeddingResponse in Data)
{
if (embeddingResponse.Embedding is JsonElement str)
{
if (str.ValueKind == JsonValueKind.Array)
{
var floats = str.EnumerateArray().Select(element => element.GetSingle()).ToArray();
embeddingResponse.Embedding = ConvertFloatArrayToBase64(floats);
}
}
else if (embeddingResponse.Embedding is IList<double> doubles)
{
embeddingResponse.Embedding = ConvertFloatArrayToBase64(doubles.ToArray());
}
}
break;
}
}
}
public static string ConvertFloatArrayToBase64(double[] floatArray)
{
// 将 float[] 转换成 byte[]
byte[] byteArray = ArrayPool<byte>.Shared.Rent(floatArray.Length * sizeof(float));
try
{
Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
// 将 byte[] 转换成 base64 字符串
return Convert.ToBase64String(byteArray);
}
finally
{
ArrayPool<byte>.Shared.Return(byteArray);
}
}
public static string ConvertFloatArrayToBase64(float[] floatArray)
{
// 将 float[] 转换成 byte[]
byte[] byteArray = ArrayPool<byte>.Shared.Rent(floatArray.Length * sizeof(float));
try
{
Buffer.BlockCopy(floatArray, 0, byteArray, 0, floatArray.Length);
// 将 byte[] 转换成 base64 字符串
return Convert.ToBase64String(byteArray);
}
finally
{
ArrayPool<byte>.Shared.Return(byteArray);
}
}
[JsonPropertyName("usage")] public ThorUsageResponse? Usage { get; set; }
}
public record EmbeddingResponse
{
[JsonPropertyName("object")] public string Object { get; set; } = "embedding";
[JsonPropertyName("index")] public int? Index { get; set; }
[JsonPropertyName("embedding")] public object Embedding { get; set; }
}

View File

@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
public sealed class ThorEmbeddingInput
{
[JsonPropertyName("model")]
public string Model { get; set; }
[JsonPropertyName("input")]
public object Input { get; set; }
[JsonPropertyName("encoding_format")]
public string? EncodingFormat { get; set; }
[JsonPropertyName("dimensions")]
public int? Dimensions { get; set; }
[JsonPropertyName("user")]
public string? User { get; set; }
}

View File

@@ -0,0 +1,54 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
/// <summary>
/// Image Create Request Model
/// </summary>
public record ImageCreateRequest : SharedImageRequestBaseModel
{
public ImageCreateRequest()
{
}
public ImageCreateRequest(string prompt)
{
Prompt = prompt;
}
/// <summary>
/// A text description of the desired image(s). The maximum length is 1000 characters for dall-e-2 and 4000 characters for dall-e-3
/// </summary>
[JsonPropertyName("prompt")]
public string Prompt { get; set; }
/// <summary>
/// The quality of the image that will be generated. Possible values are 'standard' or 'hd' (default is 'standard').
/// Hd creates images with finer details and greater consistency across the image.
/// This param is only supported for dall-e-3 model.
/// <br /><br />Check <see cref="StaticValues.ImageStatics.Quality"/> for possible values
/// </summary>
[JsonPropertyName("quality")]
public string? Quality { get; set; }
/// <summary>
/// The style of the generated images. Must be one of vivid or natural.
/// Vivid causes the model to lean towards generating hyper-real and dramatic images.
/// Natural causes the model to produce more natural, less hyper-real looking images. This param is only supported for dall-e-3.
/// <br /><br />Check <see cref="StaticValues.ImageStatics.Style"/> for possible values
/// </summary>
[JsonPropertyName("style")]
public string? Style { get; set; }
[JsonPropertyName("background")]
public string? Background { get; set; }
[JsonPropertyName("moderation")]
public string? Moderation { get; set; }
[JsonPropertyName("output_compression")]
public string? OutputCompression { get; set; }
[JsonPropertyName("output_format")]
public string? OutputFormat { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
public record ImageCreateResponse : ThorBaseResponse
{
[JsonPropertyName("data")] public List<ImageDataResult> Results { get; set; }
[JsonPropertyName("usage")] public ThorUsageResponse? Usage { get; set; } = new();
public record ImageDataResult
{
[JsonPropertyName("url")] public string Url { get; set; }
[JsonPropertyName("b64_json")] public string B64 { get; set; }
[JsonPropertyName("revised_prompt")] public string RevisedPrompt { get; set; }
}
}

View File

@@ -0,0 +1,51 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
public record ImageEditCreateRequest : SharedImageRequestBaseModel
{
/// <summary>
/// The image to edit. Must be a valid PNG file, less than 4MB, and square.
/// </summary>
public byte[]? Image { get; set; }
/// <summary>
/// Image file name
/// </summary>
public string ImageName { get; set; }
/// <summary>
/// An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where image should be edited.
/// Must be a valid PNG file, less than 4MB, and have the same dimensions as image.
/// </summary>
public byte[]? Mask { get; set; }
/// <summary>
/// Mask file name
/// </summary>
public string? MaskName { get; set; }
[JsonPropertyName("quality")]
public string Quality { get; set; }
/// <summary>
/// A text description of the desired image(s). The maximum length is 1000 characters.
/// </summary>
[JsonPropertyName("prompt")]
public string Prompt { get; set; }
[JsonPropertyName("background")]
public string? Background { get; set; }
[JsonPropertyName("moderation")]
public string? Moderation { get; set; }
[JsonPropertyName("output_compression")]
public string? OutputCompression { get; set; }
[JsonPropertyName("output_format")]
public string? OutputFormat { get; set; }
[JsonPropertyName("style")]
public string? Style { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
public record ImageVariationCreateRequest : SharedImageRequestBaseModel
{
/// <summary>
/// The image to edit. Must be a valid PNG file, less than 4MB, and square.
/// </summary>
public byte[] Image { get; set; }
/// <summary>
/// Image file name
/// </summary>
public string ImageName { get; set; }
}

View File

@@ -0,0 +1,42 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
public record SharedImageRequestBaseModel
{
/// <summary>
/// The number of images to generate. Must be between 1 and 10.
/// For dall-e-3 model, only n=1 is supported.
/// </summary>
[JsonPropertyName("n")]
public int? N { get; set; }
/// <summary>
/// The size of the generated images.
/// Must be one of 256x256, 512x512, or 1024x1024 for dall-e-2.
/// Must be one of 1024x1024, 1792x1024, or 1024x1792 for dall-e-3 models.
/// <br /><br />Check <see cref="StaticValues.ImageStatics.Size"/> for possible values
/// </summary>
[JsonPropertyName("size")]
public string? Size { get; set; }
/// <summary>
/// The format in which the generated images are returned. Must be one of url or b64_json
/// </summary>
[JsonPropertyName("response_format")]
public string? ResponseFormat { get; set; }
/// <summary>
/// A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse.
/// <a href="https://platform.openai.com/docs/usage-policies/end-user-ids">Learn more</a>.
/// </summary>
[JsonPropertyName("user")]
public string? User { get; set; }
/// <summary>
/// The model to use for image generation. Must be one of dall-e-2 or dall-e-3
/// For ImageEditCreateRequest and for ImageVariationCreateRequest only dall-e-2 modell is supported at this time.
/// </summary>
[JsonPropertyName("model")]
public string? Model { get; set; }
}

View File

@@ -0,0 +1,28 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public class ModelsListDto
{
[JsonPropertyName("object")] public string @object { get; set; }
[JsonPropertyName("data")] public List<ModelsDataDto> Data { get; set; }
public ModelsListDto()
{
Data = new();
}
}
public class ModelsDataDto
{
[JsonPropertyName("id")] public string Id { get; set; }
[JsonPropertyName("object")] public string @object { get; set; }
[JsonPropertyName("created")] public long Created { get; set; }
[JsonPropertyName("owned_by")] public string OwnedBy { get; set; }
[JsonPropertyName("type")] public string Type { get; set; }
}

View File

@@ -0,0 +1,28 @@
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// OpenAI常量
/// </summary>
public static class OpenAIConstant
{
/// <summary>
/// 字符串utf-8编码
/// </summary>
/// <returns></returns>
public const string Done = "[DONE]";
/// <summary>
/// Data: 协议头
/// </summary>
public const string Data = "data:";
/// <summary>
/// think: 协议头
/// </summary>
public const string ThinkStart = "<think>";
/// <summary>
/// think: 协议尾
/// </summary>
public const string ThinkEnd = "</think>";
}

View File

@@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public record ThorBaseResponse
{
/// <summary>
/// 对象类型
/// </summary>
[JsonPropertyName("object")]
public string? ObjectTypeName { get; set; }
/// <summary>
///
/// </summary>
public bool Successful => Error == null;
/// <summary>
///
/// </summary>
[JsonPropertyName("error")]
public ThorError? Error { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public sealed class ThorChatAudioRequest
{
[JsonPropertyName("voice")]
public string? Voice { get; set; }
[JsonPropertyName("format")]
public string? Format { get; set; }
}

View File

@@ -0,0 +1,66 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 聊天完成选项列
/// </summary>
public record ThorChatChoiceResponse
{
/// <summary>
/// 模型生成的聊天完成消息。【流式】模型响应生成的聊天完成增量存储在此属性。<br/>
/// 在当前模型中无论流式还是非流式Message 和 Delta存储相同的值
/// </summary>
[JsonPropertyName("delta")]
public ThorChatMessage Delta
{
get => Message;
set => Message = value;
}
/// <summary>
/// 模型生成的聊天完成消息。【非流式】返回的消息存储在此属性。<br/>
/// 在当前模型中无论流式还是非流式Message 和 Delta存储相同的值
/// </summary>
[JsonPropertyName("message")]
public ThorChatMessage Message { get; set; }
/// <summary>
/// 选项列表中选项的索引。
/// </summary>
[JsonPropertyName("index")]
public int? Index { get; set; }
/// <summary>
/// 用于处理请求的服务层。仅当在请求中指定了 service_tier 参数时,才包含此字段。
/// </summary>
[JsonPropertyName("service_tier")]
public string? ServiceTier { get; set; }
/// <summary>
/// 模型停止生成令牌的原因。
/// stop 如果模型达到自然停止点或提供的停止序列,
/// length 如果达到请求中指定的最大标记数,
/// content_filter 如果由于内容过滤器中的标志而省略了内容,
/// tool_calls 如果模型调用了工具,或者 function_call (已弃用)
/// 如果模型调用了函数,则会出现这种情况。
/// </summary>
[JsonPropertyName("finish_reason")]
public string? FinishReason { get; set; }
/// <summary>
/// 此指纹表示模型运行时使用的后端配置。
/// 可以与 seed 请求参数结合使用,以了解何时进行了可能影响确定性的后端更改。
/// </summary>
[JsonPropertyName("finish_details")]
public FinishDetailsResponse? FinishDetails { get; set; }
/// <summary>
///
/// </summary>
public class FinishDetailsResponse
{
[JsonPropertyName("type")] public string Type { get; set; }
[JsonPropertyName("stop")] public string Stop { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public class ThorChatClaudeThinking
{
[JsonPropertyName("type")]
public string? Type { get; set; }
[JsonPropertyName("budget_tokens")]
public int? BudgetToken { get; set; }
}

View File

@@ -0,0 +1,288 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 对话补全请求参数对象
/// </summary>
public class ThorChatCompletionsRequest
{
[JsonPropertyName("store")] public bool? Store { get; set; }
/// <summary>
/// 表示对话中支持的模态类型数组。可以为 null。
/// </summary>
[JsonPropertyName("modalities")]
public string[]? Modalities { get; set; }
/// <summary>
/// 表示对话中的音频请求参数。可以为 null。
/// </summary>
[JsonPropertyName("audio")]
public ThorChatAudioRequest? Audio { get; set; }
/// <summary>
/// 包含迄今为止对话的消息列表
/// </summary>
[JsonPropertyName("messages")]
public List<ThorChatMessage>? Messages { get; set; }
/// <summary>
/// 模型唯一编码值,如 gpt-4gpt-3.5-turbo,moonshot-v1-8k看底层具体平台定义
/// </summary>
[JsonPropertyName("model")]
public string Model { get; set; }
/// <summary>
/// 温度采样的替代方法称为核采样,介于 0 和 1 之间,其中模型考虑具有 top_p 概率质量的标记的结果。
/// 因此 0.1 意味着仅考虑包含前 10% 概率质量的标记。
/// 我们通常建议更改此项或 temperature ,但不要同时更改两者。
/// 默认 1
/// </summary>
[JsonPropertyName("top_p")]
public float? TopP { get; set; }
/// <summary>
/// 使用什么采样温度,介于 0 和 2 之间。
/// 较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定性。
/// 我们通常建议更改此项或 top_p ,但不要同时更改两者。
/// 默认 1
/// </summary>
[JsonPropertyName("temperature")]
public float? Temperature { get; set; }
/// <summary>
/// 为每条输入消息生成多少个结果
/// <para>
/// 默认为 1不得大于 5。特别的当 temperature 非常小靠近 0 的时候,
/// 我们只能返回 1 个结果,如果这个时候 n 已经设置并且 > 1
/// 我们的服务会返回不合法的输入参数(invalid_request_error)
/// </para>
/// </summary>
[JsonPropertyName("n")]
public int? N { get; set; }
/// <summary>
/// 如果设置,将发送部分消息增量,就像在 ChatGPT 中一样。
/// 令牌可用时将作为仅数据服务器发送事件发送,流由 data: [DONE] 消息终止。
/// </summary>
[JsonPropertyName("stream")]
public bool? Stream { get; set; }
/// <summary>
/// 流响应选项。仅当您设置 stream: true 时才设置此项。
/// </summary>
[JsonPropertyName("stream_options")]
public ThorStreamOptions? StreamOptions { get; set; }
/// <summary>
/// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。
/// 最多不能超过 5 个字符串,每个字符串不得超过 32 字节,
/// 默认 null
/// </summary>
[JsonIgnore]
public string? Stop { get; set; }
/// <summary>
/// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。
/// 最多不能超过 5 个字符串,每个字符串不得超过 32 字节,
/// 默认 null
/// </summary>
[JsonIgnore]
public IList<string>? StopAsList { get; set; }
/// <summary>
/// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。
/// 最多不能超过 5 个字符串,每个字符串不得超过 32 字节,
/// 默认 null
/// </summary>
[JsonPropertyName("stop")]
public IList<string>? StopCalculated
{
get
{
if (Stop is not null && StopAsList is not null)
{
throw new ValidationException(
"Stop 和 StopAsList 不能同时有值,其中一个应该为 null");
}
if (Stop is not null)
{
return new List<string> { Stop };
}
return StopAsList;
}
}
/// <summary>
/// 生成的答案允许的最大令牌数。默认情况下模型可以返回的令牌数量为4096个提示令牌
/// </summary>
/// <see href="https://platform.openai.com/docs/api-reference/completions/create#completions/create-max_tokens" />
[JsonPropertyName("max_tokens")]
public int? MaxTokens { get; set; }
/// <summary>
/// 可为补全生成的令牌数量的上限,包括可见输出令牌和推理令牌。
/// </summary>
[JsonPropertyName("max_completion_tokens")]
public int? MaxCompletionTokens { get; set; }
/// <summary>
/// 存在惩罚,介于 -2.0 到 2.0 之间的数字。
/// 正值会根据新生成的词汇是否出现在文本中来进行惩罚,增加模型讨论新话题的可能性,
/// 默认为 0
/// </summary>
/// <seealso href="https://platform.openai.com/docs/api-reference/parameter-details" />
[JsonPropertyName("presence_penalty")]
public float? PresencePenalty { get; set; }
/// <summary>
/// 频率惩罚,介于-2.0到2.0之间的数字。
/// 正值会根据新生成的词汇在文本中现有的频率来进行惩罚,减少模型一字不差重复同样话语的可能性.
/// 默认为 0
/// </summary>
/// <seealso href="https://platform.openai.com/docs/api-reference/parameter-details" />
[JsonPropertyName("frequency_penalty")]
public float? FrequencyPenalty { get; set; }
/// <summary>
/// 接受一个 JSON 对象,该对象将标记(由标记生成器中的标记 ID 指定)映射到从 -100 到 100 的关联偏差值。
/// 从数学上讲,偏差会在采样之前添加到模型生成的 logits 中。
/// 每个模型的确切效果会有所不同,但 -1 和 1 之间的值应该会降低或增加选择的可能性;
/// 像 -100 或 100 这样的值应该会导致相关令牌的禁止或独占选择。
/// </summary>
/// <seealso href="https://platform.openai.com/tokenizer?view=bpe" />
[JsonPropertyName("logit_bias")]
public object? LogitBias { get; set; }
/// <summary>
/// 是否返回输出标记的对数概率。如果为 true则返回 message 的 content 中返回的每个输出标记的对数概率。
/// </summary>
[JsonPropertyName("logprobs")]
public bool? Logprobs { get; set; }
/// <summary>
/// 0 到 20 之间的整数,指定每个标记位置最有可能返回的标记数量,每个标记都有关联的对数概率。
/// 如果使用此参数, logprobs 必须设置为 true 。
/// </summary>
[JsonPropertyName("top_logprobs")]
public int? TopLogprobs { get; set; }
/// <summary>
/// 指定用于处理请求的延迟层。此参数与订阅规模层服务的客户相关:
/// 如果设置为“auto”系统将使用规模等级积分直至用完。
/// 如果设置为“default”则将使用具有较低正常运行时间 SLA 且无延迟保证的默认服务层来处理请求。
/// 默认null
/// </summary>
[JsonPropertyName("service_tier")]
public string? ServiceTier { get; set; }
/// <summary>
/// 模型可能调用的工具列表。目前,仅支持函数作为工具。使用它来提供模型可以为其生成 JSON 输入的函数列表。最多支持 128 个功能。
/// </summary>
[JsonPropertyName("tools")]
public List<ThorToolDefinition>? Tools { get; set; }
/// <summary>
/// 控制模型调用哪个(如果有)工具。
/// none 表示模型不会调用任何工具,而是生成一条消息。
/// auto 表示模型可以在生成消息或调用一个或多个工具之间进行选择。
/// required 表示模型必须调用一个或多个工具。
/// 通过 {"type": "function", "function": {"name": "my_function"}} 指定特定工具会强制模型调用该工具。
/// 当不存在任何工具时, none 是默认值。如果存在工具,则 auto 是默认值。
/// </summary>
[JsonIgnore]
public ThorToolChoice? ToolChoice { get; set; }
[JsonPropertyName("tool_choice")]
public object? ToolChoiceCalculated
{
get
{
if (ToolChoice != null &&
ToolChoice.Type != ThorToolChoiceTypeConst.Function &&
ToolChoice.Function != null)
{
throw new ValidationException(
"当 type 为 \"function\" 时,属性 Function 不可为null。");
}
if (ToolChoice?.Type == ThorToolChoiceTypeConst.Function)
{
return ToolChoice;
}
return ToolChoice?.Type;
}
set
{
if (value is JsonElement jsonElement)
{
// if (jsonElement.ValueKind == JsonValueKind.String)
// {
// ToolChoice = new ThorToolChoice
// {
// Type = jsonElement.GetString()
// };
// }
if (jsonElement.ValueKind == JsonValueKind.Object)
{
ToolChoice = jsonElement.Deserialize<ThorToolChoice>();
}
}
else if (value is string text)
{
ToolChoice = new ThorToolChoice
{
Type = text
};
}
else
{
ToolChoice = (ThorToolChoice)value;
}
}
}
/// <summary>
/// 设置为 {"type": "json_object"} 可启用 JSON 模式,从而保证模型生成的信息是有效的 JSON。
/// 当你将 response_format 设置为 {"type": "json_object"} 时,
/// 你需要在 prompt 中明确地引导模型输出 JSON 格式的内容,
/// 并告知模型该 JSON 的具体格式,否则将可能导致不符合预期的结果。
/// 默认为 {"type": "text"}
/// </summary>
[JsonPropertyName("response_format")]
public ThorResponseFormat? ResponseFormat { get; set; }
[JsonPropertyName("metadata")] public Dictionary<string, string>? Metadata { get; set; }
/// <summary>
/// 此功能处于测试阶段。
/// 如果指定,我们的系统将尽最大努力进行确定性采样,
/// 以便具有相同 seed 和参数的重复请求应返回相同的结果。
/// 不保证确定性,您应该参考 system_fingerprint 响应参数来监控后端的变化。
/// </summary>
[JsonPropertyName("seed")]
public int? Seed { get; set; }
/// <summary>
/// 代表您的最终用户的唯一标识符,可以帮助 OpenAI 监控和检测滥用行为。
/// </summary>
[JsonPropertyName("user")]
public string? User { get; set; }
[JsonPropertyName("thinking")] public ThorChatClaudeThinking? Thinking { get; set; }
[JsonPropertyName("enable_thinking")] public bool? EnableThinking { get; set; }
[JsonPropertyName("web_search_options")]
public ThorChatWebSearchOptions? WebSearchOptions { get; set; } = null;
[JsonPropertyName("reasoning_effort")] public string? ReasoningEffort { get; set; }
}

View File

@@ -0,0 +1,63 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 对话补全服务返回结果
/// </summary>
public record ThorChatCompletionsResponse
{
/// <summary>
/// 对话补全的唯一标识符。<br/>
/// 聊天完成的唯一标识符。如果是流式对话,每个区块都具有相同的 ID。
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; }
/// <summary>
/// 用于对话补全的模型。
/// </summary>
[JsonPropertyName("model")]
public string? Model { get; set; }
/// <summary>
/// 对象类型<br/>
/// 非流式对话补全始终为 chat.completion<br/>
/// 流式对话补全始终为 chat.completion.chunk<br/>
/// </summary>
[JsonPropertyName("object")]
public string? ObjectTypeName { get; set; }
/// <summary>
/// 对话补全选项列表。如果 n 大于 1则可以是多个。
/// </summary>
[JsonPropertyName("choices")]
public List<ThorChatChoiceResponse>? Choices { get; set; }
/// <summary>
/// 完成请求的使用情况统计信息。
/// 仅在您 stream_options: {"include_usage": true} 设置请求时才会显示。
/// 如果存在,则它包含一个 null 值,但最后一个块除外,该块包含整个请求的令牌使用情况统计信息。
/// </summary>
[JsonPropertyName("usage")]
public ThorUsageResponse? Usage { get; set; }
/// <summary>
/// 创建对话补全时的 Unix 时间戳(以秒为单位)。
/// </summary>
[JsonPropertyName("created")]
public int Created { get; set; }
/// <summary>
/// 此指纹表示模型运行时使用的后端配置。
/// 可以与 seed 请求参数结合使用,以了解何时进行了可能影响确定性的后端更改。
/// </summary>
[JsonPropertyName("system_fingerprint")]
public string SystemFingerPrint { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonPropertyName("error")]
public ThorError? Error { get; set; }
}

View File

@@ -0,0 +1,192 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 聊天消息体建议使用CreeateXXX系列方法构建内容
/// </summary>
public class ThorChatMessage
{
/// <summary>
///
/// </summary>
public ThorChatMessage()
{
}
/// <summary>
/// 【必填】发出消息的角色,请使用<see cref="ThorChatMessageRoleConst.User"/>赋值,如ThorChatMessageRoleConst.User
/// </summary>
[JsonPropertyName("role")]
public string Role { get; set; }
/// <summary>
/// 发出的消息内容,如:你好
/// </summary>
[JsonIgnore]
public string? Content { get; set; }
/// <summary>
/// 发出的消息内容,仅当使用 gpt-4o 模型时才支持图像输入。
/// </summary>
/// <example>
/// 示例数据:
/// "content": [
/// {
/// "type": "text",
/// "text": "What'\''s in this image?"
/// },
/// {
/// "type": "image_url",
/// "image_url": {
/// "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
/// }
/// }
/// ]
/// </example>
[JsonIgnore]
public IList<ThorChatMessageContent>? Contents { get; set; }
/// <summary>
/// 发出的消息内容计算用于json序列号和反序列化Content 和 Contents 不能同时赋值,只能二选一
/// 如果是工具调用,还真可能为空
/// </summary>
[JsonPropertyName("content")]
public object? ContentCalculated
{
get
{
// if (Content is not null && Contents is not null)
// {
// throw new ValidationException("Messages 中 Content 和 Contents 字段不能同时有值");
// }
if (Content is not null)
{
return Content;
}
return Contents!;
}
set
{
if (value is JsonElement str)
{
if (str.ValueKind == JsonValueKind.String)
{
Content = value?.ToString();
}
else if (str.ValueKind == JsonValueKind.Array)
{
Contents = JsonSerializer.Deserialize<IList<ThorChatMessageContent>>(value?.ToString());
}
}
else
{
Content = value?.ToString();
}
}
}
/// <summary>
/// 【可选】参与者的可选名称。提供模型信息以区分相同角色的参与者。
/// </summary>
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// 工具调用 ID,此消息正在响应的工具调用。
/// </summary>
[JsonPropertyName("tool_call_id")]
public string? ToolCallId { get; set; }
/// <summary>
/// 函数调用,已过期,不要使用,请使用 ToolCalls
/// </summary>
[JsonPropertyName("function_call")]
public ThorChatMessageFunction? FunctionCall { get; set; }
/// <summary>
/// 【可选】推理内容
/// </summary>
[JsonPropertyName("reasoning_content")]
public string? ReasoningContent { get; set; }
[JsonPropertyName("id")] public string? Id { get; set; }
/// <summary>
/// 工具调用列表,模型生成的工具调用,例如函数调用。<br/>
/// 此属性存储在客户端进行tool use 第一次调用模型返回的使用的函数名和传入的参数
/// </summary>
[JsonPropertyName("tool_calls")]
public List<ThorToolCall>? ToolCalls { get; set; }
/// <summary>
/// 创建系统消息
/// </summary>
/// <param name="content">系统消息内容</param>
/// <param name="name">参与者的可选名称。提供模型信息以区分同一角色的参与者。</param>
/// <returns></returns>
public static ThorChatMessage CreateSystemMessage(string content, string? name = null)
{
return new()
{
Role = ThorChatMessageRoleConst.System,
Content = content,
Name = name
};
}
/// <summary>
/// 创建用户消息
/// </summary>
/// <param name="content">系统消息内容</param>
/// <param name="name">参与者的可选名称。提供模型信息以区分同一角色的参与者。</param>
/// <returns></returns>
public static ThorChatMessage CreateUserMessage(string content, string? name = null)
{
return new()
{
Role = ThorChatMessageRoleConst.User,
Content = content,
Name = name
};
}
/// <summary>
/// 创建助手消息
/// </summary>
/// <param name="content">系统消息内容</param>
/// <param name="name">参与者的可选名称。提供模型信息以区分同一角色的参与者。</param>
/// <param name="toolCalls">工具调用参数列表</param>
/// <returns></returns>
public static ThorChatMessage CreateAssistantMessage(string content, string? name = null,
List<ThorToolCall> toolCalls = null)
{
return new()
{
Role = ThorChatMessageRoleConst.Assistant,
Content = content,
Name = name,
ToolCalls = toolCalls,
};
}
/// <summary>
/// 创建工具消息
/// </summary>
/// <param name="content">系统消息内容</param>
/// <param name="toolCallId">工具调用 ID,此消息正在响应的工具调用。</param>
/// <returns></returns>
public static ThorChatMessage CreateToolMessage(string content, string toolCallId = null)
{
return new()
{
Role = ThorChatMessageRoleConst.Tool,
Content = content,
ToolCallId = toolCallId
};
}
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public sealed class ThorChatMessageAudioContent
{
[JsonPropertyName("data")]
public string? Data { get; set; }
[JsonPropertyName("format")]
public string? Format { get; set; }
}

View File

@@ -0,0 +1,98 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 发出的消息内容包含图文一般是一文一图一文多图两种情况请使用CreeateXXX系列方法构建内容
/// </summary>
public class ThorChatMessageContent
{
public ThorChatMessageContent()
{
}
/// <summary>
/// 消息内容类型,只能使用<see cref="ThorMessageContentTypeConst"/> 定义的值赋值ThorMessageContentTypeConst.Text
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; }
/// <summary>
/// 消息内容类型为 text 时候的赋值,如:图片上描述了什么
/// </summary>
[JsonPropertyName("text")]
public string? Text { get; set; }
/// <summary>
/// 消息内容类型为 image_url 时候的赋值
/// </summary>
[JsonPropertyName("image_url")]
public ThorVisionImageUrl? ImageUrl { get; set; }
/// <summary>
/// 音频消息内容,包含音频数据和格式信息。
/// </summary>
[JsonPropertyName("input_audio")]
public ThorChatMessageAudioContent? InputAudio { get; set; }
/// <summary>
/// 创建文本类消息
/// <param name="text">文本内容</param>
/// </summary>
public static ThorChatMessageContent CreateTextContent(string text)
{
return new()
{
Type = ThorMessageContentTypeConst.Text,
Text = text
};
}
/// <summary>
/// 创建图片类消息图片url形式
/// <param name="imageUrl">图片 url</param>
/// <param name="detail">指定图像的详细程度。通过控制 detail 参数(该参数具有三个选项: low 、 high 或 auto ),您
/// 可以控制模型的处理方式图像并生成其文本理解。默认情况下,模型将使用 auto 设置,
/// 该设置将查看图像输入大小并决定是否应使用 low 或 high 设置。</param>
/// </summary>
public static ThorChatMessageContent CreateImageUrlContent(string imageUrl, string? detail = "auto")
{
return new()
{
Type = ThorMessageContentTypeConst.ImageUrl,
ImageUrl = new()
{
Url = imageUrl,
Detail = detail
}
};
}
/// <summary>
/// 创建图片类消息,字节流转base64字符串形式
/// <param name="binaryImage">The image binary data as byte array</param>
/// <param name="imageType">图片类型,如 png,jpg</param>
/// <param name="detail">指定图像的详细程度。</param>
/// </summary>
public static ThorChatMessageContent CreateImageBinaryContent(
byte[] binaryImage,
string imageType,
string? detail = "auto"
)
{
return new()
{
Type = ThorMessageContentTypeConst.ImageUrl,
ImageUrl = new()
{
Url = string.Format(
"data:image/{0};base64,{1}",
imageType,
Convert.ToBase64String(binaryImage)
),
Detail = detail
}
};
}
}

View File

@@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
{
/// <summary>
/// 模型调用的函数。
/// </summary>
public class ThorChatMessageFunction
{
/// <summary>
/// 功能名,如get_current_weather
/// </summary>
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// 调用函数所用的参数,由模型以 JSON 格式生成。请注意,该模型并不总是生成有效的 JSON
/// 并且可能会产生未由函数架构定义的参数。
/// 在调用函数之前验证代码中的参数。
/// 如:"{\"location\": \"San Francisco, USA\", \"format\": \"celsius\"}"
/// </summary>
[JsonPropertyName("arguments")]
public string? Arguments { get; set; }
/// <summary>
/// 转换参数为字典
/// </summary>
/// <returns></returns>
public Dictionary<string, object> ParseArguments()
{
var result = string.IsNullOrWhiteSpace(Arguments) == false ? JsonSerializer.Deserialize<Dictionary<string, object>>(Arguments) : new Dictionary<string, object>();
return result;
}
}
}

View File

@@ -0,0 +1,45 @@
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
{
/// <summary>
/// 对话消息角色定义
/// </summary>
public class ThorChatMessageRoleConst
{
/// <summary>
/// 系统角色
/// <para>
/// 用于为聊天助手分配特定的行为或上下文,以影响对话的模型行为。
/// 例如,可以将系统角色设定为“您是足球专家”,
/// 那么 ChatGPT 在对话中会表现出特定的个性或专业知识。
/// </para>
/// </summary>
public static string System => "system";
/// <summary>
/// 用户角色
/// <para>
/// 代表实际的最终用户,向 ChatGPT 发送提示或消息,
/// 用于指示消息/提示来自最终用户或人类。
/// </para>
/// </summary>
public static string User => "user";
/// <summary>
/// 助手角色
/// <para>
/// 表示对最终用户提示的响应实体,用于保持对话的连贯性。
/// 它是由模型自动生成并回复的,用于设置模型的先前响应,以继续对话流程。
/// </para>
/// </summary>
public static string Assistant => "assistant";
/// <summary>
/// 工具角色
/// <para>
/// 表示对最终用户提示的响应实体,用于保持对话的连贯性。
/// 它是由模型自动生成并回复的,用于设置模型的先前响应,以继续对话流程。
/// </para>
/// </summary>
public static string Tool => "tool";
}
}

View File

@@ -0,0 +1,35 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public class ThorChatWebSearchOptions
{
[JsonPropertyName("search_context_size")]
public string? SearchContextSize { get; set; }
[JsonPropertyName("user_location")]
public ThorUserLocation? UserLocation { get; set; }
}
public sealed class ThorUserLocation
{
[JsonPropertyName("type")] public required string Type { get; set; }
[JsonPropertyName("approximate")]
public ThorUserLocationApproximate? Approximate { get; set; }
}
public sealed class ThorUserLocationApproximate
{
[JsonPropertyName("city")]
public string? City { get; set; }
[JsonPropertyName("country")]
public string? Country { get; set; }
[JsonPropertyName("region")]
public string? Region { get; set; }
[JsonPropertyName("timezone")]
public string? Timezone { get; set; }
}

View File

@@ -0,0 +1,70 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
{
public class ThorError
{
/// <summary>
/// 错误码
/// </summary>
[JsonPropertyName("code")]
public string? Code { get; set; }
/// <summary>
/// 参数
/// </summary>
[JsonPropertyName("param")]
public string? Param { get; set; }
/// <summary>
/// 类型
/// </summary>
[JsonPropertyName("type")]
public string? Type { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonIgnore]
public string? Message { get; private set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonIgnore]
public List<string?> Messages { get; private set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonPropertyName("message")]
public object MessageObject
{
set
{
switch (value)
{
case string s:
Message = s;
Messages = new() { s };
break;
case List<object> list when list.All(i => i is JsonElement):
Messages = list.Cast<JsonElement>().Select(e => e.GetString()).ToList();
Message = string.Join(Environment.NewLine, Messages);
break;
}
}
get
{
if (Messages?.Count > 1)
{
return Messages;
}
return Message;
}
}
}
}

View File

@@ -0,0 +1,23 @@
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
{
/// <summary>
/// 支持图片识别的消息体内容类型
/// </summary>
public class ThorMessageContentTypeConst
{
/// <summary>
/// 文本内容
/// </summary>
public static string Text => "text";
/// <summary>
/// 图片 Url 类型
/// </summary>
public static string ImageUrl => "image_url";
/// <summary>
/// 图片 Url 类型
/// </summary>
public static string Image => "image";
}
}

View File

@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 指定模型必须输出的格式的对象。用于启用JSON模式。
/// </summary>
public class ThorResponseFormat
{
/// <summary>
/// 设置为json_object启用json模式。
/// 这保证了模型生成的消息是有效的JSON。
/// 注意如果finish_reason=“length”则消息内容可能是部分的
/// 这表示生成超过了max_tokens或对话超过了最大上下文长度。
/// </summary>
[JsonPropertyName("type")]
public string? Type { get; set; }
[JsonPropertyName("json_schema")]
public ThorResponseJsonSchema JsonSchema { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
public class ThorResponseJsonSchema
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("description")]
public string? Description { get; set; }
[JsonPropertyName("strict")]
public bool? Strict { get; set; }
[JsonPropertyName("schema")]
public object Schema { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
{
/// <summary>
/// 流响应选项。仅当您设置 stream: true 时才设置此项。
/// </summary>
public class ThorStreamOptions
{
/// <summary>
/// 如果设置,则会在 data: [DONE] 消息之前传输附加块。
/// 该块上的 usage 字段显示整个请求的令牌使用统计信息,
/// choices 字段将始终为空数组。所有其他块也将包含一个 usage 字段,但具有空值。
/// </summary>
[JsonPropertyName("include_usage")]
public bool? IncludeUsage { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 工具调用对象定义
/// </summary>
public class ThorToolCall
{
public ThorToolCall()
{
Id = Guid.NewGuid().ToString("N");
}
/// <summary>
/// 工具调用序号值
/// </summary>
[JsonPropertyName("index")]
public int Index { get; set; }
/// <summary>
/// 工具调用的 ID
/// </summary>
[JsonPropertyName("id")]
public string? Id { get; set; }
/// <summary>
/// 工具的类型。目前仅支持 function
/// </summary>
[JsonPropertyName("type")]
public string? Type { get; set; } = "function";
/// <summary>
/// 模型调用的函数。
/// </summary>
[JsonPropertyName("function")]
public ThorChatMessageFunction? Function { get; set; }
}

View File

@@ -0,0 +1,55 @@
using System.Text.Json.Serialization;
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
/// <summary>
/// 工具
/// </summary>
public class ThorToolChoice
{
/// <summary>
/// 表示模型不会调用任何工具
/// </summary>
public static ThorToolChoice GetNone() => new() { Type = ThorToolChoiceTypeConst.None };
/// <summary>
/// 表示模型可以在生成消息或调用一个或多个工具之间进行选择
/// </summary>
public static ThorToolChoice GetAuto() => new() { Type = ThorToolChoiceTypeConst.Auto };
/// <summary>
/// 表示模型必须调用一个或多个工具
/// </summary>
public static ThorToolChoice GetRequired() => new() { Type = ThorToolChoiceTypeConst.Required };
/// <summary>
/// 指定特定工具会强制模型调用该工具
/// </summary>
/// <param name="functionName">函数名</param>
/// <returns></returns>
public static ThorToolChoice GetFunction(string functionName) => new()
{
Type = ThorToolChoiceTypeConst.Function,
Function = new ThorToolChoiceFunctionTool()
{
Name = functionName
}
};
/// <summary>
/// "none" 表示模型不会调用任何工具<br />
/// "auto" 表示模型可以在生成消息或调用一个或多个工具之间进行选择 <br />
/// "required" 表示模型必须调用一个或多个工具 <br />
/// "function" 指定特定工具会强制模型调用该工具<br />
/// 使用<see cref="ThorToolChoiceTypeConst"/> 赋值
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; }
/// <summary>
/// 调用的函数定义
/// </summary>
[JsonPropertyName("function")]
public ThorToolChoiceFunctionTool? Function { get; set; }
}

Some files were not shown because too many files have changed in this diff Show More