diff --git a/relay/channel/api_request.go b/relay/channel/api_request.go index 43def3e..7b502c2 100644 --- a/relay/channel/api_request.go +++ b/relay/channel/api_request.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "bytes" "io" "net/http" "regexp" @@ -293,13 +294,12 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody if err != nil { return nil, fmt.Errorf("setup request header failed: %w", err) } - // 在 SetupRequestHeader 之后应用 Header Override,确保用户设置优先级最高 - // 这样可以覆盖默认的 Authorization header 设置 headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err } applyHeaderOverrideToRequest(req, headerOverride) + dumpUpstreamRequest(req) resp, err := doRequest(c, req, info) if err != nil { return nil, fmt.Errorf("do request failed: %w", err) @@ -319,20 +319,18 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod if err != nil { return nil, fmt.Errorf("new request failed: %w", err) } - // set form data req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) headers := req.Header err = a.SetupRequestHeader(c, &headers, info) if err != nil { return nil, fmt.Errorf("setup request header failed: %w", err) } - // 在 SetupRequestHeader 之后应用 Header Override,确保用户设置优先级最高 - // 这样可以覆盖默认的 Authorization header 设置 headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err } applyHeaderOverrideToRequest(req, headerOverride) + dumpUpstreamRequest(req) resp, err := doRequest(c, req, info) if err != nil { return nil, fmt.Errorf("do request failed: %w", err) @@ -340,6 +338,15 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod return resp, nil } +func dumpUpstreamRequest(req *http.Request) { + if !common2.DebugEnabled || req == nil { + return + } + bodyBytes, _ := io.ReadAll(req.Body) + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + common2.SysLog(fmt.Sprintf("[UpstreamRequest] URL: %s\nHeaders: %v\nBody: %s", req.URL.String(), req.Header, string(bodyBytes))) +} + func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody io.Reader) (*websocket.Conn, error) { fullRequestURL, err := a.GetRequestURL(info) if err != nil { @@ -347,11 +354,6 @@ func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody } targetHeader := http.Header{} err = a.SetupRequestHeader(c, &targetHeader, info) - if err != nil { - return nil, fmt.Errorf("setup request header failed: %w", err) - } - // 在 SetupRequestHeader 之后应用 Header Override,确保用户设置优先级最高 - // 这样可以覆盖默认的 Authorization header 设置 headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err diff --git a/service/log_info_generate.go b/service/log_info_generate.go index 1c44091..46cca48 100644 --- a/service/log_info_generate.go +++ b/service/log_info_generate.go @@ -41,6 +41,9 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m other["cache_ratio"] = cacheRatio other["model_price"] = modelPrice other["user_group_ratio"] = userGroupRatio + if relayInfo != nil && relayInfo.PriceData.UserChannelRatio != 1.0 { + other["user_channel_ratio"] = relayInfo.PriceData.UserChannelRatio + } other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli()) if relayInfo.ReasoningEffort != "" { other["reasoning_effort"] = relayInfo.ReasoningEffort diff --git a/web/src/components/table/users/modals/UserRatioSection.jsx b/web/src/components/table/users/modals/UserRatioSection.jsx index d404d49..2e9e3de 100644 --- a/web/src/components/table/users/modals/UserRatioSection.jsx +++ b/web/src/components/table/users/modals/UserRatioSection.jsx @@ -20,6 +20,7 @@ const UserRatioSection = ({ userId }) => { const [ratios, setRatios] = useState([]); const [loading, setLoading] = useState(false); const [addModalVisible, setAddModalVisible] = useState(false); + const [channelOptions, setChannelOptions] = useState([]); const formApiRef = useRef(null); const loadRatios = async () => { @@ -39,8 +40,29 @@ const UserRatioSection = ({ userId }) => { setLoading(false); }; + const loadChannels = async () => { + try { + const res = await API.get('/api/channel/?p=0&page_size=500'); + const { success, data, message } = res.data; + if (success) { + const options = (data?.items || []).map((ch) => ({ + label: ch.remark + ? `${ch.name} (${ch.remark})` + : `${ch.name} (ID: ${ch.id})`, + value: ch.id, + })); + setChannelOptions(options); + } else { + showError(message); + } + } catch (e) { + showError(e.message); + } + }; + useEffect(() => { loadRatios(); + loadChannels(); }, [userId]); const handleAdd = async (values) => { @@ -86,7 +108,15 @@ const UserRatioSection = ({ userId }) => { const columns = [ { title: t('模型'), dataIndex: 'model_name', key: 'model_name' }, - { title: t('渠道 ID'), dataIndex: 'channel_id', key: 'channel_id' }, + { + title: t('渠道'), + dataIndex: 'channel_id', + key: 'channel_id', + render: (channelId) => { + const ch = channelOptions.find((c) => c.value === channelId); + return ch ? ch.label : channelId; + }, + }, { title: t('倍率'), dataIndex: 'ratio', @@ -166,11 +196,14 @@ const UserRatioSection = ({ userId }) => { placeholder='gpt-4o' rules={[{ required: true, message: t('请输入模型名称') }]} /> -