mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-03-11 20:19:27 +08:00
Merge remote-tracking branch 'origin/ai-hub' into ai-hub
This commit is contained in:
@@ -40,6 +40,11 @@ public class TokenGetListOutputDto
|
||||
/// </summary>
|
||||
public bool IsDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用请求日志记录
|
||||
/// </summary>
|
||||
public bool IsEnableLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
|
||||
@@ -83,6 +83,7 @@ public class TokenService : ApplicationService
|
||||
PremiumQuotaLimit = t.PremiumQuotaLimit,
|
||||
PremiumUsedQuota = usedQuota,
|
||||
IsDisabled = t.IsDisabled,
|
||||
IsEnableLog = t.IsEnableLog,
|
||||
CreationTime = t.CreationTime
|
||||
};
|
||||
}).ToList();
|
||||
@@ -158,6 +159,7 @@ public class TokenService : ApplicationService
|
||||
PremiumQuotaLimit = token.PremiumQuotaLimit,
|
||||
PremiumUsedQuota = 0,
|
||||
IsDisabled = token.IsDisabled,
|
||||
IsEnableLog = token.IsEnableLog,
|
||||
CreationTime = token.CreationTime
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Uow;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
||||
using Yi.Framework.AiHub.Domain.Extensions;
|
||||
using Yi.Framework.AiHub.Domain.Managers;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
@@ -33,10 +36,12 @@ public class OpenApiService : ApplicationService
|
||||
private readonly PremiumPackageManager _premiumPackageManager;
|
||||
private readonly ISqlSugarRepository<ImageStoreTaskAggregateRoot> _imageStoreRepository;
|
||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
public OpenApiService(IHttpContextAccessor httpContextAccessor, ILogger<OpenApiService> logger,
|
||||
TokenManager tokenManager, AiGateWayManager aiGateWayManager,
|
||||
ModelManager modelManager, AiBlacklistManager aiBlacklistManager,
|
||||
IAccountService accountService, PremiumPackageManager premiumPackageManager, ISqlSugarRepository<ImageStoreTaskAggregateRoot> imageStoreRepository, ISqlSugarRepository<AiModelEntity> aiModelRepository)
|
||||
IAccountService accountService, PremiumPackageManager premiumPackageManager, ISqlSugarRepository<ImageStoreTaskAggregateRoot> imageStoreRepository, ISqlSugarRepository<AiModelEntity> aiModelRepository,
|
||||
IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_logger = logger;
|
||||
@@ -48,6 +53,7 @@ public class OpenApiService : ApplicationService
|
||||
_premiumPackageManager = premiumPackageManager;
|
||||
_imageStoreRepository = imageStoreRepository;
|
||||
_aiModelRepository = aiModelRepository;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,6 +97,12 @@ public class OpenApiService : ApplicationService
|
||||
null, tokenId,
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
// 记录请求日志
|
||||
if (tokenValidation.IsEnableLog)
|
||||
{
|
||||
FireAndForgetMessageLog(JsonSerializer.Serialize(input), tokenValidation.Token, tokenValidation.TokenName, input.Model, ModelApiTypeEnum.Completions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +218,12 @@ public class OpenApiService : ApplicationService
|
||||
null, tokenId,
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
// 记录请求日志
|
||||
if (tokenValidation.IsEnableLog)
|
||||
{
|
||||
FireAndForgetMessageLog(JsonSerializer.Serialize(input), tokenValidation.Token, tokenValidation.TokenName, input.Model, ModelApiTypeEnum.Messages);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +276,12 @@ public class OpenApiService : ApplicationService
|
||||
null, tokenId,
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
// 记录请求日志
|
||||
if (tokenValidation.IsEnableLog)
|
||||
{
|
||||
FireAndForgetMessageLog(JsonSerializer.Serialize(input), tokenValidation.Token, tokenValidation.TokenName, input.Model, ModelApiTypeEnum.Responses);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +342,12 @@ public class OpenApiService : ApplicationService
|
||||
null, tokenId,
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
// 记录请求日志
|
||||
if (tokenValidation.IsEnableLog)
|
||||
{
|
||||
FireAndForgetMessageLog(input.GetRawText(), tokenValidation.Token, tokenValidation.TokenName, modelId, ModelApiTypeEnum.GenerateContent);
|
||||
}
|
||||
}
|
||||
|
||||
#region 私有
|
||||
@@ -357,5 +387,25 @@ public class OpenApiService : ApplicationService
|
||||
}
|
||||
}
|
||||
|
||||
private void FireAndForgetMessageLog(string requestBody, string apiKey, string apiKeyName, string modelId, ModelApiTypeEnum apiType)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
|
||||
var manager = scope.ServiceProvider.GetRequiredService<MessageLogManager>();
|
||||
using var uow = uowManager.Begin(requiresNew: true);
|
||||
await manager.CreateAsync(requestBody, apiKey, apiKeyName, modelId, apiType);
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录消息日志失败, 请求体长度: {RequestBodyLength}", requestBody?.Length ?? 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -3,4 +3,4 @@
|
||||
public class AiHubConst
|
||||
{
|
||||
public const string VipRole = "YiXinAi-Vip";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
|
||||
public class ModelConst
|
||||
{
|
||||
/// <summary>
|
||||
/// 需要移除的模型前缀列表
|
||||
/// </summary>
|
||||
private static readonly List<string> ModelPrefixesToRemove =
|
||||
[
|
||||
"yi-",
|
||||
"ma-"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 获取模型ID的前缀(如果存在)
|
||||
/// </summary>
|
||||
private static string? GetModelPrefix(string? modelId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(modelId)) return null;
|
||||
|
||||
return ModelPrefixesToRemove.FirstOrDefault(prefix =>
|
||||
modelId!.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除模型ID的前缀,返回标准模型ID
|
||||
/// </summary>
|
||||
public static string RemoveModelPrefix(string? modelId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(modelId)) return string.Empty;
|
||||
|
||||
var prefix = GetModelPrefix(modelId);
|
||||
if (prefix != null)
|
||||
{
|
||||
return modelId[prefix.Length..];
|
||||
}
|
||||
return modelId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理模型ID,如有前缀则移除并返回新字符串
|
||||
/// </summary>
|
||||
public static string ProcessModelId(string? modelId)
|
||||
{
|
||||
return RemoveModelPrefix(modelId);
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,8 @@ public enum GoodsTypeEnum
|
||||
[Price(83.7, 3, 27.9)] [DisplayName("YiXinVip 3 month", "3个月", "短期体验")] [GoodsCategory(GoodsCategoryType.Vip)]
|
||||
YiXinVip3 = 3,
|
||||
|
||||
[Price(114.5, 5, 22.9)] [DisplayName("YiXinVip 5 month", "5个月", "年度热销")] [GoodsCategory(GoodsCategoryType.Vip)]
|
||||
YiXinVip5 = 15,
|
||||
[Price(91.6, 4, 22.9)] [DisplayName("YiXinVip 4 month", "4个月", "年度热销")] [GoodsCategory(GoodsCategoryType.Vip)]
|
||||
YiXinVip5 = 14,
|
||||
|
||||
// 尊享包服务 - 需要VIP资格才能购买
|
||||
[Price(188.9, 0, 1750)]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using SqlSugar;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
||||
|
||||
[SugarTable("Ai_Message_Log")]
|
||||
public class MessageLogAggregateRoot : Entity<Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求内容(httpbody)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "text")]
|
||||
public string? RequestBody { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求apikey
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 255)]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求apikey名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 255)]
|
||||
public string ApiKeyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型id
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 64)]
|
||||
public string ModelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// api类型
|
||||
/// </summary>
|
||||
public ModelApiTypeEnum ApiType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// api类型名称
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 16)]
|
||||
public string ApiTypeName { get; set; }
|
||||
}
|
||||
@@ -51,6 +51,11 @@ public class TokenAggregateRoot : FullAuditedAggregateRoot<Guid>
|
||||
/// </summary>
|
||||
public bool IsDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用请求日志记录(仅数据库手动修改)
|
||||
/// </summary>
|
||||
public bool IsEnableLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 检查Token是否可用
|
||||
/// </summary>
|
||||
|
||||
@@ -15,6 +15,7 @@ using Yi.Framework.AiHub.Domain.AiGateWay.Exceptions;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Chat;
|
||||
using Yi.Framework.AiHub.Domain.Entities.Model;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
using ModelConst = Yi.Framework.AiHub.Domain.Shared.Consts.ModelConst;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.Anthropic;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Dtos.Gemini;
|
||||
@@ -97,12 +98,8 @@ public class AiGateWayManager : DomainService
|
||||
throw new UserFriendlyException($"【{modelId}】模型当前版本【{modelApiType}】格式不支持");
|
||||
}
|
||||
|
||||
// ✅ 统一处理 yi- 后缀(网关层模型规范化)
|
||||
if (!string.IsNullOrEmpty(aiModelDescribe.ModelId) &&
|
||||
aiModelDescribe.ModelId.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
aiModelDescribe.ModelId = aiModelDescribe.ModelId[3..];
|
||||
}
|
||||
// ✅ 统一处理模型前缀(网关层模型规范化)
|
||||
aiModelDescribe.ModelId = ModelConst.RemoveModelPrefix(aiModelDescribe.ModelId);
|
||||
|
||||
return aiModelDescribe;
|
||||
}
|
||||
@@ -134,11 +131,7 @@ public class AiGateWayManager : DomainService
|
||||
LazyServiceProvider.GetRequiredKeyedService<IChatCompletionService>(modelDescribe.HandlerName);
|
||||
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var data = await chatService.CompleteChatAsync(modelDescribe, request, cancellationToken);
|
||||
data.SupplementalMultiplier(modelDescribe.Multiplier);
|
||||
@@ -208,11 +201,7 @@ public class AiGateWayManager : DomainService
|
||||
LazyServiceProvider.GetRequiredKeyedService<IChatCompletionService>(modelDescribe.HandlerName);
|
||||
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var completeChatResponse = chatService.CompleteChatStreamAsync(modelDescribe, request, cancellationToken);
|
||||
var tokenUsage = new ThorUsageResponse();
|
||||
@@ -540,11 +529,7 @@ public class AiGateWayManager : DomainService
|
||||
var modelDescribe = await GetModelAsync(ModelApiTypeEnum.Messages, request.Model);
|
||||
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var chatService =
|
||||
LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
|
||||
@@ -620,11 +605,7 @@ public class AiGateWayManager : DomainService
|
||||
LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
|
||||
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var completeChatResponse = chatService.StreamChatCompletionsAsync(modelDescribe, request, cancellationToken);
|
||||
ThorUsageResponse? tokenUsage = new ThorUsageResponse();
|
||||
@@ -744,11 +725,7 @@ public class AiGateWayManager : DomainService
|
||||
var chatService =
|
||||
LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName);
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var data = await chatService.ResponsesAsync(modelDescribe, request, cancellationToken);
|
||||
|
||||
@@ -820,11 +797,7 @@ public class AiGateWayManager : DomainService
|
||||
var chatService =
|
||||
LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName);
|
||||
var sourceModelId = request.Model;
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var completeChatResponse = chatService.ResponsesStreamAsync(modelDescribe, request, cancellationToken);
|
||||
ThorUsageResponse? tokenUsage = null;
|
||||
@@ -1164,12 +1137,8 @@ public class AiGateWayManager : DomainService
|
||||
response.Headers.TryAdd("Connection", "keep-alive");
|
||||
|
||||
var sourceModelId = modelId;
|
||||
// 处理 yi- 前缀
|
||||
if (!string.IsNullOrEmpty(modelId) &&
|
||||
modelId.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modelId = modelId[3..];
|
||||
}
|
||||
// 处理模型前缀
|
||||
modelId = ModelConst.RemoveModelPrefix(modelId);
|
||||
|
||||
var modelDescribe = await GetModelAsync(apiType, sourceModelId);
|
||||
|
||||
@@ -1302,12 +1271,8 @@ public class AiGateWayManager : DomainService
|
||||
// 提取用户最后一条消息
|
||||
var userContent = request.Messages?.LastOrDefault()?.MessagesStore ?? string.Empty;
|
||||
|
||||
// 处理 yi- 前缀
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
// 处理模型前缀
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var chatService = LazyServiceProvider.GetRequiredKeyedService<IChatCompletionService>(modelDescribe.HandlerName);
|
||||
var completeChatResponse = chatService.CompleteChatStreamAsync(modelDescribe, request, cancellationToken);
|
||||
@@ -1391,12 +1356,8 @@ public class AiGateWayManager : DomainService
|
||||
userContent = textContent?.Text ?? System.Text.Json.JsonSerializer.Serialize(lastMessage.Contents);
|
||||
}
|
||||
|
||||
// 处理 yi- 前缀
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
// 处理模型前缀
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var chatService = LazyServiceProvider.GetRequiredKeyedService<IAnthropicChatCompletionService>(modelDescribe.HandlerName);
|
||||
var completeChatResponse = chatService.StreamChatCompletionsAsync(modelDescribe, request, cancellationToken);
|
||||
@@ -1509,12 +1470,8 @@ public class AiGateWayManager : DomainService
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 yi- 前缀
|
||||
if (!string.IsNullOrEmpty(request.Model) &&
|
||||
request.Model.StartsWith("yi-", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Model = request.Model[3..];
|
||||
}
|
||||
// 处理模型前缀
|
||||
request.Model = ModelConst.ProcessModelId(request.Model);
|
||||
|
||||
var chatService = LazyServiceProvider.GetRequiredKeyedService<IOpenAiResponseService>(modelDescribe.HandlerName);
|
||||
var completeChatResponse = chatService.ResponsesStreamAsync(modelDescribe, request, cancellationToken);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Yi.Framework.AiHub.Domain.Entities.OpenApi;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums;
|
||||
using Yi.Framework.SqlSugarCore.Abstractions;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Managers;
|
||||
|
||||
public class MessageLogManager : DomainService
|
||||
{
|
||||
private readonly ISqlSugarRepository<MessageLogAggregateRoot> _repository;
|
||||
|
||||
public MessageLogManager(ISqlSugarRepository<MessageLogAggregateRoot> repository)
|
||||
{
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建消息日志
|
||||
/// </summary>
|
||||
public async Task CreateAsync(string requestBody, string apiKey, string apiKeyName, string modelId, ModelApiTypeEnum apiType)
|
||||
{
|
||||
var entity = new MessageLogAggregateRoot
|
||||
{
|
||||
RequestBody = requestBody,
|
||||
ApiKey = apiKey,
|
||||
ApiKeyName = apiKeyName,
|
||||
ModelId = modelId,
|
||||
ApiType = apiType,
|
||||
ApiTypeName = apiType.ToString(),
|
||||
CreationTime = DateTime.Now
|
||||
};
|
||||
await _repository.InsertAsync(entity);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,16 @@ public class TokenValidationResult
|
||||
/// token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Token名称
|
||||
/// </summary>
|
||||
public string TokenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用请求日志记录
|
||||
/// </summary>
|
||||
public bool IsEnableLog { get; set; }
|
||||
}
|
||||
|
||||
public class TokenManager : DomainService
|
||||
@@ -117,7 +127,9 @@ public class TokenManager : DomainService
|
||||
{
|
||||
UserId = entity.UserId,
|
||||
TokenId = entity.Id,
|
||||
Token = entity.Token
|
||||
Token = entity.Token,
|
||||
TokenName = entity.Name,
|
||||
IsEnableLog = entity.IsEnableLog
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Cors.Infrastructure.CorsService", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Authorization.DefaultAuthorizationService", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Routing.EndpointMiddleware", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire.Server.ServerHeartbeatProcess", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire.Redis.StackExchange.FetchedJobsWatcher", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Hangfire.Processing.BackgroundExecution", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Async(c => c.File("logs/all/log-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Debug,outputTemplate:outputTemplate))
|
||||
.WriteTo.Async(c => c.File("logs/error/errorlog-.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Error,outputTemplate:outputTemplate))
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace Yi.Abp.Web
|
||||
var app = context.GetApplicationBuilder();
|
||||
app.UseRouting();
|
||||
|
||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<RankingItemAggregateRoot>();
|
||||
//app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<MessageLogAggregateRoot>();
|
||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<ActivationCodeRecordAggregateRoot>();
|
||||
// app.ApplicationServices.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient.CodeFirst.InitTables<UsageStatisticsAggregateRoot>();
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"Bash(npm install marked --save)",
|
||||
"Bash(pnpm add marked)",
|
||||
"Bash(pnpm lint:*)",
|
||||
"Bash(pnpm list:*)"
|
||||
"Bash(pnpm list:*)",
|
||||
"Bash(pnpm vue-tsc:*)",
|
||||
"Bash(pnpm build:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -10,11 +10,13 @@ import { useDesignStore } from '@/stores';
|
||||
interface Props {
|
||||
content: string;
|
||||
theme?: 'light' | 'dark' | 'auto';
|
||||
sanitize?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
content: '',
|
||||
theme: 'auto',
|
||||
sanitize: true,
|
||||
});
|
||||
|
||||
const designStore = useDesignStore();
|
||||
@@ -94,7 +96,12 @@ const renderer = {
|
||||
|
||||
// 行内代码
|
||||
codespan(token: { text: string }) {
|
||||
return `<code class="inline-code">${token.text}</code>`;
|
||||
// 转义 HTML 标签,防止 <script> 等标签被浏览器解析
|
||||
const escapedText = token.text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
return `<code class="inline-code">${escapedText}</code>`;
|
||||
},
|
||||
|
||||
// 链接
|
||||
@@ -148,11 +155,23 @@ async function renderContent(content: string) {
|
||||
// 包装表格,添加 table-wrapper 以支持横向滚动
|
||||
rawHtml = rawHtml.replace(/<table>/g, '<div class="table-wrapper"><table>');
|
||||
rawHtml = rawHtml.replace(/<\/table>/g, '</table></div>');
|
||||
// 使用 DOMPurify 清理 HTML,防止 XSS
|
||||
renderedHtml.value = DOMPurify.sanitize(rawHtml, {
|
||||
ADD_TAGS: ['iframe'],
|
||||
ADD_ATTR: ['target', 'data-code', 'data-html'],
|
||||
});
|
||||
// 转义 script 标签,防止浏览器将其当作真实脚本解析
|
||||
// 使用字符串拼接避免在源码中出现 script 标签字面量
|
||||
const scriptStart = '<' + 'script';
|
||||
const scriptEnd = '<' + '/script' + '>';
|
||||
rawHtml = rawHtml.replace(new RegExp(scriptStart + '(.*?)>', 'gi'), '<script$1>');
|
||||
rawHtml = rawHtml.replace(new RegExp(scriptEnd.replace('/', '\\/'), 'gi'), '</script>');
|
||||
|
||||
// 使用 DOMPurify 清理 HTML,防止 XSS(可通过 sanitize 属性禁用)
|
||||
if (props.sanitize) {
|
||||
renderedHtml.value = DOMPurify.sanitize(rawHtml, {
|
||||
ADD_TAGS: ['iframe'],
|
||||
ADD_ATTR: ['target', 'data-code', 'data-html'],
|
||||
});
|
||||
}
|
||||
else {
|
||||
renderedHtml.value = rawHtml;
|
||||
}
|
||||
|
||||
// 渲染后绑定按钮事件
|
||||
nextTick(() => {
|
||||
|
||||
@@ -8,6 +8,7 @@ interface TokenFormData {
|
||||
expireTime: string;
|
||||
premiumQuotaLimit: number | null;
|
||||
quotaUnit: string;
|
||||
isEnableLog?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -42,6 +43,7 @@ const localFormData = ref<TokenFormData>({
|
||||
const submitting = ref(false);
|
||||
const neverExpire = ref(false); // 永不过期开关
|
||||
const unlimitedQuota = ref(false); // 无限制额度开关
|
||||
const isEnableLog = ref(false); // 是否启用请求日志(只读)
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = ref(false);
|
||||
@@ -107,6 +109,9 @@ watch(() => props.visible, (newVal) => {
|
||||
// 判断是否永不过期
|
||||
neverExpire.value = !props.formData.expireTime;
|
||||
|
||||
// 读取是否启用请求日志(只读字段)
|
||||
isEnableLog.value = props.formData.isEnableLog || false;
|
||||
|
||||
localFormData.value = {
|
||||
...props.formData,
|
||||
premiumQuotaLimit: displayValue,
|
||||
@@ -196,13 +201,13 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥'
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
:width="isMobile ? '95%' : '540px'"
|
||||
:width="isMobile ? '95%' : '640px'"
|
||||
:fullscreen="isMobile"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="!submitting"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form :model="localFormData" :label-width="isMobile ? '100%' : '110px'" :label-position="isMobile ? 'top' : 'right'">
|
||||
<el-form :model="localFormData" :label-width="isMobile ? '100%' : '150px'" :label-position="isMobile ? 'top' : 'right'">
|
||||
<el-form-item label="API密钥名称" required>
|
||||
<el-input
|
||||
v-model="localFormData.name"
|
||||
@@ -288,6 +293,21 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥'
|
||||
超出配额后API密钥将无法继续使用
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 仅编辑模式显示:请求日志开关(只读) -->
|
||||
<el-form-item v-if="mode === 'edit'" label="请求日志存储">
|
||||
<div class="form-item-inline">
|
||||
<el-switch
|
||||
v-model="isEnableLog"
|
||||
disabled
|
||||
/>
|
||||
<span class="switch-status-text">{{ isEnableLog ? '已开启' : '已关闭' }}</span>
|
||||
</div>
|
||||
<div class="form-hint warning-hint">
|
||||
<el-icon><i-ep-warning-filled /></el-icon>
|
||||
此临时存储功能仅面向企业套餐用户,仅用于企业内部审计
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -356,6 +376,15 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥'
|
||||
color: #409eff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.warning-hint {
|
||||
background: #fdf6ec;
|
||||
border-left-color: #e6a23c;
|
||||
|
||||
.el-icon {
|
||||
color: #e6a23c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
@@ -364,6 +393,18 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥'
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.switch-status-text {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.form-item-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
// 主版本号 - 修改此处即可同步更新所有地方的版本显示
|
||||
export const APP_VERSION = '3.7.0';
|
||||
export const APP_VERSION = '3.7.1';
|
||||
|
||||
// 应用名称
|
||||
export const APP_NAME = '意心AI';
|
||||
|
||||
@@ -148,7 +148,7 @@ async function copyText(text: string) {
|
||||
>
|
||||
<template #default>
|
||||
<div class="leading-normal text-sm">
|
||||
自 2025 年末起,AI 领域接口标准逐渐分化,原有的统一接口 <code class="bg-yellow-100 dark:bg-yellow-900 px-1 rounded">/v1/chat/completions</code> 已不再兼容所有模型。各厂商推出的新接口差异较大,接入第三方工具时,请务必根据具体模型选择正确的 API 类型。您可前往
|
||||
自 2025 年末起,AI 领域接口标准逐渐分化,原有的统一接口 <code class="bg-yellow-100 px-1 rounded">/v1/chat/completions</code> 已不再兼容所有模型。各厂商推出的新接口差异较大,接入第三方工具时,请务必根据具体模型选择正确的 API 类型。您可前往
|
||||
<router-link to="/model-library" class="text-primary font-bold hover:underline">模型库</router-link>
|
||||
查看各模型对应的 API 信息。
|
||||
</div>
|
||||
@@ -203,7 +203,7 @@ async function copyText(text: string) {
|
||||
<span class="font-bold text-sm">调用示例 (cURL)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="code-block bg-gray-50 dark:bg-[#161b22] p-3 rounded-md border border-gray-200 dark:border-gray-700">
|
||||
<div class="code-block bg-gray-50 p-3 rounded-md border border-gray-200 ">
|
||||
<pre class="text-xs overflow-x-auto font-mono m-0"><code class="language-bash">curl {{ fullUrl }} \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
|
||||
Reference in New Issue
Block a user