Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
5db25f47
authored
Mar 17, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add param override audit modal for usage logs
parent
a4fd2246
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
550 additions
and
17 deletions
+550
-17
relay/common/override.go
+209
-16
relay/common/relay_info.go
+1
-0
service/log_info_generate.go
+8
-0
web/src/components/table/usage-logs/index.jsx
+2
-0
web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx
+278
-0
web/src/hooks/usage-logs/useUsageLogsData.jsx
+52
-1
No files found.
relay/common/override.go
View file @
5db25f47
...
...
@@ -21,10 +21,21 @@ var negativeIndexRegexp = regexp.MustCompile(`\.(-\d+)`)
const
(
paramOverrideContextRequestHeaders
=
"request_headers"
paramOverrideContextHeaderOverride
=
"header_override"
paramOverrideContextAuditRecorder
=
"__param_override_audit_recorder"
)
var
errSourceHeaderNotFound
=
errors
.
New
(
"source header does not exist"
)
var
paramOverrideKeyAuditPaths
=
map
[
string
]
struct
{}{
"model"
:
{},
"service_tier"
:
{},
"inference_geo"
:
{},
}
type
paramOverrideAuditRecorder
struct
{
lines
[]
string
}
type
ConditionOperation
struct
{
Path
string
`json:"path"`
// JSON路径
Mode
string
`json:"mode"`
// full, prefix, suffix, contains, gt, gte, lt, lte
...
...
@@ -118,6 +129,7 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
if
len
(
paramOverride
)
==
0
{
return
jsonData
,
nil
}
auditRecorder
:=
getParamOverrideAuditRecorder
(
conditionContext
)
// 尝试断言为操作格式
if
operations
,
ok
:=
tryParseOperations
(
paramOverride
);
ok
{
...
...
@@ -125,7 +137,7 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
workingJSON
:=
jsonData
var
err
error
if
len
(
legacyOverride
)
>
0
{
workingJSON
,
err
=
applyOperationsLegacy
(
workingJSON
,
legacyOverride
)
workingJSON
,
err
=
applyOperationsLegacy
(
workingJSON
,
legacyOverride
,
auditRecorder
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -137,7 +149,7 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
}
// 直接使用旧方法
return
applyOperationsLegacy
(
jsonData
,
paramOverride
)
return
applyOperationsLegacy
(
jsonData
,
paramOverride
,
auditRecorder
)
}
func
buildLegacyParamOverride
(
paramOverride
map
[
string
]
interface
{})
map
[
string
]
interface
{}
{
...
...
@@ -161,14 +173,133 @@ func ApplyParamOverrideWithRelayInfo(jsonData []byte, info *RelayInfo) ([]byte,
}
overrideCtx
:=
BuildParamOverrideContext
(
info
)
var
recorder
*
paramOverrideAuditRecorder
if
shouldEnableParamOverrideAudit
(
paramOverride
)
{
recorder
=
&
paramOverrideAuditRecorder
{}
overrideCtx
[
paramOverrideContextAuditRecorder
]
=
recorder
}
result
,
err
:=
ApplyParamOverride
(
jsonData
,
paramOverride
,
overrideCtx
)
if
err
!=
nil
{
return
nil
,
err
}
syncRuntimeHeaderOverrideFromContext
(
info
,
overrideCtx
)
if
info
!=
nil
{
if
recorder
!=
nil
{
info
.
ParamOverrideAudit
=
recorder
.
lines
}
else
{
info
.
ParamOverrideAudit
=
nil
}
}
return
result
,
nil
}
func
shouldEnableParamOverrideAudit
(
paramOverride
map
[
string
]
interface
{})
bool
{
if
common
.
DebugEnabled
{
return
true
}
if
len
(
paramOverride
)
==
0
{
return
false
}
if
operations
,
ok
:=
tryParseOperations
(
paramOverride
);
ok
{
for
_
,
operation
:=
range
operations
{
if
shouldAuditParamPath
(
strings
.
TrimSpace
(
operation
.
Path
))
||
shouldAuditParamPath
(
strings
.
TrimSpace
(
operation
.
To
))
{
return
true
}
}
for
key
:=
range
buildLegacyParamOverride
(
paramOverride
)
{
if
shouldAuditParamPath
(
strings
.
TrimSpace
(
key
))
{
return
true
}
}
return
false
}
for
key
:=
range
paramOverride
{
if
shouldAuditParamPath
(
strings
.
TrimSpace
(
key
))
{
return
true
}
}
return
false
}
func
getParamOverrideAuditRecorder
(
context
map
[
string
]
interface
{})
*
paramOverrideAuditRecorder
{
if
context
==
nil
{
return
nil
}
recorder
,
_
:=
context
[
paramOverrideContextAuditRecorder
]
.
(
*
paramOverrideAuditRecorder
)
return
recorder
}
func
(
r
*
paramOverrideAuditRecorder
)
record
(
path
string
,
beforeExists
bool
,
beforeValue
interface
{},
afterExists
bool
,
afterValue
interface
{})
{
if
r
==
nil
{
return
}
path
=
strings
.
TrimSpace
(
path
)
if
path
==
""
{
return
}
if
!
shouldAuditParamPath
(
path
)
{
return
}
beforeText
:=
"<empty>"
if
beforeExists
{
beforeText
=
formatParamOverrideAuditValue
(
beforeValue
)
}
afterText
:=
"<deleted>"
if
afterExists
{
afterText
=
formatParamOverrideAuditValue
(
afterValue
)
}
line
:=
fmt
.
Sprintf
(
"%s: %s -> %s"
,
path
,
beforeText
,
afterText
)
if
lo
.
Contains
(
r
.
lines
,
line
)
{
return
}
r
.
lines
=
append
(
r
.
lines
,
line
)
}
func
shouldAuditParamPath
(
path
string
)
bool
{
path
=
strings
.
TrimSpace
(
path
)
if
path
==
""
{
return
false
}
if
common
.
DebugEnabled
{
return
true
}
_
,
ok
:=
paramOverrideKeyAuditPaths
[
path
]
return
ok
}
func
applyAuditedPathMutation
(
result
,
path
string
,
auditRecorder
*
paramOverrideAuditRecorder
,
mutate
func
(
string
)
(
string
,
error
))
(
string
,
error
)
{
needAudit
:=
auditRecorder
!=
nil
&&
shouldAuditParamPath
(
path
)
var
beforeResult
gjson
.
Result
if
needAudit
{
beforeResult
=
gjson
.
Get
(
result
,
path
)
}
next
,
err
:=
mutate
(
result
)
if
err
!=
nil
{
return
next
,
err
}
if
needAudit
{
afterResult
:=
gjson
.
Get
(
next
,
path
)
auditRecorder
.
record
(
path
,
beforeResult
.
Exists
(),
beforeResult
.
Value
(),
afterResult
.
Exists
(),
afterResult
.
Value
())
}
return
next
,
nil
}
func
formatParamOverrideAuditValue
(
value
interface
{})
string
{
switch
typed
:=
value
.
(
type
)
{
case
nil
:
return
"<empty>"
case
string
:
return
typed
default
:
return
common
.
GetJsonString
(
typed
)
}
}
func
getParamOverrideMap
(
info
*
RelayInfo
)
map
[
string
]
interface
{}
{
if
info
==
nil
||
info
.
ChannelMeta
==
nil
{
return
nil
...
...
@@ -455,7 +586,7 @@ func compareNumeric(jsonValue, targetValue gjson.Result, operator string) (bool,
}
// applyOperationsLegacy 原参数覆盖方法
func
applyOperationsLegacy
(
jsonData
[]
byte
,
paramOverride
map
[
string
]
interface
{})
([]
byte
,
error
)
{
func
applyOperationsLegacy
(
jsonData
[]
byte
,
paramOverride
map
[
string
]
interface
{}
,
auditRecorder
*
paramOverrideAuditRecorder
)
([]
byte
,
error
)
{
reqMap
:=
make
(
map
[
string
]
interface
{})
err
:=
common
.
Unmarshal
(
jsonData
,
&
reqMap
)
if
err
!=
nil
{
...
...
@@ -463,7 +594,9 @@ func applyOperationsLegacy(jsonData []byte, paramOverride map[string]interface{}
}
for
key
,
value
:=
range
paramOverride
{
beforeValue
,
beforeExists
:=
reqMap
[
key
]
reqMap
[
key
]
=
value
auditRecorder
.
record
(
key
,
beforeExists
,
beforeValue
,
true
,
value
)
}
return
common
.
Marshal
(
reqMap
)
...
...
@@ -471,6 +604,7 @@ func applyOperationsLegacy(jsonData []byte, paramOverride map[string]interface{}
func
applyOperations
(
jsonStr
string
,
operations
[]
ParamOperation
,
conditionContext
map
[
string
]
interface
{})
(
string
,
error
)
{
context
:=
ensureContextMap
(
conditionContext
)
auditRecorder
:=
getParamOverrideAuditRecorder
(
context
)
contextJSON
,
err
:=
marshalContextJSON
(
context
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to marshal condition context: %v"
,
err
)
...
...
@@ -502,7 +636,9 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
switch
op
.
Mode
{
case
"delete"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
deleteValue
(
result
,
path
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
deleteValue
(
current
,
path
)
})
if
err
!=
nil
{
break
}
...
...
@@ -512,7 +648,9 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
if
op
.
KeepOrigin
&&
gjson
.
Get
(
result
,
path
)
.
Exists
()
{
continue
}
result
,
err
=
sjson
.
Set
(
result
,
path
,
op
.
Value
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
sjson
.
Set
(
current
,
path
,
op
.
Value
)
})
if
err
!=
nil
{
break
}
...
...
@@ -520,87 +658,137 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
case
"move"
:
opFrom
:=
processNegativeIndex
(
result
,
op
.
From
)
opTo
:=
processNegativeIndex
(
result
,
op
.
To
)
needAuditTo
:=
auditRecorder
!=
nil
&&
shouldAuditParamPath
(
opTo
)
needAuditFrom
:=
auditRecorder
!=
nil
&&
shouldAuditParamPath
(
opFrom
)
var
beforeResult
gjson
.
Result
var
fromResult
gjson
.
Result
if
needAuditTo
{
beforeResult
=
gjson
.
Get
(
result
,
opTo
)
}
if
needAuditFrom
{
fromResult
=
gjson
.
Get
(
result
,
opFrom
)
}
result
,
err
=
moveValue
(
result
,
opFrom
,
opTo
)
if
err
==
nil
{
if
needAuditTo
{
afterResult
:=
gjson
.
Get
(
result
,
opTo
)
auditRecorder
.
record
(
opTo
,
beforeResult
.
Exists
(),
beforeResult
.
Value
(),
afterResult
.
Exists
(),
afterResult
.
Value
())
}
if
needAuditFrom
&&
common
.
DebugEnabled
{
auditRecorder
.
record
(
opFrom
,
fromResult
.
Exists
(),
fromResult
.
Value
(),
false
,
nil
)
}
}
case
"copy"
:
if
op
.
From
==
""
||
op
.
To
==
""
{
return
""
,
fmt
.
Errorf
(
"copy from/to is required"
)
}
opFrom
:=
processNegativeIndex
(
result
,
op
.
From
)
opTo
:=
processNegativeIndex
(
result
,
op
.
To
)
needAudit
:=
auditRecorder
!=
nil
&&
shouldAuditParamPath
(
opTo
)
var
beforeResult
gjson
.
Result
if
needAudit
{
beforeResult
=
gjson
.
Get
(
result
,
opTo
)
}
result
,
err
=
copyValue
(
result
,
opFrom
,
opTo
)
if
err
==
nil
&&
needAudit
{
afterResult
:=
gjson
.
Get
(
result
,
opTo
)
auditRecorder
.
record
(
opTo
,
beforeResult
.
Exists
(),
beforeResult
.
Value
(),
afterResult
.
Exists
(),
afterResult
.
Value
())
}
case
"prepend"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
modifyValue
(
result
,
path
,
op
.
Value
,
op
.
KeepOrigin
,
true
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
modifyValue
(
current
,
path
,
op
.
Value
,
op
.
KeepOrigin
,
true
)
})
if
err
!=
nil
{
break
}
}
case
"append"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
modifyValue
(
result
,
path
,
op
.
Value
,
op
.
KeepOrigin
,
false
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
modifyValue
(
current
,
path
,
op
.
Value
,
op
.
KeepOrigin
,
false
)
})
if
err
!=
nil
{
break
}
}
case
"trim_prefix"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
trimStringValue
(
result
,
path
,
op
.
Value
,
true
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
trimStringValue
(
current
,
path
,
op
.
Value
,
true
)
})
if
err
!=
nil
{
break
}
}
case
"trim_suffix"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
trimStringValue
(
result
,
path
,
op
.
Value
,
false
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
trimStringValue
(
current
,
path
,
op
.
Value
,
false
)
})
if
err
!=
nil
{
break
}
}
case
"ensure_prefix"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
ensureStringAffix
(
result
,
path
,
op
.
Value
,
true
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
ensureStringAffix
(
current
,
path
,
op
.
Value
,
true
)
})
if
err
!=
nil
{
break
}
}
case
"ensure_suffix"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
ensureStringAffix
(
result
,
path
,
op
.
Value
,
false
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
ensureStringAffix
(
current
,
path
,
op
.
Value
,
false
)
})
if
err
!=
nil
{
break
}
}
case
"trim_space"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
transformStringValue
(
result
,
path
,
strings
.
TrimSpace
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
transformStringValue
(
current
,
path
,
strings
.
TrimSpace
)
})
if
err
!=
nil
{
break
}
}
case
"to_lower"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
transformStringValue
(
result
,
path
,
strings
.
ToLower
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
transformStringValue
(
current
,
path
,
strings
.
ToLower
)
})
if
err
!=
nil
{
break
}
}
case
"to_upper"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
transformStringValue
(
result
,
path
,
strings
.
ToUpper
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
transformStringValue
(
current
,
path
,
strings
.
ToUpper
)
})
if
err
!=
nil
{
break
}
}
case
"replace"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
replaceStringValue
(
result
,
path
,
op
.
From
,
op
.
To
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
replaceStringValue
(
current
,
path
,
op
.
From
,
op
.
To
)
})
if
err
!=
nil
{
break
}
}
case
"regex_replace"
:
for
_
,
path
:=
range
opPaths
{
result
,
err
=
regexReplaceStringValue
(
result
,
path
,
op
.
From
,
op
.
To
)
result
,
err
=
applyAuditedPathMutation
(
result
,
path
,
auditRecorder
,
func
(
current
string
)
(
string
,
error
)
{
return
regexReplaceStringValue
(
current
,
path
,
op
.
From
,
op
.
To
)
})
if
err
!=
nil
{
break
}
...
...
@@ -797,6 +985,7 @@ func setHeaderOverrideInContext(context map[string]interface{}, headerName strin
}
rawHeaders
:=
ensureMapKeyInContext
(
context
,
paramOverrideContextHeaderOverride
)
beforeRaw
,
beforeExists
:=
rawHeaders
[
headerName
]
if
keepOrigin
{
if
existing
,
ok
:=
rawHeaders
[
headerName
];
ok
{
existingValue
:=
strings
.
TrimSpace
(
fmt
.
Sprintf
(
"%v"
,
existing
))
...
...
@@ -812,10 +1001,12 @@ func setHeaderOverrideInContext(context map[string]interface{}, headerName strin
}
if
!
hasValue
{
delete
(
rawHeaders
,
headerName
)
getParamOverrideAuditRecorder
(
context
)
.
record
(
"header."
+
headerName
,
beforeExists
,
beforeRaw
,
false
,
nil
)
return
nil
}
rawHeaders
[
headerName
]
=
headerValue
getParamOverrideAuditRecorder
(
context
)
.
record
(
"header."
+
headerName
,
beforeExists
,
beforeRaw
,
true
,
headerValue
)
return
nil
}
...
...
@@ -987,7 +1178,9 @@ func deleteHeaderOverrideInContext(context map[string]interface{}, headerName st
return
fmt
.
Errorf
(
"header name is required"
)
}
rawHeaders
:=
ensureMapKeyInContext
(
context
,
paramOverrideContextHeaderOverride
)
beforeRaw
,
beforeExists
:=
rawHeaders
[
headerName
]
delete
(
rawHeaders
,
headerName
)
getParamOverrideAuditRecorder
(
context
)
.
record
(
"header."
+
headerName
,
beforeExists
,
beforeRaw
,
false
,
nil
)
return
nil
}
...
...
relay/common/relay_info.go
View file @
5db25f47
...
...
@@ -149,6 +149,7 @@ type RelayInfo struct {
LastError
*
types
.
NewAPIError
RuntimeHeadersOverride
map
[
string
]
interface
{}
UseRuntimeHeadersOverride
bool
ParamOverrideAudit
[]
string
PriceData
types
.
PriceData
...
...
service/log_info_generate.go
View file @
5db25f47
...
...
@@ -74,9 +74,17 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
appendRequestPath
(
ctx
,
relayInfo
,
other
)
appendRequestConversionChain
(
relayInfo
,
other
)
appendBillingInfo
(
relayInfo
,
other
)
appendParamOverrideInfo
(
relayInfo
,
other
)
return
other
}
func
appendParamOverrideInfo
(
relayInfo
*
relaycommon
.
RelayInfo
,
other
map
[
string
]
interface
{})
{
if
relayInfo
==
nil
||
other
==
nil
||
len
(
relayInfo
.
ParamOverrideAudit
)
==
0
{
return
}
other
[
"po"
]
=
relayInfo
.
ParamOverrideAudit
}
func
appendBillingInfo
(
relayInfo
*
relaycommon
.
RelayInfo
,
other
map
[
string
]
interface
{})
{
if
relayInfo
==
nil
||
other
==
nil
{
return
...
...
web/src/components/table/usage-logs/index.jsx
View file @
5db25f47
...
...
@@ -25,6 +25,7 @@ import LogsFilters from './UsageLogsFilters';
import
ColumnSelectorModal
from
'./modals/ColumnSelectorModal'
;
import
UserInfoModal
from
'./modals/UserInfoModal'
;
import
ChannelAffinityUsageCacheModal
from
'./modals/ChannelAffinityUsageCacheModal'
;
import
ParamOverrideModal
from
'./modals/ParamOverrideModal'
;
import
{
useLogsData
}
from
'../../../hooks/usage-logs/useUsageLogsData'
;
import
{
useIsMobile
}
from
'../../../hooks/common/useIsMobile'
;
import
{
createCardProPagination
}
from
'../../../helpers/utils'
;
...
...
@@ -39,6 +40,7 @@ const LogsPage = () => {
<
ColumnSelectorModal
{
...
logsData
}
/>
<
UserInfoModal
{
...
logsData
}
/>
<
ChannelAffinityUsageCacheModal
{
...
logsData
}
/>
<
ParamOverrideModal
{
...
logsData
}
/>
{
/* Main Content */
}
<
CardPro
...
...
web/src/components/table/usage-logs/modals/ParamOverrideModal.jsx
0 → 100644
View file @
5db25f47
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useMemo
}
from
'react'
;
import
{
Modal
,
Button
,
Empty
,
Space
,
Tag
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
{
IconCopy
}
from
'@douyinfe/semi-icons'
;
import
{
copy
,
showError
,
showSuccess
}
from
'../../../../helpers'
;
const
{
Text
}
=
Typography
;
const
parseAuditLine
=
(
line
)
=>
{
if
(
typeof
line
!==
'string'
)
{
return
null
;
}
const
colonIndex
=
line
.
indexOf
(
': '
);
const
arrowIndex
=
line
.
indexOf
(
' -> '
,
colonIndex
+
2
);
if
(
colonIndex
<=
0
||
arrowIndex
<=
colonIndex
)
{
return
null
;
}
return
{
field
:
line
.
slice
(
0
,
colonIndex
),
before
:
line
.
slice
(
colonIndex
+
2
,
arrowIndex
),
after
:
line
.
slice
(
arrowIndex
+
4
),
raw
:
line
,
};
};
const
ValuePanel
=
({
label
,
value
,
tone
})
=>
(
<
div
style=
{
{
flex
:
1
,
minWidth
:
0
,
padding
:
12
,
borderRadius
:
12
,
border
:
'1px solid var(--semi-color-border)'
,
background
:
tone
===
'after'
?
'rgba(var(--semi-blue-5), 0.08)'
:
'var(--semi-color-fill-0)'
,
}
}
>
<
div
style=
{
{
marginBottom
:
6
,
fontSize
:
12
,
fontWeight
:
600
,
color
:
'var(--semi-color-text-2)'
,
}
}
>
{
label
}
</
div
>
<
Text
style=
{
{
display
:
'block'
,
fontFamily
:
'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace'
,
fontSize
:
13
,
lineHeight
:
1.65
,
whiteSpace
:
'pre-wrap'
,
wordBreak
:
'break-word'
,
}
}
>
{
value
}
</
Text
>
</
div
>
);
const
ParamOverrideModal
=
({
showParamOverrideModal
,
setShowParamOverrideModal
,
paramOverrideTarget
,
t
,
})
=>
{
const
lines
=
Array
.
isArray
(
paramOverrideTarget
?.
lines
)
?
paramOverrideTarget
.
lines
:
[];
const
parsedLines
=
useMemo
(()
=>
{
return
lines
.
map
(
parseAuditLine
);
},
[
lines
]);
const
copyAll
=
async
()
=>
{
const
content
=
lines
.
join
(
'\n'
);
if
(
!
content
)
{
return
;
}
if
(
await
copy
(
content
))
{
showSuccess
(
t
(
'参数覆盖已复制'
));
return
;
}
showError
(
t
(
'无法复制到剪贴板,请手动复制'
));
};
return
(
<
Modal
title=
{
t
(
'参数覆盖详情'
)
}
visible=
{
showParamOverrideModal
}
onCancel=
{
()
=>
setShowParamOverrideModal
(
false
)
}
footer=
{
null
}
centered
closable
maskClosable
width=
{
760
}
>
<
div
style=
{
{
padding
:
20
}
}
>
<
div
style=
{
{
marginBottom
:
16
,
padding
:
16
,
borderRadius
:
16
,
background
:
'linear-gradient(135deg, rgba(var(--semi-blue-5), 0.08), rgba(var(--semi-teal-5), 0.12))'
,
border
:
'1px solid rgba(var(--semi-blue-5), 0.16)'
,
}
}
>
<
div
style=
{
{
display
:
'flex'
,
justifyContent
:
'space-between'
,
gap
:
12
,
flexWrap
:
'wrap'
,
alignItems
:
'flex-start'
,
}
}
>
<
div
>
<
div
style=
{
{
fontSize
:
18
,
fontWeight
:
700
,
color
:
'var(--semi-color-text-0)'
,
marginBottom
:
8
,
}
}
>
{
t
(
'已应用参数覆盖'
)
}
</
div
>
<
Space
wrap
spacing=
{
8
}
>
<
Tag
color=
'blue'
size=
'large'
>
{
t
(
'
{{
count
}}
项变更'
,
{
count
:
lines
.
length
})
}
</
Tag
>
{
paramOverrideTarget
?.
modelName
?
(
<
Tag
color=
'cyan'
size=
'large'
>
{
paramOverrideTarget
.
modelName
}
</
Tag
>
)
:
null
}
{
paramOverrideTarget
?.
requestId
?
(
<
Tag
color=
'grey'
size=
'large'
>
{
t
(
'Request ID'
)
}
:
{
paramOverrideTarget
.
requestId
}
</
Tag
>
)
:
null
}
</
Space
>
</
div
>
<
Button
icon=
{
<
IconCopy
/>
}
theme=
'solid'
type=
'tertiary'
onClick=
{
copyAll
}
disabled=
{
lines
.
length
===
0
}
>
{
t
(
'复制全部'
)
}
</
Button
>
</
div
>
{
paramOverrideTarget
?.
requestPath
?
(
<
div
style=
{
{
marginTop
:
12
}
}
>
<
Text
type=
'tertiary'
size=
'small'
>
{
t
(
'请求路径'
)
}
:
{
paramOverrideTarget
.
requestPath
}
</
Text
>
</
div
>
)
:
null
}
</
div
>
{
lines
.
length
===
0
?
(
<
Empty
description=
{
t
(
'暂无参数覆盖记录'
)
}
style=
{
{
padding
:
'32px 0 12px'
}
}
/>
)
:
(
<
div
style=
{
{
display
:
'flex'
,
flexDirection
:
'column'
,
gap
:
12
,
maxHeight
:
'60vh'
,
overflowY
:
'auto'
,
paddingRight
:
4
,
}
}
>
{
parsedLines
.
map
((
item
,
index
)
=>
{
if
(
!
item
)
{
return
(
<
div
key=
{
`raw-${index}`
}
style=
{
{
padding
:
14
,
borderRadius
:
14
,
border
:
'1px solid var(--semi-color-border)'
,
background
:
'var(--semi-color-fill-0)'
,
}
}
>
<
Text
style=
{
{
fontFamily
:
'ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace'
,
fontSize
:
13
,
lineHeight
:
1.65
,
whiteSpace
:
'pre-wrap'
,
wordBreak
:
'break-word'
,
}
}
>
{
lines
[
index
]
}
</
Text
>
</
div
>
);
}
return
(
<
div
key=
{
`${item.field}-${index}`
}
style=
{
{
padding
:
14
,
borderRadius
:
16
,
border
:
'1px solid var(--semi-color-border)'
,
background
:
'var(--semi-color-bg-0)'
,
boxShadow
:
'0 8px 24px rgba(15, 23, 42, 0.04)'
,
}
}
>
<
div
style=
{
{
marginBottom
:
12
}
}
>
<
Tag
color=
'blue'
shape=
'circle'
size=
'large'
>
{
item
.
field
}
</
Tag
>
</
div
>
<
div
style=
{
{
display
:
'flex'
,
gap
:
12
,
flexWrap
:
'wrap'
,
alignItems
:
'stretch'
,
}
}
>
<
ValuePanel
label=
{
t
(
'变更前'
)
}
value=
{
item
.
before
}
/>
<
ValuePanel
label=
{
t
(
'变更后'
)
}
value=
{
item
.
after
}
tone=
'after'
/>
</
div
>
</
div
>
);
})
}
</
div
>
)
}
</
div
>
</
Modal
>
);
};
export
default
ParamOverrideModal
;
web/src/hooks/usage-logs/useUsageLogsData.jsx
View file @
5db25f47
...
...
@@ -19,7 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
import
{
useState
,
useEffect
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
Modal
,
Button
,
Tag
}
from
'@douyinfe/semi-ui'
;
import
{
API
,
getTodayStartTimestamp
,
...
...
@@ -181,6 +181,8 @@ export const useLogsData = () => {
]
=
useState
(
false
);
const
[
channelAffinityUsageCacheTarget
,
setChannelAffinityUsageCacheTarget
]
=
useState
(
null
);
const
[
showParamOverrideModal
,
setShowParamOverrideModal
]
=
useState
(
false
);
const
[
paramOverrideTarget
,
setParamOverrideTarget
]
=
useState
(
null
);
// Initialize default column visibility
const
initDefaultColumns
=
()
=>
{
...
...
@@ -345,6 +347,20 @@ export const useLogsData = () => {
setShowChannelAffinityUsageCacheModal
(
true
);
};
const
openParamOverrideModal
=
(
log
,
other
)
=>
{
const
lines
=
Array
.
isArray
(
other
?.
po
)
?
other
.
po
.
filter
(
Boolean
)
:
[];
if
(
lines
.
length
===
0
)
{
return
;
}
setParamOverrideTarget
({
lines
,
modelName
:
log
?.
model_name
||
''
,
requestId
:
log
?.
request_id
||
''
,
requestPath
:
other
?.
request_path
||
''
,
});
setShowParamOverrideModal
(
true
);
};
// Format logs data
const
setLogsFormat
=
(
logs
)
=>
{
const
requestConversionDisplayValue
=
(
conversionChain
)
=>
{
...
...
@@ -584,6 +600,37 @@ export const useLogsData = () => {
value
:
other
.
request_path
,
});
}
if
(
Array
.
isArray
(
other
?.
po
)
&&
other
.
po
.
length
>
0
)
{
expandDataLocal
.
push
({
key
:
t
(
'参数覆盖'
),
value
:
(
<
div
style=
{
{
display
:
'flex'
,
alignItems
:
'center'
,
gap
:
10
,
flexWrap
:
'wrap'
,
}
}
>
<
Tag
color=
'blue'
shape=
'circle'
>
{
t
(
'
{{
count
}}
项变更'
,
{
count
:
other
.
po
.
length
})
}
</
Tag
>
<
Button
theme=
'borderless'
type=
'primary'
size=
'small'
style=
{
{
paddingLeft
:
0
}
}
onClick=
{
(
event
)
=>
{
event
.
stopPropagation
();
openParamOverrideModal
(
logs
[
i
],
other
);
}
}
>
{
t
(
'查看详情'
)
}
</
Button
>
</
div
>
),
});
}
if
(
other
?.
billing_source
===
'subscription'
)
{
const
planId
=
other
?.
subscription_plan_id
;
const
planTitle
=
other
?.
subscription_plan_title
||
''
;
...
...
@@ -811,6 +858,9 @@ export const useLogsData = () => {
setShowChannelAffinityUsageCacheModal
,
channelAffinityUsageCacheTarget
,
openChannelAffinityUsageCacheModal
,
showParamOverrideModal
,
setShowParamOverrideModal
,
paramOverrideTarget
,
// Functions
loadLogs
,
...
...
@@ -822,6 +872,7 @@ export const useLogsData = () => {
setLogsFormat
,
hasExpandableRows
,
setLogType
,
openParamOverrideModal
,
// Translation
t
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment