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 ( {dateRange.start.format('YYYY-MM-DD')} - {dateRange.end.format('YYYY-MM-DD')} ); } 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; }