diff --git a/constant/context_key.go b/constant/context_key.go index 87995fd6..f7640272 100644 --- a/constant/context_key.go +++ b/constant/context_key.go @@ -8,7 +8,6 @@ const ( ContextKeyOriginalModel ContextKey = "original_model" ContextKeyRequestStartTime ContextKey = "request_start_time" - ContextKeyRelayFormat ContextKey = "relay_format" /* token related keys */ ContextKeyTokenUnlimited ContextKey = "token_unlimited_quota" diff --git a/controller/relay.go b/controller/relay.go index a9bb16c8..4a81e356 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -299,9 +299,8 @@ func processChannelError(c *gin.Context, channelError types.ChannelError, err *t userGroup := c.GetString("group") channelId := c.GetInt("channel_id") other := make(map[string]interface{}) - relayFormat := common.GetContextKeyString(c, constant.ContextKeyRelayFormat) - if relayFormat != "" { - other["relay_format"] = relayFormat + if c.Request != nil && c.Request.URL != nil { + other["request_path"] = c.Request.URL.Path } other["error_type"] = err.GetErrorType() other["error_code"] = err.GetErrorCode() diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 5ae0f448..02cc9d99 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -465,10 +465,6 @@ func GenRelayInfo(c *gin.Context, relayFormat types.RelayFormat, request dto.Req return nil, errors.New("invalid relay format") } - if info != nil { - common.SetContextKey(c, constant.ContextKeyRelayFormat, string(info.RelayFormat)) - } - return info, nil } diff --git a/relay/relay_task.go b/relay/relay_task.go index 83879c34..bf20f5bb 100644 --- a/relay/relay_task.go +++ b/relay/relay_task.go @@ -165,8 +165,8 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto. } } other := make(map[string]interface{}) - if info.RelayFormat != "" { - other["relay_format"] = string(info.RelayFormat) + if c != nil && c.Request != nil && c.Request.URL != nil { + other["request_path"] = c.Request.URL.Path } other["model_price"] = modelPrice other["group_ratio"] = groupRatio diff --git a/service/log_info_generate.go b/service/log_info_generate.go index 58b36ced..95a88dfb 100644 --- a/service/log_info_generate.go +++ b/service/log_info_generate.go @@ -1,6 +1,8 @@ package service import ( + "strings" + "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" "github.com/QuantumNous/new-api/dto" @@ -10,12 +12,28 @@ import ( "github.com/gin-gonic/gin" ) +func appendRequestPath(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, other map[string]interface{}) { + if other == nil { + return + } + if ctx != nil && ctx.Request != nil && ctx.Request.URL != nil { + if path := ctx.Request.URL.Path; path != "" { + other["request_path"] = path + return + } + } + if relayInfo != nil && relayInfo.RequestURLPath != "" { + path := relayInfo.RequestURLPath + if idx := strings.Index(path, "?"); idx != -1 { + path = path[:idx] + } + other["request_path"] = path + } +} + func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} { other := make(map[string]interface{}) - if relayInfo != nil && relayInfo.RelayFormat != "" { - other["relay_format"] = string(relayInfo.RelayFormat) - } other["model_ratio"] = modelRatio other["group_ratio"] = groupRatio other["completion_ratio"] = completionRatio @@ -45,6 +63,7 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m adminInfo["multi_key_index"] = common.GetContextKeyInt(ctx, constant.ContextKeyChannelMultiKeyIndex) } other["admin_info"] = adminInfo + appendRequestPath(ctx, relayInfo, other) return other } @@ -83,13 +102,11 @@ func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, func GenerateMjOtherInfo(relayInfo *relaycommon.RelayInfo, priceData types.PerCallPriceData) map[string]interface{} { other := make(map[string]interface{}) - if relayInfo != nil && relayInfo.RelayFormat != "" { - other["relay_format"] = string(relayInfo.RelayFormat) - } other["model_price"] = priceData.ModelPrice other["group_ratio"] = priceData.GroupRatioInfo.GroupRatio if priceData.GroupRatioInfo.HasSpecialRatio { other["user_group_ratio"] = priceData.GroupRatioInfo.GroupSpecialRatio } + appendRequestPath(nil, relayInfo, other) return other } diff --git a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx index d41c35cc..5e9ec073 100644 --- a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx +++ b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx @@ -103,36 +103,98 @@ function renderType(type, t) { } } -const relayFormatMeta = { - openai: { color: 'blue', label: 'OpenAI' }, - claude: { color: 'purple', label: 'Claude' }, - gemini: { color: 'orange', label: 'Gemini' }, - openai_responses: { color: 'violet', label: 'Responses' }, - openai_audio: { color: 'teal', label: 'Audio' }, - openai_image: { color: 'pink', label: 'Image' }, - openai_realtime: { color: 'indigo', label: 'Realtime' }, - rerank: { color: 'cyan', label: 'Rerank' }, - embedding: { color: 'green', label: 'Embedding' }, - task: { color: 'amber', label: 'Task' }, - mj_proxy: { color: 'red', label: 'Midjourney' }, +const endpointColorMap = { + chat: 'blue', + completions: 'blue', + messages: 'purple', + responses: 'violet', + images: 'pink', + image: 'pink', + embeddings: 'green', + embedding: 'green', + audio: 'teal', + speech: 'teal', + translations: 'teal', + transcriptions: 'teal', + rerank: 'cyan', + moderations: 'red', + models: 'orange', + engines: 'orange', + mj: 'red', + submit: 'red', + suno: 'amber', + realtime: 'indigo', + notifications: 'violet', }; -function renderRelayFormat(relayFormat) { - if (!relayFormat) { +function formatPathSegment(segment) { + if (!segment) { + return ''; + } + const normalized = segment.replace(/^:/, '').replace(/[_-]/g, ' '); + return normalized + .split(' ') + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' '); +} + +function deriveEndpointMeta(path) { + if (!path) { return null; } - const meta = relayFormatMeta[relayFormat] || { - color: 'grey', - label: relayFormat - .replace(/_/g, ' ') - .replace(/\b\w/g, (c) => c.toUpperCase()), - }; + const cleanPath = path.split('?')[0]; + const segments = cleanPath.split('/').filter(Boolean); + if (segments.length === 0) { + return null; + } + let startIndex = 0; + if (/^v\d/i.test(segments[0])) { + startIndex = 1; + } - return ( + const primary = segments[startIndex] || segments[segments.length - 1]; + const tailSegments = segments + .slice(startIndex + 1) + .filter((segment) => segment && !segment.startsWith(':')); + const secondary = tailSegments[tailSegments.length - 1]; + + const labelParts = []; + const formattedPrimary = formatPathSegment(primary); + if (formattedPrimary) { + labelParts.push(formattedPrimary); + } + const formattedSecondary = formatPathSegment(secondary); + if (formattedSecondary && formattedSecondary !== formattedPrimary) { + labelParts.push(formattedSecondary); + } + const label = labelParts.join(' · '); + + const color = + endpointColorMap[primary] || + (secondary ? endpointColorMap[secondary] : undefined) || + 'grey'; + + return { + label: label || formatPathSegment(primary), + color, + }; +} + +function renderEndpointTag(requestPath) { + const meta = deriveEndpointMeta(requestPath); + if (!meta) { + return null; + } + const tag = ( {meta.label} ); + if (requestPath) { + return {tag}; + } + return tag; } function renderIsStream(bool, t) { @@ -403,11 +465,12 @@ export const getLogsColumns = ({ title: t('类型'), dataIndex: 'type', render: (text, record, index) => { - const relayFormat = getLogOther(record.other)?.relay_format; + const other = getLogOther(record.other) || {}; + const requestPath = other.request_path; return ( {renderType(text, t)} - {renderRelayFormat(relayFormat)} + {renderEndpointTag(requestPath)} ); },