feat: 完成系统监控页面
This commit is contained in:
@@ -81,162 +81,4 @@ public class AiAccountService : ApplicationService
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取利润统计数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentCost">当前成本(RMB)</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Authorize]
|
|
||||||
[HttpGet("account/profit-statistics")]
|
|
||||||
public async Task<string> 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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 指定日期(当天零点)
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 模型Id -> 1亿Token成本(RMB)
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, decimal> ModelCosts { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定日期各模型Token统计
|
|
||||||
/// </summary>
|
|
||||||
[Authorize]
|
|
||||||
[HttpPost("account/token-statistics")]
|
|
||||||
public async Task<string> 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ public class AnthropicStreamDto
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
||||||
{
|
{
|
||||||
PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
|
||||||
InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
|
||||||
OutputTokens = Usage?.OutputTokens,
|
OutputTokens = Usage?.OutputTokens??0,
|
||||||
InputTokensDetails = null,
|
InputTokensDetails = null,
|
||||||
CompletionTokens = Usage?.OutputTokens,
|
CompletionTokens = Usage?.OutputTokens??0,
|
||||||
TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
|
TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
|
||||||
Usage?.OutputTokens,
|
Usage?.OutputTokens??0,
|
||||||
PromptTokensDetails = null,
|
PromptTokensDetails = null,
|
||||||
CompletionTokensDetails = null
|
CompletionTokensDetails = null
|
||||||
};
|
};
|
||||||
@@ -119,13 +119,13 @@ public class AnthropicChatCompletionDto
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
public ThorUsageResponse TokenUsage => new ThorUsageResponse
|
||||||
{
|
{
|
||||||
PromptTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
PromptTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
|
||||||
InputTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens,
|
InputTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0,
|
||||||
OutputTokens = Usage?.OutputTokens,
|
OutputTokens = Usage?.OutputTokens??0,
|
||||||
InputTokensDetails = null,
|
InputTokensDetails = null,
|
||||||
CompletionTokens = Usage?.OutputTokens,
|
CompletionTokens = Usage?.OutputTokens??0,
|
||||||
TotalTokens = Usage?.InputTokens + Usage?.CacheCreationInputTokens + Usage?.CacheReadInputTokens +
|
TotalTokens = Usage?.InputTokens??0 + Usage?.CacheCreationInputTokens??0 + Usage?.CacheReadInputTokens??0 +
|
||||||
Usage?.OutputTokens,
|
Usage?.OutputTokens??0,
|
||||||
PromptTokensDetails = null,
|
PromptTokensDetails = null,
|
||||||
CompletionTokensDetails = null
|
CompletionTokensDetails = null
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -628,9 +628,13 @@ public class AiGateWayManager : DomainService
|
|||||||
{
|
{
|
||||||
responseResult.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
|
responseResult.Item2.SupplementalMultiplier(modelDescribe.Multiplier);
|
||||||
//message_start是为了保底机制
|
//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);
|
backupSystemContent.Append(responseResult.Item2?.Delta?.Text);
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ export const PAGE_PERMISSIONS: PermissionConfig[] = [
|
|||||||
allowedUsers: ['cc', 'Guo'],
|
allowedUsers: ['cc', 'Guo'],
|
||||||
description: '渠道商管理页面 - 仅限cc和Guo用户访问',
|
description: '渠道商管理页面 - 仅限cc和Guo用户访问',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/console/system-statistics',
|
||||||
|
allowedUsers: ['cc', 'Guo'],
|
||||||
|
description: '系统统计页面 - 仅限cc和Guo用户访问',
|
||||||
|
},
|
||||||
// 可以在这里继续添加其他需要权限控制的页面
|
// 可以在这里继续添加其他需要权限控制的页面
|
||||||
// {
|
// {
|
||||||
// path: '/console/admin',
|
// path: '/console/admin',
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ const userStore = useUserStore();
|
|||||||
|
|
||||||
const userName = userStore.userInfo?.user?.userName;
|
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' },
|
{ name: 'activation', label: '激活码兑换', icon: 'MagicStick', path: '/console/activation' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 根据权限动态添加渠道商管理
|
// 根据权限动态添加菜单项
|
||||||
const navItems = hasPermission
|
let navItems = [...baseNavItems];
|
||||||
? [...baseNavItems, { name: 'channel', label: '渠道商管理', icon: 'Setting', path: '/console/channel' }]
|
|
||||||
: 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(() => {
|
const activeNav = computed(() => {
|
||||||
|
|||||||
@@ -223,6 +223,14 @@ export const layoutRouter: RouteRecordRaw[] = [
|
|||||||
title: '意心Ai-渠道商管理',
|
title: '意心Ai-渠道商管理',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'system-statistics',
|
||||||
|
name: 'consoleSystemStatistics',
|
||||||
|
component: () => import('@/pages/console/system-statistics/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '意心Ai-系统统计',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
1
Yi.Ai.Vue3/types/import_meta.d.ts
vendored
@@ -7,7 +7,6 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_WEB_BASE_API: string;
|
readonly VITE_WEB_BASE_API: string;
|
||||||
readonly VITE_API_URL: string;
|
readonly VITE_API_URL: string;
|
||||||
readonly VITE_FILE_UPLOAD_API: string;
|
readonly VITE_FILE_UPLOAD_API: string;
|
||||||
readonly VITE_BUILD_COMPRESS: string;
|
|
||||||
readonly VITE_SSO_SEVER_URL: string;
|
readonly VITE_SSO_SEVER_URL: string;
|
||||||
readonly VITE_APP_VERSION: string;
|
readonly VITE_APP_VERSION: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user