diff --git a/src/components/echarts/config.ts b/src/components/echarts/config.ts
index 9f8d7477..de0c7742 100644
--- a/src/components/echarts/config.ts
+++ b/src/components/echarts/config.ts
@@ -41,24 +41,35 @@ export default function useChartConfig() {
borderColor: 'transparent',
formatter(params: any, callback?: (val: any) => any) {
let result = `${params[0].axisValue}`;
+ let visibleItemCount = 0;
params.forEach((item: any) => {
let value = isFunction(callback)
? callback?.(item.data.value)
- : item.data.value;
+ : item.data?.value;
+ if (value === null || value === undefined) {
+ return;
+ }
+
+ visibleItemCount += 1;
const borderRadius = item.seriesType === 'bar' ? '2px' : '8px';
result += `
-
- ${item.seriesName}:
+
+ ${item.seriesName}:
${value}
`;
});
- return `
${result}
`;
+ const wrapperClassName =
+ visibleItemCount >= 12
+ ? 'tooltip-wrapper tooltip-grid'
+ : 'tooltip-wrapper';
+
+ return `${result}
`;
}
};
diff --git a/src/global.less b/src/global.less
index 6eede573..903a1062 100644
--- a/src/global.less
+++ b/src/global.less
@@ -744,7 +744,7 @@ body {
font-size: 11px;
background-color: transparent;
min-width: 100px;
- max-width: 360px;
+ max-width: 500px;
&.scatter {
display: grid;
@@ -752,6 +752,16 @@ body {
gap: 8px;
}
+ &.tooltip-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 4px 12px;
+
+ .tooltip-x-name {
+ grid-column: 1 / -1;
+ }
+ }
+
.tooltip-x-name {
font-size: var(--font-size-small);
color: var(--ant-color-text-tertiary);
@@ -766,12 +776,34 @@ body {
justify-content: space-between;
align-items: center;
+ .tooltip-item-name {
+ display: inline-flex;
+ align-items: center;
+ min-width: 0;
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .tooltip-item-dot {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ margin-right: 5px;
+ flex-shrink: 0;
+ }
+
.tooltip-item-title {
margin-right: 2px;
+ display: inline-block;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.tooltip-value {
margin-left: 10px;
+ flex-shrink: 0;
color: var(--ant-color-text);
text-overflow: ellipsis;
overflow: hidden;
diff --git a/src/locales/en-US/usage.ts b/src/locales/en-US/usage.ts
index 607b4b3d..0b2a0814 100644
--- a/src/locales/en-US/usage.ts
+++ b/src/locales/en-US/usage.ts
@@ -27,5 +27,6 @@ export default {
'usage.export.table': 'Export Table Data',
'usage.user.currentAccount': 'Current Account',
'usage.table.cluster': 'Cluster',
- 'usage.table.provider': 'Provider'
+ 'usage.table.provider': 'Provider',
+ 'usage.table.deleted': 'Deleted'
};
diff --git a/src/locales/ja-JP/usage.ts b/src/locales/ja-JP/usage.ts
index 607b4b3d..0b2a0814 100644
--- a/src/locales/ja-JP/usage.ts
+++ b/src/locales/ja-JP/usage.ts
@@ -27,5 +27,6 @@ export default {
'usage.export.table': 'Export Table Data',
'usage.user.currentAccount': 'Current Account',
'usage.table.cluster': 'Cluster',
- 'usage.table.provider': 'Provider'
+ 'usage.table.provider': 'Provider',
+ 'usage.table.deleted': 'Deleted'
};
diff --git a/src/locales/ru-RU/usage.ts b/src/locales/ru-RU/usage.ts
index 607b4b3d..0b2a0814 100644
--- a/src/locales/ru-RU/usage.ts
+++ b/src/locales/ru-RU/usage.ts
@@ -27,5 +27,6 @@ export default {
'usage.export.table': 'Export Table Data',
'usage.user.currentAccount': 'Current Account',
'usage.table.cluster': 'Cluster',
- 'usage.table.provider': 'Provider'
+ 'usage.table.provider': 'Provider',
+ 'usage.table.deleted': 'Deleted'
};
diff --git a/src/locales/tr-TR/usage.ts b/src/locales/tr-TR/usage.ts
index 607b4b3d..0b2a0814 100644
--- a/src/locales/tr-TR/usage.ts
+++ b/src/locales/tr-TR/usage.ts
@@ -27,5 +27,6 @@ export default {
'usage.export.table': 'Export Table Data',
'usage.user.currentAccount': 'Current Account',
'usage.table.cluster': 'Cluster',
- 'usage.table.provider': 'Provider'
+ 'usage.table.provider': 'Provider',
+ 'usage.table.deleted': 'Deleted'
};
diff --git a/src/locales/zh-CN/usage.ts b/src/locales/zh-CN/usage.ts
index 3d48e851..9a8b02c4 100644
--- a/src/locales/zh-CN/usage.ts
+++ b/src/locales/zh-CN/usage.ts
@@ -27,5 +27,6 @@ export default {
'usage.export.table': '导出表格数据',
'usage.user.currentAccount': '当前账户',
'usage.table.cluster': '集群',
- 'usage.table.provider': '提供商'
+ 'usage.table.provider': '提供商',
+ 'usage.table.deleted': '已删除'
};
diff --git a/src/pages/dashboard/components/usage-inner/request-token-inner.tsx b/src/pages/dashboard/components/usage-inner/request-token-inner.tsx
index 5a32e430..3b01e140 100644
--- a/src/pages/dashboard/components/usage-inner/request-token-inner.tsx
+++ b/src/pages/dashboard/components/usage-inner/request-token-inner.tsx
@@ -97,6 +97,8 @@ const RequestTokenInner: React.FC = (props) => {
}));
}, [requestData, tokenData]);
+ console.log('xAxisData', xAxisData, tokenData);
+
return (
diff --git a/src/pages/usage/components/daily-usage.tsx b/src/pages/usage/components/daily-usage.tsx
index a941d4c6..c85f674a 100644
--- a/src/pages/usage/components/daily-usage.tsx
+++ b/src/pages/usage/components/daily-usage.tsx
@@ -28,6 +28,20 @@ const ControlLabel = styled.span`
margin-right: 8px;
`;
+function getColorForIndex(index: number) {
+ const hue = (index * 137.5) % 360;
+ return `oklch(70% 0.12 ${hue})`;
+}
+
+const colors = [
+ baseColorMap.base,
+ baseColorMap.baseR3,
+ baseColorMap.baseL1,
+ baseColorMap.baseR1,
+ baseColorMap.baseR2,
+ baseColorMap.baseL2
+];
+
interface DailyUsageProps {
timeSeriesData: TimeSeriesData | null;
metric: string;
@@ -60,22 +74,33 @@ const DailyUsage: React.FC = (props) => {
}
const series = timeSeriesData.series;
- const xAxis = series[0]?.timeline?.map((item) => item.date) || [];
+ const dateSet = new Set();
- const colors = [
- baseColorMap.base,
- baseColorMap.baseR3,
- baseColorMap.baseL1,
- baseColorMap.baseR1,
- baseColorMap.baseR2,
- baseColorMap.baseL2
- ];
+ for (const item of series) {
+ for (const point of item.timeline) {
+ dateSet.add(point.date);
+ }
+ }
- const chartSeries = series.map((item, index) => ({
- name: item.label,
- data: item.timeline.map((t) => ({ time: t.date, value: t.value })),
- color: colors[index % colors.length]
- }));
+ // sort dates in ascending order
+ const xAxis = Array.from(dateSet).sort(
+ (a, b) => dayjs(a).valueOf() - dayjs(b).valueOf()
+ );
+
+ const chartSeries = series.map((item, index) => {
+ const timelineMap = new Map(
+ item.timeline.map((point) => [
+ point.date,
+ { time: point.date, value: point.value }
+ ])
+ );
+
+ return {
+ name: item.label,
+ data: xAxis.map((date) => timelineMap.get(date) ?? null),
+ color: colors[index % colors.length]
+ };
+ });
// API requests use line chart, tokens use bar chart
return {
diff --git a/src/pages/usage/components/export-data.tsx b/src/pages/usage/components/export-data.tsx
index 6b65235a..b3ca250e 100644
--- a/src/pages/usage/components/export-data.tsx
+++ b/src/pages/usage/components/export-data.tsx
@@ -84,7 +84,8 @@ const ExportData: React.FC<{
}) => {
fetchExportData({
...pageParams,
- granularity: nextChartFilters.granularity,
+ granularity: 'day',
+ sort_by: '-date',
group_by: ['date', 'user', 'model', 'api_key'],
filters: nextFilters,
scope: initialScope,
@@ -221,9 +222,10 @@ const ExportData: React.FC<{
...pageParams,
page,
perPage: pageSize,
- granularity,
+ granularity: 'day',
group_by: ['date', 'user', 'model', 'api_key'],
filters,
+ sort_by: '-date',
scope: initialScope,
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
@@ -239,9 +241,10 @@ const ExportData: React.FC<{
setPageParams(INITIAL_PAGE_PARAMS);
fetchExportData({
...INITIAL_PAGE_PARAMS,
- granularity,
+ granularity: 'day',
group_by: ['date', 'user', 'model', 'api_key'],
filters,
+ sort_by: '-date',
scope: initialScope,
start_date: commonFilters.start_date,
end_date: commonFilters.end_date
@@ -263,7 +266,7 @@ const ExportData: React.FC<{
closable: false
}}
keyboard={false}
- width={1200}
+ width={1280}
style={{
top: '10%'
}}
diff --git a/src/pages/usage/components/filter-bar.tsx b/src/pages/usage/components/filter-bar.tsx
index 4cf162ed..82aecf20 100644
--- a/src/pages/usage/components/filter-bar.tsx
+++ b/src/pages/usage/components/filter-bar.tsx
@@ -7,7 +7,7 @@ import ProviderLogo from '@/pages/maas-provider/components/provider-logo';
import { useModel } from '@@/plugin-model';
import { DownloadOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
-import { Button, DatePicker, Dropdown, MenuProps, Segmented } from 'antd';
+import { Button, DatePicker, Dropdown, MenuProps } from 'antd';
import dayjs from 'dayjs';
import React from 'react';
import { GroupOption } from '../config';
@@ -120,8 +120,6 @@ const FilterBar: React.FC = (props) => {
}
]
});
- // const [activeModels, setActiveModels] = React.useState([]);
- // const [activeApiKeys, setActiveApiKeys] = React.useState([]);
const initialInfo = useModel('@@initialState');
const { initialState } = initialInfo || {};
@@ -149,8 +147,15 @@ const FilterBar: React.FC = (props) => {
}
];
+ const renderTag = (tag: string) => {
+ return (
+
+ [{tag}]
+
+ );
+ };
+
const handleOnModelsChange = (value: valueType[][], selectedOptions: any) => {
- // setActiveModels(value);
const selectedValues: string[] = value.map((item) => {
if (Array.isArray(item)) {
return item[item.length - 1] as string; // Get the last value in the array
@@ -165,7 +170,6 @@ const FilterBar: React.FC = (props) => {
value: valueType[][],
selectedOptions: any
) => {
- // setActiveApiKeys(value);
const selectedValues: string[] = value.map((item) => {
if (Array.isArray(item)) {
return item[item.length - 1] as string; // Get the last value in the array
@@ -194,10 +198,14 @@ const FilterBar: React.FC = (props) => {
const optionRender = (option: any) => {
const { data } = option;
- console.log('optionRender', option);
-
if (!data.isParent) {
- return {data.label};
+ return (
+
+ {data.label}
+ {data.deleted &&
+ renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
+
+ );
}
if (data.type === 'deployments') {
@@ -221,6 +229,36 @@ const FilterBar: React.FC = (props) => {
);
};
+ const apiKeyOptionRender = (option: any) => {
+ const { data } = option;
+ if (!data.isParent) {
+ return (
+
+ {data.label}
+ {data.deleted &&
+ renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
+
+ );
+ }
+
+ return (
+
+ {data.label}
+
+ );
+ };
+
+ const singleOptionRender = (option: any) => {
+ const { data } = option;
+ return (
+
+ {data.label}
+ {data.deleted &&
+ renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
+
+ );
+ };
+
const onPickerChange = (picker: DateType) => {
handleOnPickerChange(picker);
handlePickerChange(picker);
@@ -231,57 +269,15 @@ const FilterBar: React.FC = (props) => {
? normalizeRangeValue(startDate, endDate, picker)
: [dayjs().add(-DefaultDateConfig.defaultRange, 'd'), dayjs()];
- const renderFooter = () => {
- return (
-
- );
- };
-
const userOptionRender = (option: any) => {
const { data } = option;
return (
-
- {data.label}
- {data.isCurrent && (
-
- {' '}
- [{intl.formatMessage({ id: 'usage.user.currentAccount' })}]
-
- )}
+
+ {data.label}
+ {data.isCurrent &&
+ renderTag(intl.formatMessage({ id: 'usage.user.currentAccount' }))}
+ {data.deleted &&
+ renderTag(intl.formatMessage({ id: 'usage.table.deleted' }))}
);
};
@@ -394,6 +390,7 @@ const FilterBar: React.FC = (props) => {
options={apiKeyOptions}
showCheckedStrategy="SHOW_CHILD"
value={activeApiKeys}
+ optionNode={apiKeyOptionRender}
onChange={handleOnApiKeysChange}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
>
@@ -412,6 +409,7 @@ const FilterBar: React.FC = (props) => {
wrapper: { flex: 1, maxWidth: 240, minWidth: 100 }
}}
value={selectedApiKeys}
+ optionRender={singleOptionRender}
onChange={onApiKeysChange}
/>
)}
diff --git a/src/pages/usage/config/types.ts b/src/pages/usage/config/types.ts
index 5bbae53b..cfdab6a3 100644
--- a/src/pages/usage/config/types.ts
+++ b/src/pages/usage/config/types.ts
@@ -45,7 +45,7 @@ export interface TimeSeriesData {
series: TimeSeriesItem[];
}
-export type BreakdownItem = UsageFilterItem & {
+export type BreakdownItem = {
cluster_name: string;
model_name: string;
user_name: string;
@@ -60,6 +60,9 @@ export type BreakdownItem = UsageFilterItem & {
last_active: string;
provider_type: string;
provider_name: string;
+ user: UsageFilterItem;
+ model: UsageFilterItem;
+ api_key: UsageFilterItem;
};
export interface UsageMeta {
diff --git a/src/pages/usage/hooks/use-apikeys-columns.tsx b/src/pages/usage/hooks/use-apikeys-columns.tsx
index 92fc58a0..be004243 100644
--- a/src/pages/usage/hooks/use-apikeys-columns.tsx
+++ b/src/pages/usage/hooks/use-apikeys-columns.tsx
@@ -1,6 +1,7 @@
// columns.ts
import AutoTooltip from '@/components/auto-tooltip';
import { useIntl } from '@umijs/max';
+import { Tag } from 'antd';
import { useMemo } from 'react';
import { BreakdownItem as ListItem } from '../config/types';
@@ -19,9 +20,23 @@ const useModelsColumns = (): Array<{
key: 'api_key_name',
render: (text: string, record: ListItem) => (
-
+ {text}}>
{text}
+ {record.api_key?.deleted && (
+
+ {intl.formatMessage({ id: 'usage.table.deleted' })}
+
+ )}
)
},
diff --git a/src/pages/usage/hooks/use-models-columns.tsx b/src/pages/usage/hooks/use-models-columns.tsx
index bda9849d..107f9a04 100644
--- a/src/pages/usage/hooks/use-models-columns.tsx
+++ b/src/pages/usage/hooks/use-models-columns.tsx
@@ -2,6 +2,7 @@
import AutoTooltip from '@/components/auto-tooltip';
import ProviderLogo from '@/pages/maas-provider/components/provider-logo';
import { useIntl } from '@umijs/max';
+import { Tag } from 'antd';
import { useMemo } from 'react';
import { BreakdownItem as ListItem } from '../config/types';
@@ -24,9 +25,27 @@ const useModelsColumns = (): Array<{
key: 'model_name',
render: (text: string, record: ListItem) => (
-
+ {text}}
+ >
{text}
+ {record.model?.deleted && (
+
+ {intl.formatMessage({ id: 'usage.table.deleted' })}
+
+ )}
)
},
@@ -40,15 +59,14 @@ const useModelsColumns = (): Array<{
},
{
title: intl.formatMessage({ id: 'usage.table.provider' }),
- dataIndex: 'provider_name',
- key: 'provider_name',
+ dataIndex: ['model', 'identity', 'value', 'provider_type'],
+ key: 'provider_type',
render: (text: string, record: ListItem) => (
-
+
{text ||
- (!record.provider_type &&
- intl.formatMessage({ id: 'menu.models.deployment' }))}
+ (!text && intl.formatMessage({ id: 'menu.models.deployment' }))}
)
diff --git a/src/pages/usage/hooks/use-users-columns.tsx b/src/pages/usage/hooks/use-users-columns.tsx
index 3790ffee..620d963d 100644
--- a/src/pages/usage/hooks/use-users-columns.tsx
+++ b/src/pages/usage/hooks/use-users-columns.tsx
@@ -1,6 +1,7 @@
// columns.ts
import AutoTooltip from '@/components/auto-tooltip';
import { useIntl } from '@umijs/max';
+import { Tag } from 'antd';
import { useMemo } from 'react';
import { BreakdownItem as ListItem } from '../config/types';
@@ -23,9 +24,27 @@ const useModelsColumns = (): Array<{
key: 'user_name',
render: (text: string, record: ListItem) => (
-
+ {text}}
+ >
{text}
+ {record.user?.deleted && (
+
+ {intl.formatMessage({ id: 'usage.table.deleted' })}
+
+ )}
)
},
diff --git a/src/pages/usage/services/use-query-meta-data.ts b/src/pages/usage/services/use-query-meta-data.ts
index 33be9293..aded6eb0 100644
--- a/src/pages/usage/services/use-query-meta-data.ts
+++ b/src/pages/usage/services/use-query-meta-data.ts
@@ -32,8 +32,8 @@ export default function useQueryUsageMetaData() {
if (!currentUserId) return users;
const sortedUsers = [...users].sort((a, b) => {
- if (a.identity.current.user_id === currentUserId) return -1;
- if (b.identity.current.user_id === currentUserId) return 1;
+ if (a.identity.current?.user_id === currentUserId) return -1;
+ if (b.identity.current?.user_id === currentUserId) return 1;
return 0;
});
@@ -58,7 +58,7 @@ export default function useQueryUsageMetaData() {
sortedUsers.map((item) => ({
value: item.label,
isCurrent:
- item.identity.current.user_id === initialState?.currentUser?.id,
+ item.identity.current?.user_id === initialState?.currentUser?.id,
...item
})) || [],
api_keys: groupToOptions(res?.filters?.api_keys || [], {
diff --git a/src/pages/usage/tables/apikeys-table.tsx b/src/pages/usage/tables/apikeys-table.tsx
index 9e322e4e..f55eb26b 100644
--- a/src/pages/usage/tables/apikeys-table.tsx
+++ b/src/pages/usage/tables/apikeys-table.tsx
@@ -30,7 +30,7 @@ const APIKeys: React.FC<{
}>({
page: 1,
perPage: 10,
- sort_by: ''
+ sort_by: '-total_tokens'
});
const pendingPageResetRef = useRef(false);
@@ -113,7 +113,7 @@ const APIKeys: React.FC<{
getBreakdownRowKey(record, 'api_keys')}
loading={{
spinning: loading,
size: 'middle'
diff --git a/src/pages/usage/tables/models-table.tsx b/src/pages/usage/tables/models-table.tsx
index 70ab0d6c..37aa14cb 100644
--- a/src/pages/usage/tables/models-table.tsx
+++ b/src/pages/usage/tables/models-table.tsx
@@ -30,7 +30,7 @@ const Models: React.FC<{
}>({
page: 1,
perPage: 10,
- sort_by: ''
+ sort_by: '-total_tokens'
});
const pendingPageResetRef = useRef(false);
@@ -115,7 +115,7 @@ const Models: React.FC<{
getBreakdownRowKey(record, 'models')}
loading={{
spinning: loading,
size: 'middle'
diff --git a/src/pages/usage/tables/users-table.tsx b/src/pages/usage/tables/users-table.tsx
index 16ca6267..dde66e43 100644
--- a/src/pages/usage/tables/users-table.tsx
+++ b/src/pages/usage/tables/users-table.tsx
@@ -30,7 +30,7 @@ const Users: React.FC<{
}>({
page: 1,
perPage: 10,
- sort_by: ''
+ sort_by: '-total_tokens'
});
const pendingPageResetRef = useRef(false);
@@ -116,7 +116,7 @@ const Users: React.FC<{
getBreakdownRowKey(record, 'users')}
loading={{
spinning: loading,
size: 'middle'
diff --git a/src/pages/usage/utils/get-breakdown-row-key.ts b/src/pages/usage/utils/get-breakdown-row-key.ts
index 60467851..20939dd6 100644
--- a/src/pages/usage/utils/get-breakdown-row-key.ts
+++ b/src/pages/usage/utils/get-breakdown-row-key.ts
@@ -1,35 +1,22 @@
import { BreakdownItem } from '../config/types';
-export const getBreakdownRowKey = (record: BreakdownItem): string => {
- const identity = record?.identity;
- const current = identity?.current;
- const value = identity?.value;
+export const getBreakdownRowKey = (
+ record: BreakdownItem,
+ type: string
+): string => {
+ if (type === 'models') {
+ return `${record.model.label}`;
+ }
- const key = [
- current?.model_id,
- current?.user_id,
- current?.api_key_id,
- value?.model_name,
- value?.user_name,
- value?.api_key_name,
- value?.cluster_name,
- value?.provider_name,
- value?.provider_type,
- record?.label,
- record?.cluster_name,
- record?.model_name,
- record?.user_name,
- record?.api_key_name,
- record?.last_active,
- record?.input_tokens,
- record?.output_tokens,
- record?.total_tokens,
- record?.api_requests
- ]
- .filter((item) => item !== null && item !== undefined && item !== '')
- .join('__');
+ if (type === 'users') {
+ return `${record.user.label}`;
+ }
- return key || JSON.stringify(record);
+ if (type === 'api_keys') {
+ return `${record.api_key.label}`;
+ }
+
+ return record.last_active;
};
export default getBreakdownRowKey;