From 8f74fadf8ac7c90f69163e9340b7da7d4368cf9e Mon Sep 17 00:00:00 2001 From: MartialBE Date: Fri, 5 Apr 2024 17:31:52 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20user=20available=20mo?= =?UTF-8?q?del=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/menu-items/panel.js | 15 +- web/src/routes/MainRoutes.js | 5 + web/src/ui-component/TableNoData.js | 28 ++++ .../views/ModelPrice/component/TableRow.js | 39 +++++ web/src/views/ModelPrice/index.js | 136 ++++++++++++++++++ 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 web/src/ui-component/TableNoData.js create mode 100644 web/src/views/ModelPrice/component/TableRow.js create mode 100644 web/src/views/ModelPrice/index.js diff --git a/web/src/menu-items/panel.js b/web/src/menu-items/panel.js index 05b41c3f..795adc2b 100644 --- a/web/src/menu-items/panel.js +++ b/web/src/menu-items/panel.js @@ -12,7 +12,8 @@ import { IconActivity, IconBrandTelegram, IconReceipt2, - IconBrush + IconBrush, + IconBrandGithubCopilot } from '@tabler/icons-react'; // constant @@ -29,7 +30,8 @@ const icons = { IconActivity, IconBrandTelegram, IconReceipt2, - IconBrush + IconBrush, + IconBrandGithubCopilot }; // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -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: '设置', diff --git a/web/src/routes/MainRoutes.js b/web/src/routes/MainRoutes.js index 582fd1f0..52334952 100644 --- a/web/src/routes/MainRoutes.js +++ b/web/src/routes/MainRoutes.js @@ -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'))); @@ -86,6 +87,10 @@ const MainRoutes = { { path: 'midjourney', element: + }, + { + path: 'model_price', + element: } ] }; diff --git a/web/src/ui-component/TableNoData.js b/web/src/ui-component/TableNoData.js new file mode 100644 index 00000000..4d34e732 --- /dev/null +++ b/web/src/ui-component/TableNoData.js @@ -0,0 +1,28 @@ +import PropTypes from 'prop-types'; +import { Box, Typography, TableRow, TableCell } from '@mui/material'; + +const TableNoData = ({ message = '暂无数据' }) => { + return ( + + + + + {message} + + + + + ); +}; +export default TableNoData; + +TableNoData.propTypes = { + message: PropTypes.string +}; diff --git a/web/src/views/ModelPrice/component/TableRow.js b/web/src/views/ModelPrice/component/TableRow.js new file mode 100644 index 00000000..1ab37abc --- /dev/null +++ b/web/src/views/ModelPrice/component/TableRow.js @@ -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 ( + <> + + + + + + {item.type} + {item.channel_type} + + {item.input} + {item.output} + + + ); +} + +PricesTableRow.propTypes = { + item: PropTypes.object, + userModelList: PropTypes.object, + ownedby: PropTypes.array +}; diff --git a/web/src/views/ModelPrice/index.js b/web/src/views/ModelPrice/index.js new file mode 100644 index 00000000..f6bd7fe8 --- /dev/null +++ b/web/src/views/ModelPrice/index.js @@ -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 ( + <> + + + + + + + {rows.length === 0 ? ( + + ) : ( + rows.map((row) => ) + )} + +
+
+
+
+ + ); +}