Skip to content

Commit

Permalink
✨ feat: Add user available model list
Browse files Browse the repository at this point in the history
  • Loading branch information
MartialBE committed Apr 5, 2024
1 parent 3cf92da commit 8f74fad
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 2 deletions.
15 changes: 13 additions & 2 deletions web/src/menu-items/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
IconActivity,
IconBrandTelegram,
IconReceipt2,
IconBrush
IconBrush,
IconBrandGithubCopilot
} from '@tabler/icons-react';

// constant
Expand All @@ -29,7 +30,8 @@ const icons = {
IconActivity,
IconBrandTelegram,
IconReceipt2,
IconBrush
IconBrush,
IconBrandGithubCopilot
};

// ==============================|| DASHBOARD MENU ITEMS ||============================== //
Expand Down Expand Up @@ -133,6 +135,15 @@ const panel = {
breadcrumbs: false,
isAdmin: true
},
{
id: 'model_price',
title: '可用模型',
type: 'item',
url: '/panel/model_price',
icon: icons.IconBrandGithubCopilot,
breadcrumbs: false,
isAdmin: true
},
{
id: 'setting',
title: '设置',
Expand Down
5 changes: 5 additions & 0 deletions web/src/routes/MainRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const Analytics = Loadable(lazy(() => import('views/Analytics')));
const Telegram = Loadable(lazy(() => import('views/Telegram')));
const Pricing = Loadable(lazy(() => import('views/Pricing')));
const Midjourney = Loadable(lazy(() => import('views/Midjourney')));
const ModelPrice = Loadable(lazy(() => import('views/ModelPrice')));

// dashboard routing
const Dashboard = Loadable(lazy(() => import('views/Dashboard')));
Expand Down Expand Up @@ -86,6 +87,10 @@ const MainRoutes = {
{
path: 'midjourney',
element: <Midjourney />
},
{
path: 'model_price',
element: <ModelPrice />
}
]
};
Expand Down
28 changes: 28 additions & 0 deletions web/src/ui-component/TableNoData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PropTypes from 'prop-types';
import { Box, Typography, TableRow, TableCell } from '@mui/material';

const TableNoData = ({ message = '暂无数据' }) => {
return (
<TableRow>
<TableCell colSpan={1000}>
<Box
sx={{
minHeight: '490px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<Typography variant="h3" color={'#697586'}>
{message}
</Typography>
</Box>
</TableCell>
</TableRow>
);
};
export default TableNoData;

TableNoData.propTypes = {
message: PropTypes.string
};
39 changes: 39 additions & 0 deletions web/src/views/ModelPrice/component/TableRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import PropTypes from 'prop-types';

import { TableRow, TableCell } from '@mui/material';

import Label from 'ui-component/Label';
import { copy } from 'utils/common';

export default function PricesTableRow({ item }) {
return (
<>
<TableRow tabIndex={item.model}>
<TableCell>
<Label
variant="outlined"
color="primary"
key={item.model}
onClick={() => {
copy(item.model, '模型名称');
}}
>
{item.model}
</Label>
</TableCell>

<TableCell>{item.type}</TableCell>
<TableCell>{item.channel_type}</TableCell>

<TableCell>{item.input}</TableCell>
<TableCell>{item.output}</TableCell>
</TableRow>
</>
);
}

PricesTableRow.propTypes = {
item: PropTypes.object,
userModelList: PropTypes.object,
ownedby: PropTypes.array
};
136 changes: 136 additions & 0 deletions web/src/views/ModelPrice/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useState, useEffect, useCallback } from 'react';

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 { Card } from '@mui/material';
import PricesTableRow from './component/TableRow';
import TableNoData from 'ui-component/TableNoData';
import KeywordTableHead from 'ui-component/TableHead';
import { API } from 'utils/api';
import { showError } from 'utils/common';
import { ValueFormatter, priceType } from 'views/Pricing/component/util';

// ----------------------------------------------------------------------
export default function ModelPrice() {
const [rows, setRows] = useState([]);
const [userModelList, setUserModelList] = useState([]);
const [prices, setPrices] = useState({});
const [ownedby, setOwnedby] = useState([]);

const fetchOwnedby = useCallback(async () => {
try {
const res = await API.get('/api/ownedby');
const { success, message, data } = res.data;
if (success) {
let ownedbyList = [];
for (let key in data) {
ownedbyList.push({ value: parseInt(key), label: data[key] });
}
setOwnedby(ownedbyList);
} else {
showError(message);
}
} catch (error) {
console.error(error);
}
}, []);

const fetchPrices = useCallback(async () => {
try {
const res = await API.get('/api/prices');
const { success, message, data } = res.data;
if (success) {
let pricesObj = {};
data.forEach((price) => {
if (pricesObj[price.model] === undefined) {
pricesObj[price.model] = price;
}
});
setPrices(pricesObj);
} else {
showError(message);
}
} catch (error) {
console.error(error);
}
}, []);

const fetchUserModelList = useCallback(async () => {
try {
const res = await API.get('/api/user/models');
if (res === undefined) {
setUserModelList([]);
return;
}
setUserModelList(res.data.data);
} catch (error) {
console.error(error);
}
}, []);

useEffect(() => {
if (userModelList.length === 0 || Object.keys(prices).length === 0 || ownedby.length === 0) {
return;
}

let newRows = [];
userModelList.forEach((model) => {
const price = prices[model.id];
const type_label = priceType.find((pt) => pt.value === price?.type);
const channel_label = ownedby.find((ob) => ob.value === price?.channel_type);
newRows.push({
model: model.id,
type: type_label?.label || '未知',
channel_type: channel_label?.label || '未知',
input: ValueFormatter(price?.input || 30),
output: ValueFormatter(price?.output || 30)
});
});
setRows(newRows);
}, [userModelList, ownedby, prices]);

useEffect(() => {
const fetchData = async () => {
try {
await Promise.all([fetchOwnedby(), fetchUserModelList()]);
fetchPrices();
} catch (error) {
console.error(error);
}
};

fetchData();
}, [fetchOwnedby, fetchUserModelList, fetchPrices]);

return (
<>
<Card>
<PerfectScrollbar component="div">
<TableContainer sx={{ overflow: 'unset' }}>
<Table sx={{ minWidth: 800 }}>
<KeywordTableHead
headLabel={[
{ id: 'model', label: '模型名称', disableSort: true },
{ id: 'type', label: '类型', disableSort: true },
{ id: 'channel_type', label: '供应商', disableSort: true },
{ id: 'input', label: '输入(/1k tokens)', disableSort: true },
{ id: 'output', label: '输出(/1k tokens)', disableSort: true }
]}
/>
<TableBody>
{rows.length === 0 ? (
<TableNoData message="无可用模型" />
) : (
rows.map((row) => <PricesTableRow item={row} key={row.model} />)
)}
</TableBody>
</Table>
</TableContainer>
</PerfectScrollbar>
</Card>
</>
);
}

0 comments on commit 8f74fad

Please sign in to comment.