From 7403df7e9ca3bfb85e162aa617f64f64b9075594 Mon Sep 17 00:00:00 2001
From: CaIon <1808837298@qq.com>
Date: Mon, 16 Jun 2025 02:30:46 +0800
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(channel):=20enhance=20channel?=
=?UTF-8?q?=20handling=20with=20multi-key=20support?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
controller/channel.go | 16 +++++
model/channel.go | 23 ++++--
web/src/components/table/ChannelsTable.js | 86 +++++++++++++++++++++--
web/src/pages/Channel/EditChannel.js | 2 +-
4 files changed, 115 insertions(+), 12 deletions(-)
diff --git a/controller/channel.go b/controller/channel.go
index f2b9ad7e..dc4e0cbf 100644
--- a/controller/channel.go
+++ b/controller/channel.go
@@ -318,6 +318,22 @@ func AddChannel(c *gin.Context) {
})
return
}
+ toMap := common.StrToMap(addChannelRequest.Channel.Key)
+ if toMap != nil {
+ addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(toMap)
+ } else {
+ addChannelRequest.Channel.ChannelInfo.MultiKeySize = 0
+ }
+ } else {
+ cleanKeys := make([]string, 0)
+ for _, key := range strings.Split(addChannelRequest.Channel.Key, "\n") {
+ if key == "" {
+ continue
+ }
+ cleanKeys = append(cleanKeys, key)
+ }
+ addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(cleanKeys)
+ addChannelRequest.Channel.Key = strings.Join(cleanKeys, "\n")
}
keys = []string{addChannelRequest.Channel.Key}
case "batch":
diff --git a/model/channel.go b/model/channel.go
index 755bd0b2..5f460ec8 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -1,6 +1,7 @@
package model
import (
+ "database/sql/driver"
"encoding/json"
"one-api/common"
"strings"
@@ -9,11 +10,6 @@ import (
"gorm.io/gorm"
)
-type ChannelInfo struct {
- MultiKeyMode bool `json:"multi_key_mode"` // 是否多Key模式
- MultiKeyStatusList map[int]int `json:"multi_key_status_list"` // key状态列表,key index -> status
-}
-
type Channel struct {
Id int `json:"id"`
Type int `json:"type" gorm:"default:0"`
@@ -46,6 +42,23 @@ type Channel struct {
ChannelInfo ChannelInfo `json:"channel_info" gorm:"type:json"`
}
+type ChannelInfo struct {
+ MultiKeyMode bool `json:"multi_key_mode"` // 是否多Key模式
+ MultiKeyStatusList map[int]int `json:"multi_key_status_list"` // key状态列表,key index -> status
+ MultiKeySize int `json:"multi_key_size"` // 多Key模式下的key数量
+}
+
+// Value implements driver.Valuer interface
+func (c ChannelInfo) Value() (driver.Value, error) {
+ return json.Marshal(c)
+}
+
+// Scan implements sql.Scanner interface
+func (c *ChannelInfo) Scan(value interface{}) error {
+ bytesValue, _ := value.([]byte)
+ return json.Unmarshal(bytesValue, c)
+}
+
func (channel *Channel) GetModels() []string {
if channel.Models == "" {
return []string{}
diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js
index f5a78490..6d0c109d 100644
--- a/web/src/components/table/ChannelsTable.js
+++ b/web/src/components/table/ChannelsTable.js
@@ -22,7 +22,7 @@ import {
Clock,
AlertTriangle,
Coins,
- Tags
+ Tags, Boxes
} from 'lucide-react';
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../../constants/index.js';
@@ -73,7 +73,7 @@ const ChannelsTable = () => {
let type2label = undefined;
- const renderType = (type) => {
+ const renderType = (type, multiKeyMode=false) => {
if (!type2label) {
type2label = new Map();
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
@@ -81,6 +81,24 @@ const ChannelsTable = () => {
}
type2label[0] = { value: 0, label: t('未知类型'), color: 'grey' };
}
+
+ if (multiKeyMode) {
+ return (
+
+
+ {getChannelIcon(type)}
+
+ }
+ >
+ {type2label[type]?.label}
+
+ );
+ }
return (
{
);
};
- const renderStatus = (status) => {
+ const renderMultiKeyStatus = (status, channelInfo) => {
+ if (!channelInfo || !channelInfo.multi_key_mode) {
+ return renderStatus(status, channelInfo);
+ }
+
+ const { multi_key_status_list, multi_key_size } = channelInfo;
+ const totalCount = multi_key_size || 0;
+
+ // If multi_key_status_list is null, it means all keys are enabled
+ if (!multi_key_status_list) {
+ return (
+ }>
+ {t('已启用')}:{totalCount}/{totalCount}
+
+ );
+ }
+
+ // Count enabled keys from the status map
+ const statusValues = Object.values(multi_key_status_list);
+ const enabledCount = statusValues.filter(s => s === 1).length;
+
+ // Determine status text, color and icon based on enabled ratio
+ let statusText, statusColor, statusIcon;
+ const enabledRatio = totalCount > 0 ? enabledCount / totalCount : 0;
+
+ if (enabledCount === totalCount) {
+ statusText = t('已启用');
+ statusColor = 'green';
+ statusIcon = ;
+ } else if (enabledCount === 0) {
+ statusText = t('已禁用');
+ statusColor = 'red';
+ statusIcon = ;
+ } else {
+ statusText = t('部分启用');
+ // Color based on percentage: green (>80%), yellow (20-80%), red (<20%)
+ if (enabledRatio > 0.8) {
+ statusColor = 'green';
+ } else if (enabledRatio >= 0.2) {
+ statusColor = 'yellow';
+ } else {
+ statusColor = 'red';
+ }
+ statusIcon = ;
+ }
+
+ return (
+
+ {statusText}:{enabledCount}/{totalCount}
+
+ );
+ };
+
+ const renderStatus = (status, channelInfo=undefined) => {
+ if (channelInfo?.multi_key_mode) {
+ return renderMultiKeyStatus(status, channelInfo);
+ }
switch (status) {
case 1:
return (
@@ -297,7 +371,7 @@ const ChannelsTable = () => {
dataIndex: 'type',
render: (text, record, index) => {
if (record.children === undefined) {
- return <>{renderType(text)}>;
+ return <>{renderType(text, record.channel_info?.multi_key_mode)}>;
} else {
return <>{renderTagType()}>;
}
@@ -320,12 +394,12 @@ const ChannelsTable = () => {
- {renderStatus(text)}
+ {renderStatus(text, record.channel_info)}
);
} else {
- return renderStatus(text);
+ return renderStatus(text, record.channel_info);
}
},
},
diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js
index fa77be81..32d2ce49 100644
--- a/web/src/pages/Channel/EditChannel.js
+++ b/web/src/pages/Channel/EditChannel.js
@@ -664,7 +664,7 @@ const EditChannel = (props) => {
onChange={() => setMergeToSingle(!mergeToSingle)}
/>
- {t('合并为单通道(多 Key 模式)')}
+ {t('合并为单通道(多 Key 聚合模式)')}
)}