diff --git a/src/components/card-wrapper/simple-card.tsx b/src/components/card-wrapper/simple-card.tsx index 6062359e..224c4f86 100644 --- a/src/components/card-wrapper/simple-card.tsx +++ b/src/components/card-wrapper/simple-card.tsx @@ -9,6 +9,7 @@ const SimpleCardItemWrapper = styled.div` width: 100%; height: 100%; gap: 10px; + margin-bottom: 24px; `; const useStyles = createStyles(({ css, token }) => ({ @@ -25,6 +26,9 @@ const useStyles = createStyles(({ css, token }) => ({ gap: ${token.padding}px; .title { font-size: ${token.fontSize}px; + font-weight: var(--font-weight-medium); + } + .content { font-weight: var(--font-weight-500); } ` @@ -48,11 +52,12 @@ export const SimpleCardItem: React.FC<{ export const SimpleCard: React.FC<{ dataList: { label: string; value: React.ReactNode }[]; + height?: string | number; }> = (props) => { const { dataList } = props; return ( - + {dataList.map((item, index) => ( { + const handleOnFinished = () => { + if (!chart.current) return; + + const currentChart = chart.current; + const optionsYAxis = currentChart.getOption()?.yAxis; + if ( + !optionsYAxis || + !Array.isArray(optionsYAxis) || + optionsYAxis.length < 2 + ) + return; + // @ts-ignore + const model = currentChart.getModel(); + + const yAxisModels = [ + model.getComponent('yAxis', 0), + model.getComponent('yAxis', 1) + ]; + + if (!yAxisModels[0] || !yAxisModels[1]) return; + + const axes = yAxisModels.map((m) => m.axis); + + const intervals = axes.map((axis) => axis.scale.getInterval()); + const ticksList = axes.map((axis) => axis.scale.getTicks()); + const counts = ticksList.map((t) => t.length); + + if (counts[0] === counts[1]) return; + + const unifiedCount = Math.max(counts[0], counts[1]); + + const newMax0 = intervals[0] * (unifiedCount - 1); + const newMax1 = intervals[1] * (unifiedCount - 1); + + const yAxis: any[] = [{}, {}]; + + if (counts[0] < unifiedCount) { + yAxis[0].max = newMax0; + } + + if (counts[1] < unifiedCount) { + yAxis[1].max = newMax1; + } + + setTimeout(() => { + currentChart.setOption({ + yAxis: yAxis + }); + }, 0); + }; + if (container.current) { init(); + chart.current?.on('finished', handleOnFinished); } + return () => { chart.current?.dispose(); + chart.current?.off('finished', handleOnFinished); }; }, [init]); diff --git a/src/components/echarts/config.ts b/src/components/echarts/config.ts index 9e8490c7..37a19614 100644 --- a/src/components/echarts/config.ts +++ b/src/components/echarts/config.ts @@ -1,24 +1,9 @@ import useUserSettings from '@/hooks/use-user-settings'; +import { formatLargeNumber } from '@/utils'; import { theme } from 'antd'; import { isFunction } from 'lodash'; import { useMemo } from 'react'; -const formatLargeNumber = (value: number) => { - if (typeof value !== 'number' || isNaN(value)) { - return value; - } - - if (value >= 1e9) { - return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B'; - } else if (value >= 1e6) { - return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'; - } else if (value >= 1e3) { - return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K'; - } else { - return value; - } -}; - export const grid = { left: 0, right: 20, diff --git a/src/components/echarts/mix-line-bar.tsx b/src/components/echarts/mix-line-bar.tsx index 47d70777..76a3f92c 100644 --- a/src/components/echarts/mix-line-bar.tsx +++ b/src/components/echarts/mix-line-bar.tsx @@ -62,12 +62,7 @@ const MixLineBarChart: React.FC< yAxis, legend: { ...legend, - data: legendData.map((item: any) => { - return { - name: item, - icon: 'circle' - }; - }) + data: legendData }, series: [] @@ -112,16 +107,10 @@ const MixLineBarChart: React.FC< }, yAxis: [ { - ...options.yAxis, - min: 0, - - splitNumber: 7 + ...options.yAxis }, { ...options.yAxis, - min: 0, - max: 70, - splitNumber: 7, nameTextStyle: { fontSize: 12, align: 'right' diff --git a/src/components/echarts/types.ts b/src/components/echarts/types.ts index 4d1d15c5..c10e6353 100644 --- a/src/components/echarts/types.ts +++ b/src/components/echarts/types.ts @@ -1,8 +1,9 @@ +import type { LegendComponentOption } from 'echarts/components'; export interface ChartProps { seriesData: any[]; showEmpty?: boolean; xAxisData: string[]; - legendData?: string[]; + legendData?: LegendComponentOption['data']; labelFormatter?: (val?: any) => string; tooltipValueFormatter?: (val: any) => string; height: string | number; diff --git a/src/pages/dashboard/apis/index.ts b/src/pages/dashboard/apis/index.ts index bb2d0661..cb115ed0 100644 --- a/src/pages/dashboard/apis/index.ts +++ b/src/pages/dashboard/apis/index.ts @@ -5,3 +5,7 @@ export const DASHBOARD_API = '/dashboard'; export async function queryDashboardData() { return request(DASHBOARD_API); } + +export async function queryDashboardUsageData() { + return request(`${DASHBOARD_API}/usage`); +} diff --git a/src/pages/dashboard/components/usage-inner/export-data.tsx b/src/pages/dashboard/components/usage-inner/export-data.tsx new file mode 100644 index 00000000..1da45b19 --- /dev/null +++ b/src/pages/dashboard/components/usage-inner/export-data.tsx @@ -0,0 +1,96 @@ +import ModalFooter from '@/components/modal-footer'; +import ScrollerModal from '@/components/scroller-modal'; +import useTableFetch from '@/hooks/use-table-fetch'; +import { useIntl } from '@umijs/max'; +import { DatePicker, Select, Space, Table } from 'antd'; +import React from 'react'; +import { queryDashboardUsageData } from '../../apis'; +import { exportTableColumns } from '../../config'; + +const ExportData: React.FC<{ + open: boolean; + onCancel: () => void; +}> = (props) => { + const { + dataSource, + rowSelection, + queryParams, + modalRef, + handleDelete, + handleDeleteBatch, + fetchData, + handlePageChange, + handleTableChange, + handleSearch, + handleNameChange + } = useTableFetch({ + fetchAPI: queryDashboardUsageData + }); + const { open, onCancel } = props || {}; + const intl = useIntl(); + + const handleSubmit = () => {}; + + return ( + + } + > + + + + + + + {' '} +
+
+ ); +}; + +export default ExportData; diff --git a/src/pages/dashboard/components/usage-inner/index.tsx b/src/pages/dashboard/components/usage-inner/index.tsx index 295012de..8ff0bfc9 100644 --- a/src/pages/dashboard/components/usage-inner/index.tsx +++ b/src/pages/dashboard/components/usage-inner/index.tsx @@ -1,21 +1,45 @@ import PageTools from '@/components/page-tools'; +import { ExportOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Col, Row } from 'antd'; -import { FC, memo, useContext } from 'react'; +import { Button, Col, DatePicker, Row, Select } from 'antd'; +import { FC, memo, useContext, useState } from 'react'; +import styled from 'styled-components'; import { DashboardContext } from '../../config/dashboard-context'; +import ExportData from './export-data'; import RequestTokenInner from './request-token-inner'; import TopUser from './top-user'; import useUsageData from './use-usage-data'; +const FilterWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + .selection { + display: flex; + align-items: center; + gap: 8px; + } +`; + const UsageInner: FC<{ paddingRight: string }> = ({ paddingRight }) => { const intl = useIntl(); const { model_usage } = useContext(DashboardContext); + const [open, setOpen] = useState(false); const { requestTokenData, topUserData } = useUsageData(model_usage || {}); + const handleOnCancel = () => { + setOpen(false); + }; + + const handleExport = () => { + setOpen(true); + }; + return ( - <> +
= ({ paddingRight }) => { xl={16} style={{ paddingRight: paddingRight }} > + +
+ + + +
+ +
= ({ paddingRight }) => { > - + +
); }; 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 8e6c6c25..7b4d9527 100644 --- a/src/pages/dashboard/components/usage-inner/request-token-inner.tsx +++ b/src/pages/dashboard/components/usage-inner/request-token-inner.tsx @@ -1,8 +1,7 @@ import CardWrapper from '@/components/card-wrapper'; -import BarChart from '@/components/echarts/bar-chart'; -import LineChart from '@/components/echarts/line-chart'; +import { SimpleCard } from '@/components/card-wrapper/simple-card'; +import MixLineBar from '@/components/echarts/mix-line-bar'; import { useIntl } from '@umijs/max'; -import { Col, Row } from 'antd'; import dayjs from 'dayjs'; import React from 'react'; @@ -29,13 +28,19 @@ const dataList = [ { label: '120K', value: 'API Requests' } ]; +const legendData = [ + { name: 'Completion tokens', icon: 'roundRect' }, + { name: 'Prompt tokens', icon: 'roundRect' }, + { name: 'API requests', icon: 'circle' } +]; + const RequestTokenInner: React.FC = (props) => { const { requestData, tokenData, xAxisData } = props; const intl = useIntl(); return ( - + {/* = (props) => { labelFormatter={labelFormatter} > - + */} + + ); }; diff --git a/src/pages/dashboard/components/usage-inner/top-user.tsx b/src/pages/dashboard/components/usage-inner/top-user.tsx index 191c4dcf..10fae06e 100644 --- a/src/pages/dashboard/components/usage-inner/top-user.tsx +++ b/src/pages/dashboard/components/usage-inner/top-user.tsx @@ -17,7 +17,7 @@ const TopUser: React.FC = (props) => { title={intl.formatMessage({ id: 'dashboard.topusers' })} seriesData={userData} xAxisData={topUserList} - height={360} + height={520} > ); diff --git a/src/pages/dashboard/components/usage-inner/use-usage-data.ts b/src/pages/dashboard/components/usage-inner/use-usage-data.ts index 76af2ca4..8a1eea9f 100644 --- a/src/pages/dashboard/components/usage-inner/use-usage-data.ts +++ b/src/pages/dashboard/components/usage-inner/use-usage-data.ts @@ -92,8 +92,10 @@ export default function useUseageData(data: any) { data: { time: string; value: number }[]; } = { name: 'API requests', - areaStyle: {}, - color: baseColorMap.base, + areaStyle: { + color: 'rgba(13,171,219,0.15)' + }, + color: baseColorMap.baseR1, data: generateData(dateRange, generateValueMap(apiRequestHistory)) }; diff --git a/src/pages/dashboard/config/index.ts b/src/pages/dashboard/config/index.ts index fe25e99c..0e5f9d84 100644 --- a/src/pages/dashboard/config/index.ts +++ b/src/pages/dashboard/config/index.ts @@ -1,3 +1,6 @@ +import { formatLargeNumber } from '@/utils'; +import dayjs from 'dayjs'; + export const overviewConfigs = [ { key: 'worker_count', @@ -21,3 +24,49 @@ export const overviewConfigs = [ backgroundColor: 'var(--color-white-1)' } ]; + +export const exportTableColumns = [ + { + title: 'Date', + dataIndex: 'date', + render: (text: string) => { + return dayjs(text).format('YYYY-MM-DD'); + } + }, + { + title: 'User', + dataIndex: 'user', + render: (text: string) => { + return text || 'admin'; + } + }, + { + title: 'Model', + dataIndex: 'model', + render: (text: string) => { + return text || 'All Models'; + } + }, + + { + title: 'Completion Tokens', + dataIndex: 'completion_tokens', + render: (text: number) => { + return formatLargeNumber(text) || 0; + } + }, + { + title: 'Prompt Tokens', + dataIndex: 'prompt_tokens', + render: (text: number) => { + return formatLargeNumber(text) || 0; + } + }, + { + title: 'API Requests', + dataIndex: 'request_count', + render: (text: number) => { + return formatLargeNumber(text) || 0; + } + } +]; diff --git a/src/utils/index.ts b/src/utils/index.ts index 7c3be03a..9cd08f07 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -87,6 +87,22 @@ export const formatNumber = (num: number) => { } }; +export const formatLargeNumber = (value: number) => { + if (typeof value !== 'number' || isNaN(value)) { + return value; + } + + if (value >= 1e9) { + return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B'; + } else if (value >= 1e6) { + return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M'; + } else if (value >= 1e3) { + return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K'; + } else { + return value; + } +}; + export function loadLanguageConfig(language: string) { // @ts-ignore const requireContext = require.context(`./${language}`, false, /\.ts$/);