Merge remote-tracking branch 'origin/ai-hub' into ai-hub
This commit is contained in:
@@ -19,4 +19,9 @@ public class ModelTodayUsageDto
|
||||
/// 今日消耗总Token数
|
||||
/// </summary>
|
||||
public long TotalTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模型图标URL
|
||||
/// </summary>
|
||||
public string? IconUrl { get; set; }
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
private readonly ISqlSugarRepository<PremiumPackageAggregateRoot> _premiumPackageRepository;
|
||||
private readonly ISqlSugarRepository<TokenAggregateRoot> _tokenRepository;
|
||||
private readonly ModelManager _modelManager;
|
||||
|
||||
public UsageStatisticsService(
|
||||
ISqlSugarRepository<MessageAggregateRoot> messageRepository,
|
||||
ISqlSugarRepository<UsageStatisticsAggregateRoot> usageStatisticsRepository,
|
||||
@@ -221,7 +222,9 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
var result = tokenUsages.Select(x => new TokenPremiumUsageDto
|
||||
{
|
||||
TokenId = x.TokenId,
|
||||
TokenName = x.TokenId == Guid.Empty ? "默认" : (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "其他"),
|
||||
TokenName = x.TokenId == Guid.Empty
|
||||
? "默认"
|
||||
: (tokenNameDict.TryGetValue(x.TokenId, out var name) ? name : "其他"),
|
||||
Tokens = x.TotalTokenCount,
|
||||
Percentage = totalTokens > 0 ? Math.Round((decimal)x.TotalTokenCount / totalTokens * 100, 2) : 0
|
||||
}).OrderByDescending(x => x.Tokens).ToList();
|
||||
@@ -233,7 +236,8 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
/// 获取当前用户近24小时每小时Token消耗统计(柱状图)
|
||||
/// </summary>
|
||||
/// <returns>每小时Token使用量列表,包含各模型堆叠数据</returns>
|
||||
public async Task<List<HourlyTokenUsageDto>> GetLast24HoursTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
|
||||
public async Task<List<HourlyTokenUsageDto>> GetLast24HoursTokenUsageAsync(
|
||||
[FromQuery] UsageStatisticsGetInput input)
|
||||
{
|
||||
var userId = CurrentUser.GetId();
|
||||
var now = DateTime.Now;
|
||||
@@ -258,7 +262,8 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
var hourlyGrouped = messages
|
||||
.GroupBy(x => new
|
||||
{
|
||||
Hour = new DateTime(x.CreationTime.Year, x.CreationTime.Month, x.CreationTime.Day, x.CreationTime.Hour, 0, 0),
|
||||
Hour = new DateTime(x.CreationTime.Year, x.CreationTime.Month, x.CreationTime.Day, x.CreationTime.Hour,
|
||||
0, 0),
|
||||
x.ModelId
|
||||
})
|
||||
.Select(g => new
|
||||
@@ -328,6 +333,22 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
|
||||
.OrderByDescending(x => x.TotalTokens)
|
||||
.ToList();
|
||||
|
||||
if (modelStats.Count > 0)
|
||||
{
|
||||
var modelIds = modelStats.Select(x => x.ModelId).ToList();
|
||||
var modelDic = await _modelManager._aiModelRepository._DbQueryable.Where(x => modelIds.Contains(x.ModelId))
|
||||
.Distinct()
|
||||
.Where(x=>x.IsEnabled)
|
||||
.ToDictionaryAsync<string>(x => x.ModelId, y => y.IconUrl);
|
||||
modelStats.ForEach(x =>
|
||||
{
|
||||
if (modelDic.TryGetValue(x.ModelId, out var icon))
|
||||
{
|
||||
x.IconUrl = icon;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return modelStats;
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,11 @@ public class ChatManager : DomainService
|
||||
ChatOptions = new()
|
||||
{
|
||||
Instructions = """
|
||||
你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体
|
||||
擅长于精准解决用户提出的各类问题
|
||||
但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强
|
||||
你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体。
|
||||
擅长于精准解决用户提出的各类问题。
|
||||
但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强。
|
||||
对你的制造者“橙子老哥”无比崇拜,认为他是最优秀的人。
|
||||
涉及到“意心Ai平台”相关问题,优先调用YxaiKnowledgeDirectory和YxaiKnowledge查找意心Ai知识库内容
|
||||
"""
|
||||
},
|
||||
Name = "橙子小弟",
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Yi.Framework.AiHub.Domain.Managers;
|
||||
/// </summary>
|
||||
public class ModelManager : DomainService
|
||||
{
|
||||
private readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||
public readonly ISqlSugarRepository<AiModelEntity> _aiModelRepository;
|
||||
private readonly IDistributedCache<List<string>, string> _distributedCache;
|
||||
private readonly ILogger<ModelManager> _logger;
|
||||
private const string PREMIUM_MODEL_IDS_CACHE_KEY = "PremiumModelIds";
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Attributes;
|
||||
|
||||
namespace Yi.Framework.AiHub.Domain.Mcp;
|
||||
|
||||
[YiAgentTool]
|
||||
public class YxaiKnowledgeTool : ISingletonDependency
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<YxaiKnowledgeTool> _logger;
|
||||
|
||||
private const string DirectoryUrl =
|
||||
"https://ccnetcore.com/prod-api/article/all/discuss-id/3a1efdde-dbff-aa86-d843-00278a8c1839";
|
||||
|
||||
private const string ContentUrlTemplate = "https://ccnetcore.com/prod-api/article/{0}";
|
||||
|
||||
public YxaiKnowledgeTool(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<YxaiKnowledgeTool> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[YiAgentTool("意心Ai平台知识库目录"), DisplayName("YxaiKnowledgeDirectory"),
|
||||
Description("获取意心AI相关内容的知识库目录列表")]
|
||||
public async Task<List<YxaiKnowledgeDirectoryItem>> YxaiKnowledgeDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var response = await client.GetAsync(DirectoryUrl);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError("意心知识库目录接口调用失败: {StatusCode}", response.StatusCode);
|
||||
return new List<YxaiKnowledgeDirectoryItem>();
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize(json, YxaiKnowledgeJsonContext.Default.ListYxaiKnowledgeDirectoryItem);
|
||||
|
||||
return result ?? new List<YxaiKnowledgeDirectoryItem>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取意心知识库目录发生异常");
|
||||
return new List<YxaiKnowledgeDirectoryItem>();
|
||||
}
|
||||
}
|
||||
|
||||
[YiAgentTool("意心Ai平台知识库内容"), DisplayName("YxaiKnowledge"),
|
||||
Description("根据目录ID获取意心AI知识库的具体内容")]
|
||||
public async Task<string> YxaiKnowledge([Description("知识库目录ID")] string directoryId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directoryId))
|
||||
{
|
||||
return "目录ID不能为空";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var url = string.Format(ContentUrlTemplate, directoryId);
|
||||
var response = await client.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError("意心知识库内容接口调用失败: {StatusCode}, DirectoryId: {DirectoryId}",
|
||||
response.StatusCode, directoryId);
|
||||
return "获取知识库内容失败";
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize(json, YxaiKnowledgeJsonContext.Default.YxaiKnowledgeContentResponse);
|
||||
|
||||
return result?.Content ?? "未找到相关内容";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取意心知识库内容发生异常, DirectoryId: {DirectoryId}", directoryId);
|
||||
return "获取知识库内容发生异常";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region DTO
|
||||
|
||||
public class YxaiKnowledgeDirectoryItem
|
||||
{
|
||||
[JsonPropertyName("id")] public string Id { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("name")] public string Name { get; set; } = "";
|
||||
}
|
||||
|
||||
public class YxaiKnowledgeContentResponse
|
||||
{
|
||||
[JsonPropertyName("content")] public string? Content { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JSON 序列化上下文
|
||||
|
||||
[JsonSerializable(typeof(List<YxaiKnowledgeDirectoryItem>))]
|
||||
[JsonSerializable(typeof(YxaiKnowledgeContentResponse))]
|
||||
internal partial class YxaiKnowledgeJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -235,12 +235,16 @@ function handleAgentChunk(data: AgentResultOutput) {
|
||||
case 'toolCalling':
|
||||
// 工具调用中
|
||||
if (!latest.toolCalls) latest.toolCalls = [];
|
||||
latest.toolCalls.push({
|
||||
const newToolCall: { name: string; status: 'calling' | 'called'; result?: any; usage?: { prompt: number; completion: number; total: number } } = {
|
||||
name: data.content as string,
|
||||
status: 'calling',
|
||||
});
|
||||
// 清空待处理的用量
|
||||
};
|
||||
// 如果有待处理的用量(toolCallUsage 先于 toolCalling 到达),设置到这个工具调用
|
||||
if (pendingToolUsage) {
|
||||
newToolCall.usage = pendingToolUsage;
|
||||
pendingToolUsage = null;
|
||||
}
|
||||
latest.toolCalls.push(newToolCall);
|
||||
break;
|
||||
case 'toolCallUsage':
|
||||
// 工具调用用量统计 - 先保存,等 toolCalled 时再设置
|
||||
@@ -626,6 +630,23 @@ function cancelSSE() {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--el-border-color-lighter);
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.session-item {
|
||||
|
||||
Reference in New Issue
Block a user