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)}
);
},