style: card style
This commit is contained in:
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
|
|||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-lg);
|
||||||
background-color: var(--ant-color-bg-container);
|
background-color: var(--ant-color-bg-container);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
padding: 10px 16px;
|
padding: 8px 16px;
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const SimpleCard: React.FC<{
|
|||||||
label: string;
|
label: string;
|
||||||
value: React.ReactNode;
|
value: React.ReactNode;
|
||||||
color: string;
|
color: string;
|
||||||
iconType: string;
|
iconType?: string;
|
||||||
}[];
|
}[];
|
||||||
height?: string | number;
|
height?: string | number;
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
|||||||
// import './iconfont/iconfont.js';
|
// import './iconfont/iconfont.js';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_htxf2x5ewb.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_mk9rqojjoqk.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
+1
-1
@@ -169,7 +169,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-container .ant-table-content table {
|
.ant-table-container table {
|
||||||
// border-spacing: 0 20px;
|
// border-spacing: 0 20px;
|
||||||
|
|
||||||
.ant-table-thead th.ant-table-column-sort {
|
.ant-table-thead th.ant-table-column-sort {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ interface RangePickerPreset {
|
|||||||
disabledDate?: boolean;
|
disabledDate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
|
||||||
|
|
||||||
export default function useRangePickerPreset(options?: RangePickerPreset): {
|
export default function useRangePickerPreset(options?: RangePickerPreset): {
|
||||||
disabledRangeDaysDate: DatePickerProps['disabledDate'];
|
disabledRangeDaysDate: DatePickerProps['disabledDate'];
|
||||||
rangePresets: {
|
rangePresets: {
|
||||||
@@ -21,19 +23,15 @@ export default function useRangePickerPreset(options?: RangePickerPreset): {
|
|||||||
normalizeRangeValue: (
|
normalizeRangeValue: (
|
||||||
startDate: string,
|
startDate: string,
|
||||||
endDate: string,
|
endDate: string,
|
||||||
picker: 'date' | 'week' | 'month' | 'quarter' | 'year'
|
picker: DateType
|
||||||
) => [Dayjs, Dayjs];
|
) => [Dayjs, Dayjs];
|
||||||
handleOnPickerChange: (
|
handleOnPickerChange: (value: DateType) => void;
|
||||||
value: 'date' | 'week' | 'month' | 'quarter' | 'year'
|
picker: DateType;
|
||||||
) => void;
|
|
||||||
picker: 'date' | 'week' | 'month' | 'quarter' | 'year';
|
|
||||||
range: number;
|
range: number;
|
||||||
} {
|
} {
|
||||||
const { range = 60, disabledDate, presetRanges } = options || {};
|
const { range = 60, disabledDate, presetRanges } = options || {};
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [picker, setPicker] = useState<
|
const [picker, setPicker] = useState<DateType>('date');
|
||||||
'date' | 'week' | 'month' | 'quarter' | 'year'
|
|
||||||
>('date');
|
|
||||||
|
|
||||||
const getYearMonth = (date: Dayjs) => date.year() * 12 + date.month();
|
const getYearMonth = (date: Dayjs) => date.year() * 12 + date.month();
|
||||||
|
|
||||||
@@ -132,9 +130,7 @@ export default function useRangePickerPreset(options?: RangePickerPreset): {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnPickerChange = (
|
const handleOnPickerChange = (value: DateType) => {
|
||||||
value: 'date' | 'week' | 'month' | 'quarter' | 'year'
|
|
||||||
) => {
|
|
||||||
setPicker(value);
|
setPicker(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { GroupOption } from '../config';
|
import { GroupOption } from '../config';
|
||||||
import { UsageFilterItem } from '../config/types';
|
import { UsageFilterItem } from '../config/types';
|
||||||
import ApiKeysTable from '../tables/apikeys-table';
|
import ApiKeysTable from '../tables/apikeys-table';
|
||||||
@@ -8,6 +8,7 @@ import UsersTable from '../tables/users-table';
|
|||||||
|
|
||||||
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
||||||
type GroupOptionType = GroupOption<UsageFilterItem>;
|
type GroupOptionType = GroupOption<UsageFilterItem>;
|
||||||
|
const EMPTY_FILTERS: FilterOptionType[] = [];
|
||||||
|
|
||||||
const BreakdownTabs: React.FC<{
|
const BreakdownTabs: React.FC<{
|
||||||
dateRange: {
|
dateRange: {
|
||||||
@@ -15,58 +16,68 @@ const BreakdownTabs: React.FC<{
|
|||||||
end_date: string;
|
end_date: string;
|
||||||
};
|
};
|
||||||
scope: string;
|
scope: string;
|
||||||
|
refreshKey?: number;
|
||||||
filters: {
|
filters: {
|
||||||
models?: FilterOptionType[];
|
models?: FilterOptionType[];
|
||||||
users?: FilterOptionType[];
|
users?: FilterOptionType[];
|
||||||
api_keys?: FilterOptionType[];
|
api_keys?: FilterOptionType[];
|
||||||
};
|
};
|
||||||
}> = ({ filters, dateRange, scope }) => {
|
}> = ({ filters, dateRange, scope, refreshKey = 0 }) => {
|
||||||
const items = [
|
const models = filters.models || EMPTY_FILTERS;
|
||||||
{
|
const users = filters.users || EMPTY_FILTERS;
|
||||||
key: 'models',
|
const apiKeys = filters.api_keys || EMPTY_FILTERS;
|
||||||
label: 'Models',
|
|
||||||
forceRender: true,
|
const items = useMemo(() => {
|
||||||
children: (
|
return [
|
||||||
<ModelsTable
|
{
|
||||||
key="models"
|
key: 'models',
|
||||||
models={filters.models || []}
|
label: 'Models',
|
||||||
dateRange={dateRange}
|
forceRender: true,
|
||||||
scope={scope}
|
children: (
|
||||||
/>
|
<ModelsTable
|
||||||
)
|
key="models"
|
||||||
},
|
models={models}
|
||||||
{
|
dateRange={dateRange}
|
||||||
key: 'users',
|
scope={scope}
|
||||||
label: 'Users',
|
refreshKey={refreshKey}
|
||||||
forceRender: true,
|
/>
|
||||||
children: (
|
)
|
||||||
<UsersTable
|
},
|
||||||
key="users"
|
{
|
||||||
users={filters.users || []}
|
key: 'users',
|
||||||
dateRange={dateRange}
|
label: 'Users',
|
||||||
scope={scope}
|
forceRender: true,
|
||||||
/>
|
children: (
|
||||||
)
|
<UsersTable
|
||||||
},
|
key="users"
|
||||||
{
|
users={users}
|
||||||
key: 'api_keys',
|
dateRange={dateRange}
|
||||||
label: 'API Keys',
|
scope={scope}
|
||||||
forceRender: true,
|
refreshKey={refreshKey}
|
||||||
children: (
|
/>
|
||||||
<ApiKeysTable
|
)
|
||||||
key="api_keys"
|
},
|
||||||
apiKeys={filters.api_keys || []}
|
{
|
||||||
dateRange={dateRange}
|
key: 'api_keys',
|
||||||
scope={scope}
|
label: 'API Keys',
|
||||||
/>
|
forceRender: true,
|
||||||
)
|
children: (
|
||||||
}
|
<ApiKeysTable
|
||||||
].filter((item) => {
|
key="api_keys"
|
||||||
if (item.key === 'users') {
|
apiKeys={apiKeys}
|
||||||
return scope === 'all';
|
dateRange={dateRange}
|
||||||
}
|
scope={scope}
|
||||||
return true;
|
refreshKey={refreshKey}
|
||||||
});
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
].filter((item) => {
|
||||||
|
if (item.key === 'users') {
|
||||||
|
return scope === 'all';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, [apiKeys, dateRange, models, refreshKey, scope, users]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import CardWrapper from '@/components/card-wrapper';
|
|||||||
import MixLineBar from '@/components/echarts/mix-line-bar';
|
import MixLineBar from '@/components/echarts/mix-line-bar';
|
||||||
import BaseSelect from '@/components/seal-form/base/select';
|
import BaseSelect from '@/components/seal-form/base/select';
|
||||||
import { baseColorMap } from '@/pages/dashboard/config';
|
import { baseColorMap } from '@/pages/dashboard/config';
|
||||||
import { formatLargeNumber } from '@/utils';
|
|
||||||
import { Segmented } from 'antd';
|
import { Segmented } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
@@ -31,10 +30,10 @@ const ControlLabel = styled.span`
|
|||||||
interface DailyUsageProps {
|
interface DailyUsageProps {
|
||||||
timeSeriesData: TimeSeriesData | null;
|
timeSeriesData: TimeSeriesData | null;
|
||||||
metric: string;
|
metric: string;
|
||||||
groupBy: string;
|
groupBy: string | null;
|
||||||
granularity: string;
|
granularity: string;
|
||||||
onMetricChange: (value: string) => void;
|
onMetricChange: (value: string) => void;
|
||||||
onGroupByChange: (value: string) => void;
|
onGroupByChange: (value: string | null) => void;
|
||||||
onGranularityChange: (value: string) => void;
|
onGranularityChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,49 +52,6 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
|
|||||||
onGranularityChange
|
onGranularityChange
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const summary = timeSeriesData?.summary || {
|
|
||||||
input_tokens: 0,
|
|
||||||
output_tokens: 0,
|
|
||||||
total_tokens: 0,
|
|
||||||
api_requests: 0,
|
|
||||||
models_called: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const summaryCards = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: formatLargeNumber(summary.input_tokens) as string,
|
|
||||||
value: 'Input tokens',
|
|
||||||
color: baseColorMap.baseR3,
|
|
||||||
iconType: 'roundRect'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatLargeNumber(summary.output_tokens) as string,
|
|
||||||
value: 'Output tokens',
|
|
||||||
color: baseColorMap.base,
|
|
||||||
iconType: 'roundRect'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatLargeNumber(summary.total_tokens) as string,
|
|
||||||
value: 'Total tokens',
|
|
||||||
color: baseColorMap.baseL1,
|
|
||||||
iconType: 'roundRect'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatLargeNumber(summary.api_requests) as string,
|
|
||||||
value: 'API requests',
|
|
||||||
color: baseColorMap.baseR1,
|
|
||||||
iconType: 'circle'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: summary.models_called.toString(),
|
|
||||||
value: 'Models used',
|
|
||||||
color: baseColorMap.baseR2,
|
|
||||||
iconType: 'roundRect'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}, [summary]);
|
|
||||||
|
|
||||||
const { chartData, xAxisData } = useMemo(() => {
|
const { chartData, xAxisData } = useMemo(() => {
|
||||||
if (!timeSeriesData?.series || timeSeriesData.series.length === 0) {
|
if (!timeSeriesData?.series || timeSeriesData.series.length === 0) {
|
||||||
return { chartData: { line: [], bar: [] }, xAxisData: [] };
|
return { chartData: { line: [], bar: [] }, xAxisData: [] };
|
||||||
@@ -120,17 +76,10 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// API requests use line chart, tokens use bar chart
|
// API requests use line chart, tokens use bar chart
|
||||||
if (metric === 'api_requests') {
|
return {
|
||||||
return {
|
chartData: { line: [], bar: chartSeries },
|
||||||
chartData: { line: chartSeries, bar: [] },
|
xAxisData: xAxis
|
||||||
xAxisData: xAxis
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
chartData: { line: [], bar: chartSeries },
|
|
||||||
xAxisData: xAxis
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [timeSeriesData, metric]);
|
}, [timeSeriesData, metric]);
|
||||||
|
|
||||||
const legendData = useMemo(() => {
|
const legendData = useMemo(() => {
|
||||||
@@ -142,6 +91,10 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
|
|||||||
}));
|
}));
|
||||||
}, [timeSeriesData, metric]);
|
}, [timeSeriesData, metric]);
|
||||||
|
|
||||||
|
const handleOnGroupByChange = (value: string) => {
|
||||||
|
onGroupByChange(value || null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CardWrapper style={{ width: '100%', marginTop: 20 }}>
|
<CardWrapper style={{ width: '100%', marginTop: 20 }}>
|
||||||
@@ -154,16 +107,18 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
|
|||||||
value={metric}
|
value={metric}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
onChange={onMetricChange}
|
onChange={onMetricChange}
|
||||||
style={{ width: 180 }}
|
style={{ width: 'max-content' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BaseSelect
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
prefix={<ControlLabel>Group by</ControlLabel>}
|
prefix={<ControlLabel>Group by</ControlLabel>}
|
||||||
options={groupByOptions}
|
options={groupByOptions}
|
||||||
value={groupBy}
|
value={groupBy}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
onChange={onGroupByChange}
|
onChange={handleOnGroupByChange}
|
||||||
|
style={{ width: 'max-content' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Segmented
|
<Segmented
|
||||||
@@ -173,8 +128,6 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
|
|||||||
onChange={onGranularityChange}
|
onChange={onGranularityChange}
|
||||||
></Segmented>
|
></Segmented>
|
||||||
</ControlsWrapper>
|
</ControlsWrapper>
|
||||||
|
|
||||||
{/* <SimpleCard dataList={summaryCards} height={80} /> */}
|
|
||||||
<MixLineBar
|
<MixLineBar
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
seriesData={[]}
|
seriesData={[]}
|
||||||
|
|||||||
@@ -1,36 +1,88 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import ModalFooter from '@/components/modal-footer';
|
import ModalFooter from '@/components/modal-footer';
|
||||||
import ScrollerModal from '@/components/scroller-modal';
|
import ScrollerModal from '@/components/scroller-modal';
|
||||||
|
import { exportJsonToExcel } from '@/utils/excel-reader';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Table, TableColumnType } from 'antd';
|
import { Table, TableColumnType } from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useUsageFilters } from '../hooks/use-usage-filters';
|
import { useUsageFilters } from '../hooks/use-usage-filters';
|
||||||
|
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
||||||
|
import getBreakdownRowKey from '../utils/get-breakdown-row-key';
|
||||||
import FilterBar from './filter-bar';
|
import FilterBar from './filter-bar';
|
||||||
|
|
||||||
|
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
|
||||||
|
type ValueType = string | number | null;
|
||||||
|
|
||||||
const ExportData: React.FC<{
|
const ExportData: React.FC<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
initialScope: string;
|
initialScope: string;
|
||||||
metaData: any;
|
metaData: any;
|
||||||
|
granularity: string;
|
||||||
|
initialState: {
|
||||||
|
activeModels: ValueType[][];
|
||||||
|
activeApiKeys: ValueType[][];
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePickerChange: (picker: DateType) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { open, onCancel, initialScope, metaData } = props || {};
|
const {
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
initialScope,
|
||||||
|
metaData,
|
||||||
|
granularity,
|
||||||
|
handlePickerChange,
|
||||||
|
initialState
|
||||||
|
} = props || {};
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const {
|
const [pageParams, setPageParams] = React.useState<{
|
||||||
filters,
|
page: number;
|
||||||
commonFilters,
|
perPage: number;
|
||||||
fetchData,
|
}>({
|
||||||
loading,
|
page: 1,
|
||||||
timeSeriesData,
|
perPage: 100
|
||||||
filterBar
|
|
||||||
} = useUsageFilters({
|
|
||||||
initialScope: initialScope,
|
|
||||||
metaData,
|
|
||||||
chartFilters: {},
|
|
||||||
summaryColumns: {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportTableColumns: TableColumnType[] = [
|
const {
|
||||||
|
fetchData: fetchExportData,
|
||||||
|
loading,
|
||||||
|
dataSource,
|
||||||
|
cancelRequest
|
||||||
|
} = useQueryBreakdownList({
|
||||||
|
key: 'exportTableData'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { filters, commonFilters, filterBar } = useUsageFilters({
|
||||||
|
initialScope: initialScope,
|
||||||
|
metaData,
|
||||||
|
chartFilters: {
|
||||||
|
metric: 'total_tokens',
|
||||||
|
group_by: null,
|
||||||
|
granularity: props.granularity || 'day'
|
||||||
|
},
|
||||||
|
initialState: initialState,
|
||||||
|
summaryColumns: [],
|
||||||
|
autoFetchOnFilterChange: true,
|
||||||
|
onFetchData: ({
|
||||||
|
chartFilters: nextChartFilters,
|
||||||
|
filters: nextFilters,
|
||||||
|
commonFilters: nextCommonFilters
|
||||||
|
}) => {
|
||||||
|
fetchExportData({
|
||||||
|
...pageParams,
|
||||||
|
granularity: nextChartFilters.granularity,
|
||||||
|
group_by: ['date', 'user', 'model', 'api_key'],
|
||||||
|
filters: nextFilters,
|
||||||
|
scope: initialScope,
|
||||||
|
start_date: nextCommonFilters.start_date,
|
||||||
|
end_date: nextCommonFilters.end_date
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportTableColumns: TableColumnType<any>[] = [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'resources.table.index' }),
|
title: intl.formatMessage({ id: 'resources.table.index' }),
|
||||||
width: 80,
|
width: 80,
|
||||||
@@ -40,34 +92,124 @@ const ExportData: React.FC<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
|
title: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
|
||||||
dataIndex: 'date'
|
dataIndex: ['date', 'label'],
|
||||||
|
render: (text: string) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
<span className="text-primary">{text}</span>
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
|
title: 'Cluster',
|
||||||
dataIndex: 'user_name',
|
dataIndex: ['model', 'identity', 'value', 'cluster_name'],
|
||||||
render: (text: string) => {
|
render: (text: string) => {
|
||||||
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
|
title: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
|
||||||
dataIndex: 'model_id',
|
dataIndex: ['model', 'identity', 'value', 'model_name'],
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{record?.model?.identity?.value?.provider_name
|
||||||
|
? `${record?.model?.identity?.value?.provider_name}/${text}`
|
||||||
|
: text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
|
||||||
|
dataIndex: ['user', 'label'],
|
||||||
|
render: (text: string) => {
|
||||||
|
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'API Key',
|
||||||
|
dataIndex: ['api_key', 'label'],
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'API Key',
|
title: 'Input Tokens',
|
||||||
dataIndex: 'api_key',
|
dataIndex: 'input_tokens'
|
||||||
render: (text: string, record: any) => {
|
},
|
||||||
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
{
|
||||||
}
|
title: 'Output Tokens',
|
||||||
|
dataIndex: 'output_tokens'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Total Tokens',
|
||||||
|
dataIndex: 'total_tokens'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'API Requests',
|
||||||
|
dataIndex: 'api_requests'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
filterBar.onExportChart();
|
exportJsonToExcel({
|
||||||
|
fileName: `usage_export_${commonFilters.start_date}_${commonFilters.end_date}.xlsx`,
|
||||||
|
sheets: [
|
||||||
|
{
|
||||||
|
jsonData: (dataSource.dataList || []).map((item: any) => ({
|
||||||
|
date: item?.date?.label,
|
||||||
|
user: item?.user?.label,
|
||||||
|
cluster: item?.model?.identity?.value?.cluster_name,
|
||||||
|
model: item?.model?.identity?.value?.model_name,
|
||||||
|
api_key: item?.api_key?.label,
|
||||||
|
input_tokens: item?.input_tokens,
|
||||||
|
output_tokens: item?.output_tokens,
|
||||||
|
total_tokens: item?.total_tokens,
|
||||||
|
api_requests: item?.api_requests
|
||||||
|
})),
|
||||||
|
sheetName: 'usage',
|
||||||
|
fields: [
|
||||||
|
'date',
|
||||||
|
'user',
|
||||||
|
'model',
|
||||||
|
'api_key',
|
||||||
|
'input_tokens',
|
||||||
|
'output_tokens',
|
||||||
|
'total_tokens',
|
||||||
|
'api_requests'
|
||||||
|
],
|
||||||
|
fieldLabels: {
|
||||||
|
date: intl.formatMessage({ id: 'dashboard.usage.export.date' }),
|
||||||
|
user: intl.formatMessage({ id: 'dashboard.usage.export.user' }),
|
||||||
|
cluster: 'Cluster',
|
||||||
|
model: intl.formatMessage({ id: 'dashboard.usage.export.model' }),
|
||||||
|
api_key: 'API Key',
|
||||||
|
input_tokens: 'Input Tokens',
|
||||||
|
output_tokens: 'Output Tokens',
|
||||||
|
total_tokens: 'Total Tokens',
|
||||||
|
api_requests: 'API Requests'
|
||||||
|
},
|
||||||
|
formatMap: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
setPageParams({ page, perPage: pageSize });
|
||||||
|
fetchExportData({
|
||||||
|
...pageParams,
|
||||||
|
page,
|
||||||
|
perPage: pageSize,
|
||||||
|
granularity,
|
||||||
|
group_by: ['date', 'user', 'model', 'api_key'],
|
||||||
|
filters,
|
||||||
|
scope: initialScope,
|
||||||
|
start_date: commonFilters.start_date,
|
||||||
|
end_date: commonFilters.end_date
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnCancel = () => {
|
const handleOnCancel = () => {
|
||||||
@@ -76,11 +218,22 @@ const ExportData: React.FC<{
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
fetchData(commonFilters, {});
|
fetchExportData({
|
||||||
|
...pageParams,
|
||||||
|
granularity,
|
||||||
|
group_by: ['date', 'user', 'model', 'api_key'],
|
||||||
|
filters,
|
||||||
|
scope: initialScope,
|
||||||
|
start_date: commonFilters.start_date,
|
||||||
|
end_date: commonFilters.end_date
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
cancelRequest();
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
console.log('export dataSource', filterBar);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
title={intl.formatMessage({ id: 'dashboard.usage.export' })}
|
title={intl.formatMessage({ id: 'dashboard.usage.export' })}
|
||||||
@@ -105,17 +258,32 @@ const ExportData: React.FC<{
|
|||||||
></ModalFooter>
|
></ModalFooter>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FilterBar {...filterBar} pageType="modal"></FilterBar>
|
<FilterBar
|
||||||
|
{...filterBar}
|
||||||
|
pageType="modal"
|
||||||
|
handlePickerChange={handlePickerChange}
|
||||||
|
></FilterBar>
|
||||||
<Table
|
<Table
|
||||||
columns={exportTableColumns}
|
columns={exportTableColumns}
|
||||||
tableLayout={'auto'}
|
tableLayout={'auto'}
|
||||||
style={{ width: '100%', marginTop: '16px', minHeight: 300 }}
|
style={{ width: '100%', marginTop: '16px', minHeight: 300 }}
|
||||||
dataSource={timeSeriesData.series || []}
|
dataSource={dataSource.dataList || []}
|
||||||
loading={loading}
|
rowKey={getBreakdownRowKey}
|
||||||
rowKey="id"
|
loading={{
|
||||||
|
spinning: loading,
|
||||||
|
size: 'middle'
|
||||||
|
}}
|
||||||
virtual
|
virtual
|
||||||
scroll={{ y: 400 }}
|
scroll={{ y: 400 }}
|
||||||
pagination={false}
|
pagination={{
|
||||||
|
size: 'small',
|
||||||
|
pageSize: pageParams.perPage,
|
||||||
|
current: pageParams.page,
|
||||||
|
total: dataSource.total || 0,
|
||||||
|
onChange: handlePageChange,
|
||||||
|
hideOnSinglePage: pageParams.perPage === 100,
|
||||||
|
showSizeChanger: true
|
||||||
|
}}
|
||||||
></Table>
|
></Table>
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import IconFont from '@/components/icon-font';
|
||||||
import SealCascader from '@/components/seal-form/seal-cascader';
|
import SealCascader from '@/components/seal-form/seal-cascader';
|
||||||
import SimpleSelect from '@/components/seal-form/simple-select';
|
import SimpleSelect from '@/components/seal-form/simple-select';
|
||||||
import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset';
|
import useRangePickerPreset from '@/pages/dashboard/hooks/use-rangepicker-preset';
|
||||||
@@ -21,6 +22,9 @@ const DefaultDateConfig = {
|
|||||||
type OptionType = UsageFilterItem & {
|
type OptionType = UsageFilterItem & {
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
|
||||||
|
|
||||||
interface FilterBarProps {
|
interface FilterBarProps {
|
||||||
pageType?: 'page' | 'modal';
|
pageType?: 'page' | 'modal';
|
||||||
scope: string;
|
scope: string;
|
||||||
@@ -32,6 +36,7 @@ interface FilterBarProps {
|
|||||||
modelOptions: GroupOption<UsageFilterItem>[];
|
modelOptions: GroupOption<UsageFilterItem>[];
|
||||||
userOptions: OptionType[];
|
userOptions: OptionType[];
|
||||||
apiKeyOptions: GroupOption<UsageFilterItem>[];
|
apiKeyOptions: GroupOption<UsageFilterItem>[];
|
||||||
|
handlePickerChange: (picker: DateType) => void;
|
||||||
onScopeChange: (value: string) => void;
|
onScopeChange: (value: string) => void;
|
||||||
onDateChange: (dates: any, dateStrings: [string, string]) => void;
|
onDateChange: (dates: any, dateStrings: [string, string]) => void;
|
||||||
onModelsChange: (value: string[]) => void;
|
onModelsChange: (value: string[]) => void;
|
||||||
@@ -53,6 +58,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
modelOptions,
|
modelOptions,
|
||||||
userOptions,
|
userOptions,
|
||||||
apiKeyOptions,
|
apiKeyOptions,
|
||||||
|
handlePickerChange,
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onModelsChange,
|
onModelsChange,
|
||||||
onUsersChange,
|
onUsersChange,
|
||||||
@@ -107,12 +113,22 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
const exportMenuItems: MenuProps['items'] = [
|
const exportMenuItems: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
key: 'chart',
|
key: 'chart',
|
||||||
label: 'Export Chart Data',
|
label: (
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<IconFont type="icon-chart-01" style={{ fontSize: 14 }} />
|
||||||
|
<span>Export Chart Data</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
onClick: onExportChart
|
onClick: onExportChart
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'table',
|
key: 'table',
|
||||||
label: 'Export Table Data',
|
label: (
|
||||||
|
<span className="flex-center gap-8">
|
||||||
|
<IconFont type="icon-table" style={{ fontSize: 14 }} />
|
||||||
|
<span>Export Table Data</span>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
onClick: onExportTable
|
onClick: onExportTable
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -187,6 +203,11 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPickerChange = (picker: DateType) => {
|
||||||
|
handleOnPickerChange(picker);
|
||||||
|
handlePickerChange(picker);
|
||||||
|
};
|
||||||
|
|
||||||
const renderFooter = () => {
|
const renderFooter = () => {
|
||||||
return (
|
return (
|
||||||
<Segmented
|
<Segmented
|
||||||
@@ -208,19 +229,19 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
flex: 1
|
flex: 1
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={handleOnPickerChange}
|
onChange={onPickerChange}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: 'Day',
|
label: 'Day',
|
||||||
value: 'date'
|
value: 'date'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Week',
|
|
||||||
value: 'week'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Month',
|
label: 'Month',
|
||||||
value: 'month'
|
value: 'month'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Week',
|
||||||
|
value: 'week'
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
></Segmented>
|
></Segmented>
|
||||||
@@ -232,11 +253,18 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<span className="flex-center gap-8">
|
<span className="flex-center gap-8">
|
||||||
<span>{data.label}</span>
|
<span>{data.label}</span>
|
||||||
{data.isCurrent && <span className="text-tertiary"> [Current]</span>}
|
{data.isCurrent && (
|
||||||
|
<span className="text-tertiary"> [Current Account]</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// only for dates greater than the current day should be disabled.
|
||||||
|
const disabledDate = (current: dayjs.Dayjs) => {
|
||||||
|
return current && current > dayjs().endOf('day');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={FilterBarCss.wrapper}>
|
<div className={FilterBarCss.wrapper}>
|
||||||
<div className={FilterBarCss.filters}>
|
<div className={FilterBarCss.filters}>
|
||||||
@@ -248,12 +276,11 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
format={'YYYY-MM-DD'}
|
format={'YYYY-MM-DD'}
|
||||||
picker={picker}
|
picker={picker}
|
||||||
disabledDate={disabledRangeDaysDate}
|
disabledDate={disabledDate}
|
||||||
presets={rangePresets}
|
presets={rangePresets}
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
style={{ width: 240 }}
|
style={{ width: 240 }}
|
||||||
onChange={onDateChange}
|
onChange={onDateChange}
|
||||||
renderExtraFooter={renderFooter}
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -285,9 +312,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
maxTagCount={1}
|
maxTagCount={1}
|
||||||
size="small"
|
size="small"
|
||||||
placeholder={intl.formatMessage({
|
placeholder={'Filter by model'}
|
||||||
id: 'dashboard.usage.selectmodel'
|
|
||||||
})}
|
|
||||||
options={modelOptions}
|
options={modelOptions}
|
||||||
showCheckedStrategy="SHOW_CHILD"
|
showCheckedStrategy="SHOW_CHILD"
|
||||||
displayRender={displayRender}
|
displayRender={displayRender}
|
||||||
@@ -304,7 +329,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
showSearch
|
showSearch
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
options={userOptions}
|
options={userOptions}
|
||||||
placeholder="User"
|
placeholder="Filter by user"
|
||||||
styles={{
|
styles={{
|
||||||
wrapper: { flex: 1, maxWidth: 240, minWidth: 150 }
|
wrapper: { flex: 1, maxWidth: 240, minWidth: 150 }
|
||||||
}}
|
}}
|
||||||
@@ -342,7 +367,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
maxTagCount={1}
|
maxTagCount={1}
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="API Key"
|
placeholder="Filter by API key"
|
||||||
options={apiKeyOptions}
|
options={apiKeyOptions}
|
||||||
showCheckedStrategy="SHOW_CHILD"
|
showCheckedStrategy="SHOW_CHILD"
|
||||||
value={activeApiKeys}
|
value={activeApiKeys}
|
||||||
@@ -359,7 +384,7 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
mode="multiple"
|
mode="multiple"
|
||||||
options={apiKeyOptions?.[0]?.children || []}
|
options={apiKeyOptions?.[0]?.children || []}
|
||||||
maxTagCount={0}
|
maxTagCount={0}
|
||||||
placeholder="API Key"
|
placeholder="Filter by API key"
|
||||||
styles={{
|
styles={{
|
||||||
wrapper: { flex: 1, maxWidth: 240, minWidth: 100 }
|
wrapper: { flex: 1, maxWidth: 240, minWidth: 100 }
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { UsageFilterItem } from './types';
|
import { UsageFilterItem } from './types';
|
||||||
|
|
||||||
export const groupByOptions = [
|
export const groupByOptions = [
|
||||||
{
|
// {
|
||||||
value: '',
|
// value: null,
|
||||||
label: 'None'
|
// label: 'None'
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'model',
|
value: 'model',
|
||||||
label: 'Model'
|
label: 'Model'
|
||||||
@@ -163,7 +163,8 @@ export function groupToOptions<T, TChild>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedMap.get(groupKey)!.children.push(options.getChild(item));
|
const child = options.getChild(item) as TChild & { value: string };
|
||||||
|
groupedMap.get(groupKey)!.children.push(child);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(groupedMap.values());
|
return Array.from(groupedMap.values());
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { BreakdownItem as ListItem } from '../config/types';
|
|||||||
|
|
||||||
const useModelsColumns = (): Array<{
|
const useModelsColumns = (): Array<{
|
||||||
title: string;
|
title: string;
|
||||||
dataIndex: string;
|
dataIndex: string | string[];
|
||||||
key: string;
|
key: string;
|
||||||
}> => {
|
}> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -16,7 +16,7 @@ const useModelsColumns = (): Array<{
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'api_key_name',
|
dataIndex: ['api_key', 'identity', 'value', 'api_key_name'],
|
||||||
key: 'api_key_name',
|
key: 'api_key_name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
@@ -29,7 +29,7 @@ const useModelsColumns = (): Array<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'User',
|
title: 'User',
|
||||||
dataIndex: 'user_name',
|
dataIndex: ['api_key', 'identity', 'value', 'user_name'],
|
||||||
key: 'user_name',
|
key: 'user_name',
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<AutoTooltip ghost style={{ maxWidth: 400 }}>
|
<AutoTooltip ghost style={{ maxWidth: 400 }}>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface ColumnsHookProps {
|
|||||||
|
|
||||||
const useModelsColumns = (): Array<{
|
const useModelsColumns = (): Array<{
|
||||||
title: string;
|
title: string;
|
||||||
dataIndex: string;
|
dataIndex: string | string[];
|
||||||
key: string;
|
key: string;
|
||||||
}> => {
|
}> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -21,7 +21,7 @@ const useModelsColumns = (): Array<{
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'model_name',
|
dataIndex: ['model', 'identity', 'value', 'model_name'],
|
||||||
key: 'model_name',
|
key: 'model_name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
@@ -34,7 +34,7 @@ const useModelsColumns = (): Array<{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Cluster',
|
title: 'Cluster',
|
||||||
dataIndex: 'cluster_name',
|
dataIndex: ['model', 'identity', 'value', 'cluster_name'],
|
||||||
key: 'cluster_name',
|
key: 'cluster_name',
|
||||||
sorter: tableSorter(2),
|
sorter: tableSorter(2),
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const DefaultDateConfig = {
|
|||||||
type UserOptionType = UsageFilterItem & {
|
type UserOptionType = UsageFilterItem & {
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
type ValueType = string | number | null;
|
||||||
|
|
||||||
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
||||||
type GroupOptionType = GroupOption<UsageFilterItem>;
|
type GroupOptionType = GroupOption<UsageFilterItem>;
|
||||||
@@ -29,21 +30,49 @@ interface UseUsageFiltersParams {
|
|||||||
};
|
};
|
||||||
chartFilters: {
|
chartFilters: {
|
||||||
metric: string;
|
metric: string;
|
||||||
group_by: string;
|
group_by: string | null;
|
||||||
granularity: string;
|
granularity: string;
|
||||||
};
|
};
|
||||||
|
initialState?: {
|
||||||
|
activeModels?: ValueType[][];
|
||||||
|
activeApiKeys?: ValueType[][];
|
||||||
|
};
|
||||||
summaryColumns: {
|
summaryColumns: {
|
||||||
title: string;
|
title: string;
|
||||||
dataIndex: string;
|
dataIndex: string;
|
||||||
key: string;
|
key: string;
|
||||||
}[];
|
}[];
|
||||||
|
autoFetchOnFilterChange?: boolean;
|
||||||
|
onFetchData?: (params: {
|
||||||
|
chartFilters: UseUsageFiltersParams['chartFilters'];
|
||||||
|
filters: {
|
||||||
|
models?: FilterOptionType[];
|
||||||
|
users?: FilterOptionType[];
|
||||||
|
api_keys?: FilterOptionType[];
|
||||||
|
};
|
||||||
|
commonFilters: {
|
||||||
|
scope: string;
|
||||||
|
models: string[];
|
||||||
|
users: string[];
|
||||||
|
api_keys: string[];
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
};
|
||||||
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUsageFilters = ({
|
export const useUsageFilters = ({
|
||||||
initialScope,
|
initialScope,
|
||||||
metaData,
|
metaData,
|
||||||
chartFilters,
|
chartFilters,
|
||||||
summaryColumns
|
summaryColumns,
|
||||||
|
initialState: {
|
||||||
|
activeModels: initialActiveModels = [],
|
||||||
|
activeApiKeys: initialActiveApiKeys = []
|
||||||
|
} = {},
|
||||||
|
|
||||||
|
autoFetchOnFilterChange = true,
|
||||||
|
onFetchData
|
||||||
}: UseUsageFiltersParams) => {
|
}: UseUsageFiltersParams) => {
|
||||||
const {
|
const {
|
||||||
detailData: timeSeriesData,
|
detailData: timeSeriesData,
|
||||||
@@ -64,6 +93,10 @@ export const useUsageFilters = ({
|
|||||||
const modelOptions = metaData?.models || [];
|
const modelOptions = metaData?.models || [];
|
||||||
const userOptions = metaData?.users || [];
|
const userOptions = metaData?.users || [];
|
||||||
const apiKeyOptions = metaData?.api_keys || [];
|
const apiKeyOptions = metaData?.api_keys || [];
|
||||||
|
const [activeModels, setActiveModels] =
|
||||||
|
useState<ValueType[][]>(initialActiveModels);
|
||||||
|
const [activeApiKeys, setActiveApiKeys] =
|
||||||
|
useState<ValueType[][]>(initialActiveApiKeys);
|
||||||
|
|
||||||
const buildFilters = (selected: typeof commonFilters) => {
|
const buildFilters = (selected: typeof commonFilters) => {
|
||||||
const filters: {
|
const filters: {
|
||||||
@@ -114,18 +147,31 @@ export const useUsageFilters = ({
|
|||||||
currentSelectedFilters = commonFilters,
|
currentSelectedFilters = commonFilters,
|
||||||
currentChartFilters = chartFilters
|
currentChartFilters = chartFilters
|
||||||
) => {
|
) => {
|
||||||
|
const nextFilters = buildFilters(currentSelectedFilters);
|
||||||
|
|
||||||
|
if (onFetchData) {
|
||||||
|
onFetchData({
|
||||||
|
chartFilters: currentChartFilters,
|
||||||
|
filters: nextFilters,
|
||||||
|
commonFilters: currentSelectedFilters
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetchTimeSeriesData({
|
fetchTimeSeriesData({
|
||||||
...currentChartFilters,
|
...currentChartFilters,
|
||||||
start_date: currentSelectedFilters.start_date,
|
start_date: currentSelectedFilters.start_date,
|
||||||
end_date: currentSelectedFilters.end_date,
|
end_date: currentSelectedFilters.end_date,
|
||||||
filters: buildFilters(currentSelectedFilters)
|
filters: nextFilters
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScopeChange = (value: string) => {
|
const handleScopeChange = (value: string) => {
|
||||||
const next = { ...commonFilters, scope: value };
|
const next = { ...commonFilters, scope: value };
|
||||||
setCommonFilters(next);
|
setCommonFilters(next);
|
||||||
fetchData(next, chartFilters);
|
if (autoFetchOnFilterChange) {
|
||||||
|
fetchData(next, chartFilters);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = (_: any, dateStrings: [string, string]) => {
|
const handleDateChange = (_: any, dateStrings: [string, string]) => {
|
||||||
@@ -133,16 +179,27 @@ export const useUsageFilters = ({
|
|||||||
const [start_date, end_date] = dateStrings;
|
const [start_date, end_date] = dateStrings;
|
||||||
const next = { ...commonFilters, start_date, end_date };
|
const next = { ...commonFilters, start_date, end_date };
|
||||||
setCommonFilters(next);
|
setCommonFilters(next);
|
||||||
fetchData(next, chartFilters);
|
if (autoFetchOnFilterChange) {
|
||||||
|
fetchData(next, chartFilters);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterChange = (
|
const handleFilterChange = (
|
||||||
type: 'models' | 'users' | 'api_keys',
|
type: 'models' | 'users' | 'api_keys',
|
||||||
value: string[]
|
value: string[]
|
||||||
) => {
|
) => {
|
||||||
const next = { ...commonFilters, [type]: 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
|
||||||
|
}
|
||||||
|
return item as string;
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = { ...commonFilters, [type]: selectedValues };
|
||||||
setCommonFilters(next);
|
setCommonFilters(next);
|
||||||
fetchData(next, chartFilters);
|
if (autoFetchOnFilterChange) {
|
||||||
|
fetchData(next, chartFilters);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
@@ -188,6 +245,8 @@ export const useUsageFilters = ({
|
|||||||
modelOptions,
|
modelOptions,
|
||||||
userOptions,
|
userOptions,
|
||||||
apiKeyOptions,
|
apiKeyOptions,
|
||||||
|
activeApiKeys,
|
||||||
|
activeModels,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
onScopeChange: handleScopeChange,
|
onScopeChange: handleScopeChange,
|
||||||
onDateChange: handleDateChange,
|
onDateChange: handleDateChange,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface ColumnsHookProps {
|
|||||||
|
|
||||||
const useModelsColumns = (): Array<{
|
const useModelsColumns = (): Array<{
|
||||||
title: string;
|
title: string;
|
||||||
dataIndex: string;
|
dataIndex: string | string[];
|
||||||
key: string;
|
key: string;
|
||||||
}> => {
|
}> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -20,7 +20,7 @@ const useModelsColumns = (): Array<{
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'user_name',
|
dataIndex: ['user', 'identity', 'value', 'user_name'],
|
||||||
key: 'user_name',
|
key: 'user_name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ import useExportTable from './hooks/use-export-table';
|
|||||||
import { useUsageFilters } from './hooks/use-usage-filters';
|
import { useUsageFilters } from './hooks/use-usage-filters';
|
||||||
import useQueryUsageMetaData from './services/use-query-meta-data';
|
import useQueryUsageMetaData from './services/use-query-meta-data';
|
||||||
|
|
||||||
|
type DateType = 'date' | 'week' | 'month' | 'quarter' | 'year';
|
||||||
|
|
||||||
const Usage: React.FC = () => {
|
const Usage: React.FC = () => {
|
||||||
const initialInfo = useModel('@@initialState');
|
const initialInfo = useModel('@@initialState');
|
||||||
const { initialState } = initialInfo || {};
|
const { initialState } = initialInfo || {};
|
||||||
const { exportTable } = useExportTable();
|
const { exportTable } = useExportTable();
|
||||||
const [openExportModal, setOpenExportModal] = useState(false);
|
const [openExportModal, setOpenExportModal] = useState(false);
|
||||||
|
const [breakdownRefreshKey, setBreakdownRefreshKey] = useState(0);
|
||||||
|
|
||||||
const summaryColumns = [
|
const summaryColumns = [
|
||||||
{
|
{
|
||||||
@@ -47,11 +50,11 @@ const Usage: React.FC = () => {
|
|||||||
|
|
||||||
const [chartFilters, setChartFilters] = useState<{
|
const [chartFilters, setChartFilters] = useState<{
|
||||||
metric: string;
|
metric: string;
|
||||||
group_by: string;
|
group_by: string | null;
|
||||||
granularity: string;
|
granularity: string;
|
||||||
}>({
|
}>({
|
||||||
metric: 'total_tokens',
|
metric: 'total_tokens',
|
||||||
group_by: 'model',
|
group_by: null,
|
||||||
granularity: 'day'
|
granularity: 'day'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,14 +121,36 @@ const Usage: React.FC = () => {
|
|||||||
];
|
];
|
||||||
}, [timeSeriesData.summary]);
|
}, [timeSeriesData.summary]);
|
||||||
|
|
||||||
|
const breakdownDateRange = useMemo(
|
||||||
|
() => ({
|
||||||
|
start_date: commonFilters.start_date,
|
||||||
|
end_date: commonFilters.end_date
|
||||||
|
}),
|
||||||
|
[commonFilters.end_date, commonFilters.start_date]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePickerChange = (picker: DateType) => {
|
||||||
|
setChartFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
granularity: picker === 'date' ? 'day' : picker
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleExportChart = () => {
|
const handleExportChart = () => {
|
||||||
setOpenExportModal(true);
|
setOpenExportModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
filterBar.handleSearch();
|
||||||
|
setBreakdownRefreshKey((prev) => prev + 1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FilterBar
|
<FilterBar
|
||||||
{...filterBar}
|
{...filterBar}
|
||||||
|
handleSearch={handleSearch}
|
||||||
|
handlePickerChange={handlePickerChange}
|
||||||
onExportTable={exportTable}
|
onExportTable={exportTable}
|
||||||
onExportChart={handleExportChart}
|
onExportChart={handleExportChart}
|
||||||
/>
|
/>
|
||||||
@@ -139,9 +164,8 @@ const Usage: React.FC = () => {
|
|||||||
height={80}
|
height={80}
|
||||||
styles={{
|
styles={{
|
||||||
item: {
|
item: {
|
||||||
borderBottom: '1px solid var(--ant-color-split)',
|
|
||||||
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
||||||
borderRadius: '6px 6px 0 0'
|
borderRadius: '6px'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -159,17 +183,22 @@ const Usage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<BreakdownTabs
|
<BreakdownTabs
|
||||||
filters={filters}
|
filters={filters}
|
||||||
dateRange={{
|
dateRange={breakdownDateRange}
|
||||||
start_date: commonFilters.start_date,
|
|
||||||
end_date: commonFilters.end_date
|
|
||||||
}}
|
|
||||||
scope={commonFilters.scope}
|
scope={commonFilters.scope}
|
||||||
|
refreshKey={breakdownRefreshKey}
|
||||||
></BreakdownTabs>
|
></BreakdownTabs>
|
||||||
<ExportData
|
<ExportData
|
||||||
metaData={metaData}
|
metaData={metaData}
|
||||||
|
granularity={chartFilters.granularity}
|
||||||
initialScope={commonFilters.scope}
|
initialScope={commonFilters.scope}
|
||||||
|
initialCommonFilters={commonFilters}
|
||||||
open={openExportModal}
|
open={openExportModal}
|
||||||
|
handlePickerChange={handlePickerChange}
|
||||||
onCancel={() => setOpenExportModal(false)}
|
onCancel={() => setOpenExportModal(false)}
|
||||||
|
initialState={{
|
||||||
|
activeModels: filterBar.activeModels,
|
||||||
|
activeApiKeys: filterBar.activeApiKeys
|
||||||
|
}}
|
||||||
></ExportData>
|
></ExportData>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
|
|||||||
import { FilterOptionType } from '../config/types';
|
import { FilterOptionType } from '../config/types';
|
||||||
import useAPIKeys from '../hooks/use-apikeys-columns';
|
import useAPIKeys from '../hooks/use-apikeys-columns';
|
||||||
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
||||||
|
import getBreakdownRowKey from '../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const APIKeys: React.FC<{
|
const APIKeys: React.FC<{
|
||||||
apiKeys: FilterOptionType[];
|
apiKeys: FilterOptionType[];
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
}> = ({ apiKeys, dateRange, scope }) => {
|
refreshKey?: number;
|
||||||
|
}> = ({ apiKeys, dateRange, scope, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -57,14 +59,23 @@ const APIKeys: React.FC<{
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: 'api_key',
|
group_by: ['api_key'],
|
||||||
filters: {
|
filters: {
|
||||||
api_keys: apiKeys || []
|
api_keys: apiKeys
|
||||||
},
|
},
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
}, [dateRange, apiKeys, queryParams, scope]);
|
}, [
|
||||||
|
apiKeys,
|
||||||
|
dateRange.end_date,
|
||||||
|
dateRange.start_date,
|
||||||
|
queryParams.page,
|
||||||
|
queryParams.perPage,
|
||||||
|
queryParams.sort_by,
|
||||||
|
refreshKey,
|
||||||
|
scope
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -73,13 +84,13 @@ const APIKeys: React.FC<{
|
|||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
|
rowKey={getBreakdownRowKey}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: loading,
|
spinning: loading,
|
||||||
size: 'middle'
|
size: 'middle'
|
||||||
}}
|
}}
|
||||||
sortDirections={TABLE_SORT_DIRECTIONS}
|
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
rowKey="id"
|
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
size: 'middle',
|
size: 'middle',
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
|
|||||||
import { FilterOptionType } from '../config/types';
|
import { FilterOptionType } from '../config/types';
|
||||||
import useUsersColumns from '../hooks/use-models-columns';
|
import useUsersColumns from '../hooks/use-models-columns';
|
||||||
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
||||||
|
import getBreakdownRowKey from '../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const Models: React.FC<{
|
const Models: React.FC<{
|
||||||
models: FilterOptionType[];
|
models: FilterOptionType[];
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
}> = ({ models, dateRange, scope }) => {
|
refreshKey?: number;
|
||||||
|
}> = ({ models, dateRange, scope, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -43,13 +45,13 @@ const Models: React.FC<{
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
image={<IconFont type="icon-key" />}
|
image={<IconFont type="icon-resources" />}
|
||||||
filters={_.omit(queryParams, ['sort_by'])}
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
noFoundText={intl.formatMessage({
|
noFoundText={intl.formatMessage({
|
||||||
id: 'noresult.keys.nofound'
|
id: 'noresult.mymodels.nofound'
|
||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.keys.title' })}
|
title={intl.formatMessage({ id: 'noresult.deployments.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.deployments.subTitle' })}
|
||||||
></NoResult>
|
></NoResult>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -57,14 +59,23 @@ const Models: React.FC<{
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: 'model',
|
group_by: ['model'],
|
||||||
filters: {
|
filters: {
|
||||||
models: models || []
|
models
|
||||||
},
|
},
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
}, [dateRange, models, queryParams, scope]);
|
}, [
|
||||||
|
dateRange.end_date,
|
||||||
|
dateRange.start_date,
|
||||||
|
models,
|
||||||
|
queryParams.page,
|
||||||
|
queryParams.perPage,
|
||||||
|
queryParams.sort_by,
|
||||||
|
refreshKey,
|
||||||
|
scope
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -73,13 +84,13 @@ const Models: React.FC<{
|
|||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
|
rowKey={getBreakdownRowKey}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: loading,
|
spinning: loading,
|
||||||
size: 'middle'
|
size: 'middle'
|
||||||
}}
|
}}
|
||||||
sortDirections={TABLE_SORT_DIRECTIONS}
|
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
rowKey="id"
|
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
size: 'middle',
|
size: 'middle',
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import { useEffect, useState } from 'react';
|
|||||||
import { FilterOptionType } from '../config/types';
|
import { FilterOptionType } from '../config/types';
|
||||||
import useUsersColumns from '../hooks/use-users-columns';
|
import useUsersColumns from '../hooks/use-users-columns';
|
||||||
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../services/use-query-breakdown-list';
|
||||||
|
import getBreakdownRowKey from '../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const Users: React.FC<{
|
const Users: React.FC<{
|
||||||
users: FilterOptionType[];
|
users: FilterOptionType[];
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
}> = ({ users, dateRange, scope }) => {
|
refreshKey?: number;
|
||||||
|
}> = ({ users, dateRange, scope, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -43,13 +45,13 @@ const Users: React.FC<{
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
image={<IconFont type="icon-key" />}
|
image={<IconFont type="icon-users" />}
|
||||||
filters={_.omit(queryParams, ['sort_by'])}
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
noFoundText={intl.formatMessage({
|
noFoundText={intl.formatMessage({
|
||||||
id: 'noresult.keys.nofound'
|
id: 'noresult.users.nofound'
|
||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.keys.title' })}
|
title={intl.formatMessage({ id: 'noresult.users.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.keys.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.users.subTitle' })}
|
||||||
></NoResult>
|
></NoResult>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -58,15 +60,24 @@ const Users: React.FC<{
|
|||||||
if (scope === 'all') {
|
if (scope === 'all') {
|
||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: 'user',
|
group_by: ['user'],
|
||||||
filters: {
|
filters: {
|
||||||
users: users || []
|
users
|
||||||
},
|
},
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [dateRange, users, queryParams, scope]);
|
}, [
|
||||||
|
dateRange.end_date,
|
||||||
|
dateRange.start_date,
|
||||||
|
queryParams.page,
|
||||||
|
queryParams.perPage,
|
||||||
|
queryParams.sort_by,
|
||||||
|
refreshKey,
|
||||||
|
scope,
|
||||||
|
users
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -75,13 +86,13 @@ const Users: React.FC<{
|
|||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
|
rowKey={getBreakdownRowKey}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: loading,
|
spinning: loading,
|
||||||
size: 'middle'
|
size: 'middle'
|
||||||
}}
|
}}
|
||||||
sortDirections={TABLE_SORT_DIRECTIONS}
|
sortDirections={TABLE_SORT_DIRECTIONS}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
rowKey="id"
|
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{
|
pagination={{
|
||||||
size: 'middle',
|
size: 'middle',
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { BreakdownItem } from '../config/types';
|
||||||
|
|
||||||
|
export const getBreakdownRowKey = (record: BreakdownItem): string => {
|
||||||
|
const identity = record?.identity;
|
||||||
|
const current = identity?.current;
|
||||||
|
const value = identity?.value;
|
||||||
|
|
||||||
|
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('__');
|
||||||
|
|
||||||
|
return key || JSON.stringify(record);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getBreakdownRowKey;
|
||||||
Reference in New Issue
Block a user