feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
+142
-214
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user