From 0ce600ed49a9b079735207b58a4e051c6d3d66d4 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Tue, 19 Nov 2024 01:13:18 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=E6=B8=A0=E9=81=93=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel.go | 98 ++- model/ability.go | 32 +- model/channel.go | 54 ++ router/api-router.go | 3 + web/src/components/ChannelsTable.js | 961 +++++++++++++++----------- web/src/components/TextInput.js | 21 + web/src/helpers/render.js | 2 + web/src/pages/Channel/EditChannel.js | 401 ++++++----- web/src/pages/Channel/EditTagModal.js | 92 +++ 9 files changed, 1069 insertions(+), 595 deletions(-) create mode 100644 web/src/components/TextInput.js create mode 100644 web/src/pages/Channel/EditTagModal.js diff --git a/controller/channel.go b/controller/channel.go index 78525950..ff8ee5ee 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -57,10 +57,24 @@ func GetAllChannels(c *gin.Context) { }) return } + tags := make(map[string]bool) + channelData := make([]*model.Channel, 0, len(channels)) + for _, channel := range channels { + channelTag := channel.GetTag() + if channelTag != "" && !tags[channelTag] { + tags[channelTag] = true + tagChannels, err := model.GetChannelsByTag(channelTag) + if err == nil { + channelData = append(channelData, tagChannels...) + } + } else { + channelData = append(channelData, channel) + } + } c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": channels, + "data": channelData, }) return } @@ -279,6 +293,88 @@ func DeleteDisabledChannel(c *gin.Context) { return } +type ChannelTag struct { + Tag string `json:"tag"` + NewTag *string `json:"newTag"` + Priority *int64 `json:"priority"` + Weight *uint `json:"weight"` +} + +func DisableTagChannels(c *gin.Context) { + channelTag := ChannelTag{} + err := c.ShouldBindJSON(&channelTag) + if err != nil || channelTag.Tag == "" { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "参数错误", + }) + return + } + err = model.DisableChannelByTag(channelTag.Tag) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + }) + return +} + +func EnableTagChannels(c *gin.Context) { + channelTag := ChannelTag{} + err := c.ShouldBindJSON(&channelTag) + if err != nil || channelTag.Tag == "" { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "参数错误", + }) + return + } + err = model.EnableChannelByTag(channelTag.Tag) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + }) + return +} + +func EditTagChannels(c *gin.Context) { + channelTag := ChannelTag{} + err := c.ShouldBindJSON(&channelTag) + if err != nil || channelTag.Tag == "" { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "参数错误", + }) + return + } + err = model.EditChannelByTag(channelTag.Tag, channelTag.NewTag, channelTag.Priority, channelTag.Weight) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + }) + return +} + type ChannelBatch struct { Ids []int `json:"ids"` } diff --git a/model/ability.go b/model/ability.go index 115ceb19..8e084cf9 100644 --- a/model/ability.go +++ b/model/ability.go @@ -10,12 +10,13 @@ import ( ) type Ability struct { - Group string `json:"group" gorm:"type:varchar(64);primaryKey;autoIncrement:false"` - Model string `json:"model" gorm:"type:varchar(64);primaryKey;autoIncrement:false"` - ChannelId int `json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"` - Enabled bool `json:"enabled"` - Priority *int64 `json:"priority" gorm:"bigint;default:0;index"` - Weight uint `json:"weight" gorm:"default:0;index"` + Group string `json:"group" gorm:"type:varchar(64);primaryKey;autoIncrement:false"` + Model string `json:"model" gorm:"type:varchar(64);primaryKey;autoIncrement:false"` + ChannelId int `json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"` + Enabled bool `json:"enabled"` + Priority *int64 `json:"priority" gorm:"bigint;default:0;index"` + Weight uint `json:"weight" gorm:"default:0;index"` + Tag *string `json:"tag" gorm:"index"` } func GetGroupModels(group string) []string { @@ -149,6 +150,7 @@ func (channel *Channel) AddAbilities() error { Enabled: channel.Status == common.ChannelStatusEnabled, Priority: channel.Priority, Weight: uint(channel.GetWeight()), + Tag: channel.Tag, } abilities = append(abilities, ability) } @@ -190,6 +192,24 @@ func UpdateAbilityStatus(channelId int, status bool) error { return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Select("enabled").Update("enabled", status).Error } +func UpdateAbilityStatusByTag(tag string, status bool) error { + return DB.Model(&Ability{}).Where("tag = ?", tag).Select("enabled").Update("enabled", status).Error +} + +func UpdateAbilityByTag(tag string, newTag *string, priority *int64, weight *uint) error { + ability := Ability{} + if newTag != nil { + ability.Tag = newTag + } + if priority != nil { + ability.Priority = priority + } + if weight != nil { + ability.Weight = *weight + } + return DB.Model(&Ability{}).Where("tag = ?", tag).Updates(ability).Error +} + func FixAbility() (int, error) { var channelIds []int count := 0 diff --git a/model/channel.go b/model/channel.go index 34aae68a..99ccd893 100644 --- a/model/channel.go +++ b/model/channel.go @@ -32,6 +32,7 @@ type Channel struct { Priority *int64 `json:"priority" gorm:"bigint;default:0"` AutoBan *int `json:"auto_ban" gorm:"default:1"` OtherInfo string `json:"other_info"` + Tag *string `json:"tag" gorm:"index"` } func (channel *Channel) GetModels() []string { @@ -61,6 +62,17 @@ func (channel *Channel) SetOtherInfo(otherInfo map[string]interface{}) { channel.OtherInfo = string(otherInfoBytes) } +func (channel *Channel) GetTag() string { + if channel.Tag == nil { + return "" + } + return *channel.Tag +} + +func (channel *Channel) SetTag(tag string) { + channel.Tag = &tag +} + func (channel *Channel) GetAutoBan() bool { if channel.AutoBan == nil { return false @@ -87,6 +99,12 @@ func GetAllChannels(startIdx int, num int, selectAll bool, idSort bool) ([]*Chan return channels, err } +func GetChannelsByTag(tag string) ([]*Channel, error) { + var channels []*Channel + err := DB.Where("tag = ?", tag).Find(&channels).Error + return channels, err +} + func SearchChannels(keyword string, group string, model string) ([]*Channel, error) { var channels []*Channel keyCol := "`key`" @@ -288,6 +306,42 @@ func UpdateChannelStatusById(id int, status int, reason string) { } +func EnableChannelByTag(tag string) error { + err := DB.Model(&Channel{}).Where("tag = ?", tag).Update("status", common.ChannelStatusEnabled).Error + if err != nil { + return err + } + err = UpdateAbilityStatusByTag(tag, true) + return err +} + +func DisableChannelByTag(tag string) error { + err := DB.Model(&Channel{}).Where("tag = ?", tag).Update("status", common.ChannelStatusManuallyDisabled).Error + if err != nil { + return err + } + err = UpdateAbilityStatusByTag(tag, false) + return err +} + +func EditChannelByTag(tag string, newTag *string, priority *int64, weight *uint) error { + updateData := Channel{} + if newTag != nil { + updateData.Tag = newTag + } + if priority != nil { + updateData.Priority = priority + } + if weight != nil { + updateData.Weight = weight + } + err := DB.Model(&Channel{}).Where("tag = ?", tag).Updates(updateData).Error + if err != nil { + return err + } + return UpdateAbilityByTag(tag, newTag, priority, weight) +} + func UpdateChannelUsedQuota(id int, quota int) { if common.BatchUpdateEnabled { addNewRecord(BatchUpdateTypeChannelUsedQuota, id, quota) diff --git a/router/api-router.go b/router/api-router.go index 3b7eb36a..81a1341b 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -91,6 +91,9 @@ func SetApiRouter(router *gin.Engine) { channelRoute.POST("/", controller.AddChannel) channelRoute.PUT("/", controller.UpdateChannel) channelRoute.DELETE("/disabled", controller.DeleteDisabledChannel) + channelRoute.POST("/tag/disabled", controller.DisableTagChannels) + channelRoute.POST("/tag/enabled", controller.EnableTagChannels) + channelRoute.PUT("/tag", controller.EditTagChannels) channelRoute.DELETE("/:id", controller.DeleteChannel) channelRoute.POST("/batch", controller.DeleteChannelBatch) channelRoute.POST("/fix", controller.FixChannelsAbilities) diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index d09f34fa..9be5e57a 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -7,14 +7,14 @@ import { showInfo, showSuccess, showWarning, - timestamp2string, + timestamp2string } from '../helpers'; import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants'; import { renderGroup, renderNumberWithPoint, - renderQuota, + renderQuota } from '../helpers/render'; import { Button, Divider, @@ -28,11 +28,12 @@ import { Table, Tag, Tooltip, - Typography, + Typography } from '@douyinfe/semi-ui'; import EditChannel from '../pages/Channel/EditChannel'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; import { loadChannelModels } from './utils.js'; +import EditTagModal from '../pages/Channel/EditTagModal.js'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; @@ -49,7 +50,7 @@ function renderType(type) { type2label[0] = { value: 0, text: '未知类型', color: 'grey' }; } return ( - + {type2label[type]?.text} ); @@ -64,11 +65,11 @@ const ChannelsTable = () => { // }, { title: 'ID', - dataIndex: 'id', + dataIndex: 'id' }, { title: '名称', - dataIndex: 'name', + dataIndex: 'name' }, { title: '分组', @@ -77,20 +78,20 @@ const ChannelsTable = () => { return (
- {text.split(',').map((item, index) => { + {text?.split(',').map((item, index) => { return renderGroup(item); })}
); - }, + } }, { title: '类型', dataIndex: 'type', render: (text, record, index) => { return
{renderType(text)}
; - }, + } }, { title: '状态', @@ -98,7 +99,7 @@ const ChannelsTable = () => { render: (text, record, index) => { if (text === 3) { if (record.other_info === '') { - record.other_info = '{}' + record.other_info = '{}'; } let otherInfo = JSON.parse(record.other_info); let reason = otherInfo['status_reason']; @@ -113,181 +114,246 @@ const ChannelsTable = () => { } else { return renderStatus(text); } - }, + } }, { title: '响应时间', dataIndex: 'response_time', render: (text, record, index) => { return
{renderResponseTime(text)}
; - }, + } }, { title: '已用/剩余', dataIndex: 'expired_time', render: (text, record, index) => { - return ( -
- - - - {renderQuota(record.used_quota)} - - - - { - updateChannelBalance(record); - }} - > - ${renderNumberWithPoint(record.balance)} - - - -
- ); - }, + if (record.children === undefined) { + return ( +
+ + + + {renderQuota(record.used_quota)} + + + + { + updateChannelBalance(record); + }} + > + ${renderNumberWithPoint(record.balance)} + + + +
+ ); + } else { + return + + {renderQuota(record.used_quota)} + + ; + } + } }, { title: '优先级', dataIndex: 'priority', render: (text, record, index) => { - return ( -
- { - manageChannel(record.id, 'priority', record, e.target.value); - }} - keepFocus={true} - innerButtons - defaultValue={record.priority} - min={-999} - /> -
- ); - }, + if (record.children === undefined) { + return ( +
+ { + manageChannel(record.id, 'priority', record, e.target.value); + }} + keepFocus={true} + innerButtons + defaultValue={record.priority} + min={-999} + /> +
+ ); + } else { + return <> + + ; + } + } }, { title: '权重', dataIndex: 'weight', render: (text, record, index) => { - return ( -
- { - manageChannel(record.id, 'weight', record, e.target.value); - }} - keepFocus={true} - innerButtons - defaultValue={record.weight} - min={0} - /> -
- ); - }, + if (record.children === undefined) { + return ( +
+ { + manageChannel(record.id, 'weight', record, e.target.value); + }} + keepFocus={true} + innerButtons + defaultValue={record.weight} + min={0} + /> +
+ ); + } else { + return ( + + ); + } + } }, { title: '', dataIndex: 'operate', - render: (text, record, index) => ( -
- - - + render: (text, record, index) => { + if (record.children === undefined) { + return ( +
+ + + + + + + {/**/} + { + manageChannel(record.id, 'delete', record).then(() => { + removeRecord(record.id); + }); + }} + > + + + {record.status === 1 ? ( + + ) : ( + + )} - - - {/**/} - { - manageChannel(record.id, 'delete', record).then(() => { - removeRecord(record.id); - }); - }} - > - - - {record.status === 1 ? ( - - ) : ( - - )} - - { - copySelectedChannel(record.id); - }} - > - - -
- ), - }, + theme="light" + type="tertiary" + style={{ marginRight: 1 }} + onClick={() => { + setEditingChannel(record); + setShowEdit(true); + }} + > + 编辑 + + { + copySelectedChannel(record.id); + }} + > + + +
+ ); + } else { + return ( + <> + + + + + ); + } + } + } ]; const [channels, setChannels] = useState([]); @@ -301,15 +367,17 @@ const ChannelsTable = () => { const [updatingBalance, setUpdatingBalance] = useState(false); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [showPrompt, setShowPrompt] = useState( - shouldShowPrompt('channel-test'), + shouldShowPrompt('channel-test') ); const [channelCount, setChannelCount] = useState(pageSize); const [groupOptions, setGroupOptions] = useState([]); const [showEdit, setShowEdit] = useState(false); const [enableBatchDelete, setEnableBatchDelete] = useState(false); const [editingChannel, setEditingChannel] = useState({ - id: undefined, + id: undefined }); + const [showEditTag, setShowEditTag] = useState(false); + const [editingTag, setEditingTag] = useState(''); const [selectedChannels, setSelectedChannels] = useState([]); const removeRecord = (id) => { @@ -325,39 +393,82 @@ const ChannelsTable = () => { }; const setChannelFormat = (channels) => { + let channelDates = []; + let channelTags = {}; for (let i = 0; i < channels.length; i++) { - // if (channels[i].type === 8) { - // showWarning( - // '检测到您使用了“自定义渠道”类型,请更换为“OpenAI”渠道类型!', - // ); - // showWarning('下个版本将不再支持“自定义渠道”类型!'); - // } channels[i].key = '' + channels[i].id; - let test_models = []; - channels[i].models.split(',').forEach((item, index) => { - test_models.push({ - node: 'item', - name: item, - onClick: () => { - testChannel(channels[i], item); - }, + + if (channels[i].tag === '' || channels[i].tag === null) { + let test_models = []; + channels[i].models.split(',').forEach((item, index) => { + test_models.push({ + node: 'item', + name: item, + onClick: () => { + testChannel(channels[i], item); + } + }); }); - }); - channels[i].test_models = test_models; + channels[i].test_models = test_models; + channelDates.push(channels[i]); + } else { + let tag = channels[i].tag; + // find from channelTags + let tagIndex = channelTags[tag]; + let tagChannelDates = undefined; + if (tagIndex === undefined) { + // not found, create a new tag + channelTags[tag] = 1; + tagChannelDates = { + key: tag, + id: tag, + tag: tag, + name: '标签:' + tag, + group: '', + used_quota: 0, + response_time: 0 + }; + tagChannelDates.children = []; + channelDates.push(tagChannelDates); + } else { + // found, add to the tag + tagChannelDates = channelDates.find((item) => item.key === tag); + } + + if (tagChannelDates.group === '') { + tagChannelDates.group = channels[i].group; + } else { + let channelGroupsStr = channels[i].group; + channelGroupsStr.split(',').forEach((item, index) => { + if (tagChannelDates.group.indexOf(item) === -1) { + tagChannelDates.group += item + ','; + } + }); + } + + tagChannelDates.children.push(channels[i]); + if (channels[i].status === 1) { + tagChannelDates.status = 1; + } + tagChannelDates.used_quota += channels[i].used_quota; + tagChannelDates.response_time += channels[i].response_time; + tagChannelDates.response_time = tagChannelDates.response_time / 2; + } + } // data.key = '' + data.id - setChannels(channels); - if (channels.length >= pageSize) { - setChannelCount(channels.length + pageSize); + setChannels(channelDates); + if (channelDates.length >= pageSize) { + setChannelCount(channelDates.length + pageSize); } else { - setChannelCount(channels.length); + setChannelCount(channelDates.length); } }; const loadChannels = async (startIdx, pageSize, idSort) => { setLoading(true); const res = await API.get( - `/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}`, + `/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}` ); if (res === undefined) { return; @@ -379,7 +490,7 @@ const ChannelsTable = () => { const copySelectedChannel = async (id) => { const channelToCopy = channels.find( - (channel) => String(channel.id) === String(id), + (channel) => String(channel.id) === String(id) ); console.log(channelToCopy); channelToCopy.name += '_复制'; @@ -472,29 +583,63 @@ const ChannelsTable = () => { } }; + const manageTag = async (tag, action) => { + console.log(tag, action); + let res; + switch (action) { + case 'enable': + res = await API.post('/api/channel/tag/enabled', { + tag: tag + }); + break; + case 'disable': + res = await API.post('/api/channel/tag/disabled', { + tag: tag + }); + break; + } + const { success, message } = res.data; + if (success) { + showSuccess('操作成功完成!'); + let newChannels = [...channels]; + for (let i = 0; i < newChannels.length; i++) { + if (newChannels[i].tag === tag) { + let status = action === 'enable' ? 1 : 2; + newChannels[i]?.children?.forEach((channel) => { + channel.status = status; + }); + newChannels[i].status = status; + } + } + setChannels(newChannels); + } else { + showError(message); + } + }; + const renderStatus = (status) => { switch (status) { case 1: return ( - + 已启用 ); case 2: return ( - + 已禁用 ); case 3: return ( - + 自动禁用 ); default: return ( - + 未知状态 ); @@ -506,31 +651,31 @@ const ChannelsTable = () => { time = time.toFixed(2) + ' 秒'; if (responseTime === 0) { return ( - + 未测试 ); } else if (responseTime <= 1000) { return ( - + {time} ); } else if (responseTime <= 3000) { return ( - + {time} ); } else if (responseTime <= 5000) { return ( - + {time} ); } else { return ( - + {time} ); @@ -546,7 +691,7 @@ const ChannelsTable = () => { } setSearching(true); const res = await API.get( - `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}`, + `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}` ); const { success, message, data } = res.data; if (success) { @@ -649,14 +794,15 @@ const ChannelsTable = () => { let pageData = channels.slice( (activePage - 1) * pageSize, - activePage * pageSize, + activePage * pageSize ); const handlePageChange = (page) => { setActivePage(page); if (page === Math.ceil(channels.length / pageSize) + 1) { // In this case we have to load more data and then append them. - loadChannels(page - 1, pageSize, idSort).then((r) => {}); + loadChannels(page - 1, pageSize, idSort).then((r) => { + }); } }; @@ -682,8 +828,8 @@ const ChannelsTable = () => { setGroupOptions( res.data.data.map((group) => ({ label: group, - value: group, - })), + value: group + })) ); } catch (error) { showError(error.message); @@ -698,8 +844,8 @@ const ChannelsTable = () => { if (record.status !== 1) { return { style: { - background: 'var(--semi-color-disabled-border)', - }, + background: 'var(--semi-color-disabled-border)' + } }; } else { return {}; @@ -707,217 +853,224 @@ const ChannelsTable = () => { }; return ( - <> - -
{ - searchChannels(searchKeyword, searchGroup, searchModel); - }} - labelPosition='left' - > -
- - { - setSearchKeyword(v.trim()); - }} - /> - { - setSearchModel(v.trim()); - }} - /> - { - setSearchGroup(v); - searchChannels(searchKeyword, v, searchModel); - }} - /> - - -
-
- -
- - 使用ID排序 - { - localStorage.setItem('id-sort', v + ''); - setIdSort(v); - loadChannels(0, pageSize, v) - .then() - .catch((reason) => { - showError(reason); - }); - }} - > - - - - - - - - - - - - - -
-
+ <> + setShowEditTag(false)} + refresh={refresh} + /> + +
{ + searchChannels(searchKeyword, searchGroup, searchModel); + }} + labelPosition="left" + > +
- 开启批量删除 - { - setEnableBatchDelete(v); - }} - > - { + setSearchKeyword(v.trim()); + }} + /> + { + setSearchModel(v.trim()); + }} + /> + { + setSearchGroup(v); + searchChannels(searchKeyword, v, searchModel); + }} + /> + - - - - + 查询 +
- - '', - onPageSizeChange: (size) => { - handlePageSizeChange(size).then(); - }, - onPageChange: handlePageChange, + + +
+ + 使用ID排序 + { + localStorage.setItem('id-sort', v + ''); + setIdSort(v); + loadChannels(0, pageSize, v) + .then() + .catch((reason) => { + showError(reason); + }); }} - loading={loading} - onRow={handleRow} - rowSelection={ - enableBatchDelete - ? { - onChange: (selectedRowKeys, selectedRows) => { - // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); - setSelectedChannels(selectedRows); - }, - } - : null + > + + + + + + + + + + + + + +
+
+ + 开启批量删除 + { + setEnableBatchDelete(v); + }} + > + + + + + + + +
+ + +
'', + onPageSizeChange: (size) => { + handlePageSizeChange(size).then(); + }, + onPageChange: handlePageChange + }} + loading={loading} + onRow={handleRow} + rowSelection={ + enableBatchDelete + ? { + onChange: (selectedRowKeys, selectedRows) => { + // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); + setSelectedChannels(selectedRows); + } } - /> - + : null + } + /> + ); }; diff --git a/web/src/components/TextInput.js b/web/src/components/TextInput.js new file mode 100644 index 00000000..b4ace8f1 --- /dev/null +++ b/web/src/components/TextInput.js @@ -0,0 +1,21 @@ +import { Input, Typography } from '@douyinfe/semi-ui'; +import React from 'react'; + +const TextInput = ({ label, name, value, onChange, placeholder, type = 'text' }) => { + return ( + <> +
+ {label} +
+ onChange(value)} + value={value} + autoComplete="new-password" + /> + + ); +} + +export default TextInput; \ No newline at end of file diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index d89f3f10..406d9e10 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -67,6 +67,8 @@ export function renderQuotaNumberWithDigit(num, digits = 2) { } export function renderNumberWithPoint(num) { + if (num === undefined) + return ''; num = num.toFixed(2); if (num >= 100000) { // Convert number to string to manipulate it diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index dec37692..a79d0466 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -6,7 +6,7 @@ import { showError, showInfo, showSuccess, - verifyJSON, + verifyJSON } from '../../helpers'; import { CHANNEL_OPTIONS } from '../../constants'; import Title from '@douyinfe/semi-ui/lib/es/typography/title'; @@ -21,7 +21,7 @@ import { Select, TextArea, Checkbox, - Banner, + Banner } from '@douyinfe/semi-ui'; import { Divider } from 'semantic-ui-react'; import { getChannelModels, loadChannelModels } from '../../components/utils.js'; @@ -30,19 +30,19 @@ import axios from 'axios'; const MODEL_MAPPING_EXAMPLE = { 'gpt-3.5-turbo-0301': 'gpt-3.5-turbo', 'gpt-4-0314': 'gpt-4', - 'gpt-4-32k-0314': 'gpt-4-32k', + 'gpt-4-32k-0314': 'gpt-4-32k' }; const STATUS_CODE_MAPPING_EXAMPLE = { - 400: '500', + 400: '500' }; const REGION_EXAMPLE = { - "default": "us-central1", - "claude-3-5-sonnet-20240620": "europe-west1" -} + 'default': 'us-central1', + 'claude-3-5-sonnet-20240620': 'europe-west1' +}; -const fetchButtonTips = "1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出" +const fetchButtonTips = '1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出'; function type2secretPrompt(type) { // inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥') @@ -84,6 +84,9 @@ const EditChannel = (props) => { auto_ban: 1, test_model: '', groups: ['default'], + priority: 0, + weight: 0, + tag: '' }; const [batch, setBatch] = useState(false); const [autoBan, setAutoBan] = useState(true); @@ -108,7 +111,7 @@ const EditChannel = (props) => { 'mj_blend', 'mj_upscale', 'mj_describe', - 'mj_uploads', + 'mj_uploads' ]; break; case 5: @@ -128,13 +131,13 @@ const EditChannel = (props) => { 'mj_high_variation', 'mj_low_variation', 'mj_pan', - 'mj_uploads', + 'mj_uploads' ]; break; case 36: localModels = [ 'suno_music', - 'suno_lyrics', + 'suno_lyrics' ]; break; default: @@ -171,7 +174,7 @@ const EditChannel = (props) => { data.model_mapping = JSON.stringify( JSON.parse(data.model_mapping), null, - 2, + 2 ); } setInputs(data); @@ -190,61 +193,60 @@ const EditChannel = (props) => { const fetchUpstreamModelList = async (name) => { - if (inputs["type"] !== 1) { - showError("仅支持 OpenAI 接口格式") + if (inputs['type'] !== 1) { + showError('仅支持 OpenAI 接口格式'); return; } - setLoading(true) - const models = inputs["models"] || [] + setLoading(true); + const models = inputs['models'] || []; let err = false; if (isEdit) { - const res = await API.get("/api/channel/fetch_models/" + channelId) + const res = await API.get('/api/channel/fetch_models/' + channelId); if (res.data && res.data?.success) { - models.push(...res.data.data) + models.push(...res.data.data); } else { - err = true + err = true; } } else { - if (!inputs?.["key"]) { - showError("请填写密钥") - err = true + if (!inputs?.['key']) { + showError('请填写密钥'); + err = true; } else { try { - const host = new URL((inputs["base_url"] || "https://api.openai.com")) + const host = new URL((inputs['base_url'] || 'https://api.openai.com')); const url = `https://${host.hostname}/v1/models`; - const key = inputs["key"]; + const key = inputs['key']; const res = await axios.get(url, { headers: { 'Authorization': `Bearer ${key}` } - }) + }); if (res.data && res.data?.success) { - models.push(...res.data.data.map((model) => model.id)) + models.push(...res.data.data.map((model) => model.id)); } else { - err = true + err = true; } - } - catch (error) { - err = true + } catch (error) { + err = true; } } } if (!err) { handleInputChange(name, Array.from(new Set(models))); - showSuccess("获取模型列表成功"); + showSuccess('获取模型列表成功'); } else { showError('获取模型列表失败'); } setLoading(false); - } + }; const fetchModels = async () => { try { let res = await API.get(`/api/channel/models`); let localModelOptions = res.data.data.map((model) => ({ label: model.id, - value: model.id, + value: model.id })); setOriginModelOptions(localModelOptions); setFullModels(res.data.data.map((model) => model.id)); @@ -253,7 +255,7 @@ const EditChannel = (props) => { .filter((model) => { return model.id.startsWith('gpt-3') || model.id.startsWith('text-'); }) - .map((model) => model.id), + .map((model) => model.id) ); } catch (error) { showError(error.message); @@ -269,8 +271,8 @@ const EditChannel = (props) => { setGroupOptions( res.data.data.map((group) => ({ label: group, - value: group, - })), + value: group + })) ); } catch (error) { showError(error.message); @@ -283,7 +285,7 @@ const EditChannel = (props) => { if (!localModelOptions.find((option) => option.key === model)) { localModelOptions.push({ label: model, - value: model, + value: model }); } }); @@ -294,7 +296,8 @@ const EditChannel = (props) => { fetchModels().then(); fetchGroups().then(); if (isEdit) { - loadChannel().then(() => {}); + loadChannel().then(() => { + }); } else { setInputs(originInputs); let localModels = getChannelModels(inputs.type); @@ -320,7 +323,7 @@ const EditChannel = (props) => { if (localInputs.base_url && localInputs.base_url.endsWith('/')) { localInputs.base_url = localInputs.base_url.slice( 0, - localInputs.base_url.length - 1, + localInputs.base_url.length - 1 ); } if (localInputs.type === 3 && localInputs.other === '') { @@ -341,7 +344,7 @@ const EditChannel = (props) => { if (isEdit) { res = await API.put(`/api/channel/`, { ...localInputs, - id: parseInt(channelId), + id: parseInt(channelId) }); } else { res = await API.post(`/api/channel/`, localInputs); @@ -378,7 +381,7 @@ const EditChannel = (props) => { // 添加到下拉选项 key: model, text: model, - value: model, + value: model }); } else if (model) { showError('某些模型已存在!'); @@ -409,11 +412,11 @@ const EditChannel = (props) => { footer={
-
{ handleInputChange('base_url', value); }} value={inputs.base_url} - autoComplete='new-password' + autoComplete="new-password" />
默认 API 版本:
{ handleInputChange('other', value); }} value={inputs.other} - autoComplete='new-password' + autoComplete="new-password" /> )} @@ -512,7 +515,7 @@ const EditChannel = (props) => { { handleInputChange('base_url', value); }} value={inputs.base_url} - autoComplete='new-password' + autoComplete="new-password" + /> + + )} + {inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && ( + <> +
+ 代理: +
+ { + handleInputChange('base_url', value); + }} + value={inputs.base_url} + autoComplete="new-password" + /> + + )} + {inputs.type === 22 && ( + <> +
+ 私有部署地址: +
+ { + handleInputChange('base_url', value); + }} + value={inputs.base_url} + autoComplete="new-password" /> )} {inputs.type === 36 && ( - <> -
- - 注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用 - -
- { - handleInputChange('base_url', value); - }} - value={inputs.base_url} - autoComplete='new-password' - /> - + <> +
+ + 注意非Chat API,请务必填写正确的API地址,否则可能导致无法使用 + +
+ { + handleInputChange('base_url', value); + }} + value={inputs.base_url} + autoComplete="new-password" + /> + )} -
+
名称:
{ handleInputChange('name', value); }} value={inputs.name} - autoComplete='new-password' + autoComplete="new-password" />
分组:
{ handleInputChange('other', value); }} value={inputs.other} - autoComplete='new-password' + autoComplete="new-password" /> )} @@ -599,7 +637,7 @@ const EditChannel = (props) => { 部署地区: