From 413d4f0a664e334aa821781fe7ff1722ff876fef Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Fri, 1 Mar 2024 21:57:52 +0800 Subject: [PATCH 01/12] feat: Add channel search by model field (close #72) --- controller/channel.go | 3 ++- model/channel.go | 34 ++++++++++++++++++++++------- web/src/components/ChannelsTable.js | 28 +++++++++++++++++------- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/controller/channel.go b/controller/channel.go index dd71259..b98af41 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -54,8 +54,9 @@ func FixChannelsAbilities(c *gin.Context) { func SearchChannels(c *gin.Context) { keyword := c.Query("keyword") group := c.Query("group") + modelKeyword := c.Query("model") //idSort, _ := strconv.ParseBool(c.Query("id_sort")) - channels, err := model.SearchChannels(keyword, group) + channels, err := model.SearchChannels(keyword, group, modelKeyword) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/model/channel.go b/model/channel.go index 96b3635..7c7b0d9 100644 --- a/model/channel.go +++ b/model/channel.go @@ -43,21 +43,39 @@ func GetAllChannels(startIdx int, num int, selectAll bool, idSort bool) ([]*Chan return channels, err } -func SearchChannels(keyword string, group string) (channels []*Channel, err error) { +func SearchChannels(keyword string, group string, model string) ([]*Channel, error) { + var channels []*Channel keyCol := "`key`" + groupCol := "`group`" + modelsCol := "`models`" + + // 如果是 PostgreSQL,使用双引号 if common.UsingPostgreSQL { keyCol = `"key"` + groupCol = `"group"` + modelsCol = `"models"` } + + // 构造基础查询 + baseQuery := DB.Model(&Channel{}).Omit(keyCol) + + // 构造WHERE子句 + var whereClause string + var args []interface{} if group != "" { - groupCol := "`group`" - if common.UsingPostgreSQL { - groupCol = `"group"` - } - err = DB.Omit("key").Where("(id = ? or name LIKE ? or "+keyCol+" = ?) and "+groupCol+" LIKE ?", common.String2Int(keyword), keyword+"%", keyword, "%"+group+"%").Find(&channels).Error + whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ?) AND " + groupCol + " LIKE ? AND " + modelsCol + " LIKE ?" + args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+group+"%", "%"+model+"%") } else { - err = DB.Omit("key").Where("id = ? or name LIKE ? or "+keyCol+" = ?", common.String2Int(keyword), keyword+"%", keyword).Find(&channels).Error + whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ?) AND " + modelsCol + " LIKE ?" + args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+model+"%") } - return channels, err + + // 执行查询 + err := baseQuery.Where(whereClause, args...).Find(&channels).Error + if err != nil { + return nil, err + } + return channels, nil } func GetChannelById(id int, selectAll bool) (*Channel, error) { diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index 9fdd597..26da9a3 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -257,6 +257,7 @@ const ChannelsTable = () => { const [idSort, setIdSort] = useState(false); const [searchKeyword, setSearchKeyword] = useState(''); const [searchGroup, setSearchGroup] = useState(''); + const [searchModel, setSearchModel] = useState(''); const [searching, setSearching] = useState(false); const [updatingBalance, setUpdatingBalance] = useState(false); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); @@ -440,15 +441,15 @@ const ChannelsTable = () => { } }; - const searchChannels = async (searchKeyword, searchGroup) => { - if (searchKeyword === '' && searchGroup === '') { + const searchChannels = async (searchKeyword, searchGroup, searchModel) => { + if (searchKeyword === '' && searchGroup === '' && searchModel === '') { // if keyword is blank, load files instead. await loadChannels(0, pageSize, idSort); setActivePage(1); return; } setSearching(true); - const res = await API.get(`/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}`); + const res = await API.get(`/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}`); const {success, message, data} = res.data; if (success) { setChannels(data); @@ -625,13 +626,12 @@ const ChannelsTable = () => { return ( <> -
{searchChannels(searchKeyword, searchGroup)}} labelPosition='left'> - + {searchChannels(searchKeyword, searchGroup, searchModel)}} labelPosition='left'>
{ setSearchKeyword(v.trim()) }} /> + { + setSearchModel(v.trim()) + }} + /> { setSearchGroup(v) - searchChannels(searchKeyword, v) + searchChannels(searchKeyword, v, searchModel) }}/> +
From 84cac72a453533b13523ce4699219e42144d6b0b Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Fri, 1 Mar 2024 22:31:08 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E6=99=BA?= =?UTF-8?q?=E8=B0=B1GLM-4V=E6=B5=81=E6=A8=A1=E5=BC=8F=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/channel/openai/adaptor.go | 4 ++-- relay/channel/openai/relay-openai.go | 4 ++-- relay/channel/{zhipu_v4 => zhipu_4v}/adaptor.go | 10 +++++++--- relay/channel/{zhipu_v4 => zhipu_4v}/constants.go | 4 ++-- relay/channel/{zhipu_v4 => zhipu_4v}/dto.go | 2 +- relay/channel/{zhipu_v4 => zhipu_4v}/relay-zhipu_v4.go | 2 +- relay/relay_adaptor.go | 4 ++-- 7 files changed, 17 insertions(+), 13 deletions(-) rename relay/channel/{zhipu_v4 => zhipu_4v}/adaptor.go (80%) rename relay/channel/{zhipu_v4 => zhipu_4v}/constants.go (58%) rename relay/channel/{zhipu_v4 => zhipu_4v}/dto.go (99%) rename relay/channel/{zhipu_v4 => zhipu_4v}/relay-zhipu_v4.go (99%) diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index f64b8a4..cf14aee 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -71,10 +71,10 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage *dto.Usage, err *dto.OpenAIErrorWithStatusCode) { if info.IsStream { var responseText string - err, responseText = openaiStreamHandler(c, resp, info.RelayMode) + err, responseText = OpenaiStreamHandler(c, resp, info.RelayMode) usage = service.ResponseText2Usage(responseText, info.UpstreamModelName, info.PromptTokens) } else { - err, usage = openaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName) + err, usage = OpenaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName) } return } diff --git a/relay/channel/openai/relay-openai.go b/relay/channel/openai/relay-openai.go index c0b6353..b0f3aa5 100644 --- a/relay/channel/openai/relay-openai.go +++ b/relay/channel/openai/relay-openai.go @@ -16,7 +16,7 @@ import ( "time" ) -func openaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*dto.OpenAIErrorWithStatusCode, string) { +func OpenaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*dto.OpenAIErrorWithStatusCode, string) { var responseTextBuilder strings.Builder scanner := bufio.NewScanner(resp.Body) scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { @@ -111,7 +111,7 @@ func openaiStreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*d return nil, responseTextBuilder.String() } -func openaiHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) { +func OpenaiHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) { var textResponse dto.TextResponse responseBody, err := io.ReadAll(resp.Body) if err != nil { diff --git a/relay/channel/zhipu_v4/adaptor.go b/relay/channel/zhipu_4v/adaptor.go similarity index 80% rename from relay/channel/zhipu_v4/adaptor.go rename to relay/channel/zhipu_4v/adaptor.go index 076c024..546b048 100644 --- a/relay/channel/zhipu_v4/adaptor.go +++ b/relay/channel/zhipu_4v/adaptor.go @@ -1,4 +1,4 @@ -package zhipu_v4 +package zhipu_4v import ( "errors" @@ -8,7 +8,9 @@ import ( "net/http" "one-api/dto" "one-api/relay/channel" + "one-api/relay/channel/openai" relaycommon "one-api/relay/common" + "one-api/service" ) type Adaptor struct { @@ -41,9 +43,11 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage *dto.Usage, err *dto.OpenAIErrorWithStatusCode) { if info.IsStream { - err, usage = zhipuStreamHandler(c, resp) + var responseText string + err, responseText = openai.OpenaiStreamHandler(c, resp, info.RelayMode) + usage = service.ResponseText2Usage(responseText, info.UpstreamModelName, info.PromptTokens) } else { - err, usage = zhipuHandler(c, resp) + err, usage = openai.OpenaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName) } return } diff --git a/relay/channel/zhipu_v4/constants.go b/relay/channel/zhipu_4v/constants.go similarity index 58% rename from relay/channel/zhipu_v4/constants.go rename to relay/channel/zhipu_4v/constants.go index 756629a..1b0b0cc 100644 --- a/relay/channel/zhipu_v4/constants.go +++ b/relay/channel/zhipu_4v/constants.go @@ -1,7 +1,7 @@ -package zhipu_v4 +package zhipu_4v var ModelList = []string{ "glm-4", "glm-4v", "glm-3-turbo", } -var ChannelName = "zhipu_v4" +var ChannelName = "zhipu_4v" diff --git a/relay/channel/zhipu_v4/dto.go b/relay/channel/zhipu_4v/dto.go similarity index 99% rename from relay/channel/zhipu_v4/dto.go rename to relay/channel/zhipu_4v/dto.go index b17a89f..4d86767 100644 --- a/relay/channel/zhipu_v4/dto.go +++ b/relay/channel/zhipu_4v/dto.go @@ -1,4 +1,4 @@ -package zhipu_v4 +package zhipu_4v import ( "one-api/dto" diff --git a/relay/channel/zhipu_v4/relay-zhipu_v4.go b/relay/channel/zhipu_4v/relay-zhipu_v4.go similarity index 99% rename from relay/channel/zhipu_v4/relay-zhipu_v4.go rename to relay/channel/zhipu_4v/relay-zhipu_v4.go index dde1b5f..af9b1d8 100644 --- a/relay/channel/zhipu_v4/relay-zhipu_v4.go +++ b/relay/channel/zhipu_4v/relay-zhipu_v4.go @@ -1,4 +1,4 @@ -package zhipu_v4 +package zhipu_4v import ( "bufio" diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go index 328b1e6..cc76270 100644 --- a/relay/relay_adaptor.go +++ b/relay/relay_adaptor.go @@ -11,7 +11,7 @@ import ( "one-api/relay/channel/tencent" "one-api/relay/channel/xunfei" "one-api/relay/channel/zhipu" - "one-api/relay/channel/zhipu_v4" + "one-api/relay/channel/zhipu_4v" "one-api/relay/constant" ) @@ -38,7 +38,7 @@ func GetAdaptor(apiType int) channel.Adaptor { case constant.APITypeZhipu: return &zhipu.Adaptor{} case constant.APITypeZhipu_v4: - return &zhipu_v4.Adaptor{} + return &zhipu_4v.Adaptor{} } return nil } From 2b076eaed299810db95f6ce5efc49f3c044e5321 Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Fri, 1 Mar 2024 22:39:42 +0800 Subject: [PATCH 03/12] fix: fix typo --- web/src/components/TokensTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index 4075feb..fdd1329 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -153,7 +153,7 @@ const TokensTable = () => { [ {node: 'item', key: 'next', disabled: !localStorage.getItem('chat_link'), name: 'ChatGPT Next Web', onClick: () => {onOpenLink('next', record.key)}}, {node: 'item', key: 'next-mj', disabled: !localStorage.getItem('chat_link2'), name: 'ChatGPT Web & Midjourney', onClick: () => {onOpenLink('next-mj', record.key)}}, - {node: 'item', key: 'ama', name: 'AMA 问天(BotGrem)', onClick: () => {onOpenLink('ama', record.key)}}, + {node: 'item', key: 'ama', name: 'AMA 问天(BotGem)', onClick: () => {onOpenLink('ama', record.key)}}, {node: 'item', key: 'opencat', name: 'OpenCat', onClick: () => {onOpenLink('opencat', record.key)}}, ] } From a8f0c5dab2dcca5dd1f9690dd0a1c6b12c622ebb Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Sat, 2 Mar 2024 02:12:02 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=E5=8F=AF=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=8A=98=E5=8F=A0=E4=BE=A7=E8=BE=B9=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constants.go | 1 + controller/misc.go | 1 + model/option.go | 5 ++- web/src/App.js | 40 +----------------- web/src/components/OperationSetting.js | 13 +++++- web/src/components/SiderBar.js | 58 ++++++++++++++++++++------ 6 files changed, 65 insertions(+), 53 deletions(-) diff --git a/common/constants.go b/common/constants.go index 26684e3..e11d7fe 100644 --- a/common/constants.go +++ b/common/constants.go @@ -29,6 +29,7 @@ var DrawingEnabled = true var DataExportEnabled = true var DataExportInterval = 5 // unit: minute var DataExportDefaultTime = "hour" // unit: minute +var DefaultCollapseSidebar = false // default value of collapse sidebar // Any options with "Secret", "Token" in its key won't be return by GetOptions diff --git a/controller/misc.go b/controller/misc.go index f99baa8..3ed3c5e 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -38,6 +38,7 @@ func GetStatus(c *gin.Context) { "enable_drawing": common.DrawingEnabled, "enable_data_export": common.DataExportEnabled, "data_export_default_time": common.DataExportDefaultTime, + "default_collapse_sidebar": common.DefaultCollapseSidebar, "enable_online_topup": common.PayAddress != "" && common.EpayId != "" && common.EpayKey != "", }, }) diff --git a/model/option.go b/model/option.go index a651b85..03c0230 100644 --- a/model/option.go +++ b/model/option.go @@ -82,6 +82,7 @@ func InitOptionMap() { common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes) common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval) common.OptionMap["DataExportDefaultTime"] = common.DataExportDefaultTime + common.OptionMap["DefaultCollapseSidebar"] = strconv.FormatBool(common.DefaultCollapseSidebar) common.OptionMapRWMutex.Unlock() loadOptionsFromDatabase() @@ -138,7 +139,7 @@ func updateOptionMap(key string, value string) (err error) { common.ImageDownloadPermission = intValue } } - if strings.HasSuffix(key, "Enabled") { + if strings.HasSuffix(key, "Enabled") || key == "DefaultCollapseSidebar" { boolValue := value == "true" switch key { case "PasswordRegisterEnabled": @@ -171,6 +172,8 @@ func updateOptionMap(key string, value string) (err error) { common.DrawingEnabled = boolValue case "DataExportEnabled": common.DataExportEnabled = boolValue + case "DefaultCollapseSidebar": + common.DefaultCollapseSidebar = boolValue } } switch key { diff --git a/web/src/App.js b/web/src/App.js index e980397..86458b0 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -29,7 +29,7 @@ const Home = lazy(() => import('./pages/Home')); const About = lazy(() => import('./pages/About')); function App() { const [userState, userDispatch] = useContext(UserContext); - const [statusState, statusDispatch] = useContext(StatusContext); + // const [statusState, statusDispatch] = useContext(StatusContext); const loadUser = () => { let user = localStorage.getItem('user'); @@ -38,47 +38,9 @@ function App() { userDispatch({ type: 'login', payload: data }); } }; - const loadStatus = async () => { - const res = await API.get('/api/status'); - const { success, data } = res.data; - if (success) { - localStorage.setItem('status', JSON.stringify(data)); - statusDispatch({ type: 'set', payload: data }); - localStorage.setItem('system_name', data.system_name); - localStorage.setItem('logo', data.logo); - localStorage.setItem('footer_html', data.footer_html); - localStorage.setItem('quota_per_unit', data.quota_per_unit); - localStorage.setItem('display_in_currency', data.display_in_currency); - localStorage.setItem('enable_drawing', data.enable_drawing); - localStorage.setItem('enable_data_export', data.enable_data_export); - localStorage.setItem('data_export_default_time', data.data_export_default_time); - if (data.chat_link) { - localStorage.setItem('chat_link', data.chat_link); - } else { - localStorage.removeItem('chat_link'); - } - if (data.chat_link2) { - localStorage.setItem('chat_link2', data.chat_link2); - } else { - localStorage.removeItem('chat_link2'); - } - // if ( - // data.version !== process.env.REACT_APP_VERSION && - // data.version !== 'v0.0.0' && - // process.env.REACT_APP_VERSION !== '' - // ) { - // showNotice( - // `新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面` - // ); - // } - } else { - showError('无法正常连接至服务器!'); - } - }; useEffect(() => { loadUser(); - loadStatus().then(); let systemName = getSystemName(); if (systemName) { document.title = systemName; diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index a90855f..1a3a4be 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -27,6 +27,7 @@ const OperationSetting = () => { DataExportEnabled: '', DataExportDefaultTime: 'hour', DataExportInterval: 5, + DefaultCollapseSidebar: '', // 默认折叠侧边栏 RetryTimes: 0 }); const [originInputs, setOriginInputs] = useState({}); @@ -65,6 +66,10 @@ const OperationSetting = () => { if (key.endsWith('Enabled')) { value = inputs[key] === 'true' ? 'false' : 'true'; } + if (key === 'DefaultCollapseSidebar') { + value = inputs[key] === 'true' ? 'false' : 'true'; + } + console.log(key, value) const res = await API.put('/api/option/', { key, value @@ -79,7 +84,7 @@ const OperationSetting = () => { }; const handleInputChange = async (e, {name, value}) => { - if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime') { + if (name.endsWith('Enabled') || name === 'DataExportInterval' || name === 'DataExportDefaultTime' || name === 'DefaultCollapseSidebar') { if (name === 'DataExportDefaultTime') { localStorage.setItem('data_export_default_time', value); } @@ -243,6 +248,12 @@ const OperationSetting = () => { name='DrawingEnabled' onChange={handleInputChange} /> + { submitConfig('general').then(); diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js index e36599b..0d72b97 100644 --- a/web/src/components/SiderBar.js +++ b/web/src/components/SiderBar.js @@ -1,8 +1,8 @@ -import React, {useContext, useMemo, useState} from 'react'; +import React, { useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import {Link, useNavigate} from 'react-router-dom'; import {UserContext} from '../context/User'; -import {API, getLogo, getSystemName, isAdmin, isMobile, showSuccess} from '../helpers'; +import { API, getLogo, getSystemName, isAdmin, isMobile, showError, showSuccess } from '../helpers'; import '../index.css'; import { @@ -24,11 +24,14 @@ import {Nav, Avatar, Dropdown, Layout} from '@douyinfe/semi-ui'; const SiderBar = () => { const [userState, userDispatch] = useContext(UserContext); + const defaultIsCollapsed = isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true'; + let navigate = useNavigate(); const [selectedKeys, setSelectedKeys] = useState(['home']); - const [showSidebar, setShowSidebar] = useState(false); const systemName = getSystemName(); const logo = getLogo(); + const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed); + const headerButtons = useMemo(() => [ { text: '首页', @@ -110,15 +113,41 @@ const SiderBar = () => { // } ], [localStorage.getItem('enable_data_export'), localStorage.getItem('enable_drawing'), localStorage.getItem('chat_link'), isAdmin()]); + const loadStatus = async () => { + const res = await API.get('/api/status'); + const { success, data } = res.data; + if (success) { + localStorage.setItem('status', JSON.stringify(data)); + // statusDispatch({ type: 'set', payload: data }); + localStorage.setItem('system_name', data.system_name); + localStorage.setItem('logo', data.logo); + localStorage.setItem('footer_html', data.footer_html); + localStorage.setItem('quota_per_unit', data.quota_per_unit); + localStorage.setItem('display_in_currency', data.display_in_currency); + localStorage.setItem('enable_drawing', data.enable_drawing); + localStorage.setItem('enable_data_export', data.enable_data_export); + localStorage.setItem('data_export_default_time', data.data_export_default_time); + localStorage.setItem('default_collapse_sidebar', data.default_collapse_sidebar); + if (data.chat_link) { + localStorage.setItem('chat_link', data.chat_link); + } else { + localStorage.removeItem('chat_link'); + } + if (data.chat_link2) { + localStorage.setItem('chat_link2', data.chat_link2); + } else { + localStorage.removeItem('chat_link2'); + } + } else { + showError('无法正常连接至服务器!'); + } + }; - async function logout() { - setShowSidebar(false); - await API.get('/api/user/logout'); - showSuccess('注销成功!'); - userDispatch({type: 'logout'}); - localStorage.removeItem('user'); - navigate('/login'); - } + useEffect(() => { + loadStatus().then(() => { + setIsCollapsed(isMobile() || localStorage.getItem('default_collapse_sidebar') === 'true'); + }); + },[]) return ( <> @@ -127,7 +156,12 @@ const SiderBar = () => {