♻️ refactor: Refactor chat links
This commit is contained in:
parent
c6a0c87ad1
commit
801b98d6fc
@ -15,6 +15,7 @@ var Footer = ""
|
|||||||
var Logo = ""
|
var Logo = ""
|
||||||
var TopUpLink = ""
|
var TopUpLink = ""
|
||||||
var ChatLink = ""
|
var ChatLink = ""
|
||||||
|
var ChatLinks = ""
|
||||||
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
|
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
|
||||||
var DisplayInCurrencyEnabled = true
|
var DisplayInCurrencyEnabled = true
|
||||||
var DisplayTokenStatEnabled = true
|
var DisplayTokenStatEnabled = true
|
||||||
|
@ -44,6 +44,7 @@ func GetStatus(c *gin.Context) {
|
|||||||
"telegram_bot": telegram_bot,
|
"telegram_bot": telegram_bot,
|
||||||
"mj_notify_enabled": common.MjNotifyEnabled,
|
"mj_notify_enabled": common.MjNotifyEnabled,
|
||||||
"chat_cache_enabled": common.ChatCacheEnabled,
|
"chat_cache_enabled": common.ChatCacheEnabled,
|
||||||
|
"chat_links": common.ChatLinks,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,39 @@ func GetToken(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetPlaygroundToken(c *gin.Context) {
|
||||||
|
tokenName := "sys_playground"
|
||||||
|
token, err := model.GetTokenByName(tokenName)
|
||||||
|
if err != nil {
|
||||||
|
cleanToken := model.Token{
|
||||||
|
UserId: c.GetInt("id"),
|
||||||
|
Name: tokenName,
|
||||||
|
Key: common.GenerateKey(),
|
||||||
|
CreatedTime: common.GetTimestamp(),
|
||||||
|
AccessedTime: common.GetTimestamp(),
|
||||||
|
ExpiredTime: 0,
|
||||||
|
RemainQuota: 0,
|
||||||
|
UnlimitedQuota: true,
|
||||||
|
ChatCache: false,
|
||||||
|
}
|
||||||
|
err = cleanToken.Insert()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "创建令牌失败,请去系统手动配置一个名称为:sys_playground 的令牌",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token = &cleanToken
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "",
|
||||||
|
"data": token.Key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func GetTokenStatus(c *gin.Context) {
|
func GetTokenStatus(c *gin.Context) {
|
||||||
tokenId := c.GetInt("token_id")
|
tokenId := c.GetInt("token_id")
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
|
@ -70,6 +70,7 @@ func InitOptionMap() {
|
|||||||
common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString()
|
common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString()
|
||||||
common.OptionMap["TopUpLink"] = common.TopUpLink
|
common.OptionMap["TopUpLink"] = common.TopUpLink
|
||||||
common.OptionMap["ChatLink"] = common.ChatLink
|
common.OptionMap["ChatLink"] = common.ChatLink
|
||||||
|
common.OptionMap["ChatLinks"] = common.ChatLinks
|
||||||
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
|
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
|
||||||
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
|
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
|
||||||
common.OptionMap["RetryCooldownSeconds"] = strconv.Itoa(common.RetryCooldownSeconds)
|
common.OptionMap["RetryCooldownSeconds"] = strconv.Itoa(common.RetryCooldownSeconds)
|
||||||
@ -166,6 +167,7 @@ var optionStringMap = map[string]*string{
|
|||||||
"TurnstileSecretKey": &common.TurnstileSecretKey,
|
"TurnstileSecretKey": &common.TurnstileSecretKey,
|
||||||
"TopUpLink": &common.TopUpLink,
|
"TopUpLink": &common.TopUpLink,
|
||||||
"ChatLink": &common.ChatLink,
|
"ChatLink": &common.ChatLink,
|
||||||
|
"ChatLinks": &common.ChatLinks,
|
||||||
"LarkClientId": &common.LarkClientId,
|
"LarkClientId": &common.LarkClientId,
|
||||||
"LarkClientSecret": &common.LarkClientSecret,
|
"LarkClientSecret": &common.LarkClientSecret,
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,16 @@ func GetTokenById(id int) (*Token, error) {
|
|||||||
return &token, err
|
return &token, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenByName(name string) (*Token, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, errors.New("name 为空!")
|
||||||
|
}
|
||||||
|
token := Token{Name: name}
|
||||||
|
var err error = nil
|
||||||
|
err = DB.First(&token, "name = ?", name).Error
|
||||||
|
return &token, err
|
||||||
|
}
|
||||||
|
|
||||||
func (token *Token) Insert() error {
|
func (token *Token) Insert() error {
|
||||||
if token.ChatCache && !common.ChatCacheEnabled {
|
if token.ChatCache && !common.ChatCacheEnabled {
|
||||||
token.ChatCache = false
|
token.ChatCache = false
|
||||||
|
@ -93,6 +93,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
tokenRoute := apiRouter.Group("/token")
|
tokenRoute := apiRouter.Group("/token")
|
||||||
tokenRoute.Use(middleware.UserAuth())
|
tokenRoute.Use(middleware.UserAuth())
|
||||||
{
|
{
|
||||||
|
tokenRoute.GET("/playground", controller.GetPlaygroundToken)
|
||||||
tokenRoute.GET("/", controller.GetUserTokensList)
|
tokenRoute.GET("/", controller.GetUserTokensList)
|
||||||
tokenRoute.GET("/:id", controller.GetToken)
|
tokenRoute.GET("/:id", controller.GetToken)
|
||||||
tokenRoute.POST("/", controller.AddToken)
|
tokenRoute.POST("/", controller.AddToken)
|
||||||
|
22
web/src/constants/chatLinks.js
Normal file
22
web/src/constants/chatLinks.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const CHAT_LINKS = [
|
||||||
|
{
|
||||||
|
name: 'ChatGPT Next',
|
||||||
|
url: 'https://app.nextchat.dev/#/?settings={"key":"{key}","url":"{server}"}',
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'chatgpt-web-midjourney-proxy',
|
||||||
|
url: 'https://vercel.ddaiai.com/#/?settings={"key":"{key}","url":"{server}"}',
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'AMA 问天',
|
||||||
|
url: 'ama://set-api-key?server={server}&key={key}',
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OpenCat',
|
||||||
|
url: 'opencat://team/join?domain={server}&token={key}',
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
];
|
@ -74,6 +74,11 @@ const Header = () => {
|
|||||||
<Button component={Link} variant="text" to="/" color={pathname === '/' ? 'primary' : 'inherit'}>
|
<Button component={Link} variant="text" to="/" color={pathname === '/' ? 'primary' : 'inherit'}>
|
||||||
首页
|
首页
|
||||||
</Button>
|
</Button>
|
||||||
|
{account.user && (
|
||||||
|
<Button component={Link} variant="text" to="/playground" color={pathname === '/playground' ? 'primary' : 'inherit'}>
|
||||||
|
Playground
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button component={Link} variant="text" to="/about" color={pathname === '/about' ? 'primary' : 'inherit'}>
|
<Button component={Link} variant="text" to="/about" color={pathname === '/about' ? 'primary' : 'inherit'}>
|
||||||
关于
|
关于
|
||||||
</Button>
|
</Button>
|
||||||
@ -134,6 +139,12 @@ const Header = () => {
|
|||||||
<ListItemText primary={<Typography variant="body2">首页</Typography>} />
|
<ListItemText primary={<Typography variant="body2">首页</Typography>} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|
||||||
|
{account.user && (
|
||||||
|
<ListItemButton component={Link} variant="text" to="/playground">
|
||||||
|
<ListItemText primary={<Typography variant="body2">Playground</Typography>} />
|
||||||
|
</ListItemButton>
|
||||||
|
)}
|
||||||
|
|
||||||
<ListItemButton component={Link} variant="text" to="/about">
|
<ListItemButton component={Link} variant="text" to="/about">
|
||||||
<ListItemText primary={<Typography variant="body2">关于</Typography>} />
|
<ListItemText primary={<Typography variant="body2">关于</Typography>} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
@ -13,7 +13,8 @@ import {
|
|||||||
IconBrandTelegram,
|
IconBrandTelegram,
|
||||||
IconReceipt2,
|
IconReceipt2,
|
||||||
IconBrush,
|
IconBrush,
|
||||||
IconBrandGithubCopilot
|
IconBrandGithubCopilot,
|
||||||
|
IconBallFootball
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
@ -31,7 +32,8 @@ const icons = {
|
|||||||
IconBrandTelegram,
|
IconBrandTelegram,
|
||||||
IconReceipt2,
|
IconReceipt2,
|
||||||
IconBrush,
|
IconBrush,
|
||||||
IconBrandGithubCopilot
|
IconBrandGithubCopilot,
|
||||||
|
IconBallFootball
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||||
@ -75,6 +77,14 @@ const panel = {
|
|||||||
icon: icons.IconKey,
|
icon: icons.IconKey,
|
||||||
breadcrumbs: false
|
breadcrumbs: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'playground',
|
||||||
|
title: 'Playground',
|
||||||
|
type: 'item',
|
||||||
|
url: '/panel/playground',
|
||||||
|
icon: icons.IconBallFootball,
|
||||||
|
breadcrumbs: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'log',
|
id: 'log',
|
||||||
title: '日志',
|
title: '日志',
|
||||||
|
@ -18,6 +18,7 @@ const Telegram = Loadable(lazy(() => import('views/Telegram')));
|
|||||||
const Pricing = Loadable(lazy(() => import('views/Pricing')));
|
const Pricing = Loadable(lazy(() => import('views/Pricing')));
|
||||||
const Midjourney = Loadable(lazy(() => import('views/Midjourney')));
|
const Midjourney = Loadable(lazy(() => import('views/Midjourney')));
|
||||||
const ModelPrice = Loadable(lazy(() => import('views/ModelPrice')));
|
const ModelPrice = Loadable(lazy(() => import('views/ModelPrice')));
|
||||||
|
const Playground = Loadable(lazy(() => import('views/Playground')));
|
||||||
|
|
||||||
// dashboard routing
|
// dashboard routing
|
||||||
const Dashboard = Loadable(lazy(() => import('views/Dashboard')));
|
const Dashboard = Loadable(lazy(() => import('views/Dashboard')));
|
||||||
@ -91,6 +92,10 @@ const MainRoutes = {
|
|||||||
{
|
{
|
||||||
path: 'model_price',
|
path: 'model_price',
|
||||||
element: <ModelPrice />
|
element: <ModelPrice />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'playground',
|
||||||
|
element: <Playground />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ const Home = Loadable(lazy(() => import('views/Home')));
|
|||||||
const About = Loadable(lazy(() => import('views/About')));
|
const About = Loadable(lazy(() => import('views/About')));
|
||||||
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
const NotFoundView = Loadable(lazy(() => import('views/Error')));
|
||||||
const Jump = Loadable(lazy(() => import('views/Jump')));
|
const Jump = Loadable(lazy(() => import('views/Jump')));
|
||||||
|
const Playground = Loadable(lazy(() => import('views/Playground')));
|
||||||
|
|
||||||
// ==============================|| AUTHENTICATION ROUTING ||============================== //
|
// ==============================|| AUTHENTICATION ROUTING ||============================== //
|
||||||
|
|
||||||
@ -61,6 +62,10 @@ const OtherRoutes = {
|
|||||||
{
|
{
|
||||||
path: '/jump',
|
path: '/jump',
|
||||||
element: <Jump />
|
element: <Jump />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/playground',
|
||||||
|
element: <Playground />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import { snackbarConstants } from 'constants/SnackbarConstants';
|
import { snackbarConstants } from 'constants/SnackbarConstants';
|
||||||
import { API } from './api';
|
import { API } from './api';
|
||||||
|
import { CHAT_LINKS } from 'constants/chatLinks';
|
||||||
|
|
||||||
export function getSystemName() {
|
export function getSystemName() {
|
||||||
let system_name = localStorage.getItem('system_name');
|
let system_name = localStorage.getItem('system_name');
|
||||||
@ -228,3 +229,35 @@ export function trims(values) {
|
|||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getChatLinks(filterShow = false) {
|
||||||
|
let links = [];
|
||||||
|
let siteInfo = JSON.parse(localStorage.getItem('siteInfo'));
|
||||||
|
let chatLinks = JSON.parse(siteInfo?.chat_links || '[]');
|
||||||
|
|
||||||
|
if (chatLinks.length === 0) {
|
||||||
|
links = CHAT_LINKS;
|
||||||
|
if (siteInfo?.chat_link) {
|
||||||
|
// 循环找到name为ChatGPT Next的链接
|
||||||
|
for (let i = 0; i < links.length; i++) {
|
||||||
|
if (links[i].name === 'ChatGPT Next') {
|
||||||
|
links[i].url = siteInfo.chat_link + `/#/?settings={"key":"sk-{key}","url":"{server}"}`;
|
||||||
|
links[i].show = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
links = chatLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterShow) {
|
||||||
|
links = links.filter((link) => link.show);
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceChatPlaceholders(text, key, server) {
|
||||||
|
return text.replace('{key}', key).replace('{server}', server);
|
||||||
|
}
|
||||||
|
109
web/src/views/Playground/index.js
Normal file
109
web/src/views/Playground/index.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
|
import { API } from 'utils/api';
|
||||||
|
import { getChatLinks, showError, replaceChatPlaceholders } from 'utils/common';
|
||||||
|
import { Typography, Tabs, Tab, Box, Card } from '@mui/material';
|
||||||
|
import SubCard from 'ui-component/cards/SubCard';
|
||||||
|
// import { Link } from 'react-router-dom';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
function TabPanel(props) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`playground-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`playground-tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography>{children}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TabPanel.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
index: PropTypes.number.isRequired,
|
||||||
|
value: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function a11yProps(index) {
|
||||||
|
return {
|
||||||
|
id: `playground-tab-${index}`,
|
||||||
|
'aria-controls': `playground-tabpanel-${index}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Playground = () => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const siteInfo = useSelector((state) => state.siteInfo);
|
||||||
|
const chatLinks = getChatLinks(true);
|
||||||
|
const [iframeSrc, setIframeSrc] = useState(null);
|
||||||
|
|
||||||
|
const loadTokens = useCallback(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await API.get(`/api/token/playground`);
|
||||||
|
const { success, message, data } = res.data;
|
||||||
|
if (success) {
|
||||||
|
setValue(data);
|
||||||
|
} else {
|
||||||
|
showError(message);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTabChange = useCallback(
|
||||||
|
(event, newIndex) => {
|
||||||
|
setTabIndex(newIndex);
|
||||||
|
let server = '';
|
||||||
|
if (siteInfo?.server_address) {
|
||||||
|
server = siteInfo.server_address;
|
||||||
|
} else {
|
||||||
|
server = window.location.host;
|
||||||
|
}
|
||||||
|
server = encodeURIComponent(server);
|
||||||
|
const key = 'sk-' + value;
|
||||||
|
|
||||||
|
setIframeSrc(replaceChatPlaceholders(chatLinks[newIndex].url, key, server));
|
||||||
|
},
|
||||||
|
[siteInfo, value, chatLinks]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadTokens().then(() => {
|
||||||
|
if (value !== '') {
|
||||||
|
handleTabChange(null, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [loadTokens, value]);
|
||||||
|
|
||||||
|
if (chatLinks.length === 0 || isLoading || value === '') {
|
||||||
|
return (
|
||||||
|
<SubCard title="Playground">
|
||||||
|
<Typography align="center">{isLoading ? 'Loading...' : 'No playground available'}</Typography>
|
||||||
|
</SubCard>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Tabs variant="scrollable" value={tabIndex} onChange={handleTabChange} sx={{ borderRight: 1, borderColor: 'divider' }}>
|
||||||
|
{chatLinks.map((link, index) => link.show && <Tab label={link.name} {...a11yProps(index)} key={index} />)}
|
||||||
|
</Tabs>
|
||||||
|
<Box>
|
||||||
|
<iframe title="playground" src={iframeSrc} style={{ width: '100%', height: '85vh', border: 'none' }} />
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Playground;
|
255
web/src/views/Setting/component/ChatLinksDataGrid.js
Normal file
255
web/src/views/Setting/component/ChatLinksDataGrid.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import { GridRowModes, DataGrid, GridToolbarContainer, GridActionsCellItem } from '@mui/x-data-grid';
|
||||||
|
import { Box, Button } from '@mui/material';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
|
||||||
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
|
import CancelIcon from '@mui/icons-material/Close';
|
||||||
|
import { showError } from 'utils/common';
|
||||||
|
|
||||||
|
function validation(row) {
|
||||||
|
if (row.name === '') {
|
||||||
|
return '名称不能为空';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.url === '') {
|
||||||
|
return 'URL不能为空';
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomId() {
|
||||||
|
return Math.random().toString(36).substr(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditToolbar({ setRows, setRowModesModel }) {
|
||||||
|
const handleClick = () => {
|
||||||
|
const id = randomId();
|
||||||
|
setRows((oldRows) => [{ id, name: '', url: '', show: true, isNew: true }, ...oldRows]);
|
||||||
|
setRowModesModel((oldModel) => ({
|
||||||
|
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
|
||||||
|
...oldModel
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridToolbarContainer>
|
||||||
|
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
|
||||||
|
新增
|
||||||
|
</Button>
|
||||||
|
</GridToolbarContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditToolbar.propTypes = {
|
||||||
|
setRows: PropTypes.func.isRequired,
|
||||||
|
setRowModesModel: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChatLinksDataGrid = ({ links, onChange }) => {
|
||||||
|
const [rows, setRows] = useState([]);
|
||||||
|
const [rowModesModel, setRowModesModel] = useState({});
|
||||||
|
|
||||||
|
const setLinks = useCallback(
|
||||||
|
(linksRow) => {
|
||||||
|
let linksJson = [];
|
||||||
|
// 删除 linksrow 中的 isNew 属性
|
||||||
|
linksRow.forEach((row) => {
|
||||||
|
let { isNew, ...rest } = row; // eslint-disable-line no-unused-vars
|
||||||
|
linksJson.push(rest);
|
||||||
|
});
|
||||||
|
onChange({ target: { name: 'ChatLinks', value: JSON.stringify(linksJson, null, 2) } });
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEditClick = useCallback(
|
||||||
|
(id) => () => {
|
||||||
|
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
|
||||||
|
},
|
||||||
|
[rowModesModel]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSaveClick = useCallback(
|
||||||
|
(id) => () => {
|
||||||
|
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
|
||||||
|
},
|
||||||
|
[rowModesModel]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(
|
||||||
|
(id) => () => {
|
||||||
|
setLinks(rows.filter((row) => row.id !== id));
|
||||||
|
},
|
||||||
|
[rows, setLinks]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCancelClick = useCallback(
|
||||||
|
(id) => () => {
|
||||||
|
setRowModesModel({
|
||||||
|
...rowModesModel,
|
||||||
|
[id]: { mode: GridRowModes.View, ignoreModifications: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const editedRow = rows.find((row) => row.id === id);
|
||||||
|
if (editedRow.isNew) {
|
||||||
|
setRows(rows.filter((row) => row.id !== id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[rowModesModel, rows]
|
||||||
|
);
|
||||||
|
|
||||||
|
const processRowUpdate = (newRow, oldRows) => {
|
||||||
|
if (!newRow.isNew && newRow.name === oldRows.name && newRow.url === oldRows.url && newRow.show === oldRows.show) {
|
||||||
|
return oldRows;
|
||||||
|
}
|
||||||
|
const updatedRow = { ...newRow, isNew: false };
|
||||||
|
const error = validation(updatedRow);
|
||||||
|
if (error) {
|
||||||
|
return Promise.reject(new Error(error));
|
||||||
|
}
|
||||||
|
setLinks(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
|
||||||
|
return updatedRow;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProcessRowUpdateError = useCallback((error) => {
|
||||||
|
showError(error.message);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRowModesModelChange = (newRowModesModel) => {
|
||||||
|
setRowModesModel(newRowModesModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
const modelRatioColumns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
sortable: true,
|
||||||
|
headerName: '名称',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 220,
|
||||||
|
editable: true,
|
||||||
|
hideable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'url',
|
||||||
|
sortable: false,
|
||||||
|
headerName: '链接',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 300,
|
||||||
|
editable: true,
|
||||||
|
hideable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'show',
|
||||||
|
sortable: false,
|
||||||
|
headerName: '是否显示在playground',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 200,
|
||||||
|
type: 'boolean',
|
||||||
|
editable: true,
|
||||||
|
hideable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'actions',
|
||||||
|
type: 'actions',
|
||||||
|
headerName: '操作',
|
||||||
|
width: 100,
|
||||||
|
cellClassName: 'actions',
|
||||||
|
hideable: false,
|
||||||
|
getActions: ({ id }) => {
|
||||||
|
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
|
||||||
|
|
||||||
|
if (isInEditMode) {
|
||||||
|
return [
|
||||||
|
<GridActionsCellItem
|
||||||
|
icon={<SaveIcon />}
|
||||||
|
key={'Save-' + id}
|
||||||
|
label="Save"
|
||||||
|
sx={{
|
||||||
|
color: 'primary.main'
|
||||||
|
}}
|
||||||
|
onClick={handleSaveClick(id)}
|
||||||
|
/>,
|
||||||
|
<GridActionsCellItem
|
||||||
|
icon={<CancelIcon />}
|
||||||
|
key={'Cancel-' + id}
|
||||||
|
label="Cancel"
|
||||||
|
className="textPrimary"
|
||||||
|
onClick={handleCancelClick(id)}
|
||||||
|
color="inherit"
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
<GridActionsCellItem
|
||||||
|
key={'Edit-' + id}
|
||||||
|
icon={<EditIcon />}
|
||||||
|
label="Edit"
|
||||||
|
className="textPrimary"
|
||||||
|
onClick={handleEditClick(id)}
|
||||||
|
color="inherit"
|
||||||
|
/>,
|
||||||
|
<GridActionsCellItem
|
||||||
|
key={'Delete-' + id}
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
label="Delete"
|
||||||
|
onClick={handleDeleteClick(id)}
|
||||||
|
color="inherit"
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[handleEditClick, handleSaveClick, handleDeleteClick, handleCancelClick, rowModesModel]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let itemJson = JSON.parse(links);
|
||||||
|
setRows(itemJson);
|
||||||
|
}, [links]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
'& .actions': {
|
||||||
|
color: 'text.secondary'
|
||||||
|
},
|
||||||
|
'& .textPrimary': {
|
||||||
|
color: 'text.primary'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataGrid
|
||||||
|
autoHeight
|
||||||
|
rows={rows}
|
||||||
|
columns={modelRatioColumns}
|
||||||
|
editMode="row"
|
||||||
|
hideFooter
|
||||||
|
disableRowSelectionOnClick
|
||||||
|
rowModesModel={rowModesModel}
|
||||||
|
onRowModesModelChange={handleRowModesModelChange}
|
||||||
|
processRowUpdate={processRowUpdate}
|
||||||
|
onProcessRowUpdateError={handleProcessRowUpdateError}
|
||||||
|
slots={{
|
||||||
|
toolbar: EditToolbar
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
toolbar: { setRows, setRowModesModel }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ChatLinksDataGrid.propTypes = {
|
||||||
|
links: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatLinksDataGrid;
|
@ -1,12 +1,14 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import SubCard from 'ui-component/cards/SubCard';
|
import SubCard from 'ui-component/cards/SubCard';
|
||||||
import { Stack, FormControl, InputLabel, OutlinedInput, Checkbox, Button, FormControlLabel, TextField } from '@mui/material';
|
import { Stack, FormControl, InputLabel, OutlinedInput, Checkbox, Button, FormControlLabel, TextField, Alert } from '@mui/material';
|
||||||
import { showSuccess, showError, verifyJSON } from 'utils/common';
|
import { showSuccess, showError, verifyJSON } from 'utils/common';
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
||||||
|
import ChatLinksDataGrid from './ChatLinksDataGrid';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { LoadStatusContext } from 'contexts/StatusContext';
|
||||||
require('dayjs/locale/zh-cn');
|
require('dayjs/locale/zh-cn');
|
||||||
|
|
||||||
const OperationSetting = () => {
|
const OperationSetting = () => {
|
||||||
@ -20,6 +22,7 @@ const OperationSetting = () => {
|
|||||||
GroupRatio: '',
|
GroupRatio: '',
|
||||||
TopUpLink: '',
|
TopUpLink: '',
|
||||||
ChatLink: '',
|
ChatLink: '',
|
||||||
|
ChatLinks: '',
|
||||||
QuotaPerUnit: 0,
|
QuotaPerUnit: 0,
|
||||||
AutomaticDisableChannelEnabled: '',
|
AutomaticDisableChannelEnabled: '',
|
||||||
AutomaticEnableChannelEnabled: '',
|
AutomaticEnableChannelEnabled: '',
|
||||||
@ -37,6 +40,7 @@ const OperationSetting = () => {
|
|||||||
const [originInputs, setOriginInputs] = useState({});
|
const [originInputs, setOriginInputs] = useState({});
|
||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
let [historyTimestamp, setHistoryTimestamp] = useState(now.getTime() / 1000 - 30 * 24 * 3600); // a month ago new Date().getTime() / 1000 + 3600
|
let [historyTimestamp, setHistoryTimestamp] = useState(now.getTime() / 1000 - 30 * 24 * 3600); // a month ago new Date().getTime() / 1000 + 3600
|
||||||
|
const loadStatus = useContext(LoadStatusContext);
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
try {
|
try {
|
||||||
@ -78,6 +82,8 @@ const OperationSetting = () => {
|
|||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setInputs((inputs) => ({ ...inputs, [key]: value }));
|
setInputs((inputs) => ({ ...inputs, [key]: value }));
|
||||||
|
getOptions();
|
||||||
|
await loadStatus();
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
@ -118,6 +124,15 @@ const OperationSetting = () => {
|
|||||||
await updateOption('GroupRatio', inputs.GroupRatio);
|
await updateOption('GroupRatio', inputs.GroupRatio);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'chatlinks':
|
||||||
|
if (originInputs['ChatLinks'] !== inputs.ChatLinks) {
|
||||||
|
if (!verifyJSON(inputs.ChatLinks)) {
|
||||||
|
showError('links不是合法的 JSON 字符串');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateOption('ChatLinks', inputs.ChatLinks);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'quota':
|
case 'quota':
|
||||||
if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
|
if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
|
||||||
await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
|
await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
|
||||||
@ -520,6 +535,39 @@ const OperationSetting = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</SubCard>
|
</SubCard>
|
||||||
|
|
||||||
|
<SubCard title="聊天链接设置">
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Alert severity="info">
|
||||||
|
配置聊天链接,该配置在令牌中的聊天生效以及首页的Playground中的聊天生效. <br />
|
||||||
|
链接中可以使{'{key}'}替换用户的令牌,{'{server}'}替换服务器地址。例如:
|
||||||
|
{'https://chat.oneapi.pro/#/?settings={"key":"sk-{key}","url":"{server}"}'}
|
||||||
|
<br />
|
||||||
|
如果未配置,会默认配置以下4个链接:
|
||||||
|
<br />
|
||||||
|
ChatGPT Next : {'https://chat.oneapi.pro/#/?settings={"key":"{key}","url":"{server}"}'}
|
||||||
|
<br />
|
||||||
|
chatgpt-web-midjourney-proxy : {'https://vercel.ddaiai.com/#/?settings={"key":"{key}","url":"{server}"}'}
|
||||||
|
<br />
|
||||||
|
AMA 问天 : {'ama://set-api-key?server={server}&key={key}'}
|
||||||
|
<br />
|
||||||
|
opencat : {'opencat://team/join?domain={server}&token={key}'}
|
||||||
|
<br />
|
||||||
|
</Alert>
|
||||||
|
<Stack justifyContent="flex-start" alignItems="flex-start" spacing={2}>
|
||||||
|
<ChatLinksDataGrid links={inputs.ChatLinks || '[]'} onChange={handleInputChange} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
submitConfig('chatlinks').then();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存聊天链接设置
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</SubCard>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import SubCard from 'ui-component/cards/SubCard';
|
import SubCard from 'ui-component/cards/SubCard';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
@ -19,6 +19,7 @@ import Grid from '@mui/material/Unstable_Grid2';
|
|||||||
import { showError, showSuccess } from 'utils/common'; //,
|
import { showError, showSuccess } from 'utils/common'; //,
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
import { LoadStatusContext } from 'contexts/StatusContext';
|
||||||
|
|
||||||
const OtherSetting = () => {
|
const OtherSetting = () => {
|
||||||
let [inputs, setInputs] = useState({
|
let [inputs, setInputs] = useState({
|
||||||
@ -35,6 +36,7 @@ const OtherSetting = () => {
|
|||||||
tag_name: '',
|
tag_name: '',
|
||||||
content: ''
|
content: ''
|
||||||
});
|
});
|
||||||
|
const loadStatus = useContext(LoadStatusContext);
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
try {
|
try {
|
||||||
@ -70,8 +72,9 @@ const OtherSetting = () => {
|
|||||||
});
|
});
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setInputs((inputs) => ({ ...inputs, [key]: value }));
|
|
||||||
showSuccess('保存成功');
|
showSuccess('保存成功');
|
||||||
|
getOptions();
|
||||||
|
await loadStatus();
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
import SubCard from 'ui-component/cards/SubCard';
|
import SubCard from 'ui-component/cards/SubCard';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
@ -21,6 +21,7 @@ import Grid from '@mui/material/Unstable_Grid2';
|
|||||||
import { showError, showSuccess, removeTrailingSlash } from 'utils/common'; //,
|
import { showError, showSuccess, removeTrailingSlash } from 'utils/common'; //,
|
||||||
import { API } from 'utils/api';
|
import { API } from 'utils/api';
|
||||||
import { createFilterOptions } from '@mui/material/Autocomplete';
|
import { createFilterOptions } from '@mui/material/Autocomplete';
|
||||||
|
import { LoadStatusContext } from 'contexts/StatusContext';
|
||||||
|
|
||||||
const filter = createFilterOptions();
|
const filter = createFilterOptions();
|
||||||
const SystemSetting = () => {
|
const SystemSetting = () => {
|
||||||
@ -56,6 +57,7 @@ const SystemSetting = () => {
|
|||||||
let [loading, setLoading] = useState(false);
|
let [loading, setLoading] = useState(false);
|
||||||
const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
|
const [EmailDomainWhitelist, setEmailDomainWhitelist] = useState([]);
|
||||||
const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
|
const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false);
|
||||||
|
const loadStatus = useContext(LoadStatusContext);
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
try {
|
try {
|
||||||
@ -116,6 +118,8 @@ const SystemSetting = () => {
|
|||||||
...inputs,
|
...inputs,
|
||||||
[key]: value
|
[key]: value
|
||||||
}));
|
}));
|
||||||
|
getOptions();
|
||||||
|
await loadStatus();
|
||||||
showSuccess('设置成功!');
|
showSuccess('设置成功!');
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
|
@ -20,25 +20,10 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import TableSwitch from 'ui-component/Switch';
|
import TableSwitch from 'ui-component/Switch';
|
||||||
import { renderQuota, timestamp2string, copy } from 'utils/common';
|
import { renderQuota, timestamp2string, copy, getChatLinks, replaceChatPlaceholders } from 'utils/common';
|
||||||
|
|
||||||
import { IconDotsVertical, IconEdit, IconTrash, IconCaretDownFilled } from '@tabler/icons-react';
|
import { IconDotsVertical, IconEdit, IconTrash, IconCaretDownFilled } from '@tabler/icons-react';
|
||||||
|
|
||||||
const COPY_OPTIONS = [
|
|
||||||
{
|
|
||||||
key: 'next',
|
|
||||||
text: 'ChatGPT Next',
|
|
||||||
url: 'https://chat.oneapi.pro/#/?settings={"key":"sk-{key}","url":"{serverAddress}"}',
|
|
||||||
encode: false
|
|
||||||
},
|
|
||||||
{ key: 'ama', text: 'AMA 问天', url: 'ama://set-api-key?server={serverAddress}&key=sk-{key}', encode: true },
|
|
||||||
{ key: 'opencat', text: 'OpenCat', url: 'opencat://team/join?domain={serverAddress}&token=sk-{key}', encode: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
function replacePlaceholders(text, key, serverAddress) {
|
|
||||||
return text.replace('{key}', key).replace('{serverAddress}', serverAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMenu(menuItems) {
|
function createMenu(menuItems) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -73,6 +58,7 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
|
|||||||
const [openDelete, setOpenDelete] = useState(false);
|
const [openDelete, setOpenDelete] = useState(false);
|
||||||
const [statusSwitch, setStatusSwitch] = useState(item.status);
|
const [statusSwitch, setStatusSwitch] = useState(item.status);
|
||||||
const siteInfo = useSelector((state) => state.siteInfo);
|
const siteInfo = useSelector((state) => state.siteInfo);
|
||||||
|
const chatLinks = getChatLinks();
|
||||||
|
|
||||||
const handleDeleteOpen = () => {
|
const handleDeleteOpen = () => {
|
||||||
handleCloseMenu();
|
handleCloseMenu();
|
||||||
@ -134,25 +120,19 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleCopy = (option, type) => {
|
const handleCopy = (option, type) => {
|
||||||
let serverAddress = '';
|
let server = '';
|
||||||
if (siteInfo?.server_address) {
|
if (siteInfo?.server_address) {
|
||||||
serverAddress = siteInfo.server_address;
|
server = siteInfo.server_address;
|
||||||
} else {
|
} else {
|
||||||
serverAddress = window.location.host;
|
server = window.location.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.encode) {
|
server = encodeURIComponent(server);
|
||||||
serverAddress = encodeURIComponent(serverAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = option.url;
|
let url = option.url;
|
||||||
|
|
||||||
if (option.key === 'next' && siteInfo?.chat_link) {
|
const key = 'sk-' + item.key;
|
||||||
url = siteInfo.chat_link + `/#/?settings={"key":"sk-{key}","url":"{serverAddress}"}`;
|
const text = replaceChatPlaceholders(url, key, server);
|
||||||
}
|
|
||||||
|
|
||||||
const key = item.key;
|
|
||||||
const text = replacePlaceholders(url, key, serverAddress);
|
|
||||||
if (type === 'link') {
|
if (type === 'link') {
|
||||||
window.open(text);
|
window.open(text);
|
||||||
} else {
|
} else {
|
||||||
@ -162,8 +142,8 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
|
|||||||
};
|
};
|
||||||
|
|
||||||
const copyItems = createMenu(
|
const copyItems = createMenu(
|
||||||
COPY_OPTIONS.map((option) => ({
|
chatLinks.map((option) => ({
|
||||||
text: option.text,
|
text: option.name,
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
onClick: () => handleCopy(option, 'copy'),
|
onClick: () => handleCopy(option, 'copy'),
|
||||||
color: undefined
|
color: undefined
|
||||||
@ -171,8 +151,8 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
|
|||||||
);
|
);
|
||||||
|
|
||||||
const linkItems = createMenu(
|
const linkItems = createMenu(
|
||||||
COPY_OPTIONS.map((option) => ({
|
chatLinks.map((option) => ({
|
||||||
text: option.text,
|
text: option.name,
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
onClick: () => handleCopy(option, 'link'),
|
onClick: () => handleCopy(option, 'link'),
|
||||||
color: undefined
|
color: undefined
|
||||||
@ -227,9 +207,9 @@ export default function TokensTableRow({ item, manageToken, handleOpenModal, set
|
|||||||
<IconCaretDownFilled size={'16px'} />
|
<IconCaretDownFilled size={'16px'} />
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup size="small" aria-label="split button">
|
<ButtonGroup size="small" onClick={(e) => handleOpenMenu(e, 'link')} aria-label="split button">
|
||||||
<Button color="primary">聊天</Button>
|
<Button color="primary">聊天</Button>
|
||||||
<Button size="small" onClick={(e) => handleOpenMenu(e, 'link')}>
|
<Button size="small">
|
||||||
<IconCaretDownFilled size={'16px'} />
|
<IconCaretDownFilled size={'16px'} />
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user