diff --git a/Makefile b/Makefile index 35b78d5..51e569a 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ all: build-frontend start-backend build-frontend: @echo "Building frontend..." - @cd $(FRONTEND_DIR) && yarn install --network-timeout 1000000 && DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) yarn build + @cd $(FRONTEND_DIR) && yarn install --network-timeout 1000000 && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) yarn build start-backend: @echo "Starting backend dev server..." diff --git a/relay/channel/gemini/adaptor.go b/relay/channel/gemini/adaptor.go index a275175..daaadc5 100644 --- a/relay/channel/gemini/adaptor.go +++ b/relay/channel/gemini/adaptor.go @@ -18,16 +18,28 @@ type Adaptor struct { func (a *Adaptor) Init(info *relaycommon.RelayInfo, request dto.GeneralOpenAIRequest) { } +// 定义一个映射,存储模型名称和对应的版本 +var modelVersionMap = map[string]string{ + "gemini-1.5-pro-latest": "v1beta", + "gemini-ultra": "v1beta", +} + func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { - version := "v1" - if info.ApiVersion != "" { - version = info.ApiVersion - } - action := "generateContent" - if info.IsStream { - action = "streamGenerateContent" - } - return fmt.Sprintf("%s/%s/models/%s:%s", info.BaseUrl, version, info.UpstreamModelName, action), nil + // 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1" + version, beta := modelVersionMap[info.UpstreamModelName] + if !beta { + if info.ApiVersion != "" { + version = info.ApiVersion + } else { + version = "v1" + } + } + + action := "generateContent" + if info.IsStream { + action = "streamGenerateContent" + } + return fmt.Sprintf("%s/%s/models/%s:%s", info.BaseUrl, version, info.UpstreamModelName, action), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error { diff --git a/relay/channel/gemini/constant.go b/relay/channel/gemini/constant.go index 582f4c0..5e833bc 100644 --- a/relay/channel/gemini/constant.go +++ b/relay/channel/gemini/constant.go @@ -5,8 +5,8 @@ const ( ) var ModelList = []string{ - "gemini-pro", "gemini-1.0-pro-001", "gemini-1.5-pro", - "gemini-pro-vision", "gemini-1.0-pro-vision-001", + "gemini-1.0-pro-latest", "gemini-1.0-pro-001", "gemini-1.5-pro-latest", "gemini-ultra", + "gemini-1.0-pro-vision-latest", "gemini-1.0-pro-vision-001", } var ChannelName = "google gemini" diff --git a/relay/channel/xunfei/relay-xunfei.go b/relay/channel/xunfei/relay-xunfei.go index da86002..1690e96 100644 --- a/relay/channel/xunfei/relay-xunfei.go +++ b/relay/channel/xunfei/relay-xunfei.go @@ -179,7 +179,13 @@ func xunfeiHandler(c *gin.Context, textRequest dto.GeneralOpenAIRequest, appId s case stop = <-stopChan: } } - + if len(xunfeiResponse.Payload.Choices.Text) == 0 { + xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{ + { + Content: "", + }, + } + } xunfeiResponse.Payload.Choices.Text[0].Content = content response := responseXunfei2OpenAI(&xunfeiResponse) diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 0d2ed57..3e3930c 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -254,6 +254,19 @@ const ChannelsTable = () => { > 编辑 + { + copySelectedChannel(record.id); + }} + > + + ), }, @@ -340,6 +353,33 @@ const ChannelsTable = () => { setLoading(false); }; + const copySelectedChannel = async (id) => { + const channelToCopy = channels.find( + (channel) => String(channel.id) === String(id), + ); + console.log(channelToCopy); + channelToCopy.name += '_复制'; + channelToCopy.created_time = null; + channelToCopy.balance = 0; + channelToCopy.used_quota = 0; + if (!channelToCopy) { + showError('渠道未找到,请刷新页面后重试。'); + return; + } + try { + const newChannel = { ...channelToCopy, id: undefined }; + const response = await API.post('/api/channel/', newChannel); + if (response.data.success) { + showSuccess('渠道复制成功'); + await refresh(); + } else { + showError(response.data.message); + } + } catch (error) { + showError('渠道复制失败: ' + error.message); + } + }; + const refresh = async () => { await loadChannels(activePage - 1, pageSize, idSort); }; diff --git a/web/src/components/HeaderBar.js b/web/src/components/HeaderBar.js index a5da4df..5510d42 100644 --- a/web/src/components/HeaderBar.js +++ b/web/src/components/HeaderBar.js @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { UserContext } from '../context/User'; +import { useSetTheme, useTheme } from '../context/Theme'; import { API, getLogo, getSystemName, showSuccess } from '../helpers'; import '../index.css'; @@ -34,10 +35,8 @@ const HeaderBar = () => { let navigate = useNavigate(); const [showSidebar, setShowSidebar] = useState(false); - const [dark, setDark] = useState(false); const systemName = getSystemName(); const logo = getLogo(); - var themeMode = localStorage.getItem('theme-mode'); const currentDate = new Date(); // enable fireworks on new year(1.1 and 2.9-2.24) const isNewYear = @@ -66,26 +65,19 @@ const HeaderBar = () => { }, 3000); }; + const theme = useTheme(); + const setTheme = useSetTheme(); + useEffect(() => { - if (themeMode === 'dark') { - switchMode(true); + if (theme === 'dark') { + document.body.setAttribute('theme-mode', 'dark'); } + if (isNewYear) { console.log('Happy New Year!'); } }, []); - const switchMode = (model) => { - const body = document.body; - if (!model) { - body.removeAttribute('theme-mode'); - localStorage.setItem('theme-mode', 'light'); - } else { - body.setAttribute('theme-mode', 'dark'); - localStorage.setItem('theme-mode', 'dark'); - } - setDark(model); - }; return ( <> @@ -132,9 +124,11 @@ const HeaderBar = () => { { + setTheme(checked); + }} /> {userState.user ? ( <> diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 14b70d7..7a112e5 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -8,6 +8,8 @@ import { verifyJSON, } from '../helpers'; +import { useTheme } from '../context/Theme'; + const OperationSetting = () => { let now = new Date(); let [inputs, setInputs] = useState({ @@ -77,6 +79,9 @@ const OperationSetting = () => { } }; + const theme = useTheme(); + const isDark = theme === 'dark'; + useEffect(() => { getOptions().then(); }, []); @@ -219,8 +224,10 @@ const OperationSetting = () => { return ( -
-
通用设置
+ +
+ 通用设置 +
{ 保存通用设置 -
绘图设置
+
+ 绘图设置 +
{ /> -
屏蔽词过滤设置
+
+ 屏蔽词过滤设置 +
{ 保存屏蔽词设置 -
日志设置
+
+ 日志设置 +
{ 清理历史日志 -
数据看板
+
+ 数据看板 +
{ />
-
监控设置
+
+ 监控设置 +
{ 保存监控设置 -
额度设置
+
+ 额度设置 +
{ 保存额度设置 -
倍率设置
+
+ 倍率设置 +
{ let [inputs, setInputs] = useState({ PasswordLoginEnabled: '', @@ -62,6 +64,9 @@ const SystemSetting = () => { const [showPasswordWarningModal, setShowPasswordWarningModal] = useState(false); + const theme = useTheme(); + const isDark = theme === 'dark'; + const getOptions = async () => { const res = await API.get('/api/option/'); const { success, message, data } = res.data; @@ -346,8 +351,10 @@ const SystemSetting = () => { return ( - -
通用设置
+ +
+ 通用设置 +
{ 更新服务器地址 -
+
支付设置(当前仅支持Stripe Checkout) 密钥、Webhook 等设置请 @@ -455,7 +462,9 @@ const SystemSetting = () => { /> -
配置登录注册
+
+ 配置登录注册 +
{ /> -
+
配置邮箱域名白名单 用以防止恶意用户利用临时邮箱批量注册 @@ -615,7 +624,7 @@ const SystemSetting = () => { 保存邮箱域名白名单设置 -
+
配置 SMTP 用以支持系统的邮件发送
@@ -674,7 +683,7 @@ const SystemSetting = () => { 保存 SMTP 设置 -
+
配置 GitHub OAuth App 用以支持通过 GitHub 进行登录注册, @@ -768,7 +777,7 @@ const SystemSetting = () => { 保存 LINUX DO OAuth 设置 -
+
配置 WeChat Server 用以支持通过微信进行登录注册, @@ -813,7 +822,9 @@ const SystemSetting = () => { 保存 WeChat Server 设置 -
配置 Telegram 登录
+
+ 配置 Telegram 登录 +
{ 保存 Telegram 登录设置 -
+
配置 Turnstile 用以支持用户校验, diff --git a/web/src/context/Theme/index.js b/web/src/context/Theme/index.js new file mode 100644 index 0000000..7654988 --- /dev/null +++ b/web/src/context/Theme/index.js @@ -0,0 +1,36 @@ +import { createContext, useCallback, useContext, useState } from 'react'; + +const ThemeContext = createContext(null); +export const useTheme = () => useContext(ThemeContext); + +const SetThemeContext = createContext(null); +export const useSetTheme = () => useContext(SetThemeContext); + +export const ThemeProvider = ({ children }) => { + const [theme, _setTheme] = useState(() => { + try { + return localStorage.getItem('theme-mode') || null; + } catch { + return null; + } + }); + + const setTheme = useCallback((input) => { + _setTheme(input ? 'dark' : 'light'); + + const body = document.body; + if (!input) { + body.removeAttribute('theme-mode'); + localStorage.setItem('theme-mode', 'light'); + } else { + body.setAttribute('theme-mode', 'dark'); + localStorage.setItem('theme-mode', 'dark'); + } + }, []); + + return ( + + {children} + + ); +}; diff --git a/web/src/index.js b/web/src/index.js index c73daef..94b2286 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -12,6 +12,7 @@ import 'react-toastify/dist/ReactToastify.css'; import { StatusProvider } from './context/Status'; import { Layout } from '@douyinfe/semi-ui'; import SiderBar from './components/SiderBar'; +import { ThemeProvider } from './context/Theme'; // initialization @@ -22,27 +23,29 @@ root.render( - - - - + -
- -
- - - - -
-
+ + + + +
+ +
+ + + + +
+
+
+
- -
+