feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+142 -214
View File
@@ -6,13 +6,13 @@ import (
"errors"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/pkg/jsplugin"
relaychannel "github.com/QuantumNous/new-api/relay/channel"
@@ -212,14 +212,14 @@ func GetAllChannels(c *gin.Context) {
if enableTagMode {
tags, err := model.GetPaginatedChannelTags(buildChannelListQuery(groupFilter, statusFilter, typeFilter), pageInfo.GetStartIdx(), pageInfo.GetPageSize())
if err != nil {
common.SysError("failed to get paginated tags: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取标签失败,请稍后重试"})
common.SysError(common.LogText("failed to get paginated tags: %s", err.Error()))
common.ApiErrorT(c, "Failed to fetch tags. Please try again later")
return
}
total, err = model.CountChannelTags(buildChannelListQuery(groupFilter, statusFilter, typeFilter))
if err != nil {
common.SysError("failed to count tags: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取标签数量失败,请稍后重试"})
common.SysError(common.LogText("failed to count tags: %s", err.Error()))
common.ApiErrorT(c, "Failed to count tags. Please try again later")
return
}
for _, tag := range tags {
@@ -231,16 +231,16 @@ func GetAllChannels(c *gin.Context) {
Omit("key").
Find(&tagChannels).Error
if err != nil {
common.SysError("failed to get channels by tag: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取标签渠道失败,请稍后重试"})
common.SysError(common.LogText("failed to get channels by tag: %s", err.Error()))
common.ApiErrorT(c, "Failed to fetch channels with this tag. Please try again later")
return
}
channelData = append(channelData, tagChannels...)
}
} else {
if err := buildChannelListQuery(groupFilter, statusFilter, typeFilter).Count(&total).Error; err != nil {
common.SysError("failed to count channels: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取渠道数量失败,请稍后重试"})
common.SysError(common.LogText("failed to count channels: %s", err.Error()))
common.ApiErrorT(c, "Failed to count channels. Please try again later")
return
}
@@ -250,8 +250,8 @@ func GetAllChannels(c *gin.Context) {
Omit("key").
Find(&channelData).Error
if err != nil {
common.SysError("failed to get channels: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取渠道列表失败,请稍后重试"})
common.SysError(common.LogText("failed to get channels: %s", err.Error()))
common.ApiErrorT(c, "Failed to fetch channels. Please try again later")
return
}
}
@@ -266,8 +266,8 @@ func GetAllChannels(c *gin.Context) {
Count int64
}
if err := countQuery.Select("type, count(*) as count").Group("type").Find(&results).Error; err != nil {
common.SysError("failed to count channel types: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取渠道类型统计失败,请稍后重试"})
common.SysError(common.LogText("failed to count channel types: %s", err.Error()))
common.ApiErrorT(c, "Failed to count channel types. Please try again later")
return
}
typeCounts := make(map[int64]int64)
@@ -333,10 +333,7 @@ func FetchUpstreamModels(c *gin.Context) {
ids, err := fetchChannelUpstreamModelIDs(channel)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("获取模型列表失败: %s", err.Error()),
})
common.ApiErrorT(c, "Failed to fetch models: {{error}}", map[string]any{"error": err.Error()})
return
}
@@ -376,10 +373,7 @@ func SearchChannels(c *gin.Context) {
if enableTagMode {
tags, err := model.SearchTags(keyword, group, modelKeyword, idSort)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
for _, tag := range tags {
@@ -389,10 +383,7 @@ func SearchChannels(c *gin.Context) {
Omit("key").
Find(&tagChannels).Error
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
channelData = append(channelData, tagChannels...)
@@ -401,10 +392,7 @@ func SearchChannels(c *gin.Context) {
} else {
channels, err := model.SearchChannels(keyword, group, modelKeyword, idSort, sortOptions)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
channelData = channels
@@ -506,14 +494,14 @@ func GetChannel(c *gin.Context) {
func GetChannelKey(c *gin.Context) {
channelId, err := strconv.Atoi(c.Param("id"))
if err != nil || channelId <= 0 {
common.ApiErrorMsg(c, "渠道ID格式错误")
common.ApiErrorT(c, "Invalid channel ID")
return
}
// 获取渠道信息(包含密钥)
channel, err := model.GetChannelById(channelId, true)
if errors.Is(err, gorm.ErrRecordNotFound) {
common.ApiErrorI18n(c, i18n.MsgChannelNotExists)
common.ApiErrorT(c, "Channel does not exist")
return
}
if err != nil {
@@ -528,15 +516,15 @@ func GetChannelKey(c *gin.Context) {
})
// 返回渠道密钥
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "获取成功",
"data": map[string]any{
"key": channel.Key,
},
common.ApiSuccessT(c, "Fetched successfully", map[string]any{
"key": channel.Key,
})
}
// maxTaskExtendPluginKeys bounds the plugins one New API channel can be
// extended with; it comfortably covers every built-in and installed plugin.
const maxTaskExtendPluginKeys = 32
// validateChannel 通用的渠道校验函数
func validateChannel(channel *model.Channel, isAdd bool) error {
if channel == nil {
@@ -545,7 +533,7 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
// 校验 channel settings
if err := channel.ValidateSettings(); err != nil {
return fmt.Errorf("渠道额外设置[channel setting] 格式错误:%s", err.Error())
return common.NewMessage("Invalid channel settings format: {{error}}", map[string]any{"error": err.Error()})
}
if channel.Type == constant.ChannelTypeTaskPlugin {
pluginKey := strings.TrimSpace(channel.GetSetting().TaskPluginKey)
@@ -571,6 +559,42 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
}
}
setting := channel.GetSetting()
if channel.Type != constant.ChannelTypeNewAPI && len(setting.TaskExtendPluginKeys) > 0 {
return fmt.Errorf("task_extend_plugin_keys is only supported on New API channels")
}
if channel.Type == constant.ChannelTypeNewAPI {
if len(setting.TaskExtendPluginKeys) > maxTaskExtendPluginKeys {
return fmt.Errorf("task_extend_plugin_keys must not exceed %d plugins", maxTaskExtendPluginKeys)
}
// The single key stays valid on a New API channel, so both bindings are
// checked as one set.
keys := setting.TaskExtendPluginKeys
if setting.TaskPluginKey != "" {
keys = append([]string{setting.TaskPluginKey}, keys...)
}
bound := make(map[string]struct{}, len(keys))
for _, key := range keys {
if key == "" || key != strings.TrimSpace(key) {
return fmt.Errorf("task plugin key %q is invalid", key)
}
if len(key) > 30 {
return fmt.Errorf("task plugin key must not exceed 30 characters")
}
if _, duplicate := bound[key]; duplicate {
return fmt.Errorf("task plugin %q is bound more than once", key)
}
plugin, ok := jsplugin.DefaultRegistry.Get(key)
if !ok {
return fmt.Errorf("task plugin %q is not registered", key)
}
if !plugin.Meta.SupportsUpstream(jsplugin.UpstreamKindNewAPI) {
return fmt.Errorf("task plugin %q does not support a New API upstream and cannot be bound to a New API channel", key)
}
bound[key] = struct{}{}
}
}
if channel.Type == constant.ChannelTypeNewAPI && strings.TrimSpace(channel.GetBaseURL()) == "" {
return fmt.Errorf("New API channel base URL cannot be empty")
}
@@ -590,7 +614,7 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
// 检查模型名称长度是否超过 255
for _, m := range channel.GetModels() {
if len(m) > 255 {
return fmt.Errorf("模型名称过长: %s", m)
return common.NewMessage("Model name is too long: {{model}}", map[string]any{"model": m})
}
}
}
@@ -598,16 +622,16 @@ func validateChannel(channel *model.Channel, isAdd bool) error {
// VertexAI 特殊校验
if channel.Type == constant.ChannelTypeVertexAi {
if channel.Other == "" {
return fmt.Errorf("部署地区不能为空")
return common.NewMessage("Deployment region cannot be empty")
}
regionMap, err := common.StrToMap(channel.Other)
if err != nil {
return fmt.Errorf("部署地区必须是标准的Json格式,例如{\"default\": \"us-central1\", \"region2\": \"us-east1\"}")
return common.NewMessage("Deployment region must be valid JSON, for example {{example}}", map[string]any{"example": `{"default": "us-central1", "region2": "us-east1"}`})
}
if regionMap["default"] == nil {
return fmt.Errorf("部署地区必须包含default字段")
return common.NewMessage("Deployment region must include a default field")
}
}
@@ -646,8 +670,8 @@ func RefreshCodexChannelCredential(c *gin.Context) {
oauthKey, ch, err := service.RefreshCodexChannelCredential(ctx, channelId, service.CodexCredentialRefreshOptions{ResetCaches: true})
if err != nil {
common.SysError("failed to refresh codex channel credential: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "刷新凭证失败,请稍后重试"})
common.SysError(common.LogText("failed to refresh codex channel credential: %s", err.Error()))
common.ApiErrorT(c, "Failed to refresh credentials. Please try again later")
return
}
@@ -680,7 +704,7 @@ func getVertexArrayKeys(keys string) ([]string, error) {
var keyArray []any
err := common.Unmarshal([]byte(keys), &keyArray)
if err != nil {
return nil, fmt.Errorf("批量添加 Vertex AI 必须使用标准的JsonArray格式,例如[{key1}, {key2}...],请检查输入: %w", err)
return nil, common.NewMessage("Batch-adding Vertex AI keys requires a JSON array, for example {{example}}. Check the input: {{error}}", map[string]any{"example": "[{key1}, {key2}...]", "error": err.Error()})
}
cleanKeys := make([]string, 0, len(keyArray))
for _, key := range keyArray {
@@ -689,9 +713,9 @@ func getVertexArrayKeys(keys string) ([]string, error) {
case string:
keyStr = strings.TrimSpace(v)
default:
bytes, err := json.Marshal(v)
bytes, err := common.Marshal(v)
if err != nil {
return nil, fmt.Errorf("Vertex AI key JSON 编码失败: %w", err)
return nil, common.NewMessage("Failed to encode Vertex AI key JSON: {{error}}", map[string]any{"error": err.Error()})
}
keyStr = string(bytes)
}
@@ -700,7 +724,7 @@ func getVertexArrayKeys(keys string) ([]string, error) {
}
}
if len(cleanKeys) == 0 {
return nil, fmt.Errorf("批量添加 Vertex AI 的 keys 不能为空")
return nil, common.NewMessage("Vertex AI keys for batch adding cannot be empty")
}
return cleanKeys, nil
}
@@ -713,7 +737,10 @@ func AddChannel(c *gin.Context) {
return
}
if addChannelRequest.Channel != nil && addChannelRequest.Channel.Type == constant.ChannelTypeTaskPlugin &&
// Binding a plugin needs the same permission on a New API channel as the
// type-61 channel dedicated to it.
if addChannelRequest.Channel != nil &&
(addChannelRequest.Channel.Type == constant.ChannelTypeTaskPlugin || len(addChannelRequest.Channel.GetSetting().TaskPluginBindings()) > 0) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -727,10 +754,7 @@ func AddChannel(c *gin.Context) {
(addChannelRequest.Channel.BaseURL == nil || strings.TrimSpace(*addChannelRequest.Channel.BaseURL) == "")
// 使用统一的校验函数
if err := validateChannel(addChannelRequest.Channel, true); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
@@ -743,10 +767,7 @@ func AddChannel(c *gin.Context) {
if addChannelRequest.Channel.Type == constant.ChannelTypeVertexAi && addChannelRequest.Channel.GetOtherSettings().VertexKeyType != dto.VertexKeyTypeAPIKey {
array, err := getVertexArrayKeys(addChannelRequest.Channel.Key)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
addChannelRequest.Channel.ChannelInfo.MultiKeySize = len(array)
@@ -769,10 +790,7 @@ func AddChannel(c *gin.Context) {
// multi json
keys, err = getVertexArrayKeys(addChannelRequest.Channel.Key)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
} else {
@@ -781,10 +799,7 @@ func AddChannel(c *gin.Context) {
case "single":
keys = []string{addChannelRequest.Channel.Key}
default:
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "不支持的添加模式",
})
common.ApiErrorT(c, "Unsupported add mode")
return
}
@@ -905,10 +920,7 @@ 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": "参数错误",
})
common.ApiErrorT(c, "Invalid parameters")
return
}
channels, err := model.GetChannelsByTag(channelTag.Tag, false, false)
@@ -938,10 +950,7 @@ 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": "参数错误",
})
common.ApiErrorT(c, "Invalid parameters")
return
}
err = model.EnableChannelByTag(channelTag.Tag)
@@ -964,31 +973,22 @@ func EditTagChannels(c *gin.Context) {
channelTag := ChannelTag{}
err := c.ShouldBindJSON(&channelTag)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数错误",
})
common.ApiErrorT(c, "Invalid parameters")
return
}
if channelTag.Tag == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "tag不能为空",
})
common.ApiErrorT(c, "Tag cannot be empty")
return
}
if (channelTag.ParamOverride != nil || channelTag.HeaderOverride != nil) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.ChannelSensitiveWrite) {
common.ApiErrorI18n(c, i18n.MsgAuthInsufficientPrivilege)
common.ApiErrorT(c, "Unauthorized, insufficient privileges")
return
}
if channelTag.ParamOverride != nil {
trimmed := strings.TrimSpace(*channelTag.ParamOverride)
if trimmed != "" && !json.Valid([]byte(trimmed)) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数覆盖必须是合法的 JSON 格式",
})
common.ApiErrorT(c, "Parameter override must be valid JSON format")
return
}
channelTag.ParamOverride = common.GetPointer[string](trimmed)
@@ -996,10 +996,7 @@ func EditTagChannels(c *gin.Context) {
if channelTag.HeaderOverride != nil {
trimmed := strings.TrimSpace(*channelTag.HeaderOverride)
if trimmed != "" && !json.Valid([]byte(trimmed)) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "请求头覆盖必须是合法的 JSON 格式",
})
common.ApiErrorT(c, "Header override must be valid JSON format")
return
}
channelTag.HeaderOverride = common.GetPointer[string](trimmed)
@@ -1029,10 +1026,7 @@ func DeleteChannelBatch(c *gin.Context) {
channelBatch := ChannelBatch{}
err := c.ShouldBindJSON(&channelBatch)
if err != nil || len(channelBatch.Ids) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数错误",
})
common.ApiErrorT(c, "Invalid parameters")
return
}
deletedCount, err := model.BatchDeleteChannels(channelBatch.Ids)
@@ -1088,7 +1082,7 @@ func UpdateChannel(c *gin.Context) {
return
}
if _, ok := requestData["status"]; ok {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
common.ApiErrorT(c, "Invalid parameters")
return
}
clearChannelReadOnlyFields(&channel, requestData)
@@ -1106,35 +1100,42 @@ func UpdateChannel(c *gin.Context) {
(channel.BaseURL == nil || strings.TrimSpace(*channel.BaseURL) == "")
// 使用统一的校验函数
if err := validateChannel(&channel.Channel, false); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
// Preserve existing ChannelInfo to ensure multi-key channels keep correct state even if the client does not send ChannelInfo in the request.
originChannel, err := model.GetChannelById(channel.Id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
originProxy := originChannel.GetSetting().Proxy
proxyChanged := false
if _, settingProvided := requestData["setting"]; settingProvided {
_, settingProvided := requestData["setting"]
if settingProvided {
newProxy, _ := service.NormalizeProxyURL(channel.GetSetting().Proxy)
normalizedOriginProxy, originProxyErr := service.NormalizeProxyURL(originProxy)
proxyChanged = originProxyErr != nil || normalizedOriginProxy != newProxy
}
// Changing which plugins a channel binds needs the bind permission on any
// channel type; resubmitting an unchanged New API binding list does not, so
// administrators without it can still edit the rest of a gateway channel.
if settingProvided &&
!slices.Equal(channel.GetSetting().TaskPluginBindings(), originChannel.GetSetting().TaskPluginBindings()) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "task plugin channels require the task_plugin.bind permission",
})
return
}
// Always copy the original ChannelInfo so that fields like IsMultiKey and MultiKeySize are retained.
channel.ChannelInfo = originChannel.ChannelInfo
if channelHasSensitiveChanges(&channel, originChannel, requestData) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.ChannelSensitiveWrite) {
common.ApiErrorI18n(c, i18n.MsgAuthInsufficientPrivilege)
common.ApiErrorT(c, "Unauthorized, insufficient privileges")
return
}
@@ -1173,10 +1174,7 @@ func UpdateChannel(c *gin.Context) {
if strings.HasPrefix(strings.TrimSpace(channel.Key), "[") {
array, err := getVertexArrayKeys(channel.Key)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "追加密钥解析失败: " + err.Error(),
})
common.ApiErrorT(c, "Failed to parse appended keys: {{error}}", map[string]any{"error": err.Error()})
return
}
newKeys = array
@@ -1271,12 +1269,12 @@ func UpdateChannel(c *gin.Context) {
func UpdateChannelStatus(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
common.ApiErrorT(c, "Invalid parameters")
return
}
req := ChannelStatusRequest{}
if err := c.ShouldBindJSON(&req); err != nil || !isManageableChannelStatus(req.Status) {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
common.ApiErrorT(c, "Invalid parameters")
return
}
changed := model.UpdateChannelStatus(id, "", req.Status, "manual operation")
@@ -1301,7 +1299,7 @@ func UpdateChannelStatus(c *gin.Context) {
func BatchUpdateChannelStatus(c *gin.Context) {
req := ChannelStatusBatchRequest{}
if err := c.ShouldBindJSON(&req); err != nil || len(req.Ids) == 0 || !isManageableChannelStatus(req.Status) {
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
common.ApiErrorT(c, "Invalid parameters")
return
}
changedCount := 0
@@ -1441,10 +1439,7 @@ func FetchModels(c *gin.Context) {
var err error
channel, err = buildAdvancedCustomModelPreviewChannel(req)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
common.ApiError(c, err)
return
}
} else {
@@ -1469,10 +1464,7 @@ func FetchModels(c *gin.Context) {
models, err := fetchChannelUpstreamModelIDs(channel)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("获取模型列表失败: %s", err.Error()),
})
common.ApiErrorT(c, "Failed to fetch models: {{error}}", map[string]any{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
@@ -1486,10 +1478,7 @@ func BatchSetChannelTag(c *gin.Context) {
channelBatch := ChannelBatch{}
err := c.ShouldBindJSON(&channelBatch)
if err != nil || len(channelBatch.Ids) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数错误",
})
common.ApiErrorT(c, "Invalid parameters")
return
}
err = model.BatchSetChannelTag(channelBatch.Ids, channelBatch.Tag)
@@ -1512,10 +1501,7 @@ func BatchSetChannelTag(c *gin.Context) {
func GetTagModels(c *gin.Context) {
tag := c.Query("tag")
if tag == "" {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "tag不能为空",
})
common.ApiErrorStatus(c, http.StatusBadRequest, common.NewMessage("Tag cannot be empty"))
return
}
@@ -1554,7 +1540,7 @@ func GetTagModels(c *gin.Context) {
// POST /api/channel/copy/:id
// Optional query params:
//
// suffix - string appended to the original name (default "_复制")
// suffix - string appended to the original name (default "_copy")
// reset_balance - bool, when true will reset balance & used_quota to 0 (default true)
func CopyChannel(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
@@ -1563,7 +1549,7 @@ func CopyChannel(c *gin.Context) {
return
}
suffix := c.DefaultQuery("suffix", "_复制")
suffix := c.DefaultQuery("suffix", "_copy")
resetBalance := true
if rbStr := c.DefaultQuery("reset_balance", "true"); rbStr != "" {
if v, err := strconv.ParseBool(rbStr); err == nil {
@@ -1574,11 +1560,11 @@ func CopyChannel(c *gin.Context) {
// fetch original channel with key
origin, err := model.GetChannelById(id, true)
if err != nil {
common.SysError("failed to get channel by id: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "获取渠道信息失败,请稍后重试"})
common.SysError(common.LogText("failed to get channel by id: %s", err.Error()))
common.ApiErrorT(c, "Failed to fetch channel. Please try again later")
return
}
if origin.Type == constant.ChannelTypeTaskPlugin &&
if (origin.Type == constant.ChannelTypeTaskPlugin || len(origin.GetSetting().TaskPluginBindings()) > 0) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.TaskPluginBind) {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "task plugin channels require the task_plugin.bind permission"})
return
@@ -1597,15 +1583,15 @@ func CopyChannel(c *gin.Context) {
}
if err := clone.ValidateSettings(); err != nil {
common.SysError("failed to validate cloned channel: " + err.Error())
common.SysError(common.LogText("failed to validate cloned channel: %s", err.Error()))
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Failed to copy channel: invalid channel settings"})
return
}
// insert
if err := clone.Insert(); err != nil {
common.SysError("failed to clone channel: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "复制渠道失败,请稍后重试"})
common.SysError(common.LogText("failed to clone channel: %s", err.Error()))
common.ApiErrorT(c, "Failed to copy channel. Please try again later")
return
}
model.InitChannelCache()
@@ -1660,23 +1646,17 @@ func ManageMultiKeys(c *gin.Context) {
channel, err := model.GetChannelById(request.ChannelId, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "渠道不存在",
})
common.ApiErrorT(c, "Channel does not exist")
return
}
if !channel.ChannelInfo.IsMultiKey {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "该渠道不是多密钥模式",
})
common.ApiErrorT(c, "This channel is not in multi-key mode")
return
}
if multiKeyActionRequiresSensitiveWrite(request.Action) &&
!authz.Can(c.GetInt("id"), c.GetInt("role"), authz.ChannelSensitiveWrite) {
common.ApiErrorI18n(c, i18n.MsgAuthInsufficientPrivilege)
common.ApiErrorT(c, "Unauthorized, insufficient privileges")
return
}
@@ -1808,19 +1788,13 @@ func ManageMultiKeys(c *gin.Context) {
case "disable_key":
if request.KeyIndex == nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "未指定要禁用的密钥索引",
})
common.ApiErrorT(c, "No key index specified to disable")
return
}
keyIndex := *request.KeyIndex
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "密钥索引超出范围",
})
common.ApiErrorT(c, "Key index out of range")
return
}
@@ -1846,27 +1820,18 @@ func ManageMultiKeys(c *gin.Context) {
if shouldCloseWebSocket {
closeActiveChannelWebSockets([]int{channel.Id})
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "密钥已禁用",
})
common.ApiSuccessT(c, "Key disabled", nil)
return
case "enable_key":
if request.KeyIndex == nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "未指定要启用的密钥索引",
})
common.ApiErrorT(c, "No key index specified to enable")
return
}
keyIndex := *request.KeyIndex
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "密钥索引超出范围",
})
common.ApiErrorT(c, "Key index out of range")
return
}
@@ -1889,10 +1854,7 @@ func ManageMultiKeys(c *gin.Context) {
}
model.InitChannelCache()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "密钥已启用",
})
common.ApiSuccessT(c, "Key enabled", nil)
return
case "enable_all_keys":
@@ -1914,10 +1876,7 @@ func ManageMultiKeys(c *gin.Context) {
}
model.InitChannelCache()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("已启用 %d 个密钥", enabledCount),
})
common.ApiSuccessT(c, "Enabled {{count}} keys", nil, map[string]any{"count": enabledCount})
return
case "disable_all_keys":
@@ -1947,10 +1906,7 @@ func ManageMultiKeys(c *gin.Context) {
}
if disabledCount == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "没有可禁用的密钥",
})
common.ApiErrorT(c, "No keys to disable")
return
}
@@ -1964,27 +1920,18 @@ func ManageMultiKeys(c *gin.Context) {
if shouldCloseWebSocket {
closeActiveChannelWebSockets([]int{channel.Id})
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("已禁用 %d 个密钥", disabledCount),
})
common.ApiSuccessT(c, "Disabled {{count}} keys", nil, map[string]any{"count": disabledCount})
return
case "delete_key":
if request.KeyIndex == nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "未指定要删除的密钥索引",
})
common.ApiErrorT(c, "No key index specified to delete")
return
}
keyIndex := *request.KeyIndex
if keyIndex < 0 || keyIndex >= channel.ChannelInfo.MultiKeySize {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "密钥索引超出范围",
})
common.ApiErrorT(c, "Key index out of range")
return
}
@@ -2023,10 +1970,7 @@ func ManageMultiKeys(c *gin.Context) {
}
if len(remainingKeys) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "不能删除最后一个密钥",
})
common.ApiErrorT(c, "Cannot delete the last key")
return
}
@@ -2047,10 +1991,7 @@ func ManageMultiKeys(c *gin.Context) {
if shouldCloseWebSocket {
closeActiveChannelWebSockets([]int{channel.Id})
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "密钥已删除",
})
common.ApiSuccessT(c, "Key deleted", nil)
return
case "delete_disabled_keys":
@@ -2094,10 +2035,7 @@ func ManageMultiKeys(c *gin.Context) {
}
if deletedCount == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "没有需要删除的自动禁用密钥",
})
common.ApiErrorT(c, "No auto-disabled keys to delete")
return
}
@@ -2118,18 +2056,11 @@ func ManageMultiKeys(c *gin.Context) {
if shouldCloseWebSocket {
closeActiveChannelWebSockets([]int{channel.Id})
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": fmt.Sprintf("已删除 %d 个自动禁用的密钥", deletedCount),
"data": deletedCount,
})
common.ApiSuccessT(c, "Deleted {{count}} auto-disabled keys", deletedCount, map[string]any{"count": deletedCount})
return
default:
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "不支持的操作",
})
common.ApiErrorT(c, "Unsupported operation")
return
}
}
@@ -2382,10 +2313,7 @@ func OllamaVersion(c *gin.Context) {
key := strings.Split(channel.Key, "\n")[0]
version, err := ollama.FetchOllamaVersion(baseURL, key)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("获取Ollama版本失败: %s", err.Error()),
})
common.ApiErrorT(c, "Failed to fetch Ollama version: {{error}}", map[string]any{"error": err.Error()})
return
}