Ver Fonte

✨ feat(api): add header override processing with variable support

CaIon há 2 meses atrás
pai
commit
01bcbf09c6

+ 34 - 14
relay/channel/api_request.go

@@ -14,6 +14,7 @@ import (
 	"one-api/service"
 	"one-api/setting/operation_setting"
 	"one-api/types"
+	"strings"
 	"sync"
 	"time"
 
@@ -36,6 +37,26 @@ func SetupApiRequestHeader(info *common.RelayInfo, c *gin.Context, req *http.Hea
 	}
 }
 
+// processHeaderOverride 处理请求头覆盖,支持变量替换
+// 支持的变量:{api_key}
+func processHeaderOverride(info *common.RelayInfo) (map[string]string, error) {
+	headerOverride := make(map[string]string)
+	for k, v := range info.HeadersOverride {
+		str, ok := v.(string)
+		if !ok {
+			return nil, types.NewError(nil, types.ErrorCodeChannelHeaderOverrideInvalid)
+		}
+
+		// 替换支持的变量
+		if strings.Contains(str, "{api_key}") {
+			str = strings.ReplaceAll(str, "{api_key}", info.ApiKey)
+		}
+
+		headerOverride[k] = str
+	}
+	return headerOverride, nil
+}
+
 func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody io.Reader) (*http.Response, error) {
 	fullRequestURL, err := a.GetRequestURL(info)
 	if err != nil {
@@ -49,13 +70,9 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
 		return nil, fmt.Errorf("new request failed: %w", err)
 	}
 	headers := req.Header
-	headerOverride := make(map[string]string)
-	for k, v := range info.HeadersOverride {
-		if str, ok := v.(string); ok {
-			headerOverride[k] = str
-		} else {
-			return nil, types.NewError(err, types.ErrorCodeChannelHeaderOverrideInvalid)
-		}
+	headerOverride, err := processHeaderOverride(info)
+	if err != nil {
+		return nil, err
 	}
 	for key, value := range headerOverride {
 		headers.Set(key, value)
@@ -86,13 +103,9 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod
 	// set form data
 	req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
 	headers := req.Header
-	headerOverride := make(map[string]string)
-	for k, v := range info.HeadersOverride {
-		if str, ok := v.(string); ok {
-			headerOverride[k] = str
-		} else {
-			return nil, types.NewError(err, types.ErrorCodeChannelHeaderOverrideInvalid)
-		}
+	headerOverride, err := processHeaderOverride(info)
+	if err != nil {
+		return nil, err
 	}
 	for key, value := range headerOverride {
 		headers.Set(key, value)
@@ -114,6 +127,13 @@ func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody
 		return nil, fmt.Errorf("get request url failed: %w", err)
 	}
 	targetHeader := http.Header{}
+	headerOverride, err := processHeaderOverride(info)
+	if err != nil {
+		return nil, err
+	}
+	for key, value := range headerOverride {
+		targetHeader.Set(key, value)
+	}
 	err = a.SetupRequestHeader(c, &targetHeader, info)
 	if err != nil {
 		return nil, fmt.Errorf("setup request header failed: %w", err)

+ 32 - 20
web/src/components/table/channels/modals/EditChannelModal.jsx

@@ -2452,32 +2452,44 @@ const EditChannelModal = (props) => {
                       t('此项可选,用于覆盖请求头参数') +
                       '\n' +
                       t('格式示例:') +
-                      '\n{\n  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0"\n}'
+                      '\n{\n  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",\n  "Authorization": "Bearer {api_key}"\n}'
                     }
                     autosize
                     onChange={(value) =>
                       handleInputChange('header_override', value)
                     }
                     extraText={
-                      <div className='flex gap-2 flex-wrap'>
-                        <Text
-                          className='!text-semi-color-primary cursor-pointer'
-                          onClick={() =>
-                            handleInputChange(
-                              'header_override',
-                              JSON.stringify(
-                                {
-                                  'User-Agent':
-                                    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
-                                },
-                                null,
-                                2,
-                              ),
-                            )
-                          }
-                        >
-                          {t('格式模板')}
-                        </Text>
+
+                      <div className='flex flex-col gap-1'>
+                        <div className='flex gap-2 flex-wrap items-center'>
+                          <Text
+                            className='!text-semi-color-primary cursor-pointer'
+                            onClick={() =>
+                              handleInputChange(
+                                'header_override',
+                                JSON.stringify(
+                                  {
+                                    'User-Agent':
+                                      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0',
+                                    'Authorization': 'Bearer{api_key}',
+                                  },
+                                  null,
+                                  2,
+                                ),
+                              )
+                            }
+                          >
+                            {t('填入模板')}
+                          </Text>
+                        </div>
+                        <div>
+                          <Text type='tertiary' size='small'>
+                            {t('支持变量:')}
+                          </Text>
+                          <div className='text-xs text-tertiary ml-2'>
+                            <div>{t('渠道密钥')}: {'{api_key}'}</div>
+                          </div>
+                        </div>
                       </div>
                     }
                     showClear

+ 15 - 1
web/src/i18n/locales/en.json

@@ -2221,7 +2221,6 @@
   "留空将自动使用服务器地址,多个 Origin 用于支持多域名部署": "Leave blank to auto-use server address, multiple Origins for multi-domain deployment",
   "输入 Origin 后回车,如:https://example.com": "Enter Origin and press Enter, e.g.: https://example.com",
   "保存 Passkey 设置": "Save Passkey Settings",
-  "黑名单": "Blacklist",
   "字段透传控制": "Field Pass-through Control",
   "允许 service_tier 透传": "Allow service_tier Pass-through",
   "禁用 store 透传": "Disable store Pass-through",
@@ -2229,6 +2228,21 @@
   "service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用": "The service_tier field is used to specify service level. Allowing pass-through may result in higher billing than expected. Disabled by default to avoid extra charges",
   "store 字段用于授权 OpenAI 存储请求数据以评估和优化产品。默认关闭,开启后可能导致 Codex 无法正常使用": "The store field authorizes OpenAI to store request data for product evaluation and optimization. Disabled by default. Enabling may cause Codex to malfunction",
   "safety_identifier 字段用于帮助 OpenAI 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私": "The safety_identifier field helps OpenAI identify application users who may violate usage policies. Disabled by default to protect user privacy",
+  "支持变量:": "Supported variables:",
+  "请求头覆盖": "Request header override",
+  "旧格式模板": "Old format template",
+  "新格式模板": "New format template",
+  "系统提示词拼接": "System prompt append",
+  "如果用户请求中包含系统提示词,则使用此设置拼接到用户的系统提示词前面": "If the user request contains a system prompt, this setting will be appended to the user's system prompt",
+  "键为请求中的模型名称,值为要替换的模型名称": "Key is the model name in the request, value is the model name to replace",
+  "仅影响本地判断,不修改返回到上游的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:": "Only affects local judgment, does not modify the status code returned to the upstream, for example, rewrite the 400 error of the claude channel to 500 (for retry). Please do not abuse this function, for example:",
+  "密钥更新模式": "Key update mode",
+  "请选择密钥更新模式": "Please select key update mode",
+  "追加到现有密钥": "Append to existing key",
+  "覆盖现有密钥": "Overwrite existing key",
+  "追加模式:将新密钥添加到现有密钥列表末尾": "Append mode: add new keys to the end of the existing key list",
+  "覆盖模式:将完全替换现有的所有密钥": "Overwrite mode: completely replace all existing keys",
+  "轮询模式必须搭配Redis和内存缓存功能使用,否则性能将大幅降低,并且无法实现轮询功能": "Polling mode must be used with Redis and memory cache functions, otherwise the performance will be significantly reduced and the polling function will not be implemented",
   "common": {
     "changeLanguage": "Change Language"
   }