diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
index 83833ebc..28b0c1f2 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/AiAccountService.cs
@@ -81,162 +81,4 @@ public class AiAccountService : ApplicationService
return output;
}
- ///
- /// 获取利润统计数据
- ///
- /// 当前成本(RMB)
- ///
- [Authorize]
- [HttpGet("account/profit-statistics")]
- public async Task GetProfitStatisticsAsync([FromQuery] decimal currentCost)
- {
- if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc")
- {
- throw new UserFriendlyException("您暂无权限访问");
- }
-
- // 1. 获取尊享包总消耗和剩余库存
- var premiumPackages = await _premiumPackageRepository._DbQueryable.ToListAsync();
- long totalUsedTokens = premiumPackages.Sum(p => p.UsedTokens);
- long totalRemainingTokens = premiumPackages.Sum(p => p.RemainingTokens);
-
- // 2. 计算1亿Token成本
- decimal costPerHundredMillion = totalUsedTokens > 0
- ? currentCost / (totalUsedTokens / 100000000m)
- : 0;
-
- // 3. 计算总成本(剩余+已使用的总成本)
- long totalTokens = totalUsedTokens + totalRemainingTokens;
- decimal totalCost = totalTokens > 0
- ? (totalTokens / 100000000m) * costPerHundredMillion
- : 0;
-
- // 4. 获取总收益(RechargeType=PremiumPackage的充值金额总和)
- decimal totalRevenue = await _rechargeRepository._DbQueryable
- .Where(x => x.RechargeType == Domain.Shared.Enums.RechargeTypeEnum.PremiumPackage)
- .SumAsync(x => x.RechargeAmount);
-
- // 5. 计算利润率
- decimal profitRate = totalCost > 0
- ? (totalRevenue / totalCost - 1) * 100
- : 0;
-
- // 6. 按200售价计算成本
- decimal costAt200Price = totalRevenue > 0
- ? (totalCost / totalRevenue) * 200
- : 0;
-
- // 7. 格式化输出
- var today = DateTime.Now;
- string dayOfWeek = today.ToString("dddd", new System.Globalization.CultureInfo("zh-CN"));
- string weekDay = dayOfWeek switch
- {
- "星期一" => "周1",
- "星期二" => "周2",
- "星期三" => "周3",
- "星期四" => "周4",
- "星期五" => "周5",
- "星期六" => "周6",
- "星期日" => "周日",
- _ => dayOfWeek
- };
-
- var result = $@"{today:M月d日} {weekDay}
-尊享包已消耗({totalUsedTokens / 100000000m:F2}亿){totalUsedTokens}
-尊享包剩余库存({totalRemainingTokens / 100000000m:F2}亿){totalRemainingTokens}
-当前成本:{currentCost:F2}RMB
-1亿Token成本:{costPerHundredMillion:F2} RMB=1亿 Token
-总成本:{totalCost:F2} RMB
-总收益:{totalRevenue:F2}RMB
-利润率: {profitRate:F1}%
-按200售价来算,成本在{costAt200Price:F2}";
-
- return result;
- }
-
- public class TokenStatisticsInput
- {
- ///
- /// 指定日期(当天零点)
- ///
- public DateTime Date { get; set; }
-
- ///
- /// 模型Id -> 1亿Token成本(RMB)
- ///
- public Dictionary ModelCosts { get; set; } = new();
- }
-
- ///
- /// 获取指定日期各模型Token统计
- ///
- [Authorize]
- [HttpPost("account/token-statistics")]
- public async Task GetTokenStatisticsAsync([FromBody] TokenStatisticsInput input)
- {
- if (CurrentUser.UserName != "Guo" && CurrentUser.UserName != "cc")
- {
- throw new UserFriendlyException("您暂无权限访问");
- }
-
- if (input.ModelCosts is null || input.ModelCosts.Count == 0)
- {
- throw new UserFriendlyException("请提供模型成本配置");
- }
-
- var day = input.Date.Date;
- var nextDay = day.AddDays(1);
- var modelIds = input.ModelCosts.Keys.ToList();
-
- var modelStats = await _messageRepository._DbQueryable
- .Where(x => modelIds.Contains(x.ModelId))
- .Where(x => x.CreationTime >= day && x.CreationTime < nextDay)
- .Where(x => x.Role == "system")
- .GroupBy(x => x.ModelId)
- .Select(x => new
- {
- ModelId = x.ModelId,
- Tokens = SqlFunc.AggregateSum(x.TokenUsage.TotalTokenCount),
- Count = SqlFunc.AggregateCount(x.Id)
- })
- .ToListAsync();
-
- var modelStatDict = modelStats.ToDictionary(x => x.ModelId, x => x);
-
- string weekDay = day.ToString("dddd", new CultureInfo("zh-CN")) switch
- {
- "星期一" => "周1",
- "星期二" => "周2",
- "星期三" => "周3",
- "星期四" => "周4",
- "星期五" => "周5",
- "星期六" => "周6",
- "星期日" => "周日",
- _ => day.ToString("dddd", new CultureInfo("zh-CN"))
- };
-
- var sb = new StringBuilder();
- sb.AppendLine($"{day:M月d日} {weekDay}");
-
- foreach (var kvp in input.ModelCosts)
- {
- var modelId = kvp.Key;
- var cost = kvp.Value;
-
- modelStatDict.TryGetValue(modelId, out var stat);
- long tokens = stat?.Tokens ?? 0;
- long count = stat?.Count ?? 0;
-
- decimal costPerHundredMillion = tokens > 0
- ? cost / (tokens / 100000000m)
- : 0;
-
- decimal tokensInWan = tokens / 10000m;
-
- sb.AppendLine();
- sb.AppendLine($"{modelId} 成本:【{cost:F2}RMB】 次数:【{count}次】 token:【{tokensInWan:F0}w】 1亿token成本:【{costPerHundredMillion:F2}RMB】");
- }
-
- return sb.ToString().TrimEnd();
- }
}
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Anthropic/AnthropicChatCompletionDto.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Anthropic/AnthropicChatCompletionDto.cs
index 78e6dc5e..05242651 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Anthropic/AnthropicChatCompletionDto.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain.Shared/Dtos/Anthropic/AnthropicChatCompletionDto.cs
@@ -22,13 +22,13 @@ public class AnthropicStreamDto
[JsonIgnore]
public ThorUsageResponse TokenUsage => new ThorUsageResponse
{
- PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
- InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
- OutputTokens = Usage?.OutputTokens,
+ PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
+ InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
+ OutputTokens = Usage?.OutputTokens??0,
InputTokensDetails = null,
- CompletionTokens = Usage?.OutputTokens,
- TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
- Usage?.OutputTokens,
+ CompletionTokens = Usage?.OutputTokens??0,
+ TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
+ Usage?.OutputTokens??0,
PromptTokensDetails = null,
CompletionTokensDetails = null
};
@@ -119,13 +119,13 @@ public class AnthropicChatCompletionDto
[JsonIgnore]
public ThorUsageResponse TokenUsage => new ThorUsageResponse
{
- PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
- InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
- OutputTokens = Usage?.OutputTokens,
+ PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
+ InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
+ OutputTokens = Usage?.OutputTokens??0,
InputTokensDetails = null,
- CompletionTokens = Usage?.OutputTokens,
- TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
- Usage?.OutputTokens,
+ CompletionTokens = Usage?.OutputTokens??0,
+ TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
+ Usage?.OutputTokens??0,
PromptTokensDetails = null,
CompletionTokensDetails = null
};
diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
index b2fa19d5..1cda2133 100644
--- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
+++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/AiGateWayManager.cs
@@ -628,9 +628,13 @@ public class AiGateWayManager : DomainService
{
responseResult.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
//message_start是为了保底机制
- if (responseResult.Item1.Contains("message_delta") || responseResult.Item1.Contains("message_start"))
+ if (responseResult.Item1.Contains("message_delta") || responseResult.Item1.Contains("message_start")||responseResult.Item1.Contains("message_stop"))
{
- tokenUsage = responseResult.Item2?.TokenUsage;
+ if (responseResult.Item2?.TokenUsage is not null||responseResult.Item2?.TokenUsage.TotalTokens>0)
+ {
+ tokenUsage = responseResult.Item2?.TokenUsage;
+ }
+
}
backupSystemContent.Append(responseResult.Item2?.Delta?.Text);
diff --git a/Yi.Ai.Vue3/src/config/permission.ts b/Yi.Ai.Vue3/src/config/permission.ts
index c1c3c64a..975246fd 100644
--- a/Yi.Ai.Vue3/src/config/permission.ts
+++ b/Yi.Ai.Vue3/src/config/permission.ts
@@ -25,6 +25,11 @@ export const PAGE_PERMISSIONS: PermissionConfig[] = [
allowedUsers: ['cc', 'Guo'],
description: '渠道商管理页面 - 仅限cc和Guo用户访问',
},
+ {
+ path: '/console/system-statistics',
+ allowedUsers: ['cc', 'Guo'],
+ description: '系统统计页面 - 仅限cc和Guo用户访问',
+ },
// 可以在这里继续添加其他需要权限控制的页面
// {
// path: '/console/admin',
diff --git a/Yi.Ai.Vue3/src/pages/console/index.vue b/Yi.Ai.Vue3/src/pages/console/index.vue
index d1ff9bc3..a78a7108 100644
--- a/Yi.Ai.Vue3/src/pages/console/index.vue
+++ b/Yi.Ai.Vue3/src/pages/console/index.vue
@@ -19,7 +19,8 @@ const userStore = useUserStore();
const userName = userStore.userInfo?.user?.userName;
-const hasPermission = checkPagePermission('/console/channel', userName);
+const hasChannelPermission = checkPagePermission('/console/channel', userName);
+const hasSystemStatisticsPermission = checkPagePermission('/console/system-statistics', userName);
// 菜单项配置
@@ -35,10 +36,16 @@ const baseNavItems = [
{ name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' },
];
-// 根据权限动态添加渠道商管理
-const navItems = hasPermission
- ? [...baseNavItems, { name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' }]
- : baseNavItems;
+// 根据权限动态添加菜单项
+let navItems = [...baseNavItems];
+
+if (hasChannelPermission) {
+ navItems.push({ name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' });
+}
+
+if (hasSystemStatisticsPermission) {
+ navItems.push({ name: 'system-statistics', label: '系统统计', icon: 'DataAnalysis', path: '/console/system-statistics' });
+}
// 当前激活的菜单
const activeNav = computed(() => {
diff --git a/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts b/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts
index 6c38b4e7..777991f8 100644
--- a/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts
+++ b/Yi.Ai.Vue3/src/routers/modules/staticRouter.ts
@@ -223,6 +223,14 @@ export const layoutRouter: RouteRecordRaw[] = [
title: '意心Ai-渠道商管理',
},
},
+ {
+ path: 'system-statistics',
+ name: 'consoleSystemStatistics',
+ component: () => import('@/pages/console/system-statistics/index.vue'),
+ meta: {
+ title: '意心Ai-系统统计',
+ },
+ },
],
},
],
diff --git a/Yi.Ai.Vue3/types/import_meta.d.ts b/Yi.Ai.Vue3/types/import_meta.d.ts
index c98d612e..8f2a798b 100644
--- a/Yi.Ai.Vue3/types/import_meta.d.ts
+++ b/Yi.Ai.Vue3/types/import_meta.d.ts
@@ -7,7 +7,6 @@ interface ImportMetaEnv {
readonly VITE_WEB_BASE_API: string;
readonly VITE_API_URL: string;
readonly VITE_FILE_UPLOAD_API: string;
- readonly VITE_BUILD_COMPRESS: string;
readonly VITE_SSO_SEVER_URL: string;
readonly VITE_APP_VERSION: string;
}