mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-29 14:46:38 +08:00
337 lines
9.6 KiB
JavaScript
337 lines
9.6 KiB
JavaScript
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} md={6}>
|
||
<ApexCharts id="cost" isLoading={channelLoading} chartDatas={channelData?.costs || {}} title="消费统计" decimal={3} />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<ApexCharts id="token" isLoading={channelLoading} chartDatas={channelData?.tokens || {}} title="Tokens统计" unit="" />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<ApexCharts id="latency" isLoading={channelLoading} chartDatas={channelData?.latency || {}} title="平均延迟" unit="" />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<ApexCharts id="requests" isLoading={channelLoading} chartDatas={channelData?.requests || {}} title="请求数" unit="" />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<ApexCharts isLoading={redemptionLoading} chartDatas={redemptionData} title="兑换统计" />
|
||
</Grid>
|
||
<Grid item xs={12} md={6}>
|
||
<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
|
||
},
|
||
background: 'transparent'
|
||
};
|
||
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',
|
||
background: 'transparent'
|
||
},
|
||
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;
|
||
}
|