diff --git a/dto/channel_settings.go b/dto/channel_settings.go index 871d6716..47f8bf95 100644 --- a/dto/channel_settings.go +++ b/dto/channel_settings.go @@ -1,7 +1,9 @@ package dto type ChannelSettings struct { - ForceFormat bool `json:"force_format,omitempty"` - ThinkingToContent bool `json:"thinking_to_content,omitempty"` - Proxy string `json:"proxy"` + ForceFormat bool `json:"force_format,omitempty"` + ThinkingToContent bool `json:"thinking_to_content,omitempty"` + Proxy string `json:"proxy"` + PassThroughBodyEnabled bool `json:"pass_through_body_enabled,omitempty"` + SystemPrompt string `json:"system_prompt,omitempty"` } diff --git a/dto/openai_request.go b/dto/openai_request.go index a35ee6b6..29076ef6 100644 --- a/dto/openai_request.go +++ b/dto/openai_request.go @@ -7,15 +7,15 @@ import ( ) type ResponseFormat struct { - Type string `json:"type,omitempty"` - JsonSchema *FormatJsonSchema `json:"json_schema,omitempty"` + Type string `json:"type,omitempty"` + JsonSchema json.RawMessage `json:"json_schema,omitempty"` } type FormatJsonSchema struct { - Description string `json:"description,omitempty"` - Name string `json:"name"` - Schema any `json:"schema,omitempty"` - Strict any `json:"strict,omitempty"` + Description string `json:"description,omitempty"` + Name string `json:"name"` + Schema any `json:"schema,omitempty"` + Strict json.RawMessage `json:"strict,omitempty"` } type GeneralOpenAIRequest struct { @@ -73,6 +73,15 @@ func (r *GeneralOpenAIRequest) ToMap() map[string]any { return result } +func (r *GeneralOpenAIRequest) GetSystemRoleName() string { + if strings.HasPrefix(r.Model, "o") { + if !strings.HasPrefix(r.Model, "o1-mini") && !strings.HasPrefix(r.Model, "o1-preview") { + return "developer" + } + } + return "system" +} + type ToolCallRequest struct { ID string `json:"id,omitempty"` Type string `json:"type"` diff --git a/i18n/zh-cn.json b/i18n/zh-cn.json index 160fc0a4..0c838c5c 100644 --- a/i18n/zh-cn.json +++ b/i18n/zh-cn.json @@ -585,6 +585,20 @@ "渠道权重": "渠道权重", "渠道额外设置": "渠道额外设置", "此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:": "此项可选,用于配置渠道特定设置,为一个 JSON 字符串,例如:", + "强制格式化": "强制格式化", + "强制格式化(只适用于OpenAI渠道类型)": "强制格式化(只适用于OpenAI渠道类型)", + "强制将响应格式化为 OpenAI 标准格式": "强制将响应格式化为 OpenAI 标准格式", + "思考内容转换": "思考内容转换", + "将 reasoning_content 转换为 标签拼接到内容中": "将 reasoning_content 转换为 标签拼接到内容中", + "透传请求体": "透传请求体", + "启用请求体透传功能": "启用请求体透传功能", + "代理地址": "代理地址", + "例如: socks5://user:pass@host:port": "例如: socks5://user:pass@host:port", + "用于配置网络代理": "用于配置网络代理", + "用于配置网络代理,支持 socks5 协议": "用于配置网络代理,支持 socks5 协议", + "系统提示词": "系统提示词", + "输入系统提示词,用户的系统提示词将优先于此设置": "输入系统提示词,用户的系统提示词将优先于此设置", + "用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置": "用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置", "参数覆盖": "参数覆盖", "此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:": "此项可选,用于覆盖请求参数。不支持覆盖 stream 参数。为一个 JSON 字符串,例如:", "请输入组织org-xxx": "请输入组织org-xxx", diff --git a/middleware/distributor.go b/middleware/distributor.go index 3c529a41..cba9b521 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -111,18 +111,17 @@ func Distribute() func(c *gin.Context) { if userGroup == "auto" { showGroup = fmt.Sprintf("auto(%s)", selectGroup) } - message := fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(distributor): %s", showGroup, modelRequest.Model, err.Error()) + message := fmt.Sprintf("获取分组 %s 下模型 %s 的可用渠道失败(数据库一致性已被破坏,distributor): %s", showGroup, modelRequest.Model, err.Error()) // 如果错误,但是渠道不为空,说明是数据库一致性问题 - if channel != nil { - common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) - message = "数据库一致性已被破坏,请联系管理员" - } - // 如果错误,而且渠道为空,说明是没有可用渠道 + //if channel != nil { + // common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) + // message = "数据库一致性已被破坏,请联系管理员" + //} abortWithOpenAiMessage(c, http.StatusServiceUnavailable, message) return } if channel == nil { - abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("分组 %s 下模型 %s 的可用渠道不存在(数据库一致性已被破坏,distributor)", userGroup, modelRequest.Model)) + abortWithOpenAiMessage(c, http.StatusServiceUnavailable, fmt.Sprintf("分组 %s 下模型 %s 无可用渠道(distributor)", userGroup, modelRequest.Model)) return } } diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index 6f3babeb..d19ee1ae 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -219,9 +219,13 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon if textRequest.ResponseFormat != nil && (textRequest.ResponseFormat.Type == "json_schema" || textRequest.ResponseFormat.Type == "json_object") { geminiRequest.GenerationConfig.ResponseMimeType = "application/json" - if textRequest.ResponseFormat.JsonSchema != nil && textRequest.ResponseFormat.JsonSchema.Schema != nil { - cleanedSchema := removeAdditionalPropertiesWithDepth(textRequest.ResponseFormat.JsonSchema.Schema, 0) - geminiRequest.GenerationConfig.ResponseSchema = cleanedSchema + if len(textRequest.ResponseFormat.JsonSchema) > 0 { + // 先将json.RawMessage解析 + var jsonSchema dto.FormatJsonSchema + if err := common.Unmarshal(textRequest.ResponseFormat.JsonSchema, &jsonSchema); err == nil { + cleanedSchema := removeAdditionalPropertiesWithDepth(jsonSchema.Schema, 0) + geminiRequest.GenerationConfig.ResponseSchema = cleanedSchema + } } } tool_call_ids := make(map[string]string) diff --git a/relay/claude_handler.go b/relay/claude_handler.go index 5f38960e..2c60a91e 100644 --- a/relay/claude_handler.go +++ b/relay/claude_handler.go @@ -80,7 +80,6 @@ func ClaudeHelper(c *gin.Context) (newAPIError *types.NewAPIError) { return types.NewError(fmt.Errorf("invalid api type: %d", relayInfo.ApiType), types.ErrorCodeInvalidApiType) } adaptor.Init(relayInfo) - var requestBody io.Reader if textRequest.MaxTokens == 0 { textRequest.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(textRequest.Model)) @@ -108,18 +107,41 @@ func ClaudeHelper(c *gin.Context) (newAPIError *types.NewAPIError) { relayInfo.UpstreamModelName = textRequest.Model } - convertedRequest, err := adaptor.ConvertClaudeRequest(c, relayInfo, textRequest) - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) + var requestBody io.Reader + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled { + body, err := common.GetRequestBody(c) + if err != nil { + return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest) + } + requestBody = bytes.NewBuffer(body) + } else { + convertedRequest, err := adaptor.ConvertClaudeRequest(c, relayInfo, textRequest) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } + jsonData, err := common.Marshal(convertedRequest) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } + + // apply param override + if len(relayInfo.ParamOverride) > 0 { + reqMap := make(map[string]interface{}) + _ = common.Unmarshal(jsonData, &reqMap) + for key, value := range relayInfo.ParamOverride { + reqMap[key] = value + } + jsonData, err = common.Marshal(reqMap) + if err != nil { + return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid) + } + } + + if common.DebugEnabled { + println("requestBody: ", string(jsonData)) + } + requestBody = bytes.NewBuffer(jsonData) } - jsonData, err := common.Marshal(convertedRequest) - if common.DebugEnabled { - println("requestBody: ", string(jsonData)) - } - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) - } - requestBody = bytes.NewBuffer(jsonData) statusCodeMappingStr := c.GetString("status_code_mapping") var httpResp *http.Response diff --git a/relay/gemini_handler.go b/relay/gemini_handler.go index e448b491..0f1aa5bf 100644 --- a/relay/gemini_handler.go +++ b/relay/gemini_handler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "one-api/common" "one-api/dto" @@ -194,16 +195,39 @@ func GeminiHelper(c *gin.Context) (newAPIError *types.NewAPIError) { } } - requestBody, err := json.Marshal(req) - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) + var requestBody io.Reader + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled { + body, err := common.GetRequestBody(c) + if err != nil { + return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest) + } + requestBody = bytes.NewReader(body) + } else { + jsonData, err := json.Marshal(req) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } + + // apply param override + if len(relayInfo.ParamOverride) > 0 { + reqMap := make(map[string]interface{}) + _ = common.Unmarshal(jsonData, &reqMap) + for key, value := range relayInfo.ParamOverride { + reqMap[key] = value + } + jsonData, err = common.Marshal(reqMap) + if err != nil { + return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid) + } + } + + if common.DebugEnabled { + println("Gemini request body: %s", string(jsonData)) + } + requestBody = bytes.NewReader(jsonData) } - if common.DebugEnabled { - println("Gemini request body: %s", string(requestBody)) - } - - resp, err := adaptor.DoRequest(c, relayInfo, bytes.NewReader(requestBody)) + resp, err := adaptor.DoRequest(c, relayInfo, requestBody) if err != nil { common.LogError(c, "Do gemini request failed: "+err.Error()) return types.NewError(err, types.ErrorCodeDoRequestFailed) diff --git a/relay/image_handler.go b/relay/image_handler.go index 8e059863..c97eb48e 100644 --- a/relay/image_handler.go +++ b/relay/image_handler.go @@ -16,6 +16,7 @@ import ( "one-api/relay/helper" "one-api/service" "one-api/setting" + "one-api/setting/model_setting" "one-api/types" "strings" @@ -187,22 +188,43 @@ func ImageHelper(c *gin.Context) (newAPIError *types.NewAPIError) { var requestBody io.Reader - convertedRequest, err := adaptor.ConvertImageRequest(c, relayInfo, *imageRequest) - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) - } - if relayInfo.RelayMode == relayconstant.RelayModeImagesEdits { - requestBody = convertedRequest.(io.Reader) + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled { + body, err := common.GetRequestBody(c) + if err != nil { + return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest) + } + requestBody = bytes.NewBuffer(body) } else { - jsonData, err := json.Marshal(convertedRequest) + convertedRequest, err := adaptor.ConvertImageRequest(c, relayInfo, *imageRequest) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed) } - requestBody = bytes.NewBuffer(jsonData) - } + if relayInfo.RelayMode == relayconstant.RelayModeImagesEdits { + requestBody = convertedRequest.(io.Reader) + } else { + jsonData, err := json.Marshal(convertedRequest) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } - if common.DebugEnabled { - println(fmt.Sprintf("image request body: %s", requestBody)) + // apply param override + if len(relayInfo.ParamOverride) > 0 { + reqMap := make(map[string]interface{}) + _ = common.Unmarshal(jsonData, &reqMap) + for key, value := range relayInfo.ParamOverride { + reqMap[key] = value + } + jsonData, err = common.Marshal(reqMap) + if err != nil { + return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid) + } + } + + if common.DebugEnabled { + println(fmt.Sprintf("image request body: %s", string(jsonData))) + } + requestBody = bytes.NewBuffer(jsonData) + } } statusCodeMappingStr := c.GetString("status_code_mapping") diff --git a/relay/relay-text.go b/relay/relay-text.go index 60327074..84d4e38b 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -2,7 +2,6 @@ package relay import ( "bytes" - "encoding/json" "errors" "fmt" "io" @@ -171,18 +170,42 @@ func TextHelper(c *gin.Context) (newAPIError *types.NewAPIError) { adaptor.Init(relayInfo) var requestBody io.Reader - if model_setting.GetGlobalSettings().PassThroughRequestEnabled { + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled { body, err := common.GetRequestBody(c) if err != nil { return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest) } + if common.DebugEnabled { + println("requestBody: ", string(body)) + } requestBody = bytes.NewBuffer(body) } else { convertedRequest, err := adaptor.ConvertOpenAIRequest(c, relayInfo, textRequest) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed) } - jsonData, err := json.Marshal(convertedRequest) + + if relayInfo.ChannelSetting.SystemPrompt != "" { + // 如果有系统提示,则将其添加到请求中 + request := convertedRequest.(*dto.GeneralOpenAIRequest) + containSystemPrompt := false + for _, message := range request.Messages { + if message.Role == request.GetSystemRoleName() { + containSystemPrompt = true + break + } + } + if !containSystemPrompt { + // 如果没有系统提示,则添加系统提示 + systemMessage := dto.Message{ + Role: request.GetSystemRoleName(), + Content: relayInfo.ChannelSetting.SystemPrompt, + } + request.Messages = append([]dto.Message{systemMessage}, request.Messages...) + } + } + + jsonData, err := common.Marshal(convertedRequest) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed) } diff --git a/relay/rerank_handler.go b/relay/rerank_handler.go index a092de4b..0190cf08 100644 --- a/relay/rerank_handler.go +++ b/relay/rerank_handler.go @@ -3,12 +3,14 @@ package relay import ( "bytes" "fmt" + "io" "net/http" "one-api/common" "one-api/dto" relaycommon "one-api/relay/common" "one-api/relay/helper" "one-api/service" + "one-api/setting/model_setting" "one-api/types" "github.com/gin-gonic/gin" @@ -70,18 +72,42 @@ func RerankHelper(c *gin.Context, relayMode int) (newAPIError *types.NewAPIError } adaptor.Init(relayInfo) - convertedRequest, err := adaptor.ConvertRerankRequest(c, relayInfo.RelayMode, *rerankRequest) - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) - } - jsonData, err := common.Marshal(convertedRequest) - if err != nil { - return types.NewError(err, types.ErrorCodeConvertRequestFailed) - } - requestBody := bytes.NewBuffer(jsonData) - if common.DebugEnabled { - println(fmt.Sprintf("Rerank request body: %s", requestBody.String())) + var requestBody io.Reader + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || relayInfo.ChannelSetting.PassThroughBodyEnabled { + body, err := common.GetRequestBody(c) + if err != nil { + return types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusBadRequest) + } + requestBody = bytes.NewBuffer(body) + } else { + convertedRequest, err := adaptor.ConvertRerankRequest(c, relayInfo.RelayMode, *rerankRequest) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } + jsonData, err := common.Marshal(convertedRequest) + if err != nil { + return types.NewError(err, types.ErrorCodeConvertRequestFailed) + } + + // apply param override + if len(relayInfo.ParamOverride) > 0 { + reqMap := make(map[string]interface{}) + _ = common.Unmarshal(jsonData, &reqMap) + for key, value := range relayInfo.ParamOverride { + reqMap[key] = value + } + jsonData, err = common.Marshal(reqMap) + if err != nil { + return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid) + } + } + + if common.DebugEnabled { + println(fmt.Sprintf("Rerank request body: %s", string(jsonData))) + } + requestBody = bytes.NewBuffer(jsonData) } + resp, err := adaptor.DoRequest(c, relayInfo, requestBody) if err != nil { return types.NewOpenAIError(err, types.ErrorCodeDoRequestFailed, http.StatusInternalServerError) diff --git a/types/error.go b/types/error.go index 4ffae2d7..fa07c231 100644 --- a/types/error.go +++ b/types/error.go @@ -28,6 +28,7 @@ const ( ErrorTypeMidjourneyError ErrorType = "midjourney_error" ErrorTypeGeminiError ErrorType = "gemini_error" ErrorTypeRerankError ErrorType = "rerank_error" + ErrorTypeUpstreamError ErrorType = "upstream_error" ) type ErrorCode string @@ -194,6 +195,9 @@ func WithOpenAIError(openAIError OpenAIError, statusCode int) *NewAPIError { if !ok { code = fmt.Sprintf("%v", openAIError.Code) } + if openAIError.Type == "" { + openAIError.Type = "upstream_error" + } return &NewAPIError{ RelayError: openAIError, errorType: ErrorTypeOpenAIError, @@ -204,6 +208,9 @@ func WithOpenAIError(openAIError OpenAIError, statusCode int) *NewAPIError { } func WithClaudeError(claudeError ClaudeError, statusCode int) *NewAPIError { + if claudeError.Type == "" { + claudeError.Type = "upstream_error" + } return &NewAPIError{ RelayError: claudeError, errorType: ErrorTypeClaudeError, diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index d2fd6758..f20c86d9 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -121,6 +121,12 @@ const EditChannelModal = (props) => { weight: 0, tag: '', multi_key_mode: 'random', + // 渠道额外设置的默认值 + force_format: false, + thinking_to_content: false, + proxy: '', + pass_through_body_enabled: false, + system_prompt: '', }; const [batch, setBatch] = useState(false); const [multiToSingle, setMultiToSingle] = useState(false); @@ -142,8 +148,69 @@ const EditChannelModal = (props) => { const [isMultiKeyChannel, setIsMultiKeyChannel] = useState(false); const [channelSearchValue, setChannelSearchValue] = useState(''); const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式 + // 渠道额外设置状态 + const [channelSettings, setChannelSettings] = useState({ + force_format: false, + thinking_to_content: false, + proxy: '', + pass_through_body_enabled: false, + system_prompt: '', + }); const showApiConfigCard = inputs.type !== 45; // 控制是否显示 API 配置卡片(仅当渠道类型不是 豆包 时显示) const getInitValues = () => ({ ...originInputs }); + + // 处理渠道额外设置的更新 + const handleChannelSettingsChange = (key, value) => { + // 更新内部状态 + setChannelSettings(prev => ({ ...prev, [key]: value })); + + // 同步更新到表单字段 + if (formApiRef.current) { + formApiRef.current.setValue(key, value); + } + + // 同步更新inputs状态 + setInputs(prev => ({ ...prev, [key]: value })); + + // 生成setting JSON并更新 + const newSettings = { ...channelSettings, [key]: value }; + const settingsJson = JSON.stringify(newSettings); + handleInputChange('setting', settingsJson); + }; + + // 解析渠道设置JSON为单独的状态 + const parseChannelSettings = (settingJson) => { + try { + if (settingJson && settingJson.trim()) { + const parsed = JSON.parse(settingJson); + setChannelSettings({ + force_format: parsed.force_format || false, + thinking_to_content: parsed.thinking_to_content || false, + proxy: parsed.proxy || '', + pass_through_body_enabled: parsed.pass_through_body_enabled || false, + system_prompt: parsed.system_prompt || '', + }); + } else { + setChannelSettings({ + force_format: false, + thinking_to_content: false, + proxy: '', + pass_through_body_enabled: false, + system_prompt: '', + }); + } + } catch (error) { + console.error('解析渠道设置失败:', error); + setChannelSettings({ + force_format: false, + thinking_to_content: false, + proxy: '', + pass_through_body_enabled: false, + system_prompt: '', + }); + } + }; + const handleInputChange = (name, value) => { if (formApiRef.current) { formApiRef.current.setValue(name, value); @@ -256,6 +323,30 @@ const EditChannelModal = (props) => { setBatch(false); setMultiToSingle(false); } + // 解析渠道额外设置并合并到data中 + if (data.setting) { + try { + const parsedSettings = JSON.parse(data.setting); + data.force_format = parsedSettings.force_format || false; + data.thinking_to_content = parsedSettings.thinking_to_content || false; + data.proxy = parsedSettings.proxy || ''; + data.pass_through_body_enabled = parsedSettings.pass_through_body_enabled || false; + data.system_prompt = parsedSettings.system_prompt || ''; + } catch (error) { + console.error('解析渠道设置失败:', error); + data.force_format = false; + data.thinking_to_content = false; + data.proxy = ''; + data.pass_through_body_enabled = false; + } + } else { + data.force_format = false; + data.thinking_to_content = false; + data.proxy = ''; + data.pass_through_body_enabled = false; + data.system_prompt = ''; + } + setInputs(data); if (formApiRef.current) { formApiRef.current.setValues(data); @@ -266,6 +357,14 @@ const EditChannelModal = (props) => { setAutoBan(true); } setBasicModels(getChannelModels(data.type)); + // 同步更新channelSettings状态显示 + setChannelSettings({ + force_format: data.force_format, + thinking_to_content: data.thinking_to_content, + proxy: data.proxy, + pass_through_body_enabled: data.pass_through_body_enabled, + system_prompt: data.system_prompt, + }); // console.log(data); } else { showError(message); @@ -446,6 +545,14 @@ const EditChannelModal = (props) => { setUseManualInput(false); } else { formApiRef.current?.reset(); + // 重置渠道设置状态 + setChannelSettings({ + force_format: false, + thinking_to_content: false, + proxy: '', + pass_through_body_enabled: false, + system_prompt: '', + }); } }, [props.visible, channelId]); @@ -579,6 +686,24 @@ const EditChannelModal = (props) => { if (localInputs.type === 18 && localInputs.other === '') { localInputs.other = 'v2.1'; } + + // 生成渠道额外设置JSON + const channelExtraSettings = { + force_format: localInputs.force_format || false, + thinking_to_content: localInputs.thinking_to_content || false, + proxy: localInputs.proxy || '', + pass_through_body_enabled: localInputs.pass_through_body_enabled || false, + system_prompt: localInputs.system_prompt || '', + }; + localInputs.setting = JSON.stringify(channelExtraSettings); + + // 清理不需要发送到后端的字段 + delete localInputs.force_format; + delete localInputs.thinking_to_content; + delete localInputs.proxy; + delete localInputs.pass_through_body_enabled; + delete localInputs.system_prompt; + let res; localInputs.auto_ban = localInputs.auto_ban ? 1 : 0; localInputs.models = localInputs.models.join(','); @@ -1446,33 +1571,103 @@ const EditChannelModal = (props) => { showClear /> - handleInputChange('setting', value)} - extraText={( - +
+ + {t('渠道额外设置')} + +
+ + +
+ {t('强制格式化(只适用于OpenAI渠道类型)')} + + {t('强制将响应格式化为 OpenAI 标准格式')} + +
+ + + handleChannelSettingsChange('force_format', val)} + /> + +
+ + + +
+ {t('思考内容转换')} + + {t('将 reasoning_content 转换为 标签拼接到内容中')} + +
+ + + handleChannelSettingsChange('thinking_to_content', val)} + /> + +
+ + + +
+ {t('透传请求体')} + + {t('启用请求体透传功能')} + +
+ + + handleChannelSettingsChange('pass_through_body_enabled', val)} + /> + +
+ +
+ handleChannelSettingsChange('proxy', val)} + showClear + helpText={t('用于配置网络代理')} + /> +
+ +
+ handleChannelSettingsChange('system_prompt', val)} + autosize + showClear + helpText={t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')} + /> +
+ +
handleInputChange('setting', JSON.stringify({ force_format: true }, null, 2))} - > - {t('填入模板')} - - window.open('https://github.com/QuantumNous/new-api/blob/main/docs/channel/other_setting.md')} > {t('设置说明')} - - )} - showClear - /> +
+
+
+ + diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 5762533f..d340d825 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1330,6 +1330,19 @@ "API地址": "Base URL", "对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写": "For official channels, the new-api has a built-in address. Unless it is a third-party proxy site or a special Azure access address, there is no need to fill it in", "渠道额外设置": "Channel extra settings", + "强制格式化": "Force format", + "强制格式化(只适用于OpenAI渠道类型)": "Force format (Only for OpenAI channel types)", + "强制将响应格式化为 OpenAI 标准格式": "Force format responses to OpenAI standard format", + "思考内容转换": "Thinking content conversion", + "将 reasoning_content 转换为 标签拼接到内容中": "Convert reasoning_content to tags and append to content", + "透传请求体": "Pass through body", + "启用请求体透传功能": "Enable request body pass-through functionality", + "代理地址": "Proxy address", + "例如: socks5://user:pass@host:port": "e.g.: socks5://user:pass@host:port", + "用于配置网络代理,支持 socks5 协议": "Used to configure network proxy, supports socks5 protocol", + "系统提示词": "System Prompt", + "输入系统提示词,用户的系统提示词将优先于此设置": "Enter system prompt, user's system prompt will take priority over this setting", + "用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置": "User priority: If the user specifies a system prompt in the request, the user's setting will be used first", "参数覆盖": "Parameters override", "模型请求速率限制": "Model request rate limit", "启用用户模型请求速率限制(可能会影响高并发性能)": "Enable user model request rate limit (may affect high concurrency performance)",