feat: add new theme berry (#860)

* feat: add theme berry

* docs: add development notes

* fix: fix blank page

* chore: update implementation

* fix: fix package.json

* chore: update ui copy

---------

Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
Buer
2024-01-07 14:20:07 +08:00
committed by GitHub
parent 6227eee5bc
commit 48989d4a0b
157 changed files with 13979 additions and 5 deletions

View File

@@ -0,0 +1,169 @@
import PropTypes from 'prop-types';
// material-ui
import { Grid, Typography } from '@mui/material';
// third-party
import Chart from 'react-apexcharts';
// project imports
import SkeletonTotalGrowthBarChart from 'ui-component/cards/Skeleton/TotalGrowthBarChart';
import MainCard from 'ui-component/cards/MainCard';
import { gridSpacing } from 'store/constant';
import { Box } from '@mui/material';
// ==============================|| DASHBOARD DEFAULT - TOTAL GROWTH BAR CHART ||============================== //
const StatisticalBarChart = ({ isLoading, chartDatas }) => {
chartData.options.xaxis.categories = chartDatas.xaxis;
chartData.series = chartDatas.data;
return (
<>
{isLoading ? (
<SkeletonTotalGrowthBarChart />
) : (
<MainCard>
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item>
<Typography variant="h3">统计</Typography>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
{chartData.series ? (
<Chart {...chartData} />
) : (
<Box
sx={{
minHeight: '490px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography variant="h3" color={'#697586'}>
暂无数据
</Typography>
</Box>
)}
</Grid>
</Grid>
</MainCard>
)}
</>
);
};
StatisticalBarChart.propTypes = {
isLoading: PropTypes.bool
};
export default StatisticalBarChart;
const chartData = {
height: 480,
type: 'bar',
options: {
colors: [
'#008FFB',
'#00E396',
'#FEB019',
'#FF4560',
'#775DD0',
'#55efc4',
'#81ecec',
'#74b9ff',
'#a29bfe',
'#00b894',
'#00cec9',
'#0984e3',
'#6c5ce7',
'#ffeaa7',
'#fab1a0',
'#ff7675',
'#fd79a8',
'#fdcb6e',
'#e17055',
'#d63031',
'#e84393'
],
chart: {
id: 'bar-chart',
stacked: true,
toolbar: {
show: true
},
zoom: {
enabled: true
}
},
responsive: [
{
breakpoint: 480,
options: {
legend: {
position: 'bottom',
offsetX: -10,
offsetY: 0
}
}
}
],
plotOptions: {
bar: {
horizontal: false,
columnWidth: '50%'
}
},
xaxis: {
type: 'category',
categories: []
},
legend: {
show: true,
fontSize: '14px',
fontFamily: `'Roboto', sans-serif`,
position: 'bottom',
offsetX: 20,
labels: {
useSeriesColors: false
},
markers: {
width: 16,
height: 16,
radius: 5
},
itemMargin: {
horizontal: 15,
vertical: 8
}
},
fill: {
type: 'solid'
},
dataLabels: {
enabled: false
},
grid: {
show: true
},
tooltip: {
theme: 'dark',
fixed: {
enabled: false
},
y: {
formatter: function (val) {
return '$' + val;
}
},
marker: {
show: false
}
}
},
series: []
};

View File

@@ -0,0 +1,99 @@
import PropTypes from 'prop-types';
// material-ui
import { styled, useTheme } from '@mui/material/styles';
import { Avatar, Box, List, ListItem, ListItemAvatar, ListItemText, Typography } from '@mui/material';
// project imports
import MainCard from 'ui-component/cards/MainCard';
import TotalIncomeCard from 'ui-component/cards/Skeleton/TotalIncomeCard';
// assets
import TableChartOutlinedIcon from '@mui/icons-material/TableChartOutlined';
// styles
const CardWrapper = styled(MainCard)(({ theme }) => ({
backgroundColor: theme.palette.primary.dark,
color: theme.palette.primary.light,
overflow: 'hidden',
position: 'relative',
'&:after': {
content: '""',
position: 'absolute',
width: 210,
height: 210,
background: `linear-gradient(210.04deg, ${theme.palette.primary[200]} -50.94%, rgba(144, 202, 249, 0) 83.49%)`,
borderRadius: '50%',
top: -30,
right: -180
},
'&:before': {
content: '""',
position: 'absolute',
width: 210,
height: 210,
background: `linear-gradient(140.9deg, ${theme.palette.primary[200]} -14.02%, rgba(144, 202, 249, 0) 77.58%)`,
borderRadius: '50%',
top: -160,
right: -130
}
}));
// ==============================|| DASHBOARD - TOTAL INCOME DARK CARD ||============================== //
const StatisticalCard = ({ isLoading }) => {
const theme = useTheme();
return (
<>
{isLoading ? (
<TotalIncomeCard />
) : (
<CardWrapper border={false} content={false}>
<Box sx={{ p: 2 }}>
<List sx={{ py: 0 }}>
<ListItem alignItems="center" disableGutters sx={{ py: 0 }}>
<ListItemAvatar>
<Avatar
variant="rounded"
sx={{
...theme.typography.commonAvatar,
...theme.typography.largeAvatar,
backgroundColor: theme.palette.primary[800],
color: '#fff'
}}
>
<TableChartOutlinedIcon fontSize="inherit" />
</Avatar>
</ListItemAvatar>
<ListItemText
sx={{
py: 0,
mt: 0.45,
mb: 0.45
}}
primary={
<Typography variant="h4" sx={{ color: '#fff' }}>
$203k
</Typography>
}
secondary={
<Typography variant="subtitle2" sx={{ color: 'primary.light', mt: 0.25 }}>
Total Income
</Typography>
}
/>
</ListItem>
</List>
</Box>
</CardWrapper>
)}
</>
);
};
StatisticalCard.propTypes = {
isLoading: PropTypes.bool
};
export default StatisticalCard;

View File

@@ -0,0 +1,122 @@
import PropTypes from 'prop-types';
// material-ui
import { useTheme, styled } from '@mui/material/styles';
import { Box, Grid, Typography } from '@mui/material';
// third-party
import Chart from 'react-apexcharts';
// project imports
import MainCard from 'ui-component/cards/MainCard';
import SkeletonTotalOrderCard from 'ui-component/cards/Skeleton/EarningCard';
const CardWrapper = styled(MainCard)(({ theme }) => ({
backgroundColor: theme.palette.primary.dark,
color: '#fff',
overflow: 'hidden',
position: 'relative',
'&>div': {
position: 'relative',
zIndex: 5
},
'&:after': {
content: '""',
position: 'absolute',
width: 210,
height: 210,
background: theme.palette.primary[800],
borderRadius: '50%',
zIndex: 1,
top: -85,
right: -95,
[theme.breakpoints.down('sm')]: {
top: -105,
right: -140
}
},
'&:before': {
content: '""',
position: 'absolute',
zIndex: 1,
width: 210,
height: 210,
background: theme.palette.primary[800],
borderRadius: '50%',
top: -125,
right: -15,
opacity: 0.5,
[theme.breakpoints.down('sm')]: {
top: -155,
right: -70
}
}
}));
// ==============================|| DASHBOARD - TOTAL ORDER LINE CHART CARD ||============================== //
const StatisticalLineChartCard = ({ isLoading, title, chartData, todayValue }) => {
const theme = useTheme();
return (
<>
{isLoading ? (
<SkeletonTotalOrderCard />
) : (
<CardWrapper border={false} content={false}>
<Box sx={{ p: 2.25 }}>
<Grid container direction="column">
<Grid item sx={{ mb: 0.75 }}>
<Grid container alignItems="center">
<Grid item xs={6}>
<Grid container alignItems="center">
<Grid item>
<Typography sx={{ fontSize: '2.125rem', fontWeight: 500, mr: 1, mt: 1.75, mb: 0.75 }}>
{todayValue || '0'}
</Typography>
</Grid>
<Grid item></Grid>
<Grid item xs={12}>
<Typography
sx={{
fontSize: '1rem',
fontWeight: 500,
color: theme.palette.primary[200]
}}
>
{title}
</Typography>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
{chartData ? (
<Chart {...chartData} />
) : (
<Typography
sx={{
fontSize: '1rem',
fontWeight: 500,
color: theme.palette.primary[200]
}}
>
无数据
</Typography>
)}
</Grid>
</Grid>
</Grid>
</Grid>
</Box>
</CardWrapper>
)}
</>
);
};
StatisticalLineChartCard.propTypes = {
isLoading: PropTypes.bool,
title: PropTypes.string
};
export default StatisticalLineChartCard;

View File

@@ -0,0 +1,218 @@
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 { generateChartOptions, getLastSevenDays } from 'utils/chart';
import { API } from 'utils/api';
import { showError, calculateQuota, renderNumber } from 'utils/common';
import UserCard from 'ui-component/cards/UserCard';
const Dashboard = () => {
const [isLoading, setLoading] = useState(true);
const [statisticalData, setStatisticalData] = useState([]);
const [requestChart, setRequestChart] = useState(null);
const [quotaChart, setQuotaChart] = useState(null);
const [tokenChart, setTokenChart] = useState(null);
const [users, setUsers] = useState([]);
const userDashboard = async () => {
const res = await API.get('/api/user/dashboard');
const { success, message, data } = res.data;
if (success) {
if (data) {
let lineData = getLineDataGroup(data);
setRequestChart(getLineCardOption(lineData, 'RequestCount'));
setQuotaChart(getLineCardOption(lineData, 'Quota'));
setTokenChart(getLineCardOption(lineData, 'PromptTokens'));
setStatisticalData(getBarDataGroup(data));
}
} else {
showError(message);
}
setLoading(false);
};
const loadUser = async () => {
let res = await API.get(`/api/user/self`);
const { success, message, data } = res.data;
if (success) {
setUsers(data);
} else {
showError(message);
}
};
useEffect(() => {
userDashboard();
loadUser();
}, []);
return (
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<Grid container spacing={gridSpacing}>
<Grid item lg={4} xs={12}>
<StatisticalLineChartCard
isLoading={isLoading}
title="今日请求量"
chartData={requestChart?.chartData}
todayValue={requestChart?.todayValue}
/>
</Grid>
<Grid item lg={4} xs={12}>
<StatisticalLineChartCard
isLoading={isLoading}
title="今日消费"
chartData={quotaChart?.chartData}
todayValue={quotaChart?.todayValue}
/>
</Grid>
<Grid item lg={4} xs={12}>
<StatisticalLineChartCard
isLoading={isLoading}
title="今日 token"
chartData={tokenChart?.chartData}
todayValue={tokenChart?.todayValue}
/>
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={gridSpacing}>
<Grid item lg={8} xs={12}>
<StatisticalBarChart isLoading={isLoading} chartDatas={statisticalData} />
</Grid>
<Grid item lg={4} xs={12}>
<UserCard>
<Grid container spacing={gridSpacing} justifyContent="center" alignItems="center" paddingTop={'20px'}>
<Grid item xs={4}>
<Typography variant="h4">余额</Typography>
</Grid>
<Grid item xs={8}>
<Typography variant="h3"> {users?.quota ? '$' + calculateQuota(users.quota) : '未知'}</Typography>
</Grid>
<Grid item xs={4}>
<Typography variant="h4">已使用</Typography>
</Grid>
<Grid item xs={8}>
<Typography variant="h3"> {users?.used_quota ? '$' + calculateQuota(users.used_quota) : '未知'}</Typography>
</Grid>
<Grid item xs={4}>
<Typography variant="h4">调用次数</Typography>
</Grid>
<Grid item xs={8}>
<Typography variant="h3"> {users?.request_count || '未知'}</Typography>
</Grid>
</Grid>
</UserCard>
</Grid>
</Grid>
</Grid>
</Grid>
);
};
export default Dashboard;
function getLineDataGroup(statisticalData) {
let groupedData = statisticalData.reduce((acc, cur) => {
if (!acc[cur.Day]) {
acc[cur.Day] = {
date: cur.Day,
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;
return acc;
}, {});
let lastSevenDays = getLastSevenDays();
return lastSevenDays.map((day) => {
if (!groupedData[day]) {
return {
date: day,
RequestCount: 0,
Quota: 0,
PromptTokens: 0,
CompletionTokens: 0
};
} else {
return groupedData[day];
}
});
}
function getBarDataGroup(data) {
const lastSevenDays = getLastSevenDays();
const result = [];
const map = new Map();
for (const item of data) {
if (!map.has(item.ModelName)) {
const newData = { name: item.ModelName, data: new Array(7) };
map.set(item.ModelName, newData);
result.push(newData);
}
const index = lastSevenDays.indexOf(item.Day);
if (index !== -1) {
map.get(item.ModelName).data[index] = calculateQuota(item.Quota, 3);
}
}
for (const item of result) {
for (let i = 0; i < 7; i++) {
if (item.data[i] === undefined) {
item.data[i] = 0;
}
}
}
return { data: result, xaxis: lastSevenDays };
}
function getLineCardOption(lineDataGroup, field) {
let todayValue = 0;
let chartData = null;
const lastItem = lineDataGroup.length - 1;
let lineData = lineDataGroup.map((item, index) => {
let tmp = {
date: item.date,
value: item[field]
};
switch (field) {
case 'Quota':
tmp.value = calculateQuota(item.Quota, 3);
break;
case 'PromptTokens':
tmp.value += item.CompletionTokens;
break;
}
if (index == lastItem) {
todayValue = tmp.value;
}
return tmp;
});
switch (field) {
case 'RequestCount':
chartData = generateChartOptions(lineData, '次');
todayValue = renderNumber(todayValue);
break;
case 'Quota':
chartData = generateChartOptions(lineData, '美元');
todayValue = '$' + renderNumber(todayValue);
break;
case 'PromptTokens':
chartData = generateChartOptions(lineData, '');
todayValue = renderNumber(todayValue);
break;
}
return { chartData: chartData, todayValue: todayValue };
}