mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-17 13:43:42 +08:00
✨ feat: add admin statistics (#59)
This commit is contained in:
@@ -14,7 +14,7 @@ import { Box } from '@mui/material';
|
||||
|
||||
// ==============================|| DASHBOARD DEFAULT - TOTAL GROWTH BAR CHART ||============================== //
|
||||
|
||||
const StatisticalBarChart = ({ isLoading, chartDatas }) => {
|
||||
const BubbleChard = ({ isLoading, chartDatas, title = '统计' }) => {
|
||||
chartData.options.xaxis.categories = chartDatas.xaxis;
|
||||
chartData.series = chartDatas.data;
|
||||
|
||||
@@ -28,7 +28,7 @@ const StatisticalBarChart = ({ isLoading, chartDatas }) => {
|
||||
<Grid item xs={12}>
|
||||
<Grid container alignItems="center" justifyContent="space-between">
|
||||
<Grid item>
|
||||
<Typography variant="h3">统计</Typography>
|
||||
<Typography variant="h3">{title}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -57,16 +57,17 @@ const StatisticalBarChart = ({ isLoading, chartDatas }) => {
|
||||
);
|
||||
};
|
||||
|
||||
StatisticalBarChart.propTypes = {
|
||||
BubbleChard.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
chartDatas: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||
chartDatas: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
export default StatisticalBarChart;
|
||||
export default BubbleChard;
|
||||
|
||||
const chartData = {
|
||||
height: 480,
|
||||
type: 'bar',
|
||||
type: 'bubble',
|
||||
options: {
|
||||
colors: [
|
||||
'#008FFB',
|
||||
@@ -92,7 +93,7 @@ const chartData = {
|
||||
'#e84393'
|
||||
],
|
||||
chart: {
|
||||
id: 'bar-chart',
|
||||
id: 'bubble',
|
||||
stacked: true,
|
||||
toolbar: {
|
||||
show: true
|
||||
@@ -156,11 +157,6 @@ const chartData = {
|
||||
fixed: {
|
||||
enabled: false
|
||||
},
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return '$' + val;
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
334
web/src/views/Analytics/component/Overview.js
Normal file
334
web/src/views/Analytics/component/Overview.js
Normal file
@@ -0,0 +1,334 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Grid, Typography, Divider } from '@mui/material';
|
||||
import { gridSpacing } from 'store/constant';
|
||||
import DateRangePicker from 'ui-component/DateRangePicker';
|
||||
import ApexCharts from 'ui-component/chart/ApexCharts';
|
||||
import { showError, calculateQuota } from 'utils/common';
|
||||
import dayjs from 'dayjs';
|
||||
import { API } from 'utils/api';
|
||||
import { generateBarChartOptions, renderChartNumber } from 'utils/chart';
|
||||
|
||||
export default function Overview() {
|
||||
const [channelLoading, setChannelLoading] = useState(true);
|
||||
const [redemptionLoading, setRedemptionLoading] = useState(true);
|
||||
const [usersLoading, setUsersLoading] = useState(true);
|
||||
const [channelData, setChannelData] = useState([]);
|
||||
const [redemptionData, setRedemptionData] = useState([]);
|
||||
const [usersData, setUsersData] = useState([]);
|
||||
const [dateRange, setDateRange] = useState({ start: dayjs().subtract(6, 'day').startOf('day'), end: dayjs().endOf('day') });
|
||||
const handleDateRangeChange = (value) => {
|
||||
setDateRange(value);
|
||||
};
|
||||
|
||||
const channelChart = useCallback(async () => {
|
||||
setChannelLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/analytics/channel_period', {
|
||||
params: {
|
||||
start_timestamp: dateRange.start.unix(),
|
||||
end_timestamp: dateRange.end.unix()
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (data) {
|
||||
setChannelData(getBarChartOptions(data, dateRange));
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setChannelLoading(false);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, [dateRange]);
|
||||
|
||||
const redemptionChart = useCallback(async () => {
|
||||
setRedemptionLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/analytics/redemption_period', {
|
||||
params: {
|
||||
start_timestamp: dateRange.start.unix(),
|
||||
end_timestamp: dateRange.end.unix()
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (data) {
|
||||
let chartData = getRedemptionData(data, dateRange);
|
||||
setRedemptionData(chartData);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setRedemptionLoading(false);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, [dateRange]);
|
||||
|
||||
const usersChart = useCallback(async () => {
|
||||
setUsersLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/analytics/users_period', {
|
||||
params: {
|
||||
start_timestamp: dateRange.start.unix(),
|
||||
end_timestamp: dateRange.end.unix()
|
||||
}
|
||||
});
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (data) {
|
||||
setUsersData(getUsersData(data, dateRange));
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setUsersLoading(false);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, [dateRange]);
|
||||
|
||||
useEffect(() => {
|
||||
channelChart();
|
||||
redemptionChart();
|
||||
usersChart();
|
||||
}, [dateRange, channelChart, redemptionChart, usersChart]);
|
||||
|
||||
return (
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item lg={8} xs={12}>
|
||||
<DateRangePicker defaultValue={dateRange} onChange={handleDateRangeChange} localeText={{ start: '开始时间', end: '结束时间' }} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
{dateRange.start.format('YYYY-MM-DD')} - {dateRange.end.format('YYYY-MM-DD')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts id="cost" isLoading={channelLoading} chartDatas={channelData?.costs || {}} title="消费统计" decimal={3} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts id="token" isLoading={channelLoading} chartDatas={channelData?.tokens || {}} title="Tokens统计" unit="" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts id="latency" isLoading={channelLoading} chartDatas={channelData?.latency || {}} title="平均延迟" unit="" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts id="requests" isLoading={channelLoading} chartDatas={channelData?.requests || {}} title="请求数" unit="" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts isLoading={redemptionLoading} chartDatas={redemptionData} title="兑换统计" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<ApexCharts isLoading={usersLoading} chartDatas={usersData} title="注册统计" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function getDates(start, end) {
|
||||
var dates = [];
|
||||
var current = start;
|
||||
|
||||
while (current.isBefore(end) || current.isSame(end)) {
|
||||
dates.push(current.format('YYYY-MM-DD'));
|
||||
current = current.add(1, 'day');
|
||||
}
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
function calculateDailyData(item, dateMap) {
|
||||
const index = dateMap.get(item.Date);
|
||||
if (index === undefined) return null;
|
||||
|
||||
return {
|
||||
name: item.Channel,
|
||||
costs: calculateQuota(item.Quota, 3),
|
||||
tokens: item.PromptTokens + item.CompletionTokens,
|
||||
requests: item.RequestCount,
|
||||
latency: Number(item.RequestTime / 1000 / item.RequestCount).toFixed(3),
|
||||
index: index
|
||||
};
|
||||
}
|
||||
|
||||
function getBarDataGroup(data, dates) {
|
||||
const dateMap = new Map(dates.map((date, index) => [date, index]));
|
||||
|
||||
const result = {
|
||||
costs: { total: 0, data: new Map() },
|
||||
tokens: { total: 0, data: new Map() },
|
||||
requests: { total: 0, data: new Map() },
|
||||
latency: { total: 0, data: new Map() }
|
||||
};
|
||||
|
||||
for (const item of data) {
|
||||
const dailyData = calculateDailyData(item, dateMap);
|
||||
if (!dailyData) continue;
|
||||
|
||||
for (let key in result) {
|
||||
if (!result[key].data.has(dailyData.name)) {
|
||||
result[key].data.set(dailyData.name, { name: dailyData.name, data: new Array(dates.length).fill(0) });
|
||||
}
|
||||
const channelDailyData = result[key].data.get(dailyData.name);
|
||||
channelDailyData.data[dailyData.index] = dailyData[key];
|
||||
result[key].total += Number(dailyData[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getBarChartOptions(data, dateRange) {
|
||||
const dates = getDates(dateRange.start, dateRange.end);
|
||||
const result = getBarDataGroup(data, dates);
|
||||
|
||||
let channelData = {};
|
||||
|
||||
channelData.costs = generateBarChartOptions(dates, Array.from(result.costs.data.values()), '美元', 3);
|
||||
channelData.costs.options.title.text = '总消费:$' + renderChartNumber(result.costs.total, 3);
|
||||
|
||||
channelData.tokens = generateBarChartOptions(dates, Array.from(result.tokens.data.values()), '', 0);
|
||||
channelData.tokens.options.title.text = '总Tokens:' + renderChartNumber(result.tokens.total, 0);
|
||||
|
||||
channelData.requests = generateBarChartOptions(dates, Array.from(result.requests.data.values()), '次', 0);
|
||||
channelData.requests.options.title.text = '总请求数:' + renderChartNumber(result.requests.total, 0);
|
||||
|
||||
// 获取每天所有渠道的平均延迟
|
||||
let latency = Array.from(result.latency.data.values());
|
||||
let sums = [];
|
||||
let counts = [];
|
||||
for (let obj of latency) {
|
||||
for (let i = 0; i < obj.data.length; i++) {
|
||||
let value = parseFloat(obj.data[i]);
|
||||
sums[i] = sums[i] || 0;
|
||||
counts[i] = counts[i] || 0;
|
||||
if (value !== 0) {
|
||||
sums[i] = (sums[i] || 0) + value;
|
||||
counts[i] = (counts[i] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 追加latency列表后面
|
||||
latency[latency.length] = {
|
||||
name: '平均延迟',
|
||||
data: sums.map((sum, i) => Number(counts[i] ? sum / counts[i] : 0).toFixed(3))
|
||||
};
|
||||
|
||||
let dashArray = new Array(latency.length - 1).fill(0);
|
||||
dashArray.push(5);
|
||||
|
||||
channelData.latency = generateBarChartOptions(dates, latency, '秒', 3);
|
||||
channelData.latency.type = 'line';
|
||||
channelData.latency.options.chart = {
|
||||
type: 'line',
|
||||
zoom: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
channelData.latency.options.stroke = {
|
||||
curve: 'smooth',
|
||||
dashArray: dashArray
|
||||
};
|
||||
|
||||
return channelData;
|
||||
}
|
||||
|
||||
function getRedemptionData(data, dateRange) {
|
||||
const dates = getDates(dateRange.start, dateRange.end);
|
||||
const result = [
|
||||
{
|
||||
name: '兑换金额($)',
|
||||
type: 'column',
|
||||
data: new Array(dates.length).fill(0)
|
||||
},
|
||||
{
|
||||
name: '独立用户(人)',
|
||||
type: 'line',
|
||||
data: new Array(dates.length).fill(0)
|
||||
}
|
||||
];
|
||||
|
||||
for (const item of data) {
|
||||
const index = dates.indexOf(item.date);
|
||||
if (index !== -1) {
|
||||
result[0].data[index] = calculateQuota(item.quota, 3);
|
||||
result[1].data[index] = item.user_count;
|
||||
}
|
||||
}
|
||||
|
||||
let chartData = {
|
||||
height: 480,
|
||||
options: {
|
||||
chart: {
|
||||
type: 'line'
|
||||
},
|
||||
stroke: {
|
||||
width: [0, 4]
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
enabledOnSeries: [1]
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
categories: dates
|
||||
},
|
||||
yaxis: [
|
||||
{
|
||||
title: {
|
||||
text: '兑换金额($)'
|
||||
}
|
||||
},
|
||||
{
|
||||
opposite: true,
|
||||
title: {
|
||||
text: '独立用户(人)'
|
||||
}
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
theme: 'dark'
|
||||
}
|
||||
},
|
||||
series: result
|
||||
};
|
||||
|
||||
return chartData;
|
||||
}
|
||||
|
||||
function getUsersData(data, dateRange) {
|
||||
const dates = getDates(dateRange.start, dateRange.end);
|
||||
const result = [
|
||||
{
|
||||
name: '直接注册',
|
||||
data: new Array(dates.length).fill(0)
|
||||
},
|
||||
{
|
||||
name: '邀请注册',
|
||||
data: new Array(dates.length).fill(0)
|
||||
}
|
||||
];
|
||||
|
||||
let total = 0;
|
||||
|
||||
for (const item of data) {
|
||||
const index = dates.indexOf(item.date);
|
||||
if (index !== -1) {
|
||||
result[0].data[index] = item.user_count - item.inviter_user_count;
|
||||
result[1].data[index] = item.inviter_user_count;
|
||||
|
||||
total += item.user_count;
|
||||
}
|
||||
}
|
||||
|
||||
let chartData = generateBarChartOptions(dates, result, '人', 0);
|
||||
chartData.options.title.text = '总注册人数:' + total;
|
||||
|
||||
return chartData;
|
||||
}
|
||||
151
web/src/views/Analytics/component/Statistics.js
Normal file
151
web/src/views/Analytics/component/Statistics.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Grid } from '@mui/material';
|
||||
import DataCard from 'ui-component/cards/DataCard';
|
||||
import { gridSpacing } from 'store/constant';
|
||||
import { showError, renderQuota } from 'utils/common';
|
||||
import { API } from 'utils/api';
|
||||
|
||||
export default function Overview() {
|
||||
const [userLoading, setUserLoading] = useState(true);
|
||||
const [channelLoading, setChannelLoading] = useState(true);
|
||||
const [redemptionLoading, setRedemptionLoading] = useState(true);
|
||||
const [userStatistics, setUserStatistics] = useState({});
|
||||
|
||||
const [channelStatistics, setChannelStatistics] = useState({
|
||||
active: 0,
|
||||
disabled: 0,
|
||||
test_disabled: 0,
|
||||
total: 0
|
||||
});
|
||||
const [redemptionStatistics, setRedemptionStatistics] = useState({
|
||||
total: 0,
|
||||
used: 0,
|
||||
unused: 0
|
||||
});
|
||||
|
||||
const userStatisticsData = useCallback(async () => {
|
||||
try {
|
||||
const res = await API.get('/api/analytics/user_statistics');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
data.total_quota = renderQuota(data.total_quota);
|
||||
data.total_used_quota = renderQuota(data.total_used_quota);
|
||||
data.total_direct_user = data.total_user - data.total_inviter_user;
|
||||
setUserStatistics(data);
|
||||
setUserLoading(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const channelStatisticsData = useCallback(async () => {
|
||||
try {
|
||||
const res = await API.get('/api/analytics/channel_statistics');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
let channelData = channelStatistics;
|
||||
channelData.total = 0;
|
||||
data.forEach((item) => {
|
||||
if (item.status === 1) {
|
||||
channelData.active = item.total_channels;
|
||||
} else if (item.status === 2) {
|
||||
channelData.disabled = item.total_channels;
|
||||
} else if (item.status === 3) {
|
||||
channelData.test_disabled = item.total_channels;
|
||||
}
|
||||
channelData.total += item.total_channels;
|
||||
});
|
||||
setChannelStatistics(channelData);
|
||||
setChannelLoading(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, [channelStatistics]);
|
||||
|
||||
const redemptionStatisticsData = useCallback(async () => {
|
||||
try {
|
||||
const res = await API.get('/api/analytics/redemption_statistics');
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
let redemptionData = redemptionStatistics;
|
||||
redemptionData.total = 0;
|
||||
data.forEach((item) => {
|
||||
if (item.status === 1) {
|
||||
redemptionData.unused = renderQuota(item.quota);
|
||||
} else if (item.status === 3) {
|
||||
redemptionData.used = renderQuota(item.quota);
|
||||
}
|
||||
redemptionData.total += item.quota;
|
||||
});
|
||||
redemptionData.total = renderQuota(redemptionData.total);
|
||||
setRedemptionStatistics(redemptionData);
|
||||
setRedemptionLoading(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}, [redemptionStatistics]);
|
||||
|
||||
useEffect(() => {
|
||||
userStatisticsData();
|
||||
channelStatisticsData();
|
||||
redemptionStatisticsData();
|
||||
}, [userStatisticsData, channelStatisticsData, redemptionStatisticsData]);
|
||||
|
||||
return (
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item lg={3} xs={12}>
|
||||
<DataCard
|
||||
isLoading={userLoading}
|
||||
title="用户总消费金额"
|
||||
content={userStatistics?.total_used_quota || '0'}
|
||||
subContent={'用户总余额:' + (userStatistics?.total_quota || '0')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item lg={3} xs={12}>
|
||||
<DataCard
|
||||
isLoading={userLoading}
|
||||
title="用户总数"
|
||||
content={userStatistics?.total_user || '0'}
|
||||
subContent={
|
||||
<>
|
||||
直接注册:{userStatistics?.total_direct_user || '0'} <br /> 邀请注册:{userStatistics?.total_inviter_user || '0'}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item lg={3} xs={12}>
|
||||
<DataCard
|
||||
isLoading={channelLoading}
|
||||
title="渠道数量"
|
||||
content={channelStatistics.total}
|
||||
subContent={
|
||||
<>
|
||||
正常:{channelStatistics.active} / 禁用:{channelStatistics.disabled} / 测试禁用:{channelStatistics.test_disabled}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item lg={3} xs={12}>
|
||||
<DataCard
|
||||
isLoading={redemptionLoading}
|
||||
title="兑换码发行量"
|
||||
content={redemptionStatistics.total}
|
||||
subContent={
|
||||
<>
|
||||
已使用: {redemptionStatistics.used} <br /> 未使用: {redemptionStatistics.unused}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
20
web/src/views/Analytics/index.js
Normal file
20
web/src/views/Analytics/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { gridSpacing } from 'store/constant';
|
||||
import { Grid } from '@mui/material';
|
||||
import MainCard from 'ui-component/cards/MainCard';
|
||||
import Statistics from './component/Statistics';
|
||||
import Overview from './component/Overview';
|
||||
|
||||
export default function MarketingData() {
|
||||
return (
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item xs={12}>
|
||||
<Statistics />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<MainCard>
|
||||
<Overview />
|
||||
</MainCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import { useEffect, useState } from 'react';
|
||||
import { Grid, Typography } from '@mui/material';
|
||||
import { gridSpacing } from 'store/constant';
|
||||
import StatisticalLineChartCard from './component/StatisticalLineChartCard';
|
||||
import StatisticalBarChart from './component/StatisticalBarChart';
|
||||
import ApexCharts from 'ui-component/chart/ApexCharts';
|
||||
import SupportModels from './component/SupportModels';
|
||||
import { generateChartOptions, getLastSevenDays } from 'utils/chart';
|
||||
import { generateLineChartOptions, getLastSevenDays, generateBarChartOptions, renderChartNumber } from 'utils/chart';
|
||||
import { API } from 'utils/api';
|
||||
import { showError, calculateQuota, renderNumber } from 'utils/common';
|
||||
import UserCard from 'ui-component/cards/UserCard';
|
||||
@@ -94,7 +94,7 @@ const Dashboard = () => {
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
<Grid item lg={8} xs={12}>
|
||||
<StatisticalBarChart isLoading={isLoading} chartDatas={statisticalData} />
|
||||
<ApexCharts isLoading={isLoading} chartDatas={statisticalData} />
|
||||
</Grid>
|
||||
<Grid item lg={4} xs={12}>
|
||||
<UserCard>
|
||||
@@ -129,33 +129,33 @@ export default Dashboard;
|
||||
|
||||
function getLineDataGroup(statisticalData) {
|
||||
let groupedData = statisticalData.reduce((acc, cur) => {
|
||||
if (!acc[cur.Day]) {
|
||||
acc[cur.Day] = {
|
||||
date: cur.Day,
|
||||
if (!acc[cur.Date]) {
|
||||
acc[cur.Date] = {
|
||||
date: cur.Date,
|
||||
RequestCount: 0,
|
||||
Quota: 0,
|
||||
PromptTokens: 0,
|
||||
CompletionTokens: 0
|
||||
};
|
||||
}
|
||||
acc[cur.Day].RequestCount += cur.RequestCount;
|
||||
acc[cur.Day].Quota += cur.Quota;
|
||||
acc[cur.Day].PromptTokens += cur.PromptTokens;
|
||||
acc[cur.Day].CompletionTokens += cur.CompletionTokens;
|
||||
acc[cur.Date].RequestCount += cur.RequestCount;
|
||||
acc[cur.Date].Quota += cur.Quota;
|
||||
acc[cur.Date].PromptTokens += cur.PromptTokens;
|
||||
acc[cur.Date].CompletionTokens += cur.CompletionTokens;
|
||||
return acc;
|
||||
}, {});
|
||||
let lastSevenDays = getLastSevenDays();
|
||||
return lastSevenDays.map((day) => {
|
||||
if (!groupedData[day]) {
|
||||
return lastSevenDays.map((Date) => {
|
||||
if (!groupedData[Date]) {
|
||||
return {
|
||||
date: day,
|
||||
date: Date,
|
||||
RequestCount: 0,
|
||||
Quota: 0,
|
||||
PromptTokens: 0,
|
||||
CompletionTokens: 0
|
||||
};
|
||||
} else {
|
||||
return groupedData[day];
|
||||
return groupedData[Date];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -164,28 +164,26 @@ function getBarDataGroup(data) {
|
||||
const lastSevenDays = getLastSevenDays();
|
||||
const result = [];
|
||||
const map = new Map();
|
||||
let totalCosts = 0;
|
||||
|
||||
for (const item of data) {
|
||||
if (!map.has(item.ModelName)) {
|
||||
const newData = { name: item.ModelName, data: new Array(7) };
|
||||
const newData = { name: item.ModelName, data: new Array(7).fill(0) };
|
||||
map.set(item.ModelName, newData);
|
||||
result.push(newData);
|
||||
}
|
||||
const index = lastSevenDays.indexOf(item.Day);
|
||||
const index = lastSevenDays.indexOf(item.Date);
|
||||
if (index !== -1) {
|
||||
map.get(item.ModelName).data[index] = calculateQuota(item.Quota, 3);
|
||||
let costs = Number(calculateQuota(item.Quota, 3));
|
||||
map.get(item.ModelName).data[index] = costs;
|
||||
totalCosts += parseFloat(costs.toFixed(3));
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of result) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
if (item.data[i] === undefined) {
|
||||
item.data[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
let chartData = generateBarChartOptions(lastSevenDays, result, '美元', 3);
|
||||
chartData.options.title.text = '7日总消费:$' + renderChartNumber(totalCosts, 3);
|
||||
|
||||
return { data: result, xaxis: lastSevenDays };
|
||||
return chartData;
|
||||
}
|
||||
|
||||
function getLineCardOption(lineDataGroup, field) {
|
||||
@@ -214,15 +212,15 @@ function getLineCardOption(lineDataGroup, field) {
|
||||
|
||||
switch (field) {
|
||||
case 'RequestCount':
|
||||
chartData = generateChartOptions(lineData, '次');
|
||||
chartData = generateLineChartOptions(lineData, '次');
|
||||
todayValue = renderNumber(todayValue);
|
||||
break;
|
||||
case 'Quota':
|
||||
chartData = generateChartOptions(lineData, '美元');
|
||||
chartData = generateLineChartOptions(lineData, '美元');
|
||||
todayValue = '$' + renderNumber(todayValue);
|
||||
break;
|
||||
case 'PromptTokens':
|
||||
chartData = generateChartOptions(lineData, '');
|
||||
chartData = generateLineChartOptions(lineData, '');
|
||||
todayValue = renderNumber(todayValue);
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user