diff --git a/packages/admin-client/src/features/conversations/application/useConversations.ts b/packages/admin-client/src/features/conversations/application/useConversations.ts index 3d7669e..2e5bb74 100644 --- a/packages/admin-client/src/features/conversations/application/useConversations.ts +++ b/packages/admin-client/src/features/conversations/application/useConversations.ts @@ -64,4 +64,6 @@ export type { ConversationStatistics, ConversationQueryParams, TokenDetails, + GlobalTokenStats, + TodayTokenStats, } from '../infrastructure/conversations.api'; diff --git a/packages/admin-client/src/features/conversations/infrastructure/conversations.api.ts b/packages/admin-client/src/features/conversations/infrastructure/conversations.api.ts index 269ddb1..4176b85 100644 --- a/packages/admin-client/src/features/conversations/infrastructure/conversations.api.ts +++ b/packages/admin-client/src/features/conversations/infrastructure/conversations.api.ts @@ -78,6 +78,24 @@ export interface PaginatedConversations { totalPages: number; } +export interface GlobalTokenStats { + totalInputTokens: number; + totalOutputTokens: number; + totalCacheCreationTokens: number; + totalCacheReadTokens: number; + totalTokens: number; + totalEstimatedCost: number; + totalApiCalls: number; +} + +export interface TodayTokenStats { + totalInputTokens: number; + totalOutputTokens: number; + totalTokens: number; + totalEstimatedCost: number; + totalApiCalls: number; +} + export interface ConversationStatistics { total: number; active: number; @@ -85,6 +103,8 @@ export interface ConversationStatistics { converted: number; todayCount: number; conversionRate: string; + tokenStats: GlobalTokenStats; + todayTokenStats: TodayTokenStats; } export interface ConversationQueryParams { diff --git a/packages/admin-client/src/features/conversations/presentation/pages/ConversationsPage.tsx b/packages/admin-client/src/features/conversations/presentation/pages/ConversationsPage.tsx index 4829850..a16b30c 100644 --- a/packages/admin-client/src/features/conversations/presentation/pages/ConversationsPage.tsx +++ b/packages/admin-client/src/features/conversations/presentation/pages/ConversationsPage.tsx @@ -22,6 +22,9 @@ import { GlobalOutlined, LaptopOutlined, BarChartOutlined, + DollarOutlined, + ApiOutlined, + ThunderboltOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; @@ -196,7 +199,7 @@ export function ConversationsPage() {
对话管理 - {/* Statistics Cards */} + {/* Statistics Cards - Conversations */} @@ -251,6 +254,76 @@ export function ConversationsPage() { + {/* Token Usage Statistics */} + + + + + } + valueStyle={{ color: '#13c2c2', fontSize: 20 }} + formatter={(value) => formatNumber(value as number)} + /> +
+ 输入: {formatNumber(stats?.tokenStats?.totalInputTokens ?? 0)} | + 输出: {formatNumber(stats?.tokenStats?.totalOutputTokens ?? 0)} +
+
+
+ + + + + } + precision={2} + valueStyle={{ color: '#eb2f96', fontSize: 20 }} + /> +
+ API 调用: {formatNumber(stats?.tokenStats?.totalApiCalls ?? 0)} 次 +
+
+
+ + + + + } + valueStyle={{ color: '#1890ff', fontSize: 20 }} + formatter={(value) => formatNumber(value as number)} + /> +
+ 输入: {formatNumber(stats?.todayTokenStats?.totalInputTokens ?? 0)} | + 输出: {formatNumber(stats?.todayTokenStats?.totalOutputTokens ?? 0)} +
+
+
+ + + + + } + precision={4} + valueStyle={{ color: '#fa8c16', fontSize: 20 }} + /> +
+ API 调用: {formatNumber(stats?.todayTokenStats?.totalApiCalls ?? 0)} 次 +
+
+
+ +
+ {/* Filters */} @@ -542,3 +615,14 @@ function parseUserAgent(ua: string): string { return `${browser} / ${os}`; } + +// Helper function to format large numbers (e.g., 1234567 -> 1.23M) +function formatNumber(num: number): string { + if (num >= 1000000) { + return (num / 1000000).toFixed(2) + 'M'; + } + if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K'; + } + return num.toString(); +} diff --git a/packages/services/conversation-service/src/adapters/inbound/admin-conversation.controller.ts b/packages/services/conversation-service/src/adapters/inbound/admin-conversation.controller.ts index ec4bb3d..4d05189 100644 --- a/packages/services/conversation-service/src/adapters/inbound/admin-conversation.controller.ts +++ b/packages/services/conversation-service/src/adapters/inbound/admin-conversation.controller.ts @@ -323,7 +323,7 @@ export class AdminConversationController { } /** - * 获取对话统计 + * 获取对话统计(包含 Token 汇总) */ @Get('statistics/overview') async getStatistics(@Headers('authorization') auth: string) { @@ -342,6 +342,33 @@ export class AdminConversationController { .where('c.created_at >= :today', { today }) .getCount(); + // 全局 Token 统计 + const allTokenStats = await this.tokenUsageRepo + .createQueryBuilder('t') + .select([ + 'SUM(t.input_tokens) as "totalInputTokens"', + 'SUM(t.output_tokens) as "totalOutputTokens"', + 'SUM(t.cache_creation_tokens) as "totalCacheCreationTokens"', + 'SUM(t.cache_read_tokens) as "totalCacheReadTokens"', + 'SUM(t.total_tokens) as "totalTokens"', + 'SUM(t.estimated_cost) as "totalEstimatedCost"', + 'COUNT(*) as "totalApiCalls"', + ]) + .getRawOne(); + + // 今日 Token 统计 + const todayTokenStats = await this.tokenUsageRepo + .createQueryBuilder('t') + .select([ + 'SUM(t.input_tokens) as "totalInputTokens"', + 'SUM(t.output_tokens) as "totalOutputTokens"', + 'SUM(t.total_tokens) as "totalTokens"', + 'SUM(t.estimated_cost) as "totalEstimatedCost"', + 'COUNT(*) as "totalApiCalls"', + ]) + .where('t.created_at >= :today', { today }) + .getRawOne(); + return { success: true, data: { @@ -351,6 +378,23 @@ export class AdminConversationController { converted, todayCount, conversionRate: total > 0 ? ((converted / total) * 100).toFixed(1) : '0', + // Token 汇总统计 + tokenStats: { + totalInputTokens: parseInt(allTokenStats?.totalInputTokens || '0'), + totalOutputTokens: parseInt(allTokenStats?.totalOutputTokens || '0'), + totalCacheCreationTokens: parseInt(allTokenStats?.totalCacheCreationTokens || '0'), + totalCacheReadTokens: parseInt(allTokenStats?.totalCacheReadTokens || '0'), + totalTokens: parseInt(allTokenStats?.totalTokens || '0'), + totalEstimatedCost: parseFloat(allTokenStats?.totalEstimatedCost || '0'), + totalApiCalls: parseInt(allTokenStats?.totalApiCalls || '0'), + }, + todayTokenStats: { + totalInputTokens: parseInt(todayTokenStats?.totalInputTokens || '0'), + totalOutputTokens: parseInt(todayTokenStats?.totalOutputTokens || '0'), + totalTokens: parseInt(todayTokenStats?.totalTokens || '0'), + totalEstimatedCost: parseFloat(todayTokenStats?.totalEstimatedCost || '0'), + totalApiCalls: parseInt(todayTokenStats?.totalApiCalls || '0'), + }, }, }; }