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) => )
+ )}
+
+
+
+
+
+ >
+ );
+}