fix(style): chart style
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import useCoolColors from '@/hooks/use-cool-colors';
|
import useCoolColors from '@/hooks/use-cool-colors';
|
||||||
import { Chart } from '@gpustack/core-ui';
|
import { Chart } from '@gpustack/core-ui';
|
||||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||||
import { theme } from 'antd';
|
import { Empty, theme } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
@@ -254,7 +254,19 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
}, [legendIsolate, seriesData, legendData]);
|
}, [legendIsolate, seriesData, legendData]);
|
||||||
|
|
||||||
if (!seriesData.length) {
|
if (!seriesData.length) {
|
||||||
return <div style={{ width: width || '100%', height }} />;
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: width || '100%',
|
||||||
|
height,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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 useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
|
||||||
import { PageTools } from '@gpustack/core-ui';
|
import { PageTools } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
@@ -6,7 +5,13 @@ import { Col, Row } from 'antd';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
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 ApiRequestsByModel from './usage-charts/api-requests-by-model';
|
||||||
import TokenUsageByModel from './usage-charts/token-usage-by-model';
|
import TokenUsageByModel from './usage-charts/token-usage-by-model';
|
||||||
import TopTokenUsageByApiKey from './usage-charts/top-token-usage-by-api-key';
|
import TopTokenUsageByApiKey from './usage-charts/top-token-usage-by-api-key';
|
||||||
@@ -21,9 +26,6 @@ const NewUsage = () => {
|
|||||||
const tokenByModelQuery = useQueryTimeSeriesData({
|
const tokenByModelQuery = useQueryTimeSeriesData({
|
||||||
key: 'tokenUsageByModelData'
|
key: 'tokenUsageByModelData'
|
||||||
});
|
});
|
||||||
const activeUsersQuery = useQueryBreakdownList({
|
|
||||||
key: 'dashboardActiveUsersData'
|
|
||||||
});
|
|
||||||
|
|
||||||
const dateRange = useMemo(
|
const dateRange = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -58,6 +60,23 @@ const NewUsage = () => {
|
|||||||
fetchData().catch(() => {});
|
fetchData().catch(() => {});
|
||||||
}, [commonParams, dateRange]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTools
|
<PageTools
|
||||||
@@ -84,10 +103,20 @@ const NewUsage = () => {
|
|||||||
<Section>
|
<Section>
|
||||||
<Row gutter={[20, 20]}>
|
<Row gutter={[20, 20]}>
|
||||||
<Col xs={24} sm={24} md={24} lg={12}>
|
<Col xs={24} sm={24} md={24} lg={12}>
|
||||||
<TopTokenUsageByUser commonParams={commonParams} height={460} />
|
<TopTokenUsageByUser
|
||||||
|
rankData={userUsage.rankData}
|
||||||
|
loading={userUsage.loading}
|
||||||
|
height={rankHeight}
|
||||||
|
maxItems={rankMaxItems}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={24} lg={12}>
|
<Col xs={24} sm={24} md={24} lg={12}>
|
||||||
<TopTokenUsageByApiKey commonParams={commonParams} height={460} />
|
<TopTokenUsageByApiKey
|
||||||
|
rankData={apiKeyUsage.rankData}
|
||||||
|
loading={apiKeyUsage.loading}
|
||||||
|
height={rankHeight}
|
||||||
|
maxItems={rankMaxItems}
|
||||||
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,72 +1,39 @@
|
|||||||
import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
|
|
||||||
import { HBarChart } from '@gpustack/core-ui';
|
import { HBarChart } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { usageChartHeight, usageRankMaxItems } from '../../config';
|
||||||
import {
|
import type useTopTokenUsageByApiKey from '../../hooks/use-top-token-usage-by-api-key';
|
||||||
baseColorMap,
|
|
||||||
DashboardUsageCommonParams,
|
|
||||||
toUsageRankData,
|
|
||||||
usageChartHeight
|
|
||||||
} from '../../config';
|
|
||||||
import UsageChartCard, { ChartLoadingBox } from './shared';
|
import UsageChartCard, { ChartLoadingBox } from './shared';
|
||||||
|
|
||||||
|
type RankData = ReturnType<typeof useTopTokenUsageByApiKey>['rankData'];
|
||||||
|
|
||||||
interface TopTokenUsageByApiKeyProps {
|
interface TopTokenUsageByApiKeyProps {
|
||||||
commonParams: DashboardUsageCommonParams;
|
rankData: RankData;
|
||||||
|
loading: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
maxItems?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopTokenUsageByApiKey: React.FC<TopTokenUsageByApiKeyProps> = ({
|
const TopTokenUsageByApiKey: React.FC<TopTokenUsageByApiKeyProps> = ({
|
||||||
commonParams,
|
rankData,
|
||||||
height = usageChartHeight
|
loading,
|
||||||
|
height = usageChartHeight,
|
||||||
|
maxItems = usageRankMaxItems
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
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 (
|
return (
|
||||||
<UsageChartCard
|
<UsageChartCard
|
||||||
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByApiKey' })}
|
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByApiKey' })}
|
||||||
height={height + 52}
|
height={height + 52}
|
||||||
>
|
>
|
||||||
{query.loading && rankData.names.length === 0 ? (
|
{loading && rankData.names.length === 0 ? (
|
||||||
<ChartLoadingBox height={height} />
|
<ChartLoadingBox height={height} />
|
||||||
) : (
|
) : (
|
||||||
<HBarChart
|
<HBarChart
|
||||||
seriesData={rankData.series}
|
seriesData={rankData.series}
|
||||||
xAxisData={rankData.names}
|
xAxisData={rankData.names}
|
||||||
height={height}
|
height={height}
|
||||||
maxItems={10}
|
maxItems={maxItems}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</UsageChartCard>
|
</UsageChartCard>
|
||||||
|
|||||||
@@ -1,67 +1,39 @@
|
|||||||
import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
|
|
||||||
import { HBarChart } from '@gpustack/core-ui';
|
import { HBarChart } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { usageChartHeight, usageRankMaxItems } from '../../config';
|
||||||
import {
|
import type useTopTokenUsageByUser from '../../hooks/use-top-token-usage-by-user';
|
||||||
baseColorMap,
|
|
||||||
DashboardUsageCommonParams,
|
|
||||||
toUsageRankData,
|
|
||||||
usageChartHeight
|
|
||||||
} from '../../config';
|
|
||||||
import UsageChartCard, { ChartLoadingBox } from './shared';
|
import UsageChartCard, { ChartLoadingBox } from './shared';
|
||||||
|
|
||||||
|
type RankData = ReturnType<typeof useTopTokenUsageByUser>['rankData'];
|
||||||
|
|
||||||
interface TopTokenUsageByUserProps {
|
interface TopTokenUsageByUserProps {
|
||||||
commonParams: DashboardUsageCommonParams;
|
rankData: RankData;
|
||||||
|
loading: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
maxItems?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopTokenUsageByUser: React.FC<TopTokenUsageByUserProps> = ({
|
const TopTokenUsageByUser: React.FC<TopTokenUsageByUserProps> = ({
|
||||||
commonParams,
|
rankData,
|
||||||
height = usageChartHeight
|
loading,
|
||||||
|
height = usageChartHeight,
|
||||||
|
maxItems = usageRankMaxItems
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
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 (
|
return (
|
||||||
<UsageChartCard
|
<UsageChartCard
|
||||||
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByUser' })}
|
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByUser' })}
|
||||||
height={height + 52}
|
height={height + 52}
|
||||||
>
|
>
|
||||||
{query.loading && rankData.names.length === 0 ? (
|
{loading && rankData.names.length === 0 ? (
|
||||||
<ChartLoadingBox height={height} />
|
<ChartLoadingBox height={height} />
|
||||||
) : (
|
) : (
|
||||||
<HBarChart
|
<HBarChart
|
||||||
seriesData={rankData.series}
|
seriesData={rankData.series}
|
||||||
xAxisData={rankData.names}
|
xAxisData={rankData.names}
|
||||||
height={height}
|
height={height}
|
||||||
maxItems={10}
|
maxItems={maxItems}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</UsageChartCard>
|
</UsageChartCard>
|
||||||
|
|||||||
@@ -60,6 +60,24 @@ export interface DashboardUsageCommonParams {
|
|||||||
export const usageChartHeight = 300;
|
export const usageChartHeight = 300;
|
||||||
export const usageChartCardHeight = usageChartHeight + 52;
|
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) => {
|
export const buildUsageLabel = (item: BreakdownItem, groupBy: UsageGroupBy) => {
|
||||||
const rawItem = item as any;
|
const rawItem = item as any;
|
||||||
const groupItem = rawItem[groupBy];
|
const groupItem = rawItem[groupBy];
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -69,7 +69,11 @@ const useModelsColumns = (): Array<{
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }),
|
title: (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{intl.formatMessage({ id: 'usage.table.inputTokensCached' })}
|
||||||
|
</AutoTooltip>
|
||||||
|
),
|
||||||
dataIndex: 'input_cached_tokens',
|
dataIndex: 'input_cached_tokens',
|
||||||
key: 'input_cached_tokens',
|
key: 'input_cached_tokens',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ const useModelsColumns = (): Array<{
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }),
|
title: (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{intl.formatMessage({ id: 'usage.table.inputTokensCached' })}
|
||||||
|
</AutoTooltip>
|
||||||
|
),
|
||||||
dataIndex: 'input_cached_tokens',
|
dataIndex: 'input_cached_tokens',
|
||||||
key: 'input_cached_tokens',
|
key: 'input_cached_tokens',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ const useModelsColumns = (): Array<{
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'usage.table.inputTokensCached' }),
|
title: (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{intl.formatMessage({ id: 'usage.table.inputTokensCached' })}
|
||||||
|
</AutoTooltip>
|
||||||
|
),
|
||||||
dataIndex: 'input_cached_tokens',
|
dataIndex: 'input_cached_tokens',
|
||||||
key: 'input_cached_tokens',
|
key: 'input_cached_tokens',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user