[ADD] 添加透传请求头功能以支持自定义请求头传递 - #2365
Conversation
Comment out the debug log for MiniMax TTS Request.
增加MiniMax语音合成TTS支持
…ort-stream-options Ali channel support stream options
…ream feat: openai tts support streaming realtime audio
…ream feat: doubao tts support streaming realtime audio
multipart/form-data; boundary
…edit 修复豆包图像编辑(图生图)功能
…watermark fix: correct bool value for watermark
Signed-off-by: iwu <iwu@tencent.com>
feat(i-18n): Add Japanese localization
…orrect-symbol feat: topUp show correct symbol
…rt-proxy feat: aws client supports proxy settings
chore: Update AWS claude 4.5 haiku model's information
chore: Ignore .zed and debug binaries in .gitignore
…r clearing configurations
…mpty defaults in OverrideEditor
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
web/src/components/common/ui/OverrideEditor.jsx (2)
89-110: Non-stringvalueinput can cause runtime errorThis has been flagged in a previous review. The function assumes
valueis a string and callsvalue.trim()directly on line 91. If a caller passes an object (e.g., from a form library that stores parsed JSON), this will throw.The same issue applies to
handleOpenModalat line 250-251.
736-748: Invalid JSON allows switch to visual mode, causing silent data lossThis was flagged in a previous review. When switching from JSON to visual mode with invalid JSON,
importFromJSONreturnsnullbutsetEditMode(key)is still called, leaving the user on the visual tab with stale data.
🧹 Nitpick comments (1)
web/src/components/common/ui/OverrideEditor.jsx (1)
161-188: Consider movingserializeOperationsoutside component or wrapping withuseCallback
serializeOperationsis defined inline but used within thebuildPreviewcallback without being listed in its dependency array. Currently safe sinceserializeOperationshas no state dependencies, but this pattern is fragile for future maintenance.Move
serializeOperationsoutside the component to make the intent explicit:+const serializeOperations = (ops) => + ops + .filter((op) => op.path) + .map((op) => ({ + path: op.path, + mode: op.mode, + value: parseMaybeJSON(op.value), + keep_origin: !!op.keep_origin, + from: op.from, + to: op.to, + logic: op.logic || 'OR', + conditions: (op.conditions || []).map((c) => ({ + path: c.path, + mode: c.mode || 'full', + value: parseMaybeJSON(c.value), + invert: !!c.invert, + pass_missing_key: !!c.pass_missing_key, + })), + })); + const OverrideEditor = ({ ... }) => { ... - const serializeOperations = (ops) => - ops - .filter((op) => op.path) - ...
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web/src/components/common/ui/OverrideEditor.jsx(1 hunks)
🔇 Additional comments (4)
web/src/components/common/ui/OverrideEditor.jsx (4)
112-127: LGTM!Good defensive handling of the
valueprop injsonTextinitialization, converting object values to JSON strings at line 126.
283-352: LGTM!The operation and condition CRUD handlers use functional state updates with proper immutable patterns.
443-676: LGTM!The visual editor renders operations and conditions with proper unique keys and well-structured UI using Semi components.
707-755: LGTM!Good UX patterns: destructive action (clear) requires confirmation via
Popconfirm, modal has responsive sizing, and content area handles overflow properly.
| <div | ||
| style={{ | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| gap: 8, | ||
| padding: '8px 12px', | ||
| backgroundColor: 'var(--semi-color-fill-0)', | ||
| borderRadius: 6, | ||
| cursor: 'pointer', | ||
| }} | ||
| onClick={handleOpenModal} | ||
| onMouseEnter={(e) => { | ||
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)'; | ||
| }} | ||
| onMouseLeave={(e) => { | ||
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)'; | ||
| }} | ||
| > | ||
| <div style={{ flex: 1, minWidth: 0 }}> | ||
| {hasConfig ? ( | ||
| <Space wrap size='small'> | ||
| <Tag color='blue' size='small'> | ||
| {t('{{count}} 条规则', { count: configSummary.count })} | ||
| </Tag> | ||
| {configSummary.items.slice(0, 2).map((item, idx) => ( | ||
| <Text key={idx} type='tertiary' size='small'> | ||
| {item.path} | ||
| {item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>} | ||
| </Text> | ||
| ))} | ||
| {configSummary.items.length > 2 && ( | ||
| <Text type='tertiary' size='small'>...</Text> | ||
| )} | ||
| </Space> | ||
| ) : ( | ||
| <Text type='tertiary' size='small'>{t('点击配置')}</Text> | ||
| )} | ||
| </div> | ||
| <IconEdit style={{ color: 'var(--semi-color-text-2)' }} /> | ||
| </div> |
There was a problem hiding this comment.
Add keyboard accessibility to the clickable trigger
The trigger div uses onClick but lacks keyboard support. Users navigating with keyboard cannot activate this control.
Add role, tabIndex, and keyboard handler:
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 12px',
backgroundColor: 'var(--semi-color-fill-0)',
borderRadius: 6,
cursor: 'pointer',
}}
+ role="button"
+ tabIndex={0}
onClick={handleOpenModal}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleOpenModal();
+ }
+ }}
onMouseEnter={(e) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: 8, | |
| padding: '8px 12px', | |
| backgroundColor: 'var(--semi-color-fill-0)', | |
| borderRadius: 6, | |
| cursor: 'pointer', | |
| }} | |
| onClick={handleOpenModal} | |
| onMouseEnter={(e) => { | |
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)'; | |
| }} | |
| onMouseLeave={(e) => { | |
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)'; | |
| }} | |
| > | |
| <div style={{ flex: 1, minWidth: 0 }}> | |
| {hasConfig ? ( | |
| <Space wrap size='small'> | |
| <Tag color='blue' size='small'> | |
| {t('{{count}} 条规则', { count: configSummary.count })} | |
| </Tag> | |
| {configSummary.items.slice(0, 2).map((item, idx) => ( | |
| <Text key={idx} type='tertiary' size='small'> | |
| {item.path} | |
| {item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>} | |
| </Text> | |
| ))} | |
| {configSummary.items.length > 2 && ( | |
| <Text type='tertiary' size='small'>...</Text> | |
| )} | |
| </Space> | |
| ) : ( | |
| <Text type='tertiary' size='small'>{t('点击配置')}</Text> | |
| )} | |
| </div> | |
| <IconEdit style={{ color: 'var(--semi-color-text-2)' }} /> | |
| </div> | |
| <div | |
| style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: 8, | |
| padding: '8px 12px', | |
| backgroundColor: 'var(--semi-color-fill-0)', | |
| borderRadius: 6, | |
| cursor: 'pointer', | |
| }} | |
| role="button" | |
| tabIndex={0} | |
| onClick={handleOpenModal} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| handleOpenModal(); | |
| } | |
| }} | |
| onMouseEnter={(e) => { | |
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-1)'; | |
| }} | |
| onMouseLeave={(e) => { | |
| e.currentTarget.style.backgroundColor = 'var(--semi-color-fill-0)'; | |
| }} | |
| > | |
| <div style={{ flex: 1, minWidth: 0 }}> | |
| {hasConfig ? ( | |
| <Space wrap size='small'> | |
| <Tag color='blue' size='small'> | |
| {t('{{count}} 条规则', { count: configSummary.count })} | |
| </Tag> | |
| {configSummary.items.slice(0, 2).map((item, idx) => ( | |
| <Text key={idx} type='tertiary' size='small'> | |
| {item.path} | |
| {item.hasConditions && <span style={{ color: 'var(--semi-color-warning)' }}>*</span>} | |
| </Text> | |
| ))} | |
| {configSummary.items.length > 2 && ( | |
| <Text type='tertiary' size='small'>...</Text> | |
| )} | |
| </Space> | |
| ) : ( | |
| <Text type='tertiary' size='small'>{t('点击配置')}</Text> | |
| )} | |
| </div> | |
| <IconEdit style={{ color: 'var(--semi-color-text-2)' }} /> | |
| </div> |
🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 371 to 410, the
clickable div trigger only uses onClick and lacks keyboard accessibility; update
the div to behave like an accessible button by adding role="button" and
tabIndex={0}, and implement a keyboard handler (onKeyDown) that calls
handleOpenModal when Enter or Space is pressed (prevent default for Space to
avoid scrolling) so keyboard users can activate the control; keep the existing
mouse handlers and visual styles intact.
| {op.mode === 'move' && ( | ||
| <Row gutter={8}> | ||
| <Col span={12}> | ||
| <div> | ||
| <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>From</Text> | ||
| <Input | ||
| value={op.from} | ||
| onChange={(val) => updateTempOperation(op.id, 'from', val)} | ||
| placeholder={t('如 meta.old')} | ||
| size='small' | ||
| /> | ||
| </div> | ||
| </Col> | ||
| <Col span={12}> | ||
| <div> | ||
| <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>To</Text> | ||
| <Input | ||
| value={op.to} | ||
| onChange={(val) => updateTempOperation(op.id, 'to', val)} | ||
| placeholder={t('如 meta.new')} | ||
| size='small' | ||
| /> | ||
| </div> | ||
| </Col> | ||
| </Row> |
There was a problem hiding this comment.
Localize "From" and "To" labels
These labels are hardcoded in English while other UI strings use t() for localization.
<Col span={12}>
<div>
- <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>From</Text>
+ <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>{t('From')}</Text>
<Input
...
</div>
</Col>
<Col span={12}>
<div>
- <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>To</Text>
+ <Text type='tertiary' size='small' style={{ display: 'block', marginBottom: 4 }}>{t('To')}</Text>🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 517 to 541 the
"From" and "To" Text labels are hardcoded in English; wrap those strings with
the translation function (t) like the other UI strings (e.g., t('From') and
t('To') or their appropriate i18n keys), keeping the existing props (type, size,
style) intact so layout and styling remain unchanged; ensure keys exist in the
localization files or add them if necessary.
| <Tabs.TabPane tab={t('可视化')} itemKey='visual' /> | ||
| <Tabs.TabPane tab='JSON' itemKey='json' /> | ||
| </Tabs> |
There was a problem hiding this comment.
Localize "JSON" tab label
The "JSON" tab label is hardcoded while the other tab uses t('可视化').
<Tabs.TabPane tab={t('可视化')} itemKey='visual' />
- <Tabs.TabPane tab='JSON' itemKey='json' />
+ <Tabs.TabPane tab={t('JSON')} itemKey='json' />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Tabs.TabPane tab={t('可视化')} itemKey='visual' /> | |
| <Tabs.TabPane tab='JSON' itemKey='json' /> | |
| </Tabs> | |
| <Tabs.TabPane tab={t('可视化')} itemKey='visual' /> | |
| <Tabs.TabPane tab={t('JSON')} itemKey='json' /> | |
| </Tabs> |
🤖 Prompt for AI Agents
In web/src/components/common/ui/OverrideEditor.jsx around lines 746 to 748, the
second Tabs.TabPane uses a hardcoded 'JSON' label while the first tab uses the
i18n function t(...). Replace the hardcoded label with the i18n call (e.g.
t('JSON') or the project’s preferred key like t('json')) so both tabs are
localized, keeping the existing itemKey='json'; also update the locale resource
file with the appropriate translation key if it does not yet exist.
|
发现你还弄了UI,看起来挺酷的,我需要一些时间去看这个PR |
ui大概是这样了,但还没验证逻辑请求头请求体的逻辑改的有没有问题 |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
relay/common/override.go (2)
476-478: Acknowledged: API key exposure in template context.This exposes
api_keyfor templates likeBearer {{context.api_key}}(as shown in the header override templates). Since override configurations are admin-only settings stored at the channel/tag level, this is acceptable if access controls are properly enforced.Ensure override configurations remain admin-only and cannot be influenced by end-user input.
516-533: Acknowledged: Client headers exposure includes sensitive values.All client headers are exposed under
client_headers.*, including potentially sensitive headers likeAuthorization,Cookie, andX-Api-Key. This enables pass-through functionality but increases exposure risk if template configurations are misconfigured.Consider documenting this behavior and the security implications for administrators configuring header overrides.
🧹 Nitpick comments (4)
relay/relay_task.go (1)
52-57: Upstream request body capture is good, but consider logging failures and enforcing a consistent typeThis block nicely keeps the upstream task payload available for header templates, but both
GetTaskRequestandMarshalerrors are silently ignored. That can make debugging header-override templates harder when the context key is unexpectedly missing.Two concrete suggestions:
- Log unexpected failures instead of fully swallowing them, while still treating this as best-effort:
- if taskReq, err := relaycommon.GetTaskRequest(c); err == nil { - if taskJSON, err := common.Marshal(taskReq); err == nil { - common.SetContextKey(c, constant.ContextKeyUpstreamRequestBody, string(taskJSON)) - } - } + if taskReq, err := relaycommon.GetTaskRequest(c); err != nil { + common.SysLog("GetTaskRequest failed in RelayTaskSubmit: " + err.Error()) + } else if taskJSON, err := common.Marshal(taskReq); err != nil { + common.SysLog("Marshal task request failed in RelayTaskSubmit: " + err.Error()) + } else { + common.SetContextKey(c, constant.ContextKeyUpstreamRequestBody, string(taskJSON)) + }
- Verify that the stored type (
stringJSON here) matches all other writers/readers ofContextKeyUpstreamRequestBodyin the relay handlers andBuildOverrideContext. If any other code assumes[]byteormap[string]any, aligning on one shared type (or adding a clear type switch at the read site) will avoid subtle panics or template-evaluation errors at runtime.relay/websocket.go (1)
25-26: Verify and standardize the type used forContextKeyUpstreamRequestBodyInitializing the upstream body with
"{}"here is a sensible way to keep the override/template pipeline non‑nil for realtime channels. However, this also implicitly fixes the representation ofContextKeyUpstreamRequestBodyas a JSON string.To avoid subtle panics or extra conversions:
- Please confirm that all other writers/readers of
ContextKeyUpstreamRequestBody(HTTP relay handlers, middlewares, template logic) also treat this value consistently as a JSON string rather than[]byteormap[string]any.- Consider adding small helpers such as
SetUpstreamRequestBody(c, jsonStr string)/GetUpstreamRequestBody(c) (string, bool)in a shared place (e.g.,relay/common) and using them here and elsewhere, so the representation is enforced in one spot.web/src/components/table/channels/modals/EditTagModal.jsx (1)
81-168: Consider extracting shared templates to reduce duplication.The
paramOverrideTemplatesandheaderOverrideTemplatesdefinitions are identical to those inEditChannelModal.jsx. This duplication increases maintenance burden—if templates need updating, both files must change.Consider extracting these templates to a shared constants file:
// e.g., web/src/constants/overrideTemplates.js export const getParamOverrideTemplates = (t) => [ { label: t('按模型前缀设置温度'), data: { /* ... */ }, }, // ... ]; export const getHeaderOverrideTemplates = (t) => [ // ... ];Then import and use in both modals:
import { getParamOverrideTemplates, getHeaderOverrideTemplates } from '../../../../constants/overrideTemplates'; const paramOverrideTemplates = useMemo(() => getParamOverrideTemplates(t), [t]);relay/common/override.go (1)
537-547: Silent error handling is acceptable here.Marshal errors result in an empty string, which means template placeholders remain unrendered. This is a safe fallback. Consider adding debug logging for marshal failures to aid troubleshooting configuration issues.
func marshalContext(ctx map[string]interface{}) string { if ctx == nil || len(ctx) == 0 { return "" } bytes, err := common.Marshal(ctx) if err != nil { + common.SysLog("marshalContext failed: " + err.Error()) return "" } return string(bytes) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
relay/audio_handler.go(2 hunks)relay/common/override.go(4 hunks)relay/relay_task.go(1 hunks)relay/websocket.go(2 hunks)web/src/components/table/channels/modals/EditChannelModal.jsx(4 hunks)web/src/components/table/channels/modals/EditTagModal.jsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- web/src/components/table/channels/modals/EditChannelModal.jsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-08T17:12:43.157Z
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.
Applied to files:
relay/audio_handler.go
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
Applied to files:
relay/relay_task.go
🧬 Code graph analysis (5)
relay/audio_handler.go (3)
common/json.go (1)
Marshal(21-23)common/gin.go (1)
SetContextKey(62-64)constant/context_key.go (1)
ContextKeyUpstreamRequestBody(36-36)
web/src/components/table/channels/modals/EditTagModal.jsx (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)
paramOverrideTemplates(132-177)headerOverrideTemplates(179-219)web/src/components/common/ui/OverrideEditor.jsx (1)
OverrideEditor(112-756)
relay/relay_task.go (4)
relay/common/relay_utils.go (1)
GetTaskRequest(62-72)common/json.go (1)
Marshal(21-23)common/gin.go (1)
SetContextKey(62-64)constant/context_key.go (1)
ContextKeyUpstreamRequestBody(36-36)
relay/websocket.go (2)
common/gin.go (1)
SetContextKey(62-64)constant/context_key.go (1)
ContextKeyUpstreamRequestBody(36-36)
relay/common/override.go (2)
relay/common/relay_info.go (1)
RelayInfo(81-128)common/json.go (2)
Unmarshal(9-11)Marshal(21-23)
🔇 Additional comments (8)
relay/websocket.go (1)
3-15: Imports for context storage look correctThe new
commonandconstantimports are used by the context‑key write and align with existing package structure; no issues here.relay/audio_handler.go (2)
9-9: LGTM!The import addition is necessary for accessing
constant.ContextKeyUpstreamRequestBody.
43-46: The review comment cannot be definitively evaluated without access to the codebase to verify the proposed changes, check for the availability of the suggested logging function, and confirm consistency patterns across similar handlers.web/src/components/table/channels/modals/EditTagModal.jsx (2)
638-654: LGTM with minor consideration.The
OverrideEditorintegration follows the same pattern asEditChannelModal.jsx. TheformApi={formApiRef.current}may benullon initial render, but theOverrideEditorcomponent handles this gracefully with optional chaining (formApi && typeof formApi.setValue === 'function').The "不更改" reset option correctly sets the value to
null, which thehandleSavelogic interprets as "no change" for this field.
656-672: LGTM!The header override editor follows the same correct pattern as the param override editor, with appropriate
type='header'and matching templates.relay/common/override.go (3)
34-50: LGTM - Template context initialization.The refactoring to pass
templateContextthroughapplyOperationsis clean. The delegation pattern allows template rendering to use the same context as condition evaluation.
313-357: Correct rendering order and error propagation.Templates are rendered before condition evaluation, which is the correct order. The rendered operation fields are then used consistently for both condition checks and the actual modifications. Error handling is properly preserved.
569-589: LGTM - Comprehensive template field rendering.The function correctly renders templates in all relevant fields (
Path,From,To,Value, and conditionPath/Value). Only string values undergo template substitution, which is the correct behavior for numeric/boolean operation values.
| // renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串 | ||
| func renderTemplateString(raw, contextJSON string) string { | ||
| if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") { | ||
| return raw | ||
| } | ||
| return templatePattern.ReplaceAllStringFunc(raw, func(match string) string { | ||
| path := strings.TrimSpace(match[2 : len(match)-2]) | ||
| if path == "" { | ||
| return "" | ||
| } | ||
| val := gjson.Get(contextJSON, path) | ||
| if !val.Exists() { | ||
| return "" | ||
| } | ||
| return val.String() | ||
| }) | ||
| } |
There was a problem hiding this comment.
Missing template values silently become empty strings.
When a template placeholder like {{client_headers.X-Missing-Header}} references a non-existent path, it renders to an empty string. This could cause unexpected behavior:
- Empty header names in
pathfield - Empty values being set where the original was expected
Consider preserving the original placeholder when the value is not found, or logging a warning:
func renderTemplateString(raw, contextJSON string) string {
if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") {
return raw
}
return templatePattern.ReplaceAllStringFunc(raw, func(match string) string {
path := strings.TrimSpace(match[2 : len(match)-2])
if path == "" {
return ""
}
val := gjson.Get(contextJSON, path)
if !val.Exists() {
- return ""
+ // Preserve original placeholder when value not found
+ return match
}
return val.String()
})
}Alternatively, if empty string is intentional, document this behavior clearly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串 | |
| func renderTemplateString(raw, contextJSON string) string { | |
| if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") { | |
| return raw | |
| } | |
| return templatePattern.ReplaceAllStringFunc(raw, func(match string) string { | |
| path := strings.TrimSpace(match[2 : len(match)-2]) | |
| if path == "" { | |
| return "" | |
| } | |
| val := gjson.Get(contextJSON, path) | |
| if !val.Exists() { | |
| return "" | |
| } | |
| return val.String() | |
| }) | |
| } | |
| // renderTemplateString 在给定上下文中渲染模板占位符,未命中时返回空字符串 | |
| func renderTemplateString(raw, contextJSON string) string { | |
| if raw == "" || contextJSON == "" || !strings.Contains(raw, "{{") { | |
| return raw | |
| } | |
| return templatePattern.ReplaceAllStringFunc(raw, func(match string) string { | |
| path := strings.TrimSpace(match[2 : len(match)-2]) | |
| if path == "" { | |
| return "" | |
| } | |
| val := gjson.Get(contextJSON, path) | |
| if !val.Exists() { | |
| // Preserve original placeholder when value not found | |
| return match | |
| } | |
| return val.String() | |
| }) | |
| } |
🤖 Prompt for AI Agents
In relay/common/override.go around lines 551 to 567, the current
renderTemplateString function silently replaces missing or empty template values
with empty strings; change it so missing values or empty paths return the
original placeholder (match) instead of "" and add a warning log when a path is
not found to aid debugging; specifically, when path == "" or val.Exists() is
false, return the original match and emit a logger warning indicating the
template key and contextJSON (or at least the key) so callers won’t end up with
empty header names/values unexpectedly.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
web/src/components/table/channels/modals/EditChannelModal.jsx (2)
131-203: Shared override templates & header key casingThe paramOverrideTemplates/headerOverrideTemplates look good and match the structure used in EditTagModal, but they’re now duplicated in two files. Consider extracting these shared templates into a common module (e.g. a
overrideTemplates.js) so future changes stay in sync between channel-level and tag-level UIs.Also, please double‑check how
client_headersis built on the backend: if header keys are normalized (e.g. all lower‑cased, as in many Node/HTTP stacks), templates likeclient_headers.X-Trace-Idandclient_headers.session_idmay never match. You may want to normalize these template paths to whatever casing the backend actually uses (e.g.client_headers.x-trace-id) for reliability.Also applies to: 205-245
2789-2797: Channel param/header overrides lack front‑end JSON validationThe new OverrideEditor wiring for
param_overrideandheader_overridecorrectly syncs throughhandleInputChangeandformApiRef, but unlike tags (EditTagModal.handleSave), there’s no front‑end JSON validation here before submit.Given these fields ultimately drive upstream request manipulation, it would be safer and more consistent to:
- Reuse
verifyJSONonlocalInputs.param_override/localInputs.header_overrideinsubmit, mirroring the logic in EditTagModal; and- Optionally trim empty strings to
undefinedwhen you intend “no override”.This keeps malformed override JSON from reaching the backend and aligns behavior across channel/tag editors.
Also applies to: 2799-2807
web/src/components/table/channels/modals/EditTagModal.jsx (1)
20-52: OverrideEditor integration & template duplicationBringing in
useMemoandOverrideEditorhere is aligned with the channel modal usage, and the template shapes for param/header overrides look correct for the OverrideEditor API.Since these paramOverrideTemplates/headerOverrideTemplates are effectively identical to those in
EditChannelModal.jsx, consider extracting them into a shared helper (e.g.overrideTemplates.js) and importing from both places. That will avoid drift if you later tweak the presets or add new ones.Also applies to: 81-152, 154-194
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
web/src/components/table/channels/modals/EditChannelModal.jsx(4 hunks)web/src/components/table/channels/modals/EditTagModal.jsx(4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.
Applied to files:
web/src/components/table/channels/modals/EditChannelModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.
Applied to files:
web/src/components/table/channels/modals/EditChannelModal.jsx
🧬 Code graph analysis (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (5)
web/src/components/table/channels/modals/EditTagModal.jsx (3)
paramOverrideTemplates(81-152)inputs(77-77)formApiRef(78-78)web/src/components/common/ui/OverrideEditor.jsx (1)
OverrideEditor(112-756)web/src/pages/Setting/Chat/SettingsChats.jsx (1)
inputs(54-56)web/src/components/table/tokens/modals/EditTokenModal.jsx (1)
formApiRef(62-62)web/src/components/settings/personal/cards/NotificationSettings.jsx (1)
formApiRef(55-55)
web/src/components/table/channels/modals/EditTagModal.jsx (4)
web/src/components/table/channels/modals/EditChannelModal.jsx (3)
paramOverrideTemplates(132-203)inputs(287-287)formApiRef(299-299)web/src/components/common/ui/OverrideEditor.jsx (1)
OverrideEditor(112-756)web/src/pages/Setting/Chat/SettingsChats.jsx (1)
inputs(54-56)web/src/components/table/tokens/modals/EditTokenModal.jsx (1)
formApiRef(62-62)
🔇 Additional comments (2)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)
2971-2983: pass_through_body_enabled wiring looks consistentSwitching
pass_through_body_enabledto usehandleChannelSettingsChangekeepschannelSettings, the form value, and the serializedsettingJSON in sync, andsubmitstill rebuildssettingfrom the top‑level flags. The behavior is consistent with the other channel extra settings (force_format,thinking_to_content, etc.).web/src/components/table/channels/modals/EditTagModal.jsx (1)
664-672: “不更改” semantics with OverrideEditor are coherentThe OverrideEditor bindings for
param_overrideandheader_overrideplus the “不更改” links work well withhandleSave:
value={inputs.xxx || ''}ensures the editor starts empty but still functions in both visual and JSON modes.- Clicking “不更改” sets the corresponding field to
null, whichhandleSavecorrectly treats as “no change” because it only processes non‑undefined/non‑nullvalues.- When a user later edits and confirms in OverrideEditor, the stringified JSON flows back through
handleInputChangeinto bothinputsand the form.This design preserves the bulk‑edit semantics while giving explicit control over “leave unchanged” vs “clear/override”.
Also applies to: 673-680, 682-690, 691-698
|
@seefs001 需要注意 FetchUpstreamModels 也需要处理 |
…or header overrides
我只是给我自己做个TODO,你怎么就给写上了( |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
controller/channel.go (1)
293-299: Consider the implications of joining multi-value headers.Line 298 joins multiple header values with a comma. While this is acceptable for most standard authentication headers in the model fetch context, it may not be appropriate for all header types (e.g., Set-Cookie). Given the specific use case here, this is likely fine, but be aware of this limitation if the function is reused elsewhere.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
controller/channel.go(3 hunks)controller/task.go(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controller/task.go (2)
logger/logger.go (1)
LogError(65-67)common/sys_log.go (1)
SysLog(11-14)
🔇 Additional comments (5)
controller/task.go (1)
91-91: LGTM: Format specifier fix.Correctly changes
%dto%sfor error message formatting. The previous code would have produced incorrect log output.controller/channel.go (4)
14-14: LGTM!The import alias for relay/common is appropriate and follows Go conventions.
227-233: LGTM!Good refactoring to centralize header construction logic. The error handling is appropriate and the code is cleaner.
264-291: LGTM!The simple header override mode is well-implemented with appropriate type checking and placeholder replacement.
306-310: Verify the security implications of passing request headers to the override context.Line 310 passes
c.Request.HeadertoBuildOverrideContext, which means user-provided headers can be referenced in template expressions within the header override configuration. Verify that:
- Template expansion has safeguards against header-based injection attacks
- Sensitive headers are filtered or escaped before being passed to template processing
- This pass-through behavior is intentionally designed and documented
| finalHeaders := http.Header{} | ||
| for k, v := range newHeaders { | ||
| finalHeaders.Set(k, fmt.Sprintf("%v", v)) | ||
| } | ||
| return finalHeaders, nil |
There was a problem hiding this comment.
Fix unsafe type conversion for header values.
Line 323 uses fmt.Sprintf("%v", v) to convert override values to strings. This is problematic because JSON unmarshaling into map[string]interface{} can produce various types:
nilbecomes"<nil>"(invalid header value)- Numbers become their string representation (might be acceptable)
- Booleans become
"true"/"false"(likely not intended) - Arrays/objects produce Go-style formatting (e.g.,
"[1 2 3]", definitely wrong)
Apply this diff to add proper type handling:
finalHeaders := http.Header{}
for k, v := range newHeaders {
- finalHeaders.Set(k, fmt.Sprintf("%v", v))
+ strVal, ok := v.(string)
+ if !ok {
+ return nil, fmt.Errorf("header override result for %s is not a string (got %T)", k, v)
+ }
+ finalHeaders.Set(k, strVal)
}
return finalHeaders, nilAlternatively, if non-string types are expected and should be converted, implement explicit conversion logic for each supported type.
| if !responseItems.IsSuccess() { | ||
| common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %d", channelId, len(taskIds), string(responseBody))) | ||
| common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody))) | ||
| return err | ||
| } |
There was a problem hiding this comment.
Format fix correct, but logic issues in error handling.
The format specifier change from %d to %s is correct. However, there are logic issues in this error-handling block:
-
Misleading log message: The message "成功获取到任务数" (successfully obtained task count) is contradictory in an error branch (
!responseItems.IsSuccess()). -
Incorrect error propagation: Line 144 returns
err, buterrcomes from the successful unmarshal at line 138. This means the function returnsnilin an error condition, masking the failure.
Consider this fix:
if !responseItems.IsSuccess() {
- common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody)))
- return err
+ errMsg := fmt.Sprintf("渠道 #%d 获取任务失败, 请求任务数: %d, 响应: %s", channelId, len(taskIds), string(responseBody))
+ common.SysLog(errMsg)
+ return errors.New(errMsg)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if !responseItems.IsSuccess() { | |
| common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %d", channelId, len(taskIds), string(responseBody))) | |
| common.SysLog(fmt.Sprintf("渠道 #%d 未完成的任务有: %d, 成功获取到任务数: %s", channelId, len(taskIds), string(responseBody))) | |
| return err | |
| } | |
| if !responseItems.IsSuccess() { | |
| errMsg := fmt.Sprintf("渠道 #%d 获取任务失败, 请求任务数: %d, 响应: %s", channelId, len(taskIds), string(responseBody)) | |
| common.SysLog(errMsg) | |
| return errors.New(errMsg) | |
| } |
🤖 Prompt for AI Agents
In controller/task.go around lines 142-145, the error branch logs a misleading
success message and returns the wrong error variable; update the log to describe
the failure (e.g., "未完成的任务" / "failed to fetch tasks" and include responseBody
and channelId), and ensure the function returns a non-nil error from this
failure (for example return an error built from responseItems or fmt.Errorf with
context and responseBody) instead of returning the previously nil `err`; keep
the existing formatted values and include relevant context so the caller sees
the failure details.
🤣我还以为给我提的咧 |
|
蹲一下,headers透传功能实现了嘛 |
|
蹲蹲,这个功能未来还计划merge嘛 |
会从这里提取一部分内容merge,现在还顾不上。 |







✨ feat(api_request): 实现透传请求头逻辑并更新相关设置
因某些上游可能需要透传某些头,逐添加
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.