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,294 @@
import { useState, useEffect } from 'react';
import { showError, showSuccess, showInfo } from 'utils/common';
import { useTheme } from '@mui/material/styles';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableContainer from '@mui/material/TableContainer';
import PerfectScrollbar from 'react-perfect-scrollbar';
import TablePagination from '@mui/material/TablePagination';
import LinearProgress from '@mui/material/LinearProgress';
import Alert from '@mui/material/Alert';
import ButtonGroup from '@mui/material/ButtonGroup';
import Toolbar from '@mui/material/Toolbar';
import useMediaQuery from '@mui/material/useMediaQuery';
import { Button, IconButton, Card, Box, Stack, Container, Typography, Divider } from '@mui/material';
import ChannelTableRow from './component/TableRow';
import ChannelTableHead from './component/TableHead';
import TableToolBar from 'ui-component/TableToolBar';
import { API } from 'utils/api';
import { ITEMS_PER_PAGE } from 'constants';
import { IconRefresh, IconHttpDelete, IconPlus, IconBrandSpeedtest, IconCoinYuan } from '@tabler/icons-react';
import EditeModal from './component/EditModal';
// ----------------------------------------------------------------------
// CHANNEL_OPTIONS,
export default function ChannelPage() {
const [channels, setChannels] = useState([]);
const [activePage, setActivePage] = useState(0);
const [searching, setSearching] = useState(false);
const [searchKeyword, setSearchKeyword] = useState('');
const theme = useTheme();
const matchUpMd = useMediaQuery(theme.breakpoints.up('sm'));
const [openModal, setOpenModal] = useState(false);
const [editChannelId, setEditChannelId] = useState(0);
const loadChannels = async (startIdx) => {
setSearching(true);
const res = await API.get(`/api/channel/?p=${startIdx}`);
const { success, message, data } = res.data;
if (success) {
if (startIdx === 0) {
setChannels(data);
} else {
let newChannels = [...channels];
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
setChannels(newChannels);
}
} else {
showError(message);
}
setSearching(false);
};
const onPaginationChange = (event, activePage) => {
(async () => {
if (activePage === Math.ceil(channels.length / ITEMS_PER_PAGE)) {
// In this case we have to load more data and then append them.
await loadChannels(activePage);
}
setActivePage(activePage);
})();
};
const searchChannels = async (event) => {
event.preventDefault();
if (searchKeyword === '') {
await loadChannels(0);
setActivePage(0);
return;
}
setSearching(true);
const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
const { success, message, data } = res.data;
if (success) {
setChannels(data);
setActivePage(0);
} else {
showError(message);
}
setSearching(false);
};
const handleSearchKeyword = (event) => {
setSearchKeyword(event.target.value);
};
const manageChannel = async (id, action, value) => {
const url = '/api/channel/';
let data = { id };
let res;
switch (action) {
case 'delete':
res = await API.delete(url + id);
break;
case 'status':
res = await API.put(url, {
...data,
status: value
});
break;
case 'priority':
if (value === '') {
return;
}
res = await API.put(url, {
...data,
priority: parseInt(value)
});
break;
case 'test':
res = await API.get(url + `test/${id}`);
break;
}
const { success, message } = res.data;
if (success) {
showSuccess('操作成功完成!');
if (action === 'delete') {
await handleRefresh();
}
} else {
showError(message);
}
return res.data;
};
// 处理刷新
const handleRefresh = async () => {
await loadChannels(activePage);
};
// 处理测试所有启用渠道
const testAllChannels = async () => {
const res = await API.get(`/api/channel/test`);
const { success, message } = res.data;
if (success) {
showInfo('已成功开始测试所有通道,请刷新页面查看结果。');
} else {
showError(message);
}
};
// 处理删除所有禁用渠道
const deleteAllDisabledChannels = async () => {
const res = await API.delete(`/api/channel/disabled`);
const { success, message, data } = res.data;
if (success) {
showSuccess(`已删除所有禁用渠道,共计 ${data}`);
await handleRefresh();
} else {
showError(message);
}
};
// 处理更新所有启用渠道余额
const updateAllChannelsBalance = async () => {
setSearching(true);
const res = await API.get(`/api/channel/update_balance`);
const { success, message } = res.data;
if (success) {
showInfo('已更新完毕所有已启用通道余额!');
} else {
showError(message);
}
setSearching(false);
};
const handleOpenModal = (channelId) => {
setEditChannelId(channelId);
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
setEditChannelId(0);
};
const handleOkModal = (status) => {
if (status === true) {
handleCloseModal();
handleRefresh();
}
};
useEffect(() => {
loadChannels(0)
.then()
.catch((reason) => {
showError(reason);
});
}, []);
return (
<>
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={5}>
<Typography variant="h4">渠道</Typography>
<Button variant="contained" color="primary" startIcon={<IconPlus />} onClick={() => handleOpenModal(0)}>
新建渠道
</Button>
</Stack>
<Stack mb={5}>
<Alert severity="info">
当前版本测试是通过按照 OpenAI API 格式使用 gpt-3.5-turbo
模型进行非流式请求实现的因此测试报错并不一定代表通道不可用该功能后续会修复 另外OpenAI 渠道已经不再支持通过 key
获取余额因此余额显示为 0对于支持的渠道类型请点击余额进行刷新
</Alert>
</Stack>
<Card>
<Box component="form" onSubmit={searchChannels} noValidate>
<TableToolBar filterName={searchKeyword} handleFilterName={handleSearchKeyword} placeholder={'搜索渠道的 ID名称和密钥 ...'} />
</Box>
<Toolbar
sx={{
textAlign: 'right',
height: 50,
display: 'flex',
justifyContent: 'space-between',
p: (theme) => theme.spacing(0, 1, 0, 3)
}}
>
<Container>
{matchUpMd ? (
<ButtonGroup variant="outlined" aria-label="outlined small primary button group">
<Button onClick={handleRefresh} startIcon={<IconRefresh width={'18px'} />}>
刷新
</Button>
<Button onClick={testAllChannels} startIcon={<IconBrandSpeedtest width={'18px'} />}>
测试启用渠道
</Button>
<Button onClick={updateAllChannelsBalance} startIcon={<IconCoinYuan width={'18px'} />}>
更新启用余额
</Button>
<Button onClick={deleteAllDisabledChannels} startIcon={<IconHttpDelete width={'18px'} />}>
删除禁用渠道
</Button>
</ButtonGroup>
) : (
<Stack
direction="row"
spacing={1}
divider={<Divider orientation="vertical" flexItem />}
justifyContent="space-around"
alignItems="center"
>
<IconButton onClick={handleRefresh} size="large">
<IconRefresh />
</IconButton>
<IconButton onClick={testAllChannels} size="large">
<IconBrandSpeedtest />
</IconButton>
<IconButton onClick={updateAllChannelsBalance} size="large">
<IconCoinYuan />
</IconButton>
<IconButton onClick={deleteAllDisabledChannels} size="large">
<IconHttpDelete />
</IconButton>
</Stack>
)}
</Container>
</Toolbar>
{searching && <LinearProgress />}
<PerfectScrollbar component="div">
<TableContainer sx={{ overflow: 'unset' }}>
<Table sx={{ minWidth: 800 }}>
<ChannelTableHead />
<TableBody>
{channels.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
<ChannelTableRow
item={row}
manageChannel={manageChannel}
key={row.id}
handleOpenModal={handleOpenModal}
setModalChannelId={setEditChannelId}
/>
))}
</TableBody>
</Table>
</TableContainer>
</PerfectScrollbar>
<TablePagination
page={activePage}
component="div"
count={channels.length + (channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)}
rowsPerPage={ITEMS_PER_PAGE}
onPageChange={onPaginationChange}
rowsPerPageOptions={[ITEMS_PER_PAGE]}
/>
</Card>
<EditeModal open={openModal} onCancel={handleCloseModal} onOk={handleOkModal} channelId={editChannelId} />
</>
);
}