diff --git a/src/pages/_components/bar-chart/index.tsx b/src/pages/_components/bar-chart/index.tsx index 3196fbd0..34bd8649 100644 --- a/src/pages/_components/bar-chart/index.tsx +++ b/src/pages/_components/bar-chart/index.tsx @@ -1,7 +1,7 @@ import useCoolColors from '@/hooks/use-cool-colors'; import { Chart } from '@gpustack/core-ui'; import { formatLargeNumber } from '@gpustack/core-ui/utils'; -import { theme } from 'antd'; +import { Empty, theme } from 'antd'; import _ from 'lodash'; import React, { useEffect, useMemo, useRef } from 'react'; @@ -254,7 +254,19 @@ const BarChart: React.FC = (props) => { }, [legendIsolate, seriesData, legendData]); if (!seriesData.length) { - return
; + return ( +
+ +
+ ); } return ( diff --git a/src/pages/dashboard/components/new-usage.tsx b/src/pages/dashboard/components/new-usage.tsx index 9a075b91..c83cd552 100644 --- a/src/pages/dashboard/components/new-usage.tsx +++ b/src/pages/dashboard/components/new-usage.tsx @@ -1,4 +1,3 @@ -import useQueryBreakdownList from '@/pages/usage/services/use-query-breakdown-list'; import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data'; import { PageTools } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; @@ -6,7 +5,13 @@ import { Col, Row } from 'antd'; import dayjs from 'dayjs'; import { useEffect, useMemo } from 'react'; import styled from 'styled-components'; -import { DashboardUsageCommonParams } from '../config'; +import { + DashboardUsageCommonParams, + getUsageRankHeight, + getUsageRankSlotCount +} from '../config'; +import useTopTokenUsageByApiKey from '../hooks/use-top-token-usage-by-api-key'; +import useTopTokenUsageByUser from '../hooks/use-top-token-usage-by-user'; import ApiRequestsByModel from './usage-charts/api-requests-by-model'; import TokenUsageByModel from './usage-charts/token-usage-by-model'; import TopTokenUsageByApiKey from './usage-charts/top-token-usage-by-api-key'; @@ -21,9 +26,6 @@ const NewUsage = () => { const tokenByModelQuery = useQueryTimeSeriesData({ key: 'tokenUsageByModelData' }); - const activeUsersQuery = useQueryBreakdownList({ - key: 'dashboardActiveUsersData' - }); const dateRange = useMemo( () => ({ @@ -58,6 +60,23 @@ const NewUsage = () => { fetchData().catch(() => {}); }, [commonParams, dateRange]); + const userUsage = useTopTokenUsageByUser(commonParams); + const apiKeyUsage = useTopTokenUsageByApiKey(commonParams); + + const userCount = userUsage.rankData.names.length; + const apiKeyCount = apiKeyUsage.rankData.names.length; + + const rankHeight = Math.max( + getUsageRankHeight(userCount), + getUsageRankHeight(apiKeyCount) + ); + + const rankMaxItems = Math.max( + getUsageRankSlotCount(rankHeight), + userCount, + apiKeyCount + ); + return ( <> {
- + - +
diff --git a/src/pages/dashboard/components/usage-charts/top-token-usage-by-api-key.tsx b/src/pages/dashboard/components/usage-charts/top-token-usage-by-api-key.tsx index a59b06a7..95668f18 100644 --- a/src/pages/dashboard/components/usage-charts/top-token-usage-by-api-key.tsx +++ b/src/pages/dashboard/components/usage-charts/top-token-usage-by-api-key.tsx @@ -1,72 +1,39 @@ -import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data'; import { HBarChart } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; -import { useEffect, useMemo } from 'react'; -import { - baseColorMap, - DashboardUsageCommonParams, - toUsageRankData, - usageChartHeight -} from '../../config'; +import { usageChartHeight, usageRankMaxItems } from '../../config'; +import type useTopTokenUsageByApiKey from '../../hooks/use-top-token-usage-by-api-key'; import UsageChartCard, { ChartLoadingBox } from './shared'; +type RankData = ReturnType['rankData']; + interface TopTokenUsageByApiKeyProps { - commonParams: DashboardUsageCommonParams; + rankData: RankData; + loading: boolean; height?: number; + maxItems?: number; } const TopTokenUsageByApiKey: React.FC = ({ - commonParams, - height = usageChartHeight + rankData, + loading, + height = usageChartHeight, + maxItems = usageRankMaxItems }) => { const intl = useIntl(); - const query = useQueryTimeSeriesData({ - key: 'topTokenUsageByApiKeyData' - }); - const tokenUsageText = intl.formatMessage({ id: 'dashboard.tokens' }); - - useEffect(() => { - query - .fetchData({ - ...commonParams, - metric: 'total_tokens', - group_by: ['date', 'api_key'], - page: 1, - perPage: 10, - sort_by: '-total_tokens' - }) - .catch(() => {}); - }, [commonParams]); - - const rankData = useMemo(() => { - const data = toUsageRankData( - query.detailData, - 'api_key', - tokenUsageText, - baseColorMap.baseR3 - ); - return { - names: data.names.filter((name) => name !== '-'), - series: data.series.map((item) => ({ - ...item, - data: item.data.filter((point) => point.name !== '-') - })) - }; - }, [query.detailData, tokenUsageText]); return ( - {query.loading && rankData.names.length === 0 ? ( + {loading && rankData.names.length === 0 ? ( ) : ( )} diff --git a/src/pages/dashboard/components/usage-charts/top-token-usage-by-user.tsx b/src/pages/dashboard/components/usage-charts/top-token-usage-by-user.tsx index 1e1f02c5..446ee443 100644 --- a/src/pages/dashboard/components/usage-charts/top-token-usage-by-user.tsx +++ b/src/pages/dashboard/components/usage-charts/top-token-usage-by-user.tsx @@ -1,67 +1,39 @@ -import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data'; import { HBarChart } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; -import { useEffect, useMemo } from 'react'; -import { - baseColorMap, - DashboardUsageCommonParams, - toUsageRankData, - usageChartHeight -} from '../../config'; +import { usageChartHeight, usageRankMaxItems } from '../../config'; +import type useTopTokenUsageByUser from '../../hooks/use-top-token-usage-by-user'; import UsageChartCard, { ChartLoadingBox } from './shared'; +type RankData = ReturnType['rankData']; + interface TopTokenUsageByUserProps { - commonParams: DashboardUsageCommonParams; + rankData: RankData; + loading: boolean; height?: number; + maxItems?: number; } const TopTokenUsageByUser: React.FC = ({ - commonParams, - height = usageChartHeight + rankData, + loading, + height = usageChartHeight, + maxItems = usageRankMaxItems }) => { const intl = useIntl(); - const query = useQueryTimeSeriesData({ - key: 'topTokenUsageByUserData' - }); - const tokenUsageText = intl.formatMessage({ id: 'dashboard.tokens' }); - - useEffect(() => { - query - .fetchData({ - ...commonParams, - metric: 'total_tokens', - group_by: ['date', 'user'], - page: 1, - perPage: 10, - sort_by: '-total_tokens' - }) - .catch(() => {}); - }, [commonParams]); - - const rankData = useMemo( - () => - toUsageRankData( - query.detailData, - 'user', - tokenUsageText, - baseColorMap.base - ), - [query.detailData, tokenUsageText] - ); return ( - {query.loading && rankData.names.length === 0 ? ( + {loading && rankData.names.length === 0 ? ( ) : ( )} diff --git a/src/pages/dashboard/config/index.ts b/src/pages/dashboard/config/index.ts index d63bb04b..51228dd4 100644 --- a/src/pages/dashboard/config/index.ts +++ b/src/pages/dashboard/config/index.ts @@ -60,6 +60,24 @@ export interface DashboardUsageCommonParams { export const usageChartHeight = 300; export const usageChartCardHeight = usageChartHeight + 52; +export const usageRankMaxItems = 10; +export const usageRankDefaultHeight = 460; +export const usageRankMinHeight = 300; +export const usageRankItemHeight = 46; + +export const getUsageRankHeight = (count: number) => { + const safeCount = Math.min(Math.max(count, 0), usageRankMaxItems); + return Math.max( + usageRankMinHeight, + usageRankDefaultHeight - + (usageRankMaxItems - safeCount) * usageRankItemHeight + ); +}; + +export const getUsageRankSlotCount = (height: number) => { + return Math.max(Math.floor(height / usageRankItemHeight), 1); +}; + export const buildUsageLabel = (item: BreakdownItem, groupBy: UsageGroupBy) => { const rawItem = item as any; const groupItem = rawItem[groupBy]; diff --git a/src/pages/dashboard/hooks/use-top-token-usage-by-api-key.ts b/src/pages/dashboard/hooks/use-top-token-usage-by-api-key.ts new file mode 100644 index 00000000..f001dd84 --- /dev/null +++ b/src/pages/dashboard/hooks/use-top-token-usage-by-api-key.ts @@ -0,0 +1,52 @@ +import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data'; +import { useIntl } from '@umijs/max'; +import { useEffect, useMemo } from 'react'; +import { + baseColorMap, + DashboardUsageCommonParams, + toUsageRankData +} from '../config'; + +export default function useTopTokenUsageByApiKey( + commonParams: DashboardUsageCommonParams +) { + const intl = useIntl(); + const query = useQueryTimeSeriesData({ + key: 'topTokenUsageByApiKeyData' + }); + const tokenUsageText = intl.formatMessage({ id: 'dashboard.tokens' }); + + useEffect(() => { + query + .fetchData({ + ...commonParams, + metric: 'total_tokens', + group_by: ['date', 'api_key'], + page: 1, + perPage: 10, + sort_by: '-total_tokens' + }) + .catch(() => {}); + }, [commonParams]); + + const rankData = useMemo(() => { + const data = toUsageRankData( + query.detailData, + 'api_key', + tokenUsageText, + baseColorMap.baseR3 + ); + return { + names: data.names.filter((name) => name !== '-'), + series: data.series.map((item) => ({ + ...item, + data: item.data.filter((point) => point.name !== '-') + })) + }; + }, [query.detailData, tokenUsageText]); + + return { + rankData, + loading: query.loading + }; +} diff --git a/src/pages/dashboard/hooks/use-top-token-usage-by-user.ts b/src/pages/dashboard/hooks/use-top-token-usage-by-user.ts new file mode 100644 index 00000000..ab64f828 --- /dev/null +++ b/src/pages/dashboard/hooks/use-top-token-usage-by-user.ts @@ -0,0 +1,47 @@ +import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data'; +import { useIntl } from '@umijs/max'; +import { useEffect, useMemo } from 'react'; +import { + baseColorMap, + DashboardUsageCommonParams, + toUsageRankData +} from '../config'; + +export default function useTopTokenUsageByUser( + commonParams: DashboardUsageCommonParams +) { + const intl = useIntl(); + const query = useQueryTimeSeriesData({ + key: 'topTokenUsageByUserData' + }); + const tokenUsageText = intl.formatMessage({ id: 'dashboard.tokens' }); + + useEffect(() => { + query + .fetchData({ + ...commonParams, + metric: 'total_tokens', + group_by: ['date', 'user'], + page: 1, + perPage: 10, + sort_by: '-total_tokens' + }) + .catch(() => {}); + }, [commonParams]); + + const rankData = useMemo( + () => + toUsageRankData( + query.detailData, + 'user', + tokenUsageText, + baseColorMap.base + ), + [query.detailData, tokenUsageText] + ); + + return { + rankData, + loading: query.loading + }; +} diff --git a/src/pages/usage/hooks/use-apikeys-columns.tsx b/src/pages/usage/hooks/use-apikeys-columns.tsx index 1579482f..fb864ee3 100644 --- a/src/pages/usage/hooks/use-apikeys-columns.tsx +++ b/src/pages/usage/hooks/use-apikeys-columns.tsx @@ -69,7 +69,11 @@ const useModelsColumns = (): Array<{ ) }, { - title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }), + title: ( + + {intl.formatMessage({ id: 'usage.table.inputTokensCached' })} + + ), dataIndex: 'input_cached_tokens', key: 'input_cached_tokens', sorter: true, diff --git a/src/pages/usage/hooks/use-models-columns.tsx b/src/pages/usage/hooks/use-models-columns.tsx index bb7fce3c..76e5d81b 100644 --- a/src/pages/usage/hooks/use-models-columns.tsx +++ b/src/pages/usage/hooks/use-models-columns.tsx @@ -81,7 +81,11 @@ const useModelsColumns = (): Array<{ ) }, { - title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }), + title: ( + + {intl.formatMessage({ id: 'usage.table.inputTokensCached' })} + + ), dataIndex: 'input_cached_tokens', key: 'input_cached_tokens', sorter: true, diff --git a/src/pages/usage/hooks/use-users-columns.tsx b/src/pages/usage/hooks/use-users-columns.tsx index c945799b..d3bc1ec9 100644 --- a/src/pages/usage/hooks/use-users-columns.tsx +++ b/src/pages/usage/hooks/use-users-columns.tsx @@ -78,7 +78,11 @@ const useModelsColumns = (): Array<{ ) }, { - title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }), + title: ( + + {intl.formatMessage({ id: 'usage.table.inputTokensCached' })} + + ), dataIndex: 'input_cached_tokens', key: 'input_cached_tokens', sorter: true,