refactor: dashboard usage
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@
|
|||||||
"@ant-design/icons": "^6.1.0",
|
"@ant-design/icons": "^6.1.0",
|
||||||
"@ant-design/pro-components": "3.1.0-0",
|
"@ant-design/pro-components": "3.1.0-0",
|
||||||
"@braintree/sanitize-url": "^7.1.1",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"@gpustack/core-ui": "^1.0.5",
|
"@gpustack/core-ui": "^1.0.6",
|
||||||
"@huggingface/gguf": "^0.1.7",
|
"@huggingface/gguf": "^0.1.7",
|
||||||
"@huggingface/hub": "^0.15.1",
|
"@huggingface/hub": "^0.15.1",
|
||||||
"@huggingface/tasks": "^0.11.6",
|
"@huggingface/tasks": "^0.11.6",
|
||||||
|
|||||||
Generated
+4
-4
@@ -18,8 +18,8 @@ dependencies:
|
|||||||
specifier: ^7.1.1
|
specifier: ^7.1.1
|
||||||
version: 7.1.1
|
version: 7.1.1
|
||||||
'@gpustack/core-ui':
|
'@gpustack/core-ui':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.6
|
||||||
version: 1.0.5(@ant-design/icons@6.1.0)(@monaco-editor/react@4.6.0)(@types/react@18.3.27)(ahooks@3.9.6)(antd-style@3.7.1)(antd@6.3.3)(axios@1.13.2)(echarts@5.6.0)(file-saver@2.0.5)(monaco-editor@0.30.1)(monaco-yaml@4.0.0)(overlayscrollbars-react@0.5.6)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.2.0)
|
version: 1.0.6(@ant-design/icons@6.1.0)(@monaco-editor/react@4.6.0)(@types/react@18.3.27)(ahooks@3.9.6)(antd-style@3.7.1)(antd@6.3.3)(axios@1.13.2)(echarts@5.6.0)(file-saver@2.0.5)(monaco-editor@0.30.1)(monaco-yaml@4.0.0)(overlayscrollbars-react@0.5.6)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.2.0)
|
||||||
'@huggingface/gguf':
|
'@huggingface/gguf':
|
||||||
specifier: ^0.1.7
|
specifier: ^0.1.7
|
||||||
version: 0.1.18
|
version: 0.1.18
|
||||||
@@ -2905,8 +2905,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz}
|
resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz}
|
||||||
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
|
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
|
||||||
|
|
||||||
/@gpustack/core-ui@1.0.5(@ant-design/icons@6.1.0)(@monaco-editor/react@4.6.0)(@types/react@18.3.27)(ahooks@3.9.6)(antd-style@3.7.1)(antd@6.3.3)(axios@1.13.2)(echarts@5.6.0)(file-saver@2.0.5)(monaco-editor@0.30.1)(monaco-yaml@4.0.0)(overlayscrollbars-react@0.5.6)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.2.0):
|
/@gpustack/core-ui@1.0.6(@ant-design/icons@6.1.0)(@monaco-editor/react@4.6.0)(@types/react@18.3.27)(ahooks@3.9.6)(antd-style@3.7.1)(antd@6.3.3)(axios@1.13.2)(echarts@5.6.0)(file-saver@2.0.5)(monaco-editor@0.30.1)(monaco-yaml@4.0.0)(overlayscrollbars-react@0.5.6)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.2.0):
|
||||||
resolution: {integrity: sha512-IFFKXi1xIY6WWySasmKlRip/74eX/yb/iNH1iQ1sigzMWEV3j4V3VQvtBOUxsBxXmRViBSRrklIa0KDjtCwneQ==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.5.tgz}
|
resolution: {integrity: sha512-P7xs4Dh8dUKpN6YO9hNHgZUdEQvqU7TRMw+yJR5/SiIBAn04bhOZWoBMWR84TcSsT9pEt3IkTDIEmtBPtoQVnw==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.6.tgz}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@ant-design/icons': '>=6.0.0'
|
'@ant-design/icons': '>=6.0.0'
|
||||||
'@monaco-editor/react': ^4.6.0
|
'@monaco-editor/react': ^4.6.0
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
|
export const activeModelsAtom = atom<any[]>([]);
|
||||||
@@ -20,6 +20,11 @@ export default {
|
|||||||
'dashboard.tokens': 'Token Usage',
|
'dashboard.tokens': 'Token Usage',
|
||||||
'dashboard.topusers': 'Top Users',
|
'dashboard.topusers': 'Top Users',
|
||||||
'dashboard.activeModels': 'Active Models',
|
'dashboard.activeModels': 'Active Models',
|
||||||
|
'dashboard.activeUsers': 'Active Users',
|
||||||
|
'dashboard.tokenUsageByModel': 'Token Usage by Model',
|
||||||
|
'dashboard.apiRequestsByModel': 'API Requests by Model',
|
||||||
|
'dashboard.topTokenUsageByUser': 'Top 10 Token Usage by User',
|
||||||
|
'dashboard.topTokenUsageByApiKey': 'Top 10 Token Usage by API Key',
|
||||||
'dashboard.runninginstances': 'Running Instances',
|
'dashboard.runninginstances': 'Running Instances',
|
||||||
'dashboard.activeModels.name': 'Model Name',
|
'dashboard.activeModels.name': 'Model Name',
|
||||||
'dashboard.allocatevram': 'Allocated VRAM / RAM',
|
'dashboard.allocatevram': 'Allocated VRAM / RAM',
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export default {
|
|||||||
'dashboard.tokens': 'トークン使用量',
|
'dashboard.tokens': 'トークン使用量',
|
||||||
'dashboard.topusers': 'トップユーザー',
|
'dashboard.topusers': 'トップユーザー',
|
||||||
'dashboard.activeModels': 'アクティブなモデル',
|
'dashboard.activeModels': 'アクティブなモデル',
|
||||||
|
'dashboard.activeUsers': 'アクティブユーザー',
|
||||||
|
'dashboard.tokenUsageByModel': 'モデル別トークン使用量',
|
||||||
|
'dashboard.apiRequestsByModel': 'モデル別APIリクエスト',
|
||||||
|
'dashboard.topTokenUsageByUser': 'ユーザー別トークン使用量トップ10',
|
||||||
|
'dashboard.topTokenUsageByApiKey': 'APIキー別トークン使用量トップ10',
|
||||||
'dashboard.runninginstances': '稼働中のインスタンス',
|
'dashboard.runninginstances': '稼働中のインスタンス',
|
||||||
'dashboard.activeModels.name': 'モデル名',
|
'dashboard.activeModels.name': 'モデル名',
|
||||||
'dashboard.allocatevram': '割り当て済みVRAM / メモリ',
|
'dashboard.allocatevram': '割り当て済みVRAM / メモリ',
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export default {
|
|||||||
'dashboard.tokens': 'Использование токенов',
|
'dashboard.tokens': 'Использование токенов',
|
||||||
'dashboard.topusers': 'Топ пользователей',
|
'dashboard.topusers': 'Топ пользователей',
|
||||||
'dashboard.activeModels': 'Активные модели',
|
'dashboard.activeModels': 'Активные модели',
|
||||||
|
'dashboard.activeUsers': 'Активные пользователи',
|
||||||
|
'dashboard.tokenUsageByModel': 'Использование токенов по моделям',
|
||||||
|
'dashboard.apiRequestsByModel': 'API-запросы по моделям',
|
||||||
|
'dashboard.topTokenUsageByUser': 'Топ-10 пользователей по токенам',
|
||||||
|
'dashboard.topTokenUsageByApiKey': 'Топ-10 API-ключей по токенам',
|
||||||
'dashboard.runninginstances': 'Запущенные инстансы',
|
'dashboard.runninginstances': 'Запущенные инстансы',
|
||||||
'dashboard.activeModels.name': 'Название модели',
|
'dashboard.activeModels.name': 'Название модели',
|
||||||
'dashboard.allocatevram': 'Выделено VRAM / ОЗУ',
|
'dashboard.allocatevram': 'Выделено VRAM / ОЗУ',
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ export default {
|
|||||||
'dashboard.tokens': 'Token Kullanımı',
|
'dashboard.tokens': 'Token Kullanımı',
|
||||||
'dashboard.topusers': 'En Aktif Kullanıcılar',
|
'dashboard.topusers': 'En Aktif Kullanıcılar',
|
||||||
'dashboard.activeModels': 'Aktif Modeller',
|
'dashboard.activeModels': 'Aktif Modeller',
|
||||||
|
'dashboard.activeUsers': 'Aktif Kullanıcılar',
|
||||||
|
'dashboard.tokenUsageByModel': 'Modele Göre Token Kullanımı',
|
||||||
|
'dashboard.apiRequestsByModel': 'Modele Göre API İstekleri',
|
||||||
|
'dashboard.topTokenUsageByUser': 'Kullanıcıya Göre İlk 10 Token Kullanımı',
|
||||||
|
'dashboard.topTokenUsageByApiKey':
|
||||||
|
'API Anahtarına Göre İlk 10 Token Kullanımı',
|
||||||
'dashboard.runninginstances': 'Çalışan Örnekler',
|
'dashboard.runninginstances': 'Çalışan Örnekler',
|
||||||
'dashboard.activeModels.name': 'Model Adı',
|
'dashboard.activeModels.name': 'Model Adı',
|
||||||
'dashboard.allocatevram': 'Ayrılan VRAM / RAM',
|
'dashboard.allocatevram': 'Ayrılan VRAM / RAM',
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ export default {
|
|||||||
'dashboard.tokens': 'Token 使用量',
|
'dashboard.tokens': 'Token 使用量',
|
||||||
'dashboard.topusers': '用户排行',
|
'dashboard.topusers': '用户排行',
|
||||||
'dashboard.activeModels': '活跃模型',
|
'dashboard.activeModels': '活跃模型',
|
||||||
|
'dashboard.activeUsers': '活跃用户',
|
||||||
|
'dashboard.tokenUsageByModel': '按模型统计 Token 使用量',
|
||||||
|
'dashboard.apiRequestsByModel': '按模型统计 API 请求数',
|
||||||
|
'dashboard.topTokenUsageByUser': '用户 Token 使用量 Top 10',
|
||||||
|
'dashboard.topTokenUsageByApiKey': 'API 密钥 Token 使用量 Top 10',
|
||||||
'dashboard.activeModels.name': '模型名称',
|
'dashboard.activeModels.name': '模型名称',
|
||||||
'dashboard.runninginstances': '运行实例',
|
'dashboard.runninginstances': '运行实例',
|
||||||
'dashboard.allocatevram': '已分配显存 / 内存',
|
'dashboard.allocatevram': '已分配显存 / 内存',
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { Chart } from '@gpustack/core-ui';
|
||||||
|
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||||
|
import { Empty, Spin, theme } from 'antd';
|
||||||
|
import React, { useMemo, useRef } from 'react';
|
||||||
|
import { generateCoolColors } from '../bar-chart';
|
||||||
|
|
||||||
|
export interface PieChartItem {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PieChartProps {
|
||||||
|
data: PieChartItem[];
|
||||||
|
height?: number | string;
|
||||||
|
width?: number | string;
|
||||||
|
loading?: boolean;
|
||||||
|
colorOffset?: number;
|
||||||
|
total?: number;
|
||||||
|
totalLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PieChart: React.FC<PieChartProps> = ({
|
||||||
|
data,
|
||||||
|
height = 300,
|
||||||
|
width = '100%',
|
||||||
|
loading = false,
|
||||||
|
colorOffset = 0,
|
||||||
|
total,
|
||||||
|
totalLabel
|
||||||
|
}) => {
|
||||||
|
const { token } = theme.useToken();
|
||||||
|
const chartRef = useRef<{ chart: any } | null>(null);
|
||||||
|
|
||||||
|
const colors = useMemo(() => {
|
||||||
|
const generatedColors = generateCoolColors(data.length + colorOffset);
|
||||||
|
return generatedColors.slice(colorOffset);
|
||||||
|
}, [colorOffset, data.length]);
|
||||||
|
|
||||||
|
const options = useMemo(
|
||||||
|
() => ({
|
||||||
|
color: colors,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
backgroundColor: token.colorBgElevated,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
return `<div class="tooltip-wrapper">
|
||||||
|
<span class="tooltip-item">
|
||||||
|
<span class="tooltip-item-name">
|
||||||
|
<span class="tooltip-item-dot" style="border-radius:50%;background-color:${params.color};"></span>
|
||||||
|
<span class="tooltip-item-title">${params.name}</span>:
|
||||||
|
</span>
|
||||||
|
<span class="tooltip-value">${formatLargeNumber(params.value)}</span>
|
||||||
|
</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 0,
|
||||||
|
top: 18,
|
||||||
|
bottom: 18,
|
||||||
|
itemWidth: 8,
|
||||||
|
itemHeight: 8,
|
||||||
|
itemGap: 12,
|
||||||
|
textStyle: {
|
||||||
|
color: token.colorTextTertiary,
|
||||||
|
overflow: 'truncate',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
pageTextStyle: { color: token.colorTextTertiary },
|
||||||
|
pageIconColor: token.colorTextTertiary,
|
||||||
|
pageIconInactiveColor: token.colorTextDisabled,
|
||||||
|
data: data.map((item) => item.name),
|
||||||
|
show: data.length > 0
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['52%', '72%'],
|
||||||
|
center: ['34%', '50%'],
|
||||||
|
avoidLabelOverlap: true,
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: (params: any) => `${params.percent}%`,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: token.colorText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
[colors, data, token]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data.length) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Spin size="middle" />
|
||||||
|
) : (
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayTotal = total ?? data.reduce((sum, item) => sum + item.value, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width, height, position: 'relative' }}>
|
||||||
|
<Chart
|
||||||
|
ref={chartRef as any}
|
||||||
|
options={options as any}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '34%',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: token.colorText,
|
||||||
|
lineHeight: 1.2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatLargeNumber(displayTotal)}
|
||||||
|
</span>
|
||||||
|
{totalLabel && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: token.colorTextTertiary,
|
||||||
|
marginTop: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{totalLabel}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{loading && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'var(--ant-color-bg-container)',
|
||||||
|
opacity: 0.6,
|
||||||
|
pointerEvents: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="middle" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PieChart;
|
||||||
@@ -80,17 +80,17 @@ const Instance: React.FC = () => {
|
|||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
id: 'models.instance.params.configured'
|
id: 'models.form.backend_parameters'
|
||||||
}),
|
}),
|
||||||
children: renderParams(instanceData?.backend_parameters || [])
|
children: renderParams(instanceData?.backend_parameters || [])
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: '1-1',
|
// key: '1-1',
|
||||||
label: intl.formatMessage({
|
// label: intl.formatMessage({
|
||||||
id: 'models.instance.params.autoInjected'
|
// id: 'models.instance.params.autoInjected'
|
||||||
}),
|
// }),
|
||||||
children: renderParams(instanceData?.injected_backend_parameters || [])
|
// children: renderParams(instanceData?.injected_backend_parameters || [])
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
key: '3',
|
key: '3',
|
||||||
label: intl.formatMessage({ id: 'benchmark.detail.kvCache' }),
|
label: intl.formatMessage({ id: 'benchmark.detail.kvCache' }),
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { activeModelsAtom } from '@/atoms/dashboard';
|
||||||
import { modelCategoriesMap } from '@/pages/llmodels/config';
|
import { modelCategoriesMap } from '@/pages/llmodels/config';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import { AutoTooltip, PageTools } from '@gpustack/core-ui';
|
import { AutoTooltip, PageTools } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Col, Row, Table } from 'antd';
|
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';
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
const NACategories = [
|
const NACategories = [
|
||||||
modelCategoriesMap.llm,
|
modelCategoriesMap.llm,
|
||||||
@@ -14,6 +16,12 @@ const NACategories = [
|
|||||||
const ActiveTable = () => {
|
const ActiveTable = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const data = useContext(DashboardContext).active_models || [];
|
const data = useContext(DashboardContext).active_models || [];
|
||||||
|
const setActiveModels = useSetAtom(activeModelsAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveModels(data);
|
||||||
|
}, [data, setActiveModels]);
|
||||||
|
|
||||||
const modelColumns = [
|
const modelColumns = [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
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 NewUsage from './new-usage';
|
||||||
import useWindowResize from '@/hooks/use-window-resize';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import UserInner from './usage-inner';
|
|
||||||
|
|
||||||
const Usage = () => {
|
const Usage = () => {
|
||||||
const {
|
return <NewUsage />;
|
||||||
size: { width }
|
|
||||||
} = useWindowResize();
|
|
||||||
const [maxWdith, setMaxWidth] = useState<number>(breakpoints.xl);
|
|
||||||
useEffect(() => {
|
|
||||||
setMaxWidth(width);
|
|
||||||
}, [width]);
|
|
||||||
|
|
||||||
return <UserInner maxWidth={maxWdith}></UserInner>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Usage;
|
export default Usage;
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
BreakdownItem,
|
||||||
|
UsageBreakdownResponse
|
||||||
|
} from '@/pages/usage/config/types';
|
||||||
|
|
||||||
export const overviewConfigs = [
|
export const overviewConfigs = [
|
||||||
{
|
{
|
||||||
key: 'cluster_count',
|
key: 'cluster_count',
|
||||||
@@ -35,3 +40,187 @@ export const baseColorMap = {
|
|||||||
baseR2: 'rgba(48,0,255,0.8)',
|
baseR2: 'rgba(48,0,255,0.8)',
|
||||||
baseR3: 'rgba(85,167,255,0.8)'
|
baseR3: 'rgba(85,167,255,0.8)'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UsageGroupBy = 'model' | 'user' | 'api_key';
|
||||||
|
export type UsageMetric = 'total_tokens' | 'api_requests';
|
||||||
|
|
||||||
|
export interface UsageChartDatum {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardUsageCommonParams {
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
scope: string;
|
||||||
|
granularity: string;
|
||||||
|
filters: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usageChartHeight = 300;
|
||||||
|
export const usageChartCardHeight = usageChartHeight + 52;
|
||||||
|
|
||||||
|
export const buildUsageLabel = (item: BreakdownItem, groupBy: UsageGroupBy) => {
|
||||||
|
const rawItem = item as any;
|
||||||
|
const groupItem = rawItem[groupBy];
|
||||||
|
const identityValue = groupItem?.identity?.value;
|
||||||
|
const groupValue = groupItem?.value;
|
||||||
|
|
||||||
|
if (typeof groupItem === 'string') {
|
||||||
|
return groupItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBy === 'model') {
|
||||||
|
const providerName =
|
||||||
|
identityValue?.provider_name || groupValue?.provider_name;
|
||||||
|
const modelName =
|
||||||
|
identityValue?.model_name ||
|
||||||
|
groupValue?.model_name ||
|
||||||
|
rawItem.model_name ||
|
||||||
|
rawItem.model;
|
||||||
|
|
||||||
|
if (providerName && modelName) {
|
||||||
|
return `${providerName}/${modelName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupItem?.label || modelName || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
groupItem?.label ||
|
||||||
|
identityValue?.user_name ||
|
||||||
|
identityValue?.api_key_name ||
|
||||||
|
identityValue?.access_key ||
|
||||||
|
groupValue?.user_name ||
|
||||||
|
groupValue?.api_key_name ||
|
||||||
|
groupValue?.access_key ||
|
||||||
|
rawItem.user_name ||
|
||||||
|
rawItem.api_key_name ||
|
||||||
|
rawItem.access_key ||
|
||||||
|
rawItem[groupBy] ||
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsageResponseItems = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined
|
||||||
|
) => {
|
||||||
|
const response = data as any;
|
||||||
|
return response?.items || response?.data?.items || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsageResponseSeries = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined
|
||||||
|
) => {
|
||||||
|
const response = data as any;
|
||||||
|
return response?.series || response?.data?.series || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsageMetricValue = (item: any, metric: UsageMetric) => {
|
||||||
|
return Number(
|
||||||
|
item?.[metric] ??
|
||||||
|
item?.metrics?.[metric] ??
|
||||||
|
item?.summary?.[metric] ??
|
||||||
|
item?.value ??
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const aggregateUsageByGroup = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined,
|
||||||
|
groupBy: UsageGroupBy,
|
||||||
|
metric: UsageMetric
|
||||||
|
): UsageChartDatum[] => {
|
||||||
|
const itemMap = new Map<string, number>();
|
||||||
|
const items = getUsageResponseItems(data);
|
||||||
|
|
||||||
|
items.forEach((item: BreakdownItem) => {
|
||||||
|
const name = buildUsageLabel(item, groupBy);
|
||||||
|
itemMap.set(
|
||||||
|
name,
|
||||||
|
(itemMap.get(name) || 0) + getUsageMetricValue(item, metric)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
getUsageResponseSeries(data).forEach((item: any) => {
|
||||||
|
const name = item.label || buildUsageLabel(item, groupBy);
|
||||||
|
const value = (item.timeline || []).reduce(
|
||||||
|
(sum: number, point: any) => {
|
||||||
|
return sum + getUsageMetricValue(point, metric);
|
||||||
|
},
|
||||||
|
getUsageMetricValue(item, metric)
|
||||||
|
);
|
||||||
|
|
||||||
|
itemMap.set(name, (itemMap.get(name) || 0) + value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemMap.size) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPositiveValue = Array.from(itemMap.values()).some(
|
||||||
|
(value) => value > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasPositiveValue) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(itemMap.entries())
|
||||||
|
.filter(([, value]) => value > 0)
|
||||||
|
.map(([name, value]) => ({ name, value }))
|
||||||
|
.sort((a, b) => b.value - a.value)
|
||||||
|
.slice(0, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsageSummaryData = (
|
||||||
|
dataList: Array<UsageBreakdownResponse | null | undefined>
|
||||||
|
) => {
|
||||||
|
return dataList.find((item) => {
|
||||||
|
const summary = item?.summary || (item as any)?.data?.summary;
|
||||||
|
return summary;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsageSummary = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined
|
||||||
|
) => {
|
||||||
|
return data?.summary || (data as any)?.data?.summary;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toUsagePieData = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined,
|
||||||
|
groupBy: UsageGroupBy,
|
||||||
|
metric: UsageMetric
|
||||||
|
): UsageChartDatum[] => {
|
||||||
|
return aggregateUsageByGroup(data, groupBy, metric);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toUsageRankData = (
|
||||||
|
data: UsageBreakdownResponse | null | undefined,
|
||||||
|
groupBy: UsageGroupBy,
|
||||||
|
seriesName: string,
|
||||||
|
color: string
|
||||||
|
) => {
|
||||||
|
const items = aggregateUsageByGroup(data, groupBy, 'total_tokens');
|
||||||
|
const names = items.map((item) => item.name);
|
||||||
|
|
||||||
|
return {
|
||||||
|
names,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: seriesName,
|
||||||
|
color,
|
||||||
|
data: items.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [2, 2, 2, 2]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const InjectedParamsCell: React.FC<InjectedParamsCellProps> = ({
|
|||||||
if (!injectedBackendParameters.length) {
|
if (!injectedBackendParameters.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="var(--ant-color-bg-elevated)"
|
color="var(--ant-color-bg-elevated)"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
import { ListItem as workerListItem } from '@/pages/resources/config/types';
|
import { ListItem as workerListItem } from '@/pages/resources/config/types';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import { ThunderboltFilled } from '@ant-design/icons';
|
|
||||||
import { AutoTooltip, IconFont } from '@gpustack/core-ui';
|
import { AutoTooltip, IconFont } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Flex } from 'antd';
|
import { Flex } from 'antd';
|
||||||
@@ -107,7 +106,7 @@ const useInstancesColumns = (options: {
|
|||||||
<>
|
<>
|
||||||
<Flex gap={8} wrap="wrap">
|
<Flex gap={8} wrap="wrap">
|
||||||
<NameCell
|
<NameCell
|
||||||
showWorkerInfo={false}
|
showWorkerInfo={true}
|
||||||
record={record}
|
record={record}
|
||||||
modelData={{
|
modelData={{
|
||||||
backend: record.backend,
|
backend: record.backend,
|
||||||
@@ -121,7 +120,7 @@ const useInstancesColumns = (options: {
|
|||||||
></NameCell>
|
></NameCell>
|
||||||
<InjectedParamsCell record={record}></InjectedParamsCell>
|
<InjectedParamsCell record={record}></InjectedParamsCell>
|
||||||
</Flex>
|
</Flex>
|
||||||
<div className="flex-center">
|
{/* <div className="flex-center">
|
||||||
<ThunderboltFilled
|
<ThunderboltFilled
|
||||||
className="m-r-5 text-quaternary"
|
className="m-r-5 text-quaternary"
|
||||||
style={{ fontSize: 12, position: 'relative', top: 2 }}
|
style={{ fontSize: 12, position: 'relative', top: 2 }}
|
||||||
@@ -132,7 +131,7 @@ const useInstancesColumns = (options: {
|
|||||||
? `(${record.backend_version || record?.backend_version})`
|
? `(${record.backend_version || record?.backend_version})`
|
||||||
: ''}
|
: ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div> */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -153,7 +152,7 @@ const useInstancesColumns = (options: {
|
|||||||
dataIndex: 'worker_id',
|
dataIndex: 'worker_id',
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<div className="flex-center gap-8">
|
<div className="flex-center gap-8">
|
||||||
<AutoTooltip ghost>{renderWorkerCell(text, record)}</AutoTooltip>
|
<AutoTooltip ghost>{record.worker_name}</AutoTooltip>
|
||||||
<DistributeInfoCell
|
<DistributeInfoCell
|
||||||
record={record}
|
record={record}
|
||||||
workerList={workerList}
|
workerList={workerList}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export async function queryUsageTimeSeriesData(
|
|||||||
metric: string;
|
metric: string;
|
||||||
group_by: string[];
|
group_by: string[];
|
||||||
granularity: string;
|
granularity: string;
|
||||||
|
page?: number;
|
||||||
|
perPage?: number;
|
||||||
|
sort_by?: string;
|
||||||
filters: {
|
filters: {
|
||||||
models?: FilterOptionType[];
|
models?: FilterOptionType[];
|
||||||
users?: FilterOptionType[];
|
users?: FilterOptionType[];
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { useQueryData } from '@/hooks/use-query-data-list';
|
|||||||
import { queryUsageTimeSeriesData } from '../apis';
|
import { queryUsageTimeSeriesData } from '../apis';
|
||||||
import { UsageBreakdownResponse } from '../config/types';
|
import { UsageBreakdownResponse } from '../config/types';
|
||||||
|
|
||||||
export default function useQueryTimeSeriesData() {
|
export default function useQueryTimeSeriesData(option?: { key?: string }) {
|
||||||
const { detailData, loading, cancelRequest, fetchData } =
|
const { detailData, loading, cancelRequest, fetchData } =
|
||||||
useQueryData<UsageBreakdownResponse>({
|
useQueryData<UsageBreakdownResponse>({
|
||||||
fetchDetail: queryUsageTimeSeriesData,
|
fetchDetail: queryUsageTimeSeriesData,
|
||||||
key: 'timeSeriesData'
|
key: option?.key || 'timeSeriesData'
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
detailData,
|
detailData,
|
||||||
|
|||||||
Reference in New Issue
Block a user