From 801b98d6fc917957f802e591007b11a1b6801a00 Mon Sep 17 00:00:00 2001 From: Martial BE Date: Sat, 27 Apr 2024 20:50:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Refactor=20cha?= =?UTF-8?q?t=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constants.go | 1 + controller/misc.go | 1 + controller/token.go | 33 +++ model/option.go | 2 + model/token.go | 10 + router/api-router.go | 1 + web/src/constants/chatLinks.js | 22 ++ web/src/layout/MinimalLayout/Header/index.js | 11 + web/src/menu-items/panel.js | 14 +- web/src/routes/MainRoutes.js | 5 + web/src/routes/OtherRoutes.js | 5 + web/src/utils/common.js | 33 +++ web/src/views/Playground/index.js | 109 ++++++++ .../Setting/component/ChatLinksDataGrid.js | 255 ++++++++++++++++++ .../Setting/component/OperationSetting.js | 52 +++- .../views/Setting/component/OtherSetting.js | 7 +- .../views/Setting/component/SystemSetting.js | 6 +- web/src/views/Token/component/TableRow.js | 48 +--- 18 files changed, 574 insertions(+), 41 deletions(-) create mode 100644 web/src/constants/chatLinks.js create mode 100644 web/src/views/Playground/index.js create mode 100644 web/src/views/Setting/component/ChatLinksDataGrid.js diff --git a/common/constants.go b/common/constants.go index af24cdb5..2a3cf446 100644 --- a/common/constants.go +++ b/common/constants.go @@ -15,6 +15,7 @@ var Footer = "" var Logo = "" var TopUpLink = "" var ChatLink = "" +var ChatLinks = "" var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens var DisplayInCurrencyEnabled = true var DisplayTokenStatEnabled = true diff --git a/controller/misc.go b/controller/misc.go index 3c3af82e..c30bcb9c 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -44,6 +44,7 @@ func GetStatus(c *gin.Context) { "telegram_bot": telegram_bot, "mj_notify_enabled": common.MjNotifyEnabled, "chat_cache_enabled": common.ChatCacheEnabled, + "chat_links": common.ChatLinks, }, }) } diff --git a/controller/token.go b/controller/token.go index 67a576be..efbcac5d 100644 --- a/controller/token.go +++ b/controller/token.go @@ -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) { tokenId := c.GetInt("token_id") userId := c.GetInt("id") diff --git a/model/option.go b/model/option.go index 8ce6166a..9d9fcf26 100644 --- a/model/option.go +++ b/model/option.go @@ -70,6 +70,7 @@ func InitOptionMap() { common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["ChatLink"] = common.ChatLink + common.OptionMap["ChatLinks"] = common.ChatLinks common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64) common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes) common.OptionMap["RetryCooldownSeconds"] = strconv.Itoa(common.RetryCooldownSeconds) @@ -166,6 +167,7 @@ var optionStringMap = map[string]*string{ "TurnstileSecretKey": &common.TurnstileSecretKey, "TopUpLink": &common.TopUpLink, "ChatLink": &common.ChatLink, + "ChatLinks": &common.ChatLinks, "LarkClientId": &common.LarkClientId, "LarkClientSecret": &common.LarkClientSecret, } diff --git a/model/token.go b/model/token.go index c07e0548..f3a85ee8 100644 --- a/model/token.go +++ b/model/token.go @@ -115,6 +115,16 @@ func GetTokenById(id int) (*Token, error) { 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 { if token.ChatCache && !common.ChatCacheEnabled { token.ChatCache = false diff --git a/router/api-router.go b/router/api-router.go index 1aaf7373..dcc8dc08 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -93,6 +93,7 @@ func SetApiRouter(router *gin.Engine) { tokenRoute := apiRouter.Group("/token") tokenRoute.Use(middleware.UserAuth()) { + tokenRoute.GET("/playground", controller.GetPlaygroundToken) tokenRoute.GET("/", controller.GetUserTokensList) tokenRoute.GET("/:id", controller.GetToken) tokenRoute.POST("/", controller.AddToken) diff --git a/web/src/constants/chatLinks.js b/web/src/constants/chatLinks.js new file mode 100644 index 00000000..88febc77 --- /dev/null +++ b/web/src/constants/chatLinks.js @@ -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 + } +]; diff --git a/web/src/layout/MinimalLayout/Header/index.js b/web/src/layout/MinimalLayout/Header/index.js index feaeb603..c8df5854 100644 --- a/web/src/layout/MinimalLayout/Header/index.js +++ b/web/src/layout/MinimalLayout/Header/index.js @@ -74,6 +74,11 @@ const Header = () => { + {account.user && ( + + )} @@ -134,6 +139,12 @@ const Header = () => { 首页} /> + {account.user && ( + + Playground} /> + + )} + 关于} /> diff --git a/web/src/menu-items/panel.js b/web/src/menu-items/panel.js index b97a48e8..ff6181c9 100644 --- a/web/src/menu-items/panel.js +++ b/web/src/menu-items/panel.js @@ -13,7 +13,8 @@ import { IconBrandTelegram, IconReceipt2, IconBrush, - IconBrandGithubCopilot + IconBrandGithubCopilot, + IconBallFootball } from '@tabler/icons-react'; // constant @@ -31,7 +32,8 @@ const icons = { IconBrandTelegram, IconReceipt2, IconBrush, - IconBrandGithubCopilot + IconBrandGithubCopilot, + IconBallFootball }; // ==============================|| DASHBOARD MENU ITEMS ||============================== // @@ -75,6 +77,14 @@ const panel = { icon: icons.IconKey, breadcrumbs: false }, + { + id: 'playground', + title: 'Playground', + type: 'item', + url: '/panel/playground', + icon: icons.IconBallFootball, + breadcrumbs: false + }, { id: 'log', title: '日志', diff --git a/web/src/routes/MainRoutes.js b/web/src/routes/MainRoutes.js index 52334952..ef72ce69 100644 --- a/web/src/routes/MainRoutes.js +++ b/web/src/routes/MainRoutes.js @@ -18,6 +18,7 @@ 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'))); +const Playground = Loadable(lazy(() => import('views/Playground'))); // dashboard routing const Dashboard = Loadable(lazy(() => import('views/Dashboard'))); @@ -91,6 +92,10 @@ const MainRoutes = { { path: 'model_price', element: + }, + { + path: 'playground', + element: } ] }; diff --git a/web/src/routes/OtherRoutes.js b/web/src/routes/OtherRoutes.js index 479e9ad8..c7d6e1f8 100644 --- a/web/src/routes/OtherRoutes.js +++ b/web/src/routes/OtherRoutes.js @@ -15,6 +15,7 @@ const Home = Loadable(lazy(() => import('views/Home'))); const About = Loadable(lazy(() => import('views/About'))); const NotFoundView = Loadable(lazy(() => import('views/Error'))); const Jump = Loadable(lazy(() => import('views/Jump'))); +const Playground = Loadable(lazy(() => import('views/Playground'))); // ==============================|| AUTHENTICATION ROUTING ||============================== // @@ -61,6 +62,10 @@ const OtherRoutes = { { path: '/jump', element: + }, + { + path: '/playground', + element: } ] }; diff --git a/web/src/utils/common.js b/web/src/utils/common.js index b15a5a0e..459bb54e 100644 --- a/web/src/utils/common.js +++ b/web/src/utils/common.js @@ -1,6 +1,7 @@ import { enqueueSnackbar } from 'notistack'; import { snackbarConstants } from 'constants/SnackbarConstants'; import { API } from './api'; +import { CHAT_LINKS } from 'constants/chatLinks'; export function getSystemName() { let system_name = localStorage.getItem('system_name'); @@ -228,3 +229,35 @@ export function trims(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); +} diff --git a/web/src/views/Playground/index.js b/web/src/views/Playground/index.js new file mode 100644 index 00000000..e83c65bb --- /dev/null +++ b/web/src/views/Playground/index.js @@ -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 ( + + ); +} + +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 ( + + {isLoading ? 'Loading...' : 'No playground available'} + + ); + } else { + return ( + + + {chatLinks.map((link, index) => link.show && )} + + +