refactor: dashboard usage
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { activeModelsAtom } from '@/atoms/dashboard';
|
||||
import { modelCategoriesMap } from '@/pages/llmodels/config';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import { AutoTooltip, PageTools } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Col, Row, Table } from 'antd';
|
||||
import { useContext } from 'react';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { DashboardContext } from '../config/dashboard-context';
|
||||
const NACategories = [
|
||||
modelCategoriesMap.llm,
|
||||
@@ -14,6 +16,12 @@ const NACategories = [
|
||||
const ActiveTable = () => {
|
||||
const intl = useIntl();
|
||||
const data = useContext(DashboardContext).active_models || [];
|
||||
const setActiveModels = useSetAtom(activeModelsAtom);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveModels(data);
|
||||
}, [data, setActiveModels]);
|
||||
|
||||
const modelColumns = [
|
||||
{
|
||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { activeModelsAtom } from '@/atoms/dashboard';
|
||||
import useQueryBreakdownList from '@/pages/usage/services/use-query-breakdown-list';
|
||||
import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
|
||||
import { formatLargeNumber } from '@/utils';
|
||||
import { PageTools } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Col, Row } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useContext, useEffect, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
baseColorMap,
|
||||
DashboardUsageCommonParams,
|
||||
getUsageSummary
|
||||
} from '../config';
|
||||
import { DashboardContext } from '../config/dashboard-context';
|
||||
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';
|
||||
import TopTokenUsageByUser from './usage-charts/top-token-usage-by-user';
|
||||
|
||||
const Section = styled.div`
|
||||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
const NewUsage = () => {
|
||||
const intl = useIntl();
|
||||
const activeModelsFromAtom = useAtomValue(activeModelsAtom);
|
||||
const { active_models: activeModelsFromContext = [] } =
|
||||
useContext(DashboardContext);
|
||||
const tokenByModelQuery = useQueryTimeSeriesData({
|
||||
key: 'tokenUsageByModelData'
|
||||
});
|
||||
const activeUsersQuery = useQueryBreakdownList({
|
||||
key: 'dashboardActiveUsersData'
|
||||
});
|
||||
|
||||
const dateRange = useMemo(
|
||||
() => ({
|
||||
start_date: dayjs().subtract(29, 'days').format('YYYY-MM-DD'),
|
||||
end_date: dayjs().format('YYYY-MM-DD')
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const commonParams = useMemo<DashboardUsageCommonParams>(
|
||||
() => ({
|
||||
...dateRange,
|
||||
scope: 'all',
|
||||
granularity: 'day',
|
||||
filters: {}
|
||||
}),
|
||||
[dateRange]
|
||||
);
|
||||
|
||||
const activeModels = activeModelsFromAtom.length
|
||||
? activeModelsFromAtom
|
||||
: activeModelsFromContext;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
await Promise.all([
|
||||
tokenByModelQuery.fetchData({
|
||||
...commonParams,
|
||||
metric: 'total_tokens',
|
||||
group_by: ['date', 'model'],
|
||||
sort_by: '-total_tokens'
|
||||
}),
|
||||
activeUsersQuery.fetchData({
|
||||
...dateRange,
|
||||
scope: 'all',
|
||||
group_by: ['user'],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
sort_by: '-total_tokens',
|
||||
filters: {}
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
fetchData().catch(() => {});
|
||||
}, [commonParams, dateRange]);
|
||||
|
||||
const summaryCards = useMemo(() => {
|
||||
const summary = getUsageSummary(tokenByModelQuery.detailData) || {
|
||||
total_tokens: 0,
|
||||
api_requests: 0
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
label: formatLargeNumber(summary.total_tokens) as string,
|
||||
value: intl.formatMessage({ id: 'dashboard.tokens' }),
|
||||
color: baseColorMap.base
|
||||
},
|
||||
{
|
||||
label: formatLargeNumber(summary.api_requests) as string,
|
||||
value: intl.formatMessage({ id: 'dashboard.apirequest' }),
|
||||
color: baseColorMap.baseR1
|
||||
},
|
||||
{
|
||||
label: formatLargeNumber(activeModels.length) as string,
|
||||
value: intl.formatMessage({ id: 'dashboard.activeModels' }),
|
||||
color: baseColorMap.baseR2
|
||||
},
|
||||
{
|
||||
label: formatLargeNumber(activeUsersQuery.dataSource.total) as string,
|
||||
value: intl.formatMessage({ id: 'dashboard.activeUsers' }),
|
||||
color: baseColorMap.baseR3
|
||||
}
|
||||
];
|
||||
}, [
|
||||
activeModels.length,
|
||||
activeUsersQuery.dataSource.total,
|
||||
intl,
|
||||
tokenByModelQuery.detailData
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTools
|
||||
style={{ margin: '24px 0 0' }}
|
||||
left={
|
||||
<span className="font-700">
|
||||
{intl.formatMessage({ id: 'dashboard.usage' })}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Section>
|
||||
<Row gutter={[20, 20]}>
|
||||
<Col xs={24} sm={24} md={24} lg={12}>
|
||||
<TokenUsageByModel
|
||||
data={tokenByModelQuery.detailData}
|
||||
loading={tokenByModelQuery.loading}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={24} lg={12}>
|
||||
<ApiRequestsByModel commonParams={commonParams} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Section>
|
||||
<Section>
|
||||
<Row gutter={[20, 20]}>
|
||||
<Col xs={24} sm={24} md={24} lg={12}>
|
||||
<TopTokenUsageByUser commonParams={commonParams} height={450} />
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={24} lg={12}>
|
||||
<TopTokenUsageByApiKey commonParams={commonParams} height={450} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewUsage;
|
||||
@@ -0,0 +1,63 @@
|
||||
import PieChart from '@/pages/_components/pie-chart';
|
||||
import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
DashboardUsageCommonParams,
|
||||
getUsageSummary,
|
||||
toUsagePieData,
|
||||
usageChartHeight
|
||||
} from '../../config';
|
||||
import UsageChartCard from './shared';
|
||||
|
||||
interface ApiRequestsByModelProps {
|
||||
commonParams: DashboardUsageCommonParams;
|
||||
}
|
||||
|
||||
const ApiRequestsByModel: React.FC<ApiRequestsByModelProps> = ({
|
||||
commonParams
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const query = useQueryTimeSeriesData({
|
||||
key: 'apiRequestsByModelData'
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
query
|
||||
.fetchData({
|
||||
...commonParams,
|
||||
metric: 'api_requests',
|
||||
group_by: ['date', 'model'],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
sort_by: '-api_requests'
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [commonParams]);
|
||||
|
||||
const chartData = useMemo(
|
||||
() => toUsagePieData(query.detailData, 'model', 'api_requests'),
|
||||
[query.detailData]
|
||||
);
|
||||
|
||||
const total = useMemo(() => {
|
||||
const summary = getUsageSummary(query.detailData);
|
||||
return Number(summary?.api_requests ?? 0);
|
||||
}, [query.detailData]);
|
||||
|
||||
return (
|
||||
<UsageChartCard
|
||||
title={intl.formatMessage({ id: 'dashboard.apiRequestsByModel' })}
|
||||
>
|
||||
<PieChart
|
||||
data={chartData}
|
||||
height={usageChartHeight}
|
||||
loading={query.loading}
|
||||
colorOffset={1}
|
||||
total={total}
|
||||
/>
|
||||
</UsageChartCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiRequestsByModel;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { CardWrapper } from '@gpustack/core-ui';
|
||||
import { Spin } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { usageChartCardHeight, usageChartHeight } from '../../config';
|
||||
|
||||
export const ChartTitle = styled.div`
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
const UsageChartCard: React.FC<{
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
height?: number | string;
|
||||
}> = ({ title, children, height = usageChartCardHeight }) => {
|
||||
return (
|
||||
<CardWrapper style={{ height }}>
|
||||
<ChartTitle>{title}</ChartTitle>
|
||||
{children}
|
||||
</CardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageChartCard;
|
||||
|
||||
export const ChartLoadingBox: React.FC<{ height?: number | string }> = ({
|
||||
height = usageChartHeight
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Spin size="middle" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import PieChart from '@/pages/_components/pie-chart';
|
||||
import { UsageBreakdownResponse } from '@/pages/usage/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
getUsageSummary,
|
||||
toUsagePieData,
|
||||
usageChartHeight
|
||||
} from '../../config';
|
||||
import UsageChartCard from './shared';
|
||||
|
||||
interface TokenUsageByModelProps {
|
||||
data: UsageBreakdownResponse | null | undefined;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const TokenUsageByModel: React.FC<TokenUsageByModelProps> = ({
|
||||
data,
|
||||
loading = false
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const chartData = useMemo(
|
||||
() => toUsagePieData(data, 'model', 'total_tokens'),
|
||||
[data]
|
||||
);
|
||||
|
||||
const total = useMemo(() => {
|
||||
const summary = getUsageSummary(data);
|
||||
return Number(summary?.total_tokens ?? 0);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<UsageChartCard
|
||||
title={intl.formatMessage({ id: 'dashboard.tokenUsageByModel' })}
|
||||
>
|
||||
<PieChart
|
||||
data={chartData}
|
||||
height={usageChartHeight}
|
||||
loading={loading}
|
||||
colorOffset={3}
|
||||
total={total}
|
||||
/>
|
||||
</UsageChartCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenUsageByModel;
|
||||
@@ -0,0 +1,76 @@
|
||||
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 UsageChartCard, { ChartLoadingBox } from './shared';
|
||||
|
||||
interface TopTokenUsageByApiKeyProps {
|
||||
commonParams: DashboardUsageCommonParams;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const TopTokenUsageByApiKey: React.FC<TopTokenUsageByApiKeyProps> = ({
|
||||
commonParams,
|
||||
height = usageChartHeight
|
||||
}) => {
|
||||
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 (
|
||||
<UsageChartCard
|
||||
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByApiKey' })}
|
||||
height={height + 52}
|
||||
>
|
||||
{query.loading && rankData.names.length === 0 ? (
|
||||
<ChartLoadingBox height={height} />
|
||||
) : (
|
||||
<HBarChart
|
||||
seriesData={rankData.series}
|
||||
xAxisData={rankData.names}
|
||||
height={height}
|
||||
maxItems={10}
|
||||
/>
|
||||
)}
|
||||
</UsageChartCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopTokenUsageByApiKey;
|
||||
@@ -0,0 +1,71 @@
|
||||
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 UsageChartCard, { ChartLoadingBox } from './shared';
|
||||
|
||||
interface TopTokenUsageByUserProps {
|
||||
commonParams: DashboardUsageCommonParams;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const TopTokenUsageByUser: React.FC<TopTokenUsageByUserProps> = ({
|
||||
commonParams,
|
||||
height = usageChartHeight
|
||||
}) => {
|
||||
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 (
|
||||
<UsageChartCard
|
||||
title={intl.formatMessage({ id: 'dashboard.topTokenUsageByUser' })}
|
||||
height={height + 52}
|
||||
>
|
||||
{query.loading && rankData.names.length === 0 ? (
|
||||
<ChartLoadingBox height={height} />
|
||||
) : (
|
||||
<HBarChart
|
||||
seriesData={rankData.series}
|
||||
xAxisData={rankData.names}
|
||||
height={height}
|
||||
maxItems={10}
|
||||
/>
|
||||
)}
|
||||
</UsageChartCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopTokenUsageByUser;
|
||||
@@ -1,18 +1,7 @@
|
||||
import breakpoints from '@/config/breakpoints';
|
||||
import useWindowResize from '@/hooks/use-window-resize';
|
||||
import { useEffect, useState } from 'react';
|
||||
import UserInner from './usage-inner';
|
||||
import NewUsage from './new-usage';
|
||||
|
||||
const Usage = () => {
|
||||
const {
|
||||
size: { width }
|
||||
} = useWindowResize();
|
||||
const [maxWdith, setMaxWidth] = useState<number>(breakpoints.xl);
|
||||
useEffect(() => {
|
||||
setMaxWidth(width);
|
||||
}, [width]);
|
||||
|
||||
return <UserInner maxWidth={maxWdith}></UserInner>;
|
||||
return <NewUsage />;
|
||||
};
|
||||
|
||||
export default Usage;
|
||||
|
||||
Reference in New Issue
Block a user