mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-03 00:00:58 +08:00
Compare commits
200 Commits
fc5779225e
...
alipay
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a60bcc174 | ||
|
|
2b3fad16fd | ||
|
|
f0cf6bf5c8 | ||
|
|
0ba4e3240b | ||
|
|
9332b17fc1 | ||
|
|
4ec4023f40 | ||
|
|
d9971541f2 | ||
|
|
7b0e4fcc73 | ||
|
|
cfde73d13a | ||
|
|
c17c9000a8 | ||
|
|
42d537a68b | ||
|
|
25eebec8f7 | ||
|
|
bbe5b01872 | ||
|
|
6b31536de5 | ||
|
|
2e5db5500f | ||
|
|
7038d31c53 | ||
|
|
3eb27c3d35 | ||
|
|
a9c3a1bcec | ||
|
|
384926e73a | ||
|
|
4335c12659 | ||
|
|
e6e4829164 | ||
|
|
f3c67cf598 | ||
|
|
4681d468ce | ||
|
|
63e7d3d5f5 | ||
|
|
f47d8c8ce3 | ||
|
|
6f69f45ddc | ||
|
|
e73678c788 | ||
|
|
09a2f91cbf | ||
|
|
29da7499a4 | ||
|
|
5b024e9443 | ||
|
|
225932eff1 | ||
|
|
65d5f5ae86 | ||
|
|
3e647ef14d | ||
|
|
7cb3aea2e6 | ||
|
|
7f4b8f1c8a | ||
|
|
0a2710b865 | ||
|
|
2a301c4983 | ||
|
|
faa8131a1b | ||
|
|
71bd885bd0 | ||
|
|
691a1e50f0 | ||
|
|
ef6e9fd16d | ||
|
|
17f9ac6d54 | ||
|
|
3f8e6e48c0 | ||
|
|
bda4fdf69d | ||
|
|
5c85ed13fd | ||
|
|
1986901031 | ||
|
|
e1d3ec21e5 | ||
|
|
f45283dade | ||
|
|
31c44d8df7 | ||
|
|
bf443963c8 | ||
|
|
a0eb234539 | ||
|
|
b6d670c240 | ||
|
|
b5fb2c42c6 | ||
|
|
d72cc529ba | ||
|
|
660bd00cae | ||
|
|
b5489711ec | ||
|
|
76717c4f8a | ||
|
|
3d53d0bcd6 | ||
|
|
c7c9428b68 | ||
|
|
991a970d6a | ||
|
|
cbe93b9f7e | ||
|
|
5d7217b775 | ||
|
|
d6836b8bcf | ||
|
|
c367651c78 | ||
|
|
77123fd971 | ||
|
|
9d73a6837b | ||
|
|
651f0157dc | ||
|
|
90e8dbe449 | ||
|
|
ccba2667bc | ||
|
|
3ce9fc9790 | ||
|
|
2f24dd77bf | ||
|
|
2bc07cb3df | ||
|
|
30678dbbb4 | ||
|
|
c5b0f69b51 | ||
|
|
e593f2cba4 | ||
|
|
10f7499066 | ||
|
|
36b7e495f7 | ||
|
|
94b96e3c19 | ||
|
|
0d1ee18da0 | ||
|
|
cab0b61ee0 | ||
|
|
8e6611d76d | ||
|
|
1ef82e5f93 | ||
|
|
43dc962606 | ||
|
|
020d674ca2 | ||
|
|
bb0e1081cc | ||
|
|
5162f9ce3b | ||
|
|
57fae7fe4b | ||
|
|
17412d7de7 | ||
|
|
d59f40dfba | ||
|
|
5953be63cb | ||
|
|
58a4311947 | ||
|
|
c5a9b9a15f | ||
|
|
716c344780 | ||
|
|
9af8c4897b | ||
|
|
ca72024a68 | ||
|
|
0d2bc585a9 | ||
|
|
4e3edefb35 | ||
|
|
9408242726 | ||
|
|
4710208e81 | ||
|
|
4fc6a1e818 | ||
|
|
c9b79a074b | ||
|
|
2e79eb346f | ||
|
|
3f88bd4158 | ||
|
|
f58e079741 | ||
|
|
6f1eb1f4b9 | ||
|
|
826d529997 | ||
|
|
43e60eab4a | ||
|
|
6c33024790 | ||
|
|
d27e625fde | ||
|
|
23cecb9360 | ||
|
|
7e4c835ced | ||
|
|
aff460f555 | ||
|
|
52961b459e | ||
|
|
0af2f867fc | ||
|
|
85e291e0b8 | ||
|
|
6d8a859b20 | ||
|
|
a70dfb0769 | ||
|
|
c637d412e6 | ||
|
|
e996bc2d7f | ||
|
|
15be047371 | ||
|
|
0a0e0bca10 | ||
|
|
9a8f3bd161 | ||
|
|
7e2c035692 | ||
|
|
44b2ade9bc | ||
|
|
1200d02fbf | ||
|
|
b020f48325 | ||
|
|
917857f1ff | ||
|
|
69d8ff1034 | ||
|
|
9a334101ca | ||
|
|
ee53b3d9c4 | ||
|
|
01a5ad5302 | ||
|
|
f12f0e1f84 | ||
|
|
6aefcdbed8 | ||
|
|
3d22a2ef65 | ||
|
|
a33c6dbf1a | ||
|
|
2b7c779e14 | ||
|
|
0e36f7c0b3 | ||
|
|
228a309545 | ||
|
|
b15ad8eb5e | ||
|
|
a525735b0b | ||
|
|
6a58af8dfb | ||
|
|
0089e63832 | ||
|
|
d4f00eb89f | ||
|
|
d15e6e395b | ||
|
|
39eb4bef07 | ||
|
|
03de576d8c | ||
|
|
216b57a4c7 | ||
|
|
5383d2d40e | ||
|
|
1d7a2013e3 | ||
|
|
24d2908cca | ||
|
|
330845a387 | ||
|
|
bbedd01a72 | ||
|
|
2b07061c18 | ||
|
|
01a3c81359 | ||
|
|
96e275efa6 | ||
|
|
12eb6c73c3 | ||
|
|
4166eddd28 | ||
|
|
6ea1592c19 | ||
|
|
f8799a073c | ||
|
|
0eb83fc930 | ||
|
|
a5dd3946f8 | ||
|
|
2732df24af | ||
|
|
f0ae27a50b | ||
|
|
c5037ea397 | ||
|
|
695aaedfba | ||
|
|
4f71d874bd | ||
|
|
c69729fadd | ||
|
|
64d04996af | ||
|
|
8eea510583 | ||
|
|
04c2b246f6 | ||
|
|
a46eb176d7 | ||
|
|
2bea88f1a3 | ||
|
|
06617de984 | ||
|
|
6459d7c024 | ||
|
|
bd4af8039f | ||
|
|
8aaa22cea3 | ||
|
|
7d902682f8 | ||
|
|
a81be99100 | ||
|
|
b6dfe93d2c | ||
|
|
35aa022984 | ||
|
|
dfe2d4cc37 | ||
|
|
1d16502d32 | ||
|
|
25c88187a3 | ||
|
|
ac04e846fa | ||
|
|
29985e2118 | ||
|
|
3b74dfd49a | ||
|
|
6abcc49ed4 | ||
|
|
f16e1cd7a6 | ||
|
|
4341b8a24b | ||
|
|
a89e11d132 | ||
|
|
bc91a8cff2 | ||
|
|
8040010b98 | ||
|
|
b39f15c798 | ||
|
|
c3cf49c63e | ||
|
|
899bd7e316 | ||
|
|
890727d495 | ||
|
|
8a8e69596a | ||
|
|
58fcc92e4d | ||
|
|
0cd795f57a | ||
|
|
4830be6388 |
@@ -186,6 +186,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.Stock.Domain.S
|
||||
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
|
||||
@@ -468,6 +480,26 @@ Global
|
||||
{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
|
||||
@@ -551,6 +583,12 @@ Global
|
||||
{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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Yi.Framework.Core.Authentication;
|
||||
|
||||
public interface IRefreshAuthenticationHandlerProvider: IAuthenticationHandlerProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 刷新鉴权
|
||||
/// </summary>
|
||||
void RefreshAuthentication();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -58,5 +58,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
|
||||
/// 是否启用SaaS多租户
|
||||
/// </summary>
|
||||
public bool EnabledSaasMultiTenancy { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 并发乐观锁异常,否则不处理
|
||||
/// </summary>
|
||||
public bool EnabledConcurrencyException { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
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;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Linq;
|
||||
@@ -15,14 +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()
|
||||
{
|
||||
public ISqlSugarClient _Db => AsyncContext.Run(async () => await GetDbContextAsync());
|
||||
|
||||
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>
|
||||
@@ -58,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);
|
||||
}
|
||||
@@ -103,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();
|
||||
@@ -171,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);
|
||||
@@ -211,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);
|
||||
@@ -230,7 +253,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).DeleteAsync(deleteObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual async Task<bool> DeleteAsync(List<TEntity> deleteObjs)
|
||||
@@ -250,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)
|
||||
@@ -264,6 +286,11 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//反射赋值
|
||||
ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
|
||||
return await UpdateAsync(entity);
|
||||
@@ -284,6 +311,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//反射赋值
|
||||
entities.ForEach(e => ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, e));
|
||||
return await UpdateRangeAsync(entities);
|
||||
@@ -292,7 +320,6 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
{
|
||||
return await (await GetDbSimpleClientAsync()).DeleteByIdAsync(ids);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> GetByIdAsync(dynamic id)
|
||||
@@ -301,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);
|
||||
@@ -317,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 GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel() { PageIndex = pageNum, PageSize = 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 GetDbSimpleClientAsync()).GetPageListAsync(whereExpression, new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
|
||||
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)
|
||||
@@ -379,30 +410,40 @@ namespace Yi.Framework.SqlSugarCore.Repositories
|
||||
|
||||
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
|
||||
{
|
||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>())//带版本号乐观锁更新
|
||||
if (typeof(TEntity).IsAssignableTo<IHasConcurrencyStamp>()) //带版本号乐观锁更新
|
||||
{
|
||||
try
|
||||
if (Options is not null && Options.EnabledConcurrencyException)
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||
return num>0;
|
||||
try
|
||||
{
|
||||
int num = await (await GetDbSimpleClientAsync())
|
||||
.Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true);
|
||||
return num > 0;
|
||||
}
|
||||
catch (VersionExceptions ex)
|
||||
{
|
||||
throw new AbpDbConcurrencyException(
|
||||
$"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||
}
|
||||
}
|
||||
catch (VersionExceptions ex)
|
||||
else
|
||||
{
|
||||
|
||||
throw new AbpDbConcurrencyException($"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex);
|
||||
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);
|
||||
@@ -411,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Pay;
|
||||
|
||||
/// <summary>
|
||||
/// 创建订单输入DTO
|
||||
/// </summary>
|
||||
public class CreateOrderInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品类型
|
||||
/// </summary>
|
||||
public GoodsTypeEnum GoodsType { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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)
|
||||
/// </summary>
|
||||
public DateTime? ExpireDateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
[StringLength(1000, ErrorMessage = "备注不能超过1000个字符")]
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 联系方式
|
||||
/// </summary>
|
||||
[StringLength(200, ErrorMessage = "联系方式不能超过200个字符")]
|
||||
public string? ContactInfo { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.Dtos.Token;
|
||||
|
||||
public class TokenOutput
|
||||
{
|
||||
public string? ApiKey { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Yi.Framework.AiHub.Application.Contracts.IServices;
|
||||
|
||||
public interface IRechargeService
|
||||
{
|
||||
/// <summary>
|
||||
/// 移除用户vip及角色
|
||||
/// </summary>
|
||||
Task RemoveVipRoleByExpireAsync();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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>>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Application.Services;
|
||||
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.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.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;
|
||||
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
|
||||
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
|
||||
ISqlSugarRepository<AiModelEntity> aiModelRepository, AiBlacklistManager aiBlacklistManager)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_logger = logger;
|
||||
_tokenManager = tokenManager;
|
||||
_aiGateWayManager = aiGateWayManager;
|
||||
_aiModelRepository = aiModelRepository;
|
||||
_aiBlacklistManager = aiBlacklistManager;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
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;
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public PayService(
|
||||
AlipayManager alipayManager,
|
||||
PayManager payManager,
|
||||
ILogger<PayService> logger, ISqlSugarRepository<PayOrderAggregateRoot, Guid> payOrderRepository)
|
||||
{
|
||||
_alipayManager = alipayManager;
|
||||
_payManager = payManager;
|
||||
_logger = logger;
|
||||
_payOrderRepository = payOrderRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建订单并发起支付
|
||||
/// </summary>
|
||||
/// <param name="input">创建订单输入</param>
|
||||
/// <returns>订单创建结果</returns>
|
||||
[Authorize]
|
||||
[HttpPost("pay/Order")]
|
||||
public async Task<CreateOrderOutput> CreateOrderAsync(CreateOrderInput input)
|
||||
{
|
||||
// 1. 通过PayManager创建订单
|
||||
var order = await _payManager.CreateOrderAsync(input.GoodsType);
|
||||
|
||||
// 2. 通过AlipayManager发起页面支付
|
||||
var paymentPageHtml = await _alipayManager.PaymentPageAsync(
|
||||
order.GoodsName,
|
||||
order.OutTradeNo,
|
||||
order.TotalAmount);
|
||||
|
||||
// 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);
|
||||
await _payManager.UpdateOrderStatusAsync(outTradeNo, status, tradeNo);
|
||||
|
||||
_logger.LogInformation("订单状态更新成功,订单号:{OutTradeNo},状态:{TradeStatus}", outTradeNo, tradeStatus);
|
||||
}
|
||||
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>
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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)
|
||||
{
|
||||
// 创建充值记录
|
||||
var rechargeRecord = new AiRechargeAggregateRoot
|
||||
{
|
||||
UserId = input.UserId,
|
||||
RechargeAmount = input.RechargeAmount,
|
||||
Content = input.Content,
|
||||
ExpireDateTime = input.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>>());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
|
||||
public class AiHubConst
|
||||
{
|
||||
public const string VipRole = "YiXinAi-Vip";
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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>";
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
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>
|
||||
/// 兼容-代码补全
|
||||
/// </summary>
|
||||
[JsonPropertyName("suffix")]
|
||||
public string? Suffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 兼容-代码补全
|
||||
/// </summary>
|
||||
[JsonPropertyName("prompt")]
|
||||
public string? Prompt { get; set; }
|
||||
|
||||
private const string CodeCompletionPrompt = """
|
||||
You are a code modification assistant. Your task is to modify the provided code based on the user's instructions.
|
||||
|
||||
Rules:
|
||||
1. Return only the modified code, with no additional text or explanations.
|
||||
2. The first character of your response must be the first character of the code.
|
||||
3. The last character of your response must be the last character of the code.
|
||||
4. NEVER use triple backticks (```) or any other markdown formatting in your response.
|
||||
5. Do not use any code block indicators, syntax highlighting markers, or any other formatting characters.
|
||||
6. Present the code exactly as it would appear in a plain text editor, preserving all whitespace, indentation, and line breaks.
|
||||
7. Maintain the original code structure and only make changes as specified by the user's instructions.
|
||||
8. Ensure that the modified code is syntactically and semantically correct for the given programming language.
|
||||
9. Use consistent indentation and follow language-specific style guidelines.
|
||||
10. If the user's request cannot be translated into code changes, respond only with the word NULL (without quotes or any formatting).
|
||||
11. Do not include any comments or explanations within the code unless specifically requested.
|
||||
12. Assume that any necessary dependencies or libraries are already imported or available.
|
||||
|
||||
IMPORTANT: Your response must NEVER begin or end with triple backticks, single backticks, or any other formatting characters.
|
||||
|
||||
The relevant context before the current editing content is: {0}.
|
||||
After the current editing content is: {1}.
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 兼容代码补全
|
||||
/// </summary>
|
||||
public void CompatibleCodeCompletion()
|
||||
{
|
||||
if (Messages is null || !Messages.Any())
|
||||
{
|
||||
//兼容代码补全模式,Prompt为当前代码前内容,Suffix为当前代码后内容
|
||||
Messages = new List<ThorChatMessage>()
|
||||
{
|
||||
new ThorChatMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = string.Format(CodeCompletionPrompt, Prompt, Suffix)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Suffix = null;
|
||||
Prompt = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模型唯一编码值,如 gpt-4,gpt-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; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数验证
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public IEnumerable<ValidationResult> Validate()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
|
||||
{
|
||||
public class ThorToolChoiceFunctionTool
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
|
||||
{
|
||||
public class ThorToolChoiceTypeConst
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定特定工具会强制模型调用该工具
|
||||
/// </summary>
|
||||
public static string Function => "function";
|
||||
|
||||
/// <summary>
|
||||
/// 表示模型可以在生成消息或调用一个或多个工具之间进行选择
|
||||
/// </summary>
|
||||
public static string Auto => "auto";
|
||||
|
||||
/// <summary>
|
||||
/// 表示模型不会调用任何工具
|
||||
/// </summary>
|
||||
public static string None => "none";
|
||||
|
||||
/// <summary>
|
||||
/// 表示模型必须调用一个或多个工具
|
||||
/// </summary>
|
||||
public static string Required => "required ";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
/// <summary>
|
||||
/// 有效工具的定义。
|
||||
/// </summary>
|
||||
public class ThorToolDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 必修的。工具的类型。目前仅支持 function 。
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = ThorToolTypeConst.Function;
|
||||
|
||||
/// <summary>
|
||||
/// 函数对象
|
||||
/// </summary>
|
||||
[JsonPropertyName("function")]
|
||||
public ThorToolFunctionDefinition? Function { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建函数工具
|
||||
/// </summary>
|
||||
/// <param name="function"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolDefinition CreateFunctionTool(ThorToolFunctionDefinition function) => new()
|
||||
{
|
||||
Type = ThorToolTypeConst.Function,
|
||||
Function = function
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
/// <summary>
|
||||
/// 有效函数调用的定义。
|
||||
/// </summary>
|
||||
public class ThorToolFunctionDefinition
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 要调用的函数的名称。必须是 a-z、A-Z、0-9 或包含下划线和破折号,最大长度为 64。
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 函数功能的描述,模型使用它来选择何时以及如何调用函数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 函数接受的参数,描述为 JSON 架构对象。有关示例,请参阅指南,有关格式的文档,请参阅 JSON 架构参考。
|
||||
/// 省略 parameters 定义一个参数列表为空的函数。
|
||||
/// See the <a href="https://platform.openai.com/docs/guides/gpt/function-calling">guide</a> for examples,
|
||||
/// and the <a href="https://json-schema.org/understanding-json-schema/">JSON Schema reference</a> for
|
||||
/// documentation about the format.
|
||||
/// </summary>
|
||||
[JsonPropertyName("parameters")]
|
||||
public ThorToolFunctionPropertyDefinition Parameters { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
/// <summary>
|
||||
/// 函数参数是JSON格式对象
|
||||
/// https://json-schema.org/understanding-json-schema/reference/object.html
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// 定义属性示例:
|
||||
/// [JsonPropertyName("location")]
|
||||
/// public ThorToolFunctionPropertyDefinition Location = ThorToolFunctionPropertyDefinition.DefineString("The city and state, e.g. San Francisco, CA");
|
||||
///
|
||||
/// [JsonPropertyName("unit")]
|
||||
/// public ThorToolFunctionPropertyDefinition Unit = ThorToolFunctionPropertyDefinition.DefineEnum(["celsius", "fahrenheit"]);
|
||||
/// </example>
|
||||
public class ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义了函数对象的类型枚举
|
||||
/// </summary>
|
||||
public enum FunctionObjectTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示字符串类型的函数对象
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// 表示整数类型的函数对象
|
||||
/// </summary>
|
||||
Integer,
|
||||
/// <summary>
|
||||
/// 表示数字(包括浮点数等)类型的函数对象
|
||||
/// </summary>
|
||||
Number,
|
||||
/// <summary>
|
||||
/// 表示对象类型的函数对象
|
||||
/// </summary>
|
||||
Object,
|
||||
/// <summary>
|
||||
/// 表示数组类型的函数对象
|
||||
/// </summary>
|
||||
Array,
|
||||
/// <summary>
|
||||
/// 表示布尔类型的函数对象
|
||||
/// </summary>
|
||||
Boolean,
|
||||
/// <summary>
|
||||
/// 表示空值类型的函数对象
|
||||
/// </summary>
|
||||
Null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 必填的。函数参数对象类型。默认值为“object”。
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = "object";
|
||||
|
||||
/// <summary>
|
||||
/// 可选。“函数参数”列表,作为从参数名称映射的字典
|
||||
/// 对于描述类型的对象,可能还有可能的枚举值等等。
|
||||
/// </summary>
|
||||
[JsonPropertyName("properties")]
|
||||
public IDictionary<string, ThorToolFunctionPropertyDefinition>? Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选。列出必需的“function arguments”列表。
|
||||
/// </summary>
|
||||
[JsonPropertyName("required")]
|
||||
public List<string>? Required { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选。是否允许附加属性。默认值为true。
|
||||
/// </summary>
|
||||
[JsonPropertyName("additionalProperties")]
|
||||
public bool? AdditionalProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选。参数描述。
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选。此参数的允许值列表。
|
||||
/// </summary>
|
||||
[JsonPropertyName("enum")]
|
||||
public List<string>? Enum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可以使用minProperties和maxProperties关键字限制对象上的属性数量。每一个
|
||||
/// 这些必须是非负整数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("minProperties")]
|
||||
public int? MinProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可以使用minProperties和maxProperties关键字限制对象上的属性数量。每一个
|
||||
/// 这些必须是非负整数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxProperties")]
|
||||
public int? MaxProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果type为“array”,则指定数组中所有项目的元素类型。
|
||||
/// 如果类型不是“array”,则应为null。
|
||||
/// 有关更多详细信息,请参阅 https://json-schema.org/understanding-json-schema/reference/array.html
|
||||
/// </summary>
|
||||
[JsonPropertyName("items")]
|
||||
public ThorToolFunctionPropertyDefinition? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 定义数组
|
||||
/// </summary>
|
||||
/// <param name="arrayItems"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineArray(ThorToolFunctionPropertyDefinition? arrayItems = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Items = arrayItems,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Array)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义枚举
|
||||
/// </summary>
|
||||
/// <param name="enumList"></param>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineEnum(List<string> enumList, string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Enum = enumList,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.String)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义整型
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineInteger(string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Integer)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义数字
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineNumber(string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Number)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义字符串
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineString(string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.String)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义布尔值
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineBoolean(string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Boolean)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义null
|
||||
/// </summary>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineNull(string? description = null)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Description = description,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Null)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义对象
|
||||
/// </summary>
|
||||
/// <param name="properties"></param>
|
||||
/// <param name="required"></param>
|
||||
/// <param name="additionalProperties"></param>
|
||||
/// <param name="description"></param>
|
||||
/// <param name="enum"></param>
|
||||
/// <returns></returns>
|
||||
public static ThorToolFunctionPropertyDefinition DefineObject(IDictionary<string, ThorToolFunctionPropertyDefinition>? properties,
|
||||
List<string>? required,
|
||||
bool? additionalProperties,
|
||||
string? description,
|
||||
List<string>? @enum)
|
||||
{
|
||||
return new ThorToolFunctionPropertyDefinition
|
||||
{
|
||||
Properties = properties,
|
||||
Required = required,
|
||||
AdditionalProperties = additionalProperties,
|
||||
Description = description,
|
||||
Enum = @enum,
|
||||
Type = ConvertTypeToString(FunctionObjectTypes.Object)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将 `FunctionObjectTypes` 枚举值转换为其对应的字符串表示形式。
|
||||
/// </summary>
|
||||
/// <param name="type">要转换的类型</param>
|
||||
/// <returns>给定类型的字符串表示形式</returns>
|
||||
|
||||
public static string ConvertTypeToString(FunctionObjectTypes type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
FunctionObjectTypes.String => "string",
|
||||
FunctionObjectTypes.Integer => "integer",
|
||||
FunctionObjectTypes.Number => "number",
|
||||
FunctionObjectTypes.Object => "object",
|
||||
FunctionObjectTypes.Array => "array",
|
||||
FunctionObjectTypes.Boolean => "boolean",
|
||||
FunctionObjectTypes.Null => "null",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unknown type: {type}")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具类型定义
|
||||
/// </summary>
|
||||
public class ThorToolTypeConst
|
||||
{
|
||||
/// <summary>
|
||||
/// 函数
|
||||
/// </summary>
|
||||
public static string Function => "function";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
/// <summary>
|
||||
/// 统计信息模型
|
||||
/// </summary>
|
||||
public record ThorUsageResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 提示中的令牌数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("prompt_tokens")]
|
||||
public int? PromptTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("input_tokens")]
|
||||
public int? InputTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("output_tokens")]
|
||||
public int? OutputTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("input_tokens_details")]
|
||||
public ThorUsageResponseInputTokensDetails? InputTokensDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成的完成中的令牌数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("completion_tokens")]
|
||||
public long? CompletionTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求中使用的令牌总数(提示 + 完成)。
|
||||
/// </summary>
|
||||
[JsonPropertyName("total_tokens")]
|
||||
public long? TotalTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ThorUsageResponsePromptTokensDetails
|
||||
/// </summary>
|
||||
[JsonPropertyName("prompt_tokens_details")]
|
||||
public ThorUsageResponsePromptTokensDetails? PromptTokensDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ThorUsageResponseCompletionTokensDetails
|
||||
/// </summary>
|
||||
[JsonPropertyName("completion_tokens_details")]
|
||||
public ThorUsageResponseCompletionTokensDetails? CompletionTokensDetails { get; set; }
|
||||
}
|
||||
|
||||
public class ThorUsageResponseInputTokensDetails
|
||||
{
|
||||
[JsonPropertyName("image_tokens")]
|
||||
public int? ImageTokens { get; set; }
|
||||
|
||||
[JsonPropertyName("text_tokens")]
|
||||
public int? TextTokens { get; set; }
|
||||
}
|
||||
|
||||
public record ThorUsageResponsePromptTokensDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存的令牌数。
|
||||
/// </summary>
|
||||
[JsonPropertyName("cached_tokens")]
|
||||
public int? CachedTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// audio_tokens
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio_tokens")]
|
||||
public int? AudioTokens { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// completion_tokens_details
|
||||
/// </summary>
|
||||
public record ThorUsageResponseCompletionTokensDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 Predicted Outputs 时, Prediction 的 Final。
|
||||
/// </summary>
|
||||
[JsonPropertyName("accepted_prediction_tokens")]
|
||||
public int? AcceptedPredictionTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型生成的音频输入令牌。
|
||||
/// </summary>
|
||||
[JsonPropertyName("audio_tokens")]
|
||||
public int? AudioTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型生成的用于推理的 Token。
|
||||
/// </summary>
|
||||
[JsonPropertyName("reasoning_tokens")]
|
||||
public int? ReasoningTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Predicted Outputs 时, 预测,但未出现在 completion 中。但是,与 reasoning 令牌,这些令牌仍然计入总数 用于 Billing、Output 和 Context Window 的完成令牌 限制。
|
||||
/// </summary>
|
||||
[JsonPropertyName("rejected_prediction_tokens")]
|
||||
public int? RejectedPredictionTokens { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
/// <summary>
|
||||
/// 图片消息内容对象
|
||||
/// </summary>
|
||||
public class ThorVisionImageUrl
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片的url地址,如:https://localhost/logo.jpg ,一般只支持 .png , .jpg .webp .gif
|
||||
/// 也可以是base64字符串,如:data:image/jpeg;base64,{base64_image}
|
||||
/// 要看底层平台具体要求
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 指定图像的细节级别。在愿景指南中了解更多信息。https://platform.openai.com/docs/guides/vision/low-or-high-fidelity-image-understanding
|
||||
/// <para>
|
||||
/// 指定图像的详细程度。通过控制 detail 参数(该参数具有三个选项: low 、 high 或 auto ),您
|
||||
/// 可以控制模型的处理方式图像并生成其文本理解。默认情况下,模型将使用 auto 设置,
|
||||
/// 该设置将查看图像输入大小并决定是否应使用 low 或 high 设置。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("detail")]
|
||||
public string? Detail { get; set; } = "auto";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
public class TokenUsage
|
||||
{
|
||||
public int OutputTokenCount { get; set; }
|
||||
|
||||
public int InputTokenCount { get; set; }
|
||||
|
||||
public int TotalTokenCount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// 价格特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class PriceAttribute : Attribute
|
||||
{
|
||||
public decimal Price { get; }
|
||||
|
||||
public PriceAttribute(double price)
|
||||
{
|
||||
Price = (decimal)price;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DisplayNameAttribute : Attribute
|
||||
{
|
||||
public string DisplayName { get; }
|
||||
|
||||
public DisplayNameAttribute(string displayName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 商品枚举
|
||||
/// </summary>
|
||||
public enum GoodsTypeEnum
|
||||
{
|
||||
[Price(0.01)]
|
||||
[DisplayName("YiXinVip Test")]
|
||||
YiXinVipTest = 0,
|
||||
|
||||
[Price(29.9)]
|
||||
[DisplayName("YiXinVip 1 month")]
|
||||
YiXinVip1 = 1,
|
||||
|
||||
[Price(80.7)]
|
||||
[DisplayName("YiXinVip 3 month")]
|
||||
YiXinVip3 = 3,
|
||||
|
||||
[Price(143.9)]
|
||||
[DisplayName("YiXinVip 6 month")]
|
||||
YiXinVip6 = 6,
|
||||
|
||||
[Price(199.9)]
|
||||
[DisplayName("YiXinVip 10 month")]
|
||||
YiXinVip10 = 10
|
||||
}
|
||||
|
||||
public static class GoodsTypeEnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取商品总金额
|
||||
/// </summary>
|
||||
/// <param name="goodsType">商品类型</param>
|
||||
/// <returns>总金额</returns>
|
||||
public static decimal GetTotalAmount(this GoodsTypeEnum goodsType)
|
||||
{
|
||||
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||
var priceAttribute = fieldInfo?.GetCustomAttribute<PriceAttribute>();
|
||||
return priceAttribute?.Price ?? 0m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品价格描述
|
||||
/// </summary>
|
||||
/// <param name="goodsType">商品类型</param>
|
||||
/// <returns>价格描述</returns>
|
||||
public static string GetPriceDescription(this GoodsTypeEnum goodsType)
|
||||
{
|
||||
var price = goodsType.GetTotalAmount();
|
||||
return $"¥{price:F1}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品名称
|
||||
/// </summary>
|
||||
/// <param name="goodsType">商品类型</param>
|
||||
/// <returns>商品名称</returns>
|
||||
public static string GetDisplayName(this GoodsTypeEnum goodsType)
|
||||
{
|
||||
var fieldInfo = goodsType.GetType().GetField(goodsType.ToString());
|
||||
var displayNameAttribute = fieldInfo?.GetCustomAttribute<DisplayNameAttribute>();
|
||||
return displayNameAttribute?.DisplayName ?? goodsType.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
public enum MessageTypeEnum
|
||||
{
|
||||
Web = 1,
|
||||
Api = 2
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
public enum ModelTypeEnum
|
||||
{
|
||||
Chat = 0,
|
||||
Image = 1,
|
||||
Embedding = 2
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
public enum TradeStatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 准备发起
|
||||
/// </summary>
|
||||
[Description("准备发起")]
|
||||
WAIT_TRADE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 交易创建
|
||||
/// </summary>
|
||||
[Description("等待买家付款")]
|
||||
WAIT_BUYER_PAY = 10,
|
||||
|
||||
/// <summary>
|
||||
/// 交易关闭
|
||||
/// </summary>
|
||||
[Description("交易关闭")]
|
||||
TRADE_CLOSED = 20,
|
||||
|
||||
/// <summary>
|
||||
/// 交易成功
|
||||
/// </summary>
|
||||
[Description("交易成功")]
|
||||
TRADE_SUCCESS = 100,
|
||||
|
||||
/// <summary>
|
||||
/// 交易结束
|
||||
/// </summary>
|
||||
[Description("交易结束")]
|
||||
TRADE_FINISHED = -10
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\common.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="$(AbpVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Etos\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Volo.Abp.Domain;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Shared
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(AbpDddDomainSharedModule))]
|
||||
public class YiFrameworkAiHubDomainSharedModule : AbpModule
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
|
||||
public sealed class PaymentRequiredException() : Exception()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
|
||||
public class ThorRateLimitException : Exception
|
||||
{
|
||||
public ThorRateLimitException()
|
||||
{
|
||||
}
|
||||
|
||||
public ThorRateLimitException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public static class HttpClientExtensions
|
||||
{
|
||||
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
|
||||
object? postData,
|
||||
string token)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
|
||||
object? postData,
|
||||
string token, string tokenKey)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add(tokenKey, token);
|
||||
}
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
|
||||
object? postData,
|
||||
string token, Dictionary<string, string> headers)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
foreach (var header in headers)
|
||||
{
|
||||
req.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
|
||||
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, HttpRequestMessage req,
|
||||
object? postData)
|
||||
{
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url,
|
||||
object? postData,
|
||||
string token)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
var stringContent =
|
||||
new StringContent(JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions),
|
||||
Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
return await httpClient.SendAsync(req);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url,
|
||||
object? postData,
|
||||
string token, Dictionary<string, string> headers)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
foreach (var header in headers)
|
||||
{
|
||||
req.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return await httpClient.SendAsync(req);
|
||||
}
|
||||
|
||||
public static Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url, object? postData,
|
||||
string token, string tokenKey)
|
||||
{
|
||||
HttpRequestMessage req = new(HttpMethod.Post, url);
|
||||
|
||||
if (postData != null)
|
||||
{
|
||||
if (postData is HttpContent data)
|
||||
{
|
||||
req.Content = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
|
||||
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
req.Content = stringContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
req.Headers.Add(tokenKey, token);
|
||||
}
|
||||
|
||||
return httpClient.SendAsync(req);
|
||||
}
|
||||
|
||||
|
||||
public static async Task<TResponse> PostAndReadAsAsync<TResponse>(this HttpClient client, string uri,
|
||||
object? requestModel, CancellationToken cancellationToken = default) where TResponse : ThorBaseResponse, new()
|
||||
{
|
||||
var response = await client.PostAsJsonAsync(uri, requestModel, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
|
||||
}, cancellationToken);
|
||||
return await HandleResponseContent<TResponse>(response, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<TResponse> PostFileAndReadAsAsync<TResponse>(this HttpClient client, string uri,
|
||||
HttpContent content, CancellationToken cancellationToken = default) where TResponse : ThorBaseResponse, new()
|
||||
{
|
||||
var response = await client.PostAsync(uri, content, cancellationToken);
|
||||
return await HandleResponseContent<TResponse>(response, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<string> PostFileAndReadAsStringAsync(this HttpClient client, string uri,
|
||||
HttpContent content, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await client.PostAsync(uri, content, cancellationToken);
|
||||
return await response.Content.ReadAsStringAsync(cancellationToken) ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public static async Task<TResponse> DeleteAndReadAsAsync<TResponse>(this HttpClient client, string uri,
|
||||
CancellationToken cancellationToken = default) where TResponse : ThorBaseResponse, new()
|
||||
{
|
||||
var response = await client.DeleteAsync(uri, cancellationToken);
|
||||
return await HandleResponseContent<TResponse>(response, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<TResponse> HandleResponseContent<TResponse>(this HttpResponseMessage response,
|
||||
CancellationToken cancellationToken) where TResponse : ThorBaseResponse, new()
|
||||
{
|
||||
TResponse result;
|
||||
|
||||
if (!response.Content.Headers.ContentType?.MediaType?.Equals("application/json",
|
||||
StringComparison.OrdinalIgnoreCase) ?? true)
|
||||
{
|
||||
result = new()
|
||||
{
|
||||
Error = new()
|
||||
{
|
||||
MessageObject = await response.Content.ReadAsStringAsync(cancellationToken)
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken) ??
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public static class HttpClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// HttpClient池总数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static int _poolSize;
|
||||
|
||||
private static int PoolSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_poolSize == 0)
|
||||
{
|
||||
// 获取环境变量
|
||||
var poolSize = Environment.GetEnvironmentVariable("HttpClientPoolSize");
|
||||
if (!string.IsNullOrEmpty(poolSize) && int.TryParse(poolSize, out var size))
|
||||
{
|
||||
_poolSize = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
_poolSize = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
if (_poolSize < 1)
|
||||
{
|
||||
_poolSize = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return _poolSize;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Lazy<List<HttpClient>>> HttpClientPool = new();
|
||||
|
||||
public static HttpClient GetHttpClient(string key)
|
||||
{
|
||||
return HttpClientPool.GetOrAdd(key, k => new Lazy<List<HttpClient>>(() =>
|
||||
{
|
||||
var clients = new List<HttpClient>(PoolSize);
|
||||
|
||||
for (var i = 0; i < PoolSize; i++)
|
||||
{
|
||||
clients.Add(new HttpClient(new SocketsHttpHandler
|
||||
{
|
||||
PooledConnectionLifetime = TimeSpan.FromMinutes(30),
|
||||
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(30),
|
||||
EnableMultipleHttp2Connections = true,
|
||||
// 连接超时5分钟
|
||||
ConnectTimeout = TimeSpan.FromMinutes(5),
|
||||
MaxAutomaticRedirections = 3,
|
||||
AllowAutoRedirect = true,
|
||||
Expect100ContinueTimeout = TimeSpan.FromMinutes(30),
|
||||
})
|
||||
{
|
||||
Timeout = TimeSpan.FromMinutes(30),
|
||||
DefaultRequestHeaders =
|
||||
{
|
||||
{ "User-Agent", "yxai" },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return clients;
|
||||
})).Value[new Random().Next(0, PoolSize)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public interface IChatCompletionService
|
||||
{
|
||||
/// <summary>
|
||||
/// 聊天完成-流式
|
||||
/// </summary>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public IAsyncEnumerable<ThorChatCompletionsResponse> CompleteChatStreamAsync(AiModelDescribe aiModelDescribe,
|
||||
ThorChatCompletionsRequest input,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 聊天完成-非流式
|
||||
/// </summary>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public Task<ThorChatCompletionsResponse> CompleteChatAsync(AiModelDescribe aiModelDescribe,
|
||||
ThorChatCompletionsRequest input,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Images;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public interface IImageService
|
||||
{
|
||||
/// <summary>Creates an image given a prompt.</summary>
|
||||
/// <param name="imageCreate"></param>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
|
||||
/// <returns></returns>
|
||||
Task<ImageCreateResponse> CreateImage(
|
||||
ImageCreateRequest imageCreate,
|
||||
AiModelDescribe? aiModelDescribe = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an edited or extended image given an original image and a prompt.
|
||||
/// </summary>
|
||||
/// <param name="imageEditCreateRequest"></param>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
|
||||
/// <returns></returns>
|
||||
Task<ImageCreateResponse> CreateImageEdit(
|
||||
ImageEditCreateRequest imageEditCreateRequest,
|
||||
AiModelDescribe? aiModelDescribe = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Creates a variation of a given image.</summary>
|
||||
/// <param name="imageEditCreateRequest"></param>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
|
||||
/// <returns></returns>
|
||||
Task<ImageCreateResponse> CreateImageVariation(
|
||||
ImageVariationCreateRequest imageEditCreateRequest,
|
||||
AiModelDescribe? aiModelDescribe = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public interface ISpecialCompatible
|
||||
{
|
||||
public void Compatible(ThorChatCompletionsRequest request);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi.Embeddings;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay;
|
||||
|
||||
public interface ITextEmbeddingService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="createEmbeddingModel"></param>
|
||||
/// <param name="aiModelDescribe"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<EmbeddingCreateResponse> EmbeddingAsync(
|
||||
EmbeddingCreateRequest createEmbeddingModel,
|
||||
AiModelDescribe? aiModelDescribe = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureDatabricks.Chats;
|
||||
|
||||
public class AzureDatabricksChatCompletionsService(ILogger<AzureDatabricksChatCompletionsService> logger)
|
||||
: IChatCompletionService
|
||||
{
|
||||
private string GetAddress(AiModelDescribe? options, string model)
|
||||
{
|
||||
// This method should return the appropriate URL for the Azure Databricks API
|
||||
// based on the provided options and model.
|
||||
// For now, we will return a placeholder URL.
|
||||
return $"{options?.Endpoint.TrimEnd('/')}/serving-endpoints/{model}/invocations";
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ThorChatCompletionsResponse> CompleteChatStreamAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var address = GetAddress(options, chatCompletionCreate.Model);
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("OpenAI 对话流式补全");
|
||||
|
||||
chatCompletionCreate.StreamOptions = null;
|
||||
|
||||
var response = await HttpClientFactory.GetHttpClient(address).HttpRequestRaw(
|
||||
address,
|
||||
chatCompletionCreate, options.ApiKey);
|
||||
|
||||
openai?.SetTag("Model", chatCompletionCreate.Model);
|
||||
openai?.SetTag("Response", response.StatusCode.ToString());
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.PaymentRequired)
|
||||
{
|
||||
throw new PaymentRequiredException();
|
||||
}
|
||||
|
||||
// 如果限流则抛出限流异常
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new ThorRateLimitException();
|
||||
}
|
||||
|
||||
// 大于等于400的状态码都认为是异常
|
||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
logger.LogError("OpenAI对话异常 , StatusCode: {StatusCode} 错误响应内容:{Content}", response.StatusCode,
|
||||
error);
|
||||
|
||||
throw new BusinessException(response.StatusCode.ToString(), "OpenAI对话异常:" + error);
|
||||
}
|
||||
|
||||
using var stream = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||
|
||||
using StreamReader reader = new(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||
string? line = string.Empty;
|
||||
var first = true;
|
||||
var isThink = false;
|
||||
while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) != null)
|
||||
{
|
||||
line += Environment.NewLine;
|
||||
|
||||
if (line.StartsWith('{'))
|
||||
{
|
||||
logger.LogInformation("OpenAI对话异常 , StatusCode: {StatusCode} Response: {Response}", response.StatusCode,
|
||||
line);
|
||||
|
||||
throw new BusinessException("500", "OpenAI对话异常", line);
|
||||
}
|
||||
|
||||
if (line.StartsWith(OpenAIConstant.Data))
|
||||
line = line[OpenAIConstant.Data.Length..];
|
||||
|
||||
line = line.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
if (line == OpenAIConstant.Done)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.StartsWith(':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var result = JsonSerializer.Deserialize<ThorChatCompletionsResponse>(line,
|
||||
ThorJsonSerializer.DefaultOptions);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// var content = result?.Choices?.FirstOrDefault()?.Delta;
|
||||
//
|
||||
// if (first && content?.Content == OpenAIConstant.ThinkStart)
|
||||
// {
|
||||
// isThink = true;
|
||||
// continue;
|
||||
// // 需要将content的内容转换到其他字段
|
||||
// }
|
||||
//
|
||||
// if (isThink && content?.Content?.Contains(OpenAIConstant.ThinkEnd) == true)
|
||||
// {
|
||||
// isThink = false;
|
||||
// // 需要将content的内容转换到其他字段
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (isThink && result?.Choices != null)
|
||||
// {
|
||||
// // 需要将content的内容转换到其他字段
|
||||
// foreach (var choice in result.Choices)
|
||||
// {
|
||||
// choice.Delta.ReasoningContent = choice.Delta.Content;
|
||||
// choice.Delta.Content = string.Empty;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// first = false;
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ThorChatCompletionsResponse> CompleteChatAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var address = GetAddress(options, chatCompletionCreate.Model);
|
||||
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("OpenAI 对话补全");
|
||||
|
||||
var response = await HttpClientFactory.GetHttpClient(address).PostJsonAsync(
|
||||
address,
|
||||
chatCompletionCreate, options.ApiKey).ConfigureAwait(false);
|
||||
|
||||
openai?.SetTag("Model", chatCompletionCreate.Model);
|
||||
openai?.SetTag("Response", response.StatusCode.ToString());
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new BusinessException("401", "渠道未登录,请联系管理人员");
|
||||
}
|
||||
|
||||
// 如果限流则抛出限流异常
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new ThorRateLimitException();
|
||||
}
|
||||
|
||||
// 大于等于400的状态码都认为是异常
|
||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogError("OpenAI对话异常 请求地址:{Address}, StatusCode: {StatusCode} Response: {Response}",
|
||||
options.Endpoint,
|
||||
response.StatusCode, error);
|
||||
|
||||
throw new BusinessException(response.StatusCode.ToString(), "OpenAI对话异常");
|
||||
}
|
||||
|
||||
var result =
|
||||
await response.Content.ReadFromJsonAsync<ThorChatCompletionsResponse>(
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.ClientModel;
|
||||
using System.Collections.Concurrent;
|
||||
using Azure.AI.OpenAI;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureOpenAI;
|
||||
|
||||
public static class AzureOpenAIFactory
|
||||
{
|
||||
private const string AddressTemplate = "{0}/openai/deployments/{1}/chat/completions?api-version={2}";
|
||||
private const string EditImageAddressTemplate = "{0}/openai/deployments/{1}/images/edits?api-version={2}";
|
||||
private const string AudioSpeechTemplate = "{0}/openai/deployments/{1}/audio/speech?api-version={2}";
|
||||
|
||||
private const string AudioTranscriptions =
|
||||
"{0}/openai/deployments/{1}/audio/transcriptions?api-version={2}";
|
||||
|
||||
private static readonly ConcurrentDictionary<string, AzureOpenAIClient> Clients = new();
|
||||
|
||||
public static string GetAudioTranscriptionsAddress(AiModelDescribe options, string model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.AppExtraUrl))
|
||||
{
|
||||
options.AppExtraUrl = "2025-03-01-preview";
|
||||
}
|
||||
|
||||
return string.Format(AudioTranscriptions, options.Endpoint.TrimEnd('/'), model, options.AppExtraUrl);
|
||||
}
|
||||
|
||||
public static string GetAudioSpeechAddress(AiModelDescribe options, string model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.AppExtraUrl))
|
||||
{
|
||||
options.AppExtraUrl = "2025-03-01-preview";
|
||||
}
|
||||
|
||||
return string.Format(AudioSpeechTemplate, options.Endpoint.TrimEnd('/'), model, options.AppExtraUrl);
|
||||
}
|
||||
|
||||
public static string GetAddress(AiModelDescribe options, string model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.AppExtraUrl))
|
||||
{
|
||||
options.AppExtraUrl = "2025-03-01-preview";
|
||||
}
|
||||
|
||||
return string.Format(AddressTemplate, options.Endpoint.TrimEnd('/'), model, options.AppExtraUrl);
|
||||
}
|
||||
|
||||
public static string GetEditImageAddress(AiModelDescribe options, string model)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.AppExtraUrl))
|
||||
{
|
||||
options.AppExtraUrl = "2025-03-01-preview";
|
||||
}
|
||||
|
||||
return string.Format(EditImageAddressTemplate, options.Endpoint.TrimEnd('/'), model, options.AppExtraUrl);
|
||||
}
|
||||
|
||||
public static AzureOpenAIClient CreateClient(AiModelDescribe options)
|
||||
{
|
||||
return Clients.GetOrAdd($"{options.ApiKey}_{options.Endpoint}_{options.AppExtraUrl}", (_) =>
|
||||
{
|
||||
const AzureOpenAIClientOptions.ServiceVersion version = AzureOpenAIClientOptions.ServiceVersion.V2024_06_01;
|
||||
|
||||
var client = new AzureOpenAIClient(new Uri(options.Endpoint), new ApiKeyCredential(options.ApiKey),
|
||||
new AzureOpenAIClientOptions(version));
|
||||
|
||||
return client;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.OpenAi;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.AiGateWay.Impl.ThorAzureOpenAI.Chats;
|
||||
|
||||
public class AzureOpenAiChatCompletionCompletionsService(ILogger<AzureOpenAiChatCompletionCompletionsService> logger)
|
||||
: IChatCompletionService
|
||||
{
|
||||
public async IAsyncEnumerable<ThorChatCompletionsResponse> CompleteChatStreamAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("Azure OpenAI 对话流式补全");
|
||||
var url = AzureOpenAIFactory.GetAddress(options, chatCompletionCreate.Model);
|
||||
|
||||
var response = await HttpClientFactory.GetHttpClient(options.Endpoint).HttpRequestRaw(url,
|
||||
chatCompletionCreate, options.ApiKey, "Api-Key");
|
||||
|
||||
openai?.SetTag("Model", chatCompletionCreate.Model);
|
||||
openai?.SetTag("Response", response.StatusCode.ToString());
|
||||
|
||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
|
||||
logger.LogError("Azure对话异常 , StatusCode: {StatusCode} 错误响应内容:{Content}", response.StatusCode,
|
||||
error);
|
||||
|
||||
throw new BusinessException(response.StatusCode.ToString(), "AzureOpenAI对话异常:" + error);
|
||||
}
|
||||
|
||||
using StreamReader reader = new(await response.Content.ReadAsStreamAsync(cancellationToken));
|
||||
string? line = string.Empty;
|
||||
var first = true;
|
||||
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
|
||||
{
|
||||
line += Environment.NewLine;
|
||||
|
||||
if (line.StartsWith('{'))
|
||||
{
|
||||
logger.LogInformation("AzureOpenAI对话异常 , StatusCode: {StatusCode} Response: {Response}",
|
||||
response.StatusCode,
|
||||
line);
|
||||
|
||||
throw new BusinessException("500", "AzureOpenAI对话异常", line);
|
||||
}
|
||||
|
||||
if (line.StartsWith(OpenAIConstant.Data))
|
||||
line = line[OpenAIConstant.Data.Length..];
|
||||
|
||||
line = line.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
if (line == OpenAIConstant.Done)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.StartsWith(':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<ThorChatCompletionsResponse>(line,
|
||||
ThorJsonSerializer.DefaultOptions);
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ThorChatCompletionsResponse> CompleteChatAsync(AiModelDescribe options,
|
||||
ThorChatCompletionsRequest chatCompletionCreate,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var openai =
|
||||
Activity.Current?.Source.StartActivity("Azure OpenAI 对话补全");
|
||||
var url = AzureOpenAIFactory.GetAddress(options, chatCompletionCreate.Model);
|
||||
|
||||
var response =
|
||||
await HttpClientFactory.GetHttpClient(options.Endpoint)
|
||||
.PostJsonAsync(url, chatCompletionCreate, options.ApiKey, "Api-Key");
|
||||
|
||||
openai?.SetTag("Model", chatCompletionCreate.Model);
|
||||
openai?.SetTag("Response", response.StatusCode.ToString());
|
||||
// 如果限流则抛出限流异常
|
||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
throw new ThorRateLimitException();
|
||||
}
|
||||
|
||||
if (response.StatusCode >= HttpStatusCode.BadRequest)
|
||||
{
|
||||
logger.LogError("Azure对话异常 , StatusCode: {StatusCode} Response: {Response} Url:{Url}", response.StatusCode,
|
||||
await response.Content.ReadAsStringAsync(cancellationToken), url);
|
||||
}
|
||||
|
||||
var result = await response.Content
|
||||
.ReadFromJsonAsync<ThorChatCompletionsResponse>(ThorJsonSerializer.DefaultOptions,
|
||||
cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user