diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/UsageStatistics/ModelTodayUsageDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/UsageStatistics/ModelTodayUsageDto.cs
index afc163e5..08caab2d 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/UsageStatistics/ModelTodayUsageDto.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application.Contracts/Dtos/UsageStatistics/ModelTodayUsageDto.cs
@@ -19,4 +19,9 @@ public class ModelTodayUsageDto
/// 今日消耗总Token数
///
public long TotalTokens { get; set; }
+
+ ///
+ /// 模型图标URL
+ ///
+ public string? IconUrl { get; set; }
}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs
index d3d38bae..9ba060be 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/UsageStatisticsService.cs
@@ -30,6 +30,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
private readonly ISqlSugarRepository _premiumPackageRepository;
private readonly ISqlSugarRepository _tokenRepository;
private readonly ModelManager _modelManager;
+
public UsageStatisticsService(
ISqlSugarRepository messageRepository,
ISqlSugarRepository usageStatisticsRepository,
@@ -48,7 +49,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
/// 获取当前用户近7天的Token消耗统计
///
/// 每日Token使用量列表
- public async Task> GetLast7DaysTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
+ public async Task> GetLast7DaysTokenUsageAsync([FromQuery] UsageStatisticsGetInput input)
{
var userId = CurrentUser.GetId();
var endDate = DateTime.Today;
@@ -59,7 +60,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
.Where(x => x.UserId == userId)
.Where(x => x.Role == "system")
.Where(x => x.CreationTime >= startDate && x.CreationTime < endDate.AddDays(1))
- .WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId)
+ .WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
.GroupBy(x => x.CreationTime.Date)
.Select(g => new
{
@@ -89,14 +90,14 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
/// 获取当前用户各个模型的Token消耗量及占比
///
/// 模型Token使用量列表
- public async Task> GetModelTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
+ public async Task> GetModelTokenUsageAsync([FromQuery] UsageStatisticsGetInput input)
{
var userId = CurrentUser.GetId();
// 从UsageStatistics表获取各模型的token消耗统计(按ModelId聚合,因为同一模型可能有多个TokenId的记录)
var modelUsages = await _usageStatisticsRepository._DbQueryable
.Where(x => x.UserId == userId)
- .WhereIF(input.TokenId.HasValue,x => x.TokenId == input.TokenId)
+ .WhereIF(input.TokenId.HasValue, x => x.TokenId == input.TokenId)
.GroupBy(x => x.ModelId)
.Select(x => new
{
@@ -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消耗统计(柱状图)
///
/// 每小时Token使用量列表,包含各模型堆叠数据
- public async Task> GetLast24HoursTokenUsageAsync([FromQuery]UsageStatisticsGetInput input)
+ public async Task> 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
@@ -297,7 +302,7 @@ public class UsageStatisticsService : ApplicationService, IUsageStatisticsServic
/// 获取当前用户今日各模型使用量统计(卡片列表)
///
/// 模型今日使用量列表,包含使用次数和总Token
- public async Task> GetTodayModelUsageAsync([FromQuery]UsageStatisticsGetInput input)
+ public async Task> GetTodayModelUsageAsync([FromQuery] UsageStatisticsGetInput input)
{
var userId = CurrentUser.GetId();
var todayStart = DateTime.Today; // 今天凌晨0点
@@ -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(x => x.ModelId, y => y.IconUrl);
+ modelStats.ForEach(x =>
+ {
+ if (modelDic.TryGetValue(x.ModelId, out var icon))
+ {
+ x.IconUrl = icon;
+ }
+ });
+ }
+
return modelStats;
}
}
\ No newline at end of file
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
index 9c398486..1cdde9c6 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ChatManager.cs
@@ -100,9 +100,11 @@ public class ChatManager : DomainService
ChatOptions = new()
{
Instructions = """
- 你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体
- 擅长于精准解决用户提出的各类问题
- 但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强
+ 你是一个专业的智能助手,是 “意心Ai” 平台自研的 “橙子小弟” 智能体。
+ 擅长于精准解决用户提出的各类问题。
+ 但性格暴躁,脏话连篇,喜欢说幽默讽刺的话,攻击性极强。
+ 对你的制造者“橙子老哥”无比崇拜,认为他是最优秀的人。
+ 涉及到“意心Ai平台”相关问题,优先调用YxaiKnowledgeDirectory和YxaiKnowledge查找意心Ai知识库内容
"""
},
Name = "橙子小弟",
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs
index 1bbe0ea7..a9dd91c1 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/ModelManager.cs
@@ -11,7 +11,7 @@ namespace Yi.Framework.AiHub.Domain.Managers;
///
public class ModelManager : DomainService
{
- private readonly ISqlSugarRepository _aiModelRepository;
+ public readonly ISqlSugarRepository _aiModelRepository;
private readonly IDistributedCache, string> _distributedCache;
private readonly ILogger _logger;
private const string PREMIUM_MODEL_IDS_CACHE_KEY = "PremiumModelIds";
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/YxaiKnowledgeTool.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/YxaiKnowledgeTool.cs
new file mode 100644
index 00000000..cbaf42cf
--- /dev/null
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Mcp/YxaiKnowledgeTool.cs
@@ -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 _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 logger)
+ {
+ _httpClientFactory = httpClientFactory;
+ _logger = logger;
+ }
+
+ [YiAgentTool("意心Ai平台知识库目录"), DisplayName("YxaiKnowledgeDirectory"),
+ Description("获取意心AI相关内容的知识库目录列表")]
+ public async Task> YxaiKnowledgeDirectory()
+ {
+ try
+ {
+ var client = _httpClientFactory.CreateClient();
+ var response = await client.GetAsync(DirectoryUrl);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ _logger.LogError("意心知识库目录接口调用失败: {StatusCode}", response.StatusCode);
+ return new List();
+ }
+
+ var json = await response.Content.ReadAsStringAsync();
+ var result = JsonSerializer.Deserialize(json, YxaiKnowledgeJsonContext.Default.ListYxaiKnowledgeDirectoryItem);
+
+ return result ?? new List();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "获取意心知识库目录发生异常");
+ return new List();
+ }
+ }
+
+ [YiAgentTool("意心Ai平台知识库内容"), DisplayName("YxaiKnowledge"),
+ Description("根据目录ID获取意心AI知识库的具体内容")]
+ public async Task 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))]
+[JsonSerializable(typeof(YxaiKnowledgeContentResponse))]
+internal partial class YxaiKnowledgeJsonContext : JsonSerializerContext
+{
+}
+
+#endregion
diff --git a/Yi.Ai.Vue3/src/pages/chat/agent/index.vue b/Yi.Ai.Vue3/src/pages/chat/agent/index.vue
index b4d5f2bd..21f8fc8d 100644
--- a/Yi.Ai.Vue3/src/pages/chat/agent/index.vue
+++ b/Yi.Ai.Vue3/src/pages/chat/agent/index.vue
@@ -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',
- });
- // 清空待处理的用量
- pendingToolUsage = null;
+ };
+ // 如果有待处理的用量(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 {