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; }