From 2f74cc077b5b9a7e124c208c65a4000daff0a977 Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 27 Sep 2025 13:56:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=9A=E5=AF=86=E9=92=A5=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=96=B0=E5=A2=9E=E9=92=88=E5=AF=B9=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E7=9A=84=E5=88=A0=E9=99=A4=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel.go | 84 ++++++++++++++++++- .../channels/modals/MultiKeyManageModal.jsx | 43 +++++++++- web/src/i18n/locales/en.json | 4 + 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/controller/channel.go b/controller/channel.go index 17154ab0f..b88b1079a 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -1101,8 +1101,8 @@ func CopyChannel(c *gin.Context) { // MultiKeyManageRequest represents the request for multi-key management operations type MultiKeyManageRequest struct { ChannelId int `json:"channel_id"` - Action string `json:"action"` // "disable_key", "enable_key", "delete_disabled_keys", "get_key_status" - KeyIndex *int `json:"key_index,omitempty"` // for disable_key and enable_key actions + Action string `json:"action"` // "disable_key", "enable_key", "delete_key", "delete_disabled_keys", "get_key_status" + KeyIndex *int `json:"key_index,omitempty"` // for disable_key, enable_key, and delete_key actions Page int `json:"page,omitempty"` // for get_key_status pagination PageSize int `json:"page_size,omitempty"` // for get_key_status pagination Status *int `json:"status,omitempty"` // for get_key_status filtering: 1=enabled, 2=manual_disabled, 3=auto_disabled, nil=all @@ -1430,6 +1430,86 @@ func ManageMultiKeys(c *gin.Context) { }) return + case "delete_key": + if request.KeyIndex == nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "未指定要删除的密钥索引", + }) + return + } + + keyIndex := *request.KeyIndex + if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "密钥索引超出范围", + }) + return + } + + keys := channel.GetKeys() + var remainingKeys []string + var newStatusList = make(map[int]int) + var newDisabledTime = make(map[int]int64) + var newDisabledReason = make(map[int]string) + + newIndex := 0 + for i, key := range keys { + // 跳过要删除的密钥 + if i == keyIndex { + continue + } + + remainingKeys = append(remainingKeys, key) + + // 保留其他密钥的状态信息,重新索引 + if channel.ChannelInfo.MultiKeyStatusList != nil { + if status, exists := channel.ChannelInfo.MultiKeyStatusList[i]; exists && status != 1 { + newStatusList[newIndex] = status + } + } + if channel.ChannelInfo.MultiKeyDisabledTime != nil { + if t, exists := channel.ChannelInfo.MultiKeyDisabledTime[i]; exists { + newDisabledTime[newIndex] = t + } + } + if channel.ChannelInfo.MultiKeyDisabledReason != nil { + if r, exists := channel.ChannelInfo.MultiKeyDisabledReason[i]; exists { + newDisabledReason[newIndex] = r + } + } + newIndex++ + } + + if len(remainingKeys) == 0 { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "不能删除最后一个密钥", + }) + return + } + + // Update channel with remaining keys + channel.Key = strings.Join(remainingKeys, "\n") + channel.ChannelInfo.MultiKeySize = len(remainingKeys) + channel.ChannelInfo.MultiKeyStatusList = newStatusList + channel.ChannelInfo.MultiKeyDisabledTime = newDisabledTime + channel.ChannelInfo.MultiKeyDisabledReason = newDisabledReason + + err = channel.Update() + if err != nil { + common.ApiError(c, err) + return + } + + model.InitChannelCache() + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "密钥已删除", + }) + return + case "delete_disabled_keys": keys := channel.GetKeys() var remainingKeys []string diff --git a/web/src/components/table/channels/modals/MultiKeyManageModal.jsx b/web/src/components/table/channels/modals/MultiKeyManageModal.jsx index ebb219375..5e496131f 100644 --- a/web/src/components/table/channels/modals/MultiKeyManageModal.jsx +++ b/web/src/components/table/channels/modals/MultiKeyManageModal.jsx @@ -247,6 +247,32 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { } }; + // Delete a specific key + const handleDeleteKey = async (keyIndex) => { + const operationId = `delete_${keyIndex}`; + setOperationLoading((prev) => ({ ...prev, [operationId]: true })); + + try { + const res = await API.post('/api/channel/multi_key/manage', { + channel_id: channel.id, + action: 'delete_key', + key_index: keyIndex, + }); + + if (res.data.success) { + showSuccess(t('密钥已删除')); + await loadKeyStatus(currentPage, pageSize); // Reload current page + onRefresh && onRefresh(); // Refresh parent component + } else { + showError(res.data.message); + } + } catch (error) { + showError(t('删除密钥失败')); + } finally { + setOperationLoading((prev) => ({ ...prev, [operationId]: false })); + } + }; + // Handle page change const handlePageChange = (page) => { setCurrentPage(page); @@ -384,7 +410,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { title: t('操作'), key: 'action', fixed: 'right', - width: 100, + width: 150, render: (_, record) => ( {record.status === 1 ? ( @@ -406,6 +432,21 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { {t('启用')} )} + handleDeleteKey(record.index)} + okType={'danger'} + position={'topRight'} + > + + ), }, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index bceb5f089..b04698950 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1889,6 +1889,10 @@ "确定要删除所有已自动禁用的密钥吗?": "Are you sure you want to delete all automatically disabled keys?", "此操作不可撤销,将永久删除已自动禁用的密钥": "This operation cannot be undone, and all automatically disabled keys will be permanently deleted.", "删除自动禁用密钥": "Delete auto disabled keys", + "确定要删除此密钥吗?": "Are you sure you want to delete this key?", + "此操作不可撤销,将永久删除该密钥": "This operation cannot be undone, and the key will be permanently deleted.", + "密钥已删除": "Key has been deleted", + "删除密钥失败": "Failed to delete key", "图标": "Icon", "模型图标": "Model icon", "请输入图标名称": "Please enter the icon name",