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
f0886c8a
authored
Feb 22, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
6b58648d
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
883 additions
and
444 deletions
+883
-444
web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx
+883
-444
No files found.
web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx
View file @
f0886c8a
...
...
@@ -20,10 +20,10 @@ For commercial licensing, please contact support@quantumnous.com
import
React
,
{
useCallback
,
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Banner
,
Button
,
Card
,
Col
,
Collapse
,
Input
,
Modal
,
Row
,
...
...
@@ -31,6 +31,7 @@ import {
Space
,
Switch
,
Tag
,
TextArea
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
{
IconDelete
,
IconPlus
}
from
'@douyinfe/semi-icons'
;
...
...
@@ -204,6 +205,11 @@ const OPERATION_TEMPLATE = {
],
};
const
TEMPLATE_LIBRARY_OPTIONS
=
[
{
label
:
'Template · Operations'
,
value
:
'operations'
},
{
label
:
'Template · Legacy Object'
,
value
:
'legacy'
},
];
let
localIdSeed
=
0
;
const
nextLocalId
=
()
=>
`param_override_
${
Date
.
now
()}
_
${
localIdSeed
++
}
`
;
...
...
@@ -274,6 +280,28 @@ const normalizeOperation = (operation = {}) => ({
const
createDefaultOperation
=
()
=>
normalizeOperation
({
mode
:
'set'
});
const
getOperationSummary
=
(
operation
=
{},
index
=
0
)
=>
{
const
mode
=
operation
.
mode
||
'set'
;
if
(
mode
===
'sync_fields'
)
{
const
from
=
String
(
operation
.
from
||
''
).
trim
();
const
to
=
String
(
operation
.
to
||
''
).
trim
();
return
`
${
index
+
1
}
.
${
mode
}
·
${
from
||
to
||
'-'
}
`
;
}
const
path
=
String
(
operation
.
path
||
''
).
trim
();
const
from
=
String
(
operation
.
from
||
''
).
trim
();
const
to
=
String
(
operation
.
to
||
''
).
trim
();
return
`
${
index
+
1
}
.
${
mode
}
·
${
path
||
from
||
to
||
'-'
}
`
;
};
const
getOperationModeTagColor
=
(
mode
=
'set'
)
=>
{
if
(
mode
.
includes
(
'header'
))
return
'cyan'
;
if
(
mode
.
includes
(
'replace'
)
||
mode
.
includes
(
'trim'
))
return
'violet'
;
if
(
mode
.
includes
(
'copy'
)
||
mode
.
includes
(
'move'
))
return
'blue'
;
if
(
mode
.
includes
(
'error'
)
||
mode
.
includes
(
'prune'
))
return
'red'
;
if
(
mode
.
includes
(
'sync'
))
return
'green'
;
return
'grey'
;
};
const
parseInitialState
=
(
rawValue
)
=>
{
const
text
=
typeof
rawValue
===
'string'
?
rawValue
:
''
;
const
trimmed
=
text
.
trim
();
...
...
@@ -439,6 +467,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
const
[
operations
,
setOperations
]
=
useState
([
createDefaultOperation
()]);
const
[
jsonText
,
setJsonText
]
=
useState
(
''
);
const
[
jsonError
,
setJsonError
]
=
useState
(
''
);
const
[
operationSearch
,
setOperationSearch
]
=
useState
(
''
);
const
[
selectedOperationId
,
setSelectedOperationId
]
=
useState
(
''
);
const
[
expandedConditionMap
,
setExpandedConditionMap
]
=
useState
({});
const
[
templateLibraryKey
,
setTemplateLibraryKey
]
=
useState
(
'operations'
);
useEffect
(()
=>
{
if
(
!
visible
)
return
;
...
...
@@ -449,13 +481,73 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
setOperations
(
nextState
.
operations
);
setJsonText
(
nextState
.
jsonText
);
setJsonError
(
nextState
.
jsonError
);
setOperationSearch
(
''
);
setSelectedOperationId
(
nextState
.
operations
[
0
]?.
id
||
''
);
setExpandedConditionMap
({});
setTemplateLibraryKey
(
nextState
.
visualMode
===
'legacy'
?
'legacy'
:
'operations'
,
);
},
[
visible
,
value
]);
useEffect
(()
=>
{
if
(
operations
.
length
===
0
)
{
setSelectedOperationId
(
''
);
return
;
}
if
(
!
operations
.
some
((
item
)
=>
item
.
id
===
selectedOperationId
))
{
setSelectedOperationId
(
operations
[
0
].
id
);
}
},
[
operations
,
selectedOperationId
]);
useEffect
(()
=>
{
setTemplateLibraryKey
(
visualMode
===
'legacy'
?
'legacy'
:
'operations'
);
},
[
visualMode
]);
const
operationCount
=
useMemo
(
()
=>
operations
.
filter
((
item
)
=>
!
isOperationBlank
(
item
)).
length
,
[
operations
],
);
const
filteredOperations
=
useMemo
(()
=>
{
const
keyword
=
operationSearch
.
trim
().
toLowerCase
();
if
(
!
keyword
)
return
operations
;
return
operations
.
filter
((
operation
)
=>
{
const
searchableText
=
[
operation
.
mode
,
operation
.
path
,
operation
.
from
,
operation
.
to
,
operation
.
value_text
,
]
.
filter
(
Boolean
)
.
join
(
' '
)
.
toLowerCase
();
return
searchableText
.
includes
(
keyword
);
});
},
[
operationSearch
,
operations
]);
const
selectedOperation
=
useMemo
(
()
=>
operations
.
find
((
operation
)
=>
operation
.
id
===
selectedOperationId
),
[
operations
,
selectedOperationId
],
);
const
selectedOperationIndex
=
useMemo
(
()
=>
operations
.
findIndex
((
operation
)
=>
operation
.
id
===
selectedOperationId
),
[
operations
,
selectedOperationId
],
);
const
topOperationModes
=
useMemo
(()
=>
{
const
counts
=
operations
.
reduce
((
acc
,
operation
)
=>
{
const
mode
=
operation
.
mode
||
'set'
;
acc
[
mode
]
=
(
acc
[
mode
]
||
0
)
+
1
;
return
acc
;
},
{});
return
Object
.
entries
(
counts
)
.
sort
((
a
,
b
)
=>
b
[
1
]
-
a
[
1
])
.
slice
(
0
,
4
);
},
[
operations
]);
const
buildVisualJson
=
useCallback
(()
=>
{
if
(
visualMode
===
'legacy'
)
{
const
trimmed
=
legacyValue
.
trim
();
...
...
@@ -545,8 +637,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if
(
editMode
===
'visual'
)
return
;
const
trimmed
=
jsonText
.
trim
();
if
(
!
trimmed
)
{
const
fallback
=
createDefaultOperation
();
setVisualMode
(
'operations'
);
setOperations
([
createDefaultOperation
()]);
setOperations
([
fallback
]);
setSelectedOperationId
(
fallback
.
id
);
setLegacyValue
(
''
);
setJsonError
(
''
);
setEditMode
(
'visual'
);
...
...
@@ -563,21 +657,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
!
Array
.
isArray
(
parsed
)
&&
Array
.
isArray
(
parsed
.
operations
)
)
{
setVisualMode
(
'operations'
);
setOperations
(
const
nextOperations
=
parsed
.
operations
.
length
>
0
?
parsed
.
operations
.
map
(
normalizeOperation
)
:
[
createDefaultOperation
()],
);
:
[
createDefaultOperation
()];
setVisualMode
(
'operations'
);
setOperations
(
nextOperations
);
setSelectedOperationId
(
nextOperations
[
0
]?.
id
||
''
);
setLegacyValue
(
''
);
setJsonError
(
''
);
setEditMode
(
'visual'
);
return
;
}
if
(
parsed
&&
typeof
parsed
===
'object'
&&
!
Array
.
isArray
(
parsed
))
{
const
fallback
=
createDefaultOperation
();
setVisualMode
(
'legacy'
);
setLegacyValue
(
JSON
.
stringify
(
parsed
,
null
,
2
));
setOperations
([
createDefaultOperation
()]);
setOperations
([
fallback
]);
setSelectedOperationId
(
fallback
.
id
);
setJsonError
(
''
);
setEditMode
(
'visual'
);
return
;
...
...
@@ -587,27 +684,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
const
setOldTemplate
=
()
=>
{
const
text
=
JSON
.
stringify
(
LEGACY_TEMPLATE
,
null
,
2
);
const
fallback
=
createDefaultOperation
();
setVisualMode
(
'legacy'
);
setLegacyValue
(
text
);
setOperations
([
fallback
]);
setSelectedOperationId
(
fallback
.
id
);
setExpandedConditionMap
({});
setJsonText
(
text
);
setJsonError
(
''
);
setEditMode
(
'visual'
);
setTemplateLibraryKey
(
'legacy'
);
};
const
setNewTemplate
=
()
=>
{
const
nextOperations
=
OPERATION_TEMPLATE
.
operations
.
map
(
normalizeOperation
);
setVisualMode
(
'operations'
);
setOperations
(
OPERATION_TEMPLATE
.
operations
.
map
(
normalizeOperation
));
setOperations
(
nextOperations
);
setSelectedOperationId
(
nextOperations
[
0
]?.
id
||
''
);
setExpandedConditionMap
({});
setJsonText
(
JSON
.
stringify
(
OPERATION_TEMPLATE
,
null
,
2
));
setJsonError
(
''
);
setEditMode
(
'visual'
);
setTemplateLibraryKey
(
'operations'
);
};
const
clearValue
=
()
=>
{
const
fallback
=
createDefaultOperation
();
setVisualMode
(
'operations'
);
setLegacyValue
(
''
);
setOperations
([
createDefaultOperation
()]);
setOperations
([
fallback
]);
setSelectedOperationId
(
fallback
.
id
);
setExpandedConditionMap
({});
setJsonText
(
''
);
setJsonError
(
''
);
setTemplateLibraryKey
(
'operations'
);
};
const
applyTemplateFromLibrary
=
()
=>
{
if
(
templateLibraryKey
===
'legacy'
)
{
setOldTemplate
();
return
;
}
setNewTemplate
();
};
const
resetEditorState
=
()
=>
{
clearValue
();
setEditMode
(
'visual'
);
};
const
updateOperation
=
(
operationId
,
patch
)
=>
{
...
...
@@ -619,10 +743,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
};
const
addOperation
=
()
=>
{
setOperations
((
prev
)
=>
[...
prev
,
createDefaultOperation
()]);
const
created
=
createDefaultOperation
();
setOperations
((
prev
)
=>
[...
prev
,
created
]);
setSelectedOperationId
(
created
.
id
);
};
const
duplicateOperation
=
(
operationId
)
=>
{
let
insertedId
=
''
;
setOperations
((
prev
)
=>
{
const
index
=
prev
.
findIndex
((
item
)
=>
item
.
id
===
operationId
);
if
(
index
<
0
)
return
prev
;
...
...
@@ -643,10 +770,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
pass_missing_key
:
condition
.
pass_missing_key
,
})),
});
insertedId
=
cloned
.
id
;
const
next
=
[...
prev
];
next
.
splice
(
index
+
1
,
0
,
cloned
);
return
next
;
});
if
(
insertedId
)
{
setSelectedOperationId
(
insertedId
);
}
};
const
removeOperation
=
(
operationId
)
=>
{
...
...
@@ -654,19 +785,32 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if
(
prev
.
length
<=
1
)
return
[
createDefaultOperation
()];
return
prev
.
filter
((
item
)
=>
item
.
id
!==
operationId
);
});
setExpandedConditionMap
((
prev
)
=>
{
if
(
!
Object
.
prototype
.
hasOwnProperty
.
call
(
prev
,
operationId
))
{
return
prev
;
}
const
next
=
{
...
prev
};
delete
next
[
operationId
];
return
next
;
});
};
const
addCondition
=
(
operationId
)
=>
{
const
createdCondition
=
createDefaultCondition
();
setOperations
((
prev
)
=>
prev
.
map
((
operation
)
=>
operation
.
id
===
operationId
?
{
...
operation
,
conditions
:
[...(
operation
.
conditions
||
[]),
create
DefaultCondition
()
],
conditions
:
[...(
operation
.
conditions
||
[]),
create
dCondition
],
}
:
operation
,
),
);
setExpandedConditionMap
((
prev
)
=>
({
...
prev
,
[
operationId
]:
[...(
prev
[
operationId
]
||
[]),
createdCondition
.
id
],
}));
};
const
updateCondition
=
(
operationId
,
conditionId
,
patch
)
=>
{
...
...
@@ -697,8 +841,50 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
};
}),
);
setExpandedConditionMap
((
prev
)
=>
({
...
prev
,
[
operationId
]:
(
prev
[
operationId
]
||
[]).
filter
(
(
id
)
=>
id
!==
conditionId
,
),
}));
};
const
selectedConditionKeys
=
useMemo
(
()
=>
expandedConditionMap
[
selectedOperationId
]
||
[],
[
expandedConditionMap
,
selectedOperationId
],
);
const
handleConditionCollapseChange
=
useCallback
(
(
operationId
,
activeKeys
)
=>
{
const
keys
=
(
Array
.
isArray
(
activeKeys
)
?
activeKeys
:
[
activeKeys
]
).
filter
(
Boolean
);
setExpandedConditionMap
((
prev
)
=>
({
...
prev
,
[
operationId
]:
keys
,
}));
},
[],
);
const
expandAllSelectedConditions
=
useCallback
(()
=>
{
if
(
!
selectedOperationId
||
!
selectedOperation
)
return
;
setExpandedConditionMap
((
prev
)
=>
({
...
prev
,
[
selectedOperationId
]:
(
selectedOperation
.
conditions
||
[]).
map
(
(
condition
)
=>
condition
.
id
,
),
}));
},
[
selectedOperation
,
selectedOperationId
]);
const
collapseAllSelectedConditions
=
useCallback
(()
=>
{
if
(
!
selectedOperationId
)
return
;
setExpandedConditionMap
((
prev
)
=>
({
...
prev
,
[
selectedOperationId
]:
[],
}));
},
[
selectedOperationId
]);
const
handleJsonChange
=
(
nextValue
)
=>
{
setJsonText
(
nextValue
);
const
trimmed
=
String
(
nextValue
||
''
).
trim
();
...
...
@@ -761,21 +947,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<
Modal
title=
{
t
(
'参数覆盖'
)
}
visible=
{
visible
}
width=
{
98
0
}
width=
{
112
0
}
onCancel=
{
onCancel
}
onOk=
{
handleSave
}
okText=
{
t
(
'保存'
)
}
cancelText=
{
t
(
'取消'
)
}
>
<
Space
vertical
align=
'start'
spacing=
{
12
}
style=
{
{
width
:
'100%'
}
}
>
<
Banner
fullMode=
{
false
}
type=
'info'
description=
{
t
(
'支持旧格式直接覆盖,也支持新格式 operations 条件编辑;可在可视化和 JSON 之间双向切换。'
,
)
}
/>
<
Space
wrap
>
<
Button
type=
{
editMode
===
'visual'
?
'primary'
:
'tertiary'
}
...
...
@@ -789,32 +967,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
>
{
t
(
'JSON 模式'
)
}
</
Button
>
<
Button
onClick=
{
setOldTemplate
}
>
{
t
(
'旧格式模板'
)
}
</
Button
>
<
Button
onClick=
{
setNewTemplate
}
>
{
t
(
'新格式模板'
)
}
</
Button
>
<
Button
onClick=
{
clearValue
}
>
{
t
(
'不更改'
)
}
</
Button
>
<
Select
value=
{
templateLibraryKey
}
optionList=
{
TEMPLATE_LIBRARY_OPTIONS
}
onChange=
{
(
nextValue
)
=>
setTemplateLibraryKey
(
nextValue
||
'operations'
)
}
style=
{
{
width
:
240
}
}
/>
<
Button
onClick=
{
applyTemplateFromLibrary
}
>
{
t
(
'应用模板'
)
}
</
Button
>
<
Button
onClick=
{
resetEditorState
}
>
{
t
(
'重置'
)
}
</
Button
>
</
Space
>
{
editMode
===
'visual'
?
(
<
div
style=
{
{
width
:
'100%'
}
}
>
<
Space
wrap
style=
{
{
marginBottom
:
12
}
}
>
<
Button
type=
{
visualMode
===
'operations'
?
'primary'
:
'tertiary'
}
onClick=
{
()
=>
setVisualMode
(
'operations'
)
}
>
{
t
(
'新格式模板'
)
}
</
Button
>
<
Button
type=
{
visualMode
===
'legacy'
?
'primary'
:
'tertiary'
}
onClick=
{
()
=>
setVisualMode
(
'legacy'
)
}
>
{
t
(
'旧格式模板'
)
}
</
Button
>
</
Space
>
{
visualMode
===
'legacy'
?
(
<
div
>
<
Text
className=
'mb-2 block'
>
{
t
(
'旧格式(直接覆盖):'
)
}
</
Text
>
<
Input
.
TextArea
<
TextArea
value=
{
legacyValue
}
autosize=
{
{
minRows
:
10
,
maxRows
:
20
}
}
placeholder=
{
JSON
.
stringify
(
LEGACY_TEMPLATE
,
null
,
2
)
}
...
...
@@ -830,462 +1000,731 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<
div
className=
'flex items-center justify-between mb-3'
>
<
Space
>
<
Text
>
{
t
(
'新格式(支持条件判断与json自定义):'
)
}
</
Text
>
<
Tag
color=
'cyan'
>
{
`${t('规则')}: ${operationCount}`
}
</
Tag
>
<
Tag
color=
'cyan'
>
{
`${t('规则')}: ${operationCount}`
}
</
Tag
>
</
Space
>
<
Button
icon=
{
<
IconPlus
/>
}
onClick=
{
addOperation
}
>
{
t
(
'新增规则'
)
}
</
Button
>
</
div
>
<
Space
vertical
spacing=
{
8
}
style=
{
{
width
:
'100%'
}
}
>
{
operations
.
map
((
operation
,
index
)
=>
{
const
mode
=
operation
.
mode
||
'set'
;
const
meta
=
MODE_META
[
mode
]
||
MODE_META
.
set
;
const
conditions
=
operation
.
conditions
||
[];
const
syncFromTarget
=
mode
===
'sync_fields'
?
parseSyncTargetSpec
(
operation
.
from
)
:
null
;
const
syncToTarget
=
mode
===
'sync_fields'
?
parseSyncTargetSpec
(
operation
.
to
)
:
null
;
return
(
<
Card
key=
{
operation
.
id
}
className=
'!rounded-xl border'
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Space
>
<
Tag
>
{
`#${index + 1}`
}
</
Tag
>
<
Text
>
{
mode
}
</
Text
>
</
Space
>
<
Space
>
<
Button
<
Row
gutter=
{
12
}
>
<
Col
xs=
{
24
}
md=
{
8
}
>
<
Card
className=
'!rounded-2xl !border-0 h-full'
bodyStyle=
{
{
padding
:
12
,
background
:
'var(--semi-color-fill-0)'
,
display
:
'flex'
,
flexDirection
:
'column'
,
gap
:
10
,
minHeight
:
520
,
}
}
>
<
div
className=
'flex items-center justify-between'
>
<
Text
strong
>
{
t
(
'规则导航'
)
}
</
Text
>
<
Tag
color=
'grey'
>
{
`${operationCount}/${operations.length}`
}
</
Tag
>
</
div
>
{
topOperationModes
.
length
>
0
?
(
<
Space
wrap
spacing=
{
6
}
>
{
topOperationModes
.
map
(([
mode
,
count
])
=>
(
<
Tag
key=
{
`mode_stat_${mode}`
}
size=
'small'
type=
'tertiary'
onClick=
{
()
=>
duplicateOperation
(
operation
.
id
)
}
color=
{
getOperationModeTagColor
(
mode
)
}
>
{
t
(
'复制'
)
}
</
Button
>
<
Button
theme=
'borderless'
type=
'danger'
icon=
{
<
IconDelete
/>
}
onClick=
{
()
=>
removeOperation
(
operation
.
id
)
}
/>
</
Space
>
</
div
>
<
Row
gutter=
{
12
}
>
<
Col
xs=
{
24
}
md=
{
8
}
>
<
Text
type=
'tertiary'
size=
'small'
>
mode
</
Text
>
<
Select
value=
{
mode
}
optionList=
{
OPERATION_MODE_OPTIONS
}
onChange=
{
(
nextMode
)
=>
updateOperation
(
operation
.
id
,
{
mode
:
nextMode
})
}
style=
{
{
width
:
'100%'
}
}
/>
</
Col
>
{
meta
.
path
||
meta
.
pathOptional
?
(
<
Col
xs=
{
24
}
md=
{
16
}
>
<
Text
type=
'tertiary'
size=
'small'
>
{
meta
.
pathOptional
?
'path (optional)'
:
'path'
}
</
Text
>
<
Input
value=
{
operation
.
path
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'X-Debug-Mode'
:
mode
===
'prune_objects'
?
'messages (optional)'
:
'temperature'
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
operation
.
id
,
{
path
:
nextValue
,
})
}
/>
<
Space
wrap
style=
{
{
marginTop
:
6
}
}
>
{
OPERATION_PATH_SUGGESTIONS
.
map
((
pathItem
)
=>
(
<
Tag
key=
{
`${operation.id}_${pathItem}`
}
size=
'small'
color=
'grey'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
operation
.
id
,
{
path
:
pathItem
,
})
{
`${mode} · ${count}`
}
</
Tag
>
))
}
</
Space
>
)
:
null
}
<
Input
value=
{
operationSearch
}
placeholder=
{
t
(
'搜索规则(mode/path/from/to)'
)
}
onChange=
{
(
nextValue
)
=>
setOperationSearch
(
nextValue
||
''
)
}
showClear
/>
<
div
className=
'overflow-auto'
style=
{
{
flex
:
1
,
minHeight
:
320
,
paddingRight
:
2
}
}
>
{
filteredOperations
.
length
===
0
?
(
<
Text
type=
'tertiary'
size=
'small'
>
{
t
(
'没有匹配的规则'
)
}
</
Text
>
)
:
(
<
div
style=
{
{
display
:
'flex'
,
flexDirection
:
'column'
,
gap
:
8
,
width
:
'100%'
,
}
}
>
{
filteredOperations
.
map
((
operation
)
=>
{
const
index
=
operations
.
findIndex
(
(
item
)
=>
item
.
id
===
operation
.
id
,
);
const
isActive
=
operation
.
id
===
selectedOperationId
;
return
(
<
div
key=
{
operation
.
id
}
role=
'button'
tabIndex=
{
0
}
onClick=
{
()
=>
setSelectedOperationId
(
operation
.
id
)
}
onKeyDown=
{
(
event
)
=>
{
if
(
event
.
key
===
'Enter'
||
event
.
key
===
' '
)
{
event
.
preventDefault
();
setSelectedOperationId
(
operation
.
id
);
}
>
{
pathItem
}
</
Tag
>
))
}
</
Space
>
</
Col
>
)
:
null
}
</
Row
>
<
Text
type=
'tertiary'
size=
'small'
className=
'mt-1 block'
>
{
MODE_DESCRIPTIONS
[
mode
]
||
''
}
</
Text
>
{
meta
.
value
?
(
<
div
className=
'mt-2'
>
<
Text
type=
'tertiary'
size=
'small'
>
value (JSON or plain text)
</
Text
>
<
Input
.
TextArea
value=
{
operation
.
value_text
}
autosize=
{
{
minRows
:
1
,
maxRows
:
4
}
}
placeholder=
'0.7'
onChange=
{
(
nextValue
)
=>
updateOperation
(
operation
.
id
,
{
value_text
:
nextValue
,
})
}
/>
</
div
>
)
:
null
}
{
meta
.
keepOrigin
?
(
<
div
className=
'mt-2'
>
<
Switch
checked=
{
operation
.
keep_origin
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
operation
.
id
,
{
keep_origin
:
nextValue
,
})
}
/>
<
Text
type=
'tertiary'
size=
'small'
className=
'ml-2'
>
keep_origin
</
Text
>
}
}
className=
'w-full rounded-xl px-3 py-3 cursor-pointer transition-colors'
style=
{
{
background
:
isActive
?
'var(--semi-color-primary-light-default)'
:
'var(--semi-color-bg-2)'
,
border
:
isActive
?
'1px solid var(--semi-color-primary)'
:
'1px solid var(--semi-color-border)'
,
}
}
>
<
div
className=
'flex items-start justify-between gap-2'
>
<
div
>
<
Text
strong
>
{
`#${index + 1}`
}
</
Text
>
<
Text
type=
'tertiary'
size=
'small'
className=
'block mt-1'
>
{
getOperationSummary
(
operation
,
index
)
}
</
Text
>
</
div
>
<
Tag
size=
'small'
color=
'grey'
>
{
(
operation
.
conditions
||
[]).
length
}
</
Tag
>
</
div
>
<
Space
spacing=
{
6
}
style=
{
{
marginTop
:
8
}
}
>
<
Tag
size=
'small'
color=
{
getOperationModeTagColor
(
operation
.
mode
||
'set'
,
)
}
>
{
operation
.
mode
||
'set'
}
</
Tag
>
<
Text
type=
'tertiary'
size=
'small'
>
{
t
(
'条件'
)
}
</
Text
>
</
Space
>
</
div
>
);
})
}
</
div
>
)
:
null
}
)
}
</
div
>
</
Card
>
</
Col
>
<
Col
xs=
{
24
}
md=
{
16
}
>
{
selectedOperation
?
(
(()
=>
{
const
mode
=
selectedOperation
.
mode
||
'set'
;
const
meta
=
MODE_META
[
mode
]
||
MODE_META
.
set
;
const
conditions
=
selectedOperation
.
conditions
||
[];
const
syncFromTarget
=
mode
===
'sync_fields'
?
parseSyncTargetSpec
(
selectedOperation
.
from
)
:
null
;
const
syncToTarget
=
mode
===
'sync_fields'
?
parseSyncTargetSpec
(
selectedOperation
.
to
)
:
null
;
return
(
<
Card
className=
'!rounded-2xl !border-0'
bodyStyle=
{
{
padding
:
14
,
background
:
'var(--semi-color-fill-0)'
,
}
}
>
<
div
className=
'flex items-center justify-between mb-3'
>
<
Space
>
<
Tag
color=
'blue'
>
{
`#${selectedOperationIndex + 1}`
}
</
Tag
>
<
Text
strong
>
{
getOperationSummary
(
selectedOperation
,
selectedOperationIndex
,
)
}
</
Text
>
</
Space
>
<
Space
>
<
Button
size=
'small'
type=
'tertiary'
onClick=
{
()
=>
duplicateOperation
(
selectedOperation
.
id
)
}
>
{
t
(
'复制'
)
}
</
Button
>
<
Button
size=
'small'
type=
'danger'
theme=
'borderless'
icon=
{
<
IconDelete
/>
}
onClick=
{
()
=>
removeOperation
(
selectedOperation
.
id
)
}
/>
</
Space
>
</
div
>
{
mode
===
'sync_fields'
?
(
<
div
className=
'mt-2'
>
<
Text
type=
'tertiary'
size=
'small'
>
sync endpoints
</
Text
>
<
Row
gutter=
{
12
}
style=
{
{
marginTop
:
6
}
}
>
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Row
gutter=
{
12
}
>
<
Col
xs=
{
24
}
md=
{
8
}
>
<
Text
type=
'tertiary'
size=
'small'
>
from endpoint
mode
</
Text
>
<
div
className=
'flex gap-2'
>
<
Select
value=
{
syncFromTarget
?.
type
||
'json'
}
optionList=
{
SYNC_TARGET_TYPE_OPTIONS
}
style=
{
{
width
:
120
}
}
onChange=
{
(
nextType
)
=>
updateOperation
(
operation
.
id
,
{
from
:
buildSyncTargetSpec
(
nextType
,
syncFromTarget
?.
key
||
''
,
),
})
}
/>
<
Input
value=
{
syncFromTarget
?.
key
||
''
}
placeholder=
'session_id'
onChange=
{
(
nextKey
)
=>
updateOperation
(
operation
.
id
,
{
from
:
buildSyncTargetSpec
(
syncFromTarget
?.
type
||
'json'
,
nextKey
,
),
})
}
/>
</
div
>
<
Select
value=
{
mode
}
optionList=
{
OPERATION_MODE_OPTIONS
}
onChange=
{
(
nextMode
)
=>
updateOperation
(
selectedOperation
.
id
,
{
mode
:
nextMode
,
})
}
style=
{
{
width
:
'100%'
}
}
/>
</
Col
>
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
to endpoint
</
Text
>
<
div
className=
'flex gap-2'
>
<
Select
value=
{
syncToTarget
?.
type
||
'json'
}
optionList=
{
SYNC_TARGET_TYPE_OPTIONS
}
style=
{
{
width
:
120
}
}
onChange=
{
(
nextType
)
=>
updateOperation
(
operation
.
id
,
{
to
:
buildSyncTargetSpec
(
nextType
,
syncToTarget
?.
key
||
''
,
),
})
}
/>
{
meta
.
path
||
meta
.
pathOptional
?
(
<
Col
xs=
{
24
}
md=
{
16
}
>
<
Text
type=
'tertiary'
size=
'small'
>
{
meta
.
pathOptional
?
'path (optional)'
:
'path'
}
</
Text
>
<
Input
value=
{
syncToTarget
?.
key
||
''
}
placeholder=
'prompt_cache_key'
onChange=
{
(
nextKey
)
=>
updateOperation
(
operation
.
id
,
{
to
:
buildSyncTargetSpec
(
syncToTarget
?.
type
||
'json'
,
nextKey
,
),
value=
{
selectedOperation
.
path
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'X-Debug-Mode'
:
mode
===
'prune_objects'
?
'messages (optional)'
:
'temperature'
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
selectedOperation
.
id
,
{
path
:
nextValue
,
})
}
/>
</
div
>
</
Col
>
<
Space
wrap
style=
{
{
marginTop
:
6
}
}
>
{
OPERATION_PATH_SUGGESTIONS
.
map
(
(
pathItem
)
=>
(
<
Tag
key=
{
`${selectedOperation.id}_${pathItem}`
}
size=
'small'
color=
'grey'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
selectedOperation
.
id
,
{
path
:
pathItem
,
},
)
}
>
{
pathItem
}
</
Tag
>
),
)
}
</
Space
>
</
Col
>
)
:
null
}
</
Row
>
<
Space
wrap
style=
{
{
marginTop
:
8
}
}
>
<
Tag
size=
'small'
color=
'cyan'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
operation
.
id
,
{
from
:
'header:session_id'
,
to
:
'json:prompt_cache_key'
,
})
}
>
{
'header:session_id -> json:prompt_cache_key'
}
</
Tag
>
<
Tag
size=
'small'
color=
'cyan'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
operation
.
id
,
{
from
:
'json:prompt_cache_key'
,
to
:
'header:session_id'
,
})
}
>
{
'json:prompt_cache_key -> header:session_id'
}
</
Tag
>
</
Space
>
</
div
>
)
:
meta
.
from
||
meta
.
to
===
false
||
meta
.
to
?
(
<
Row
gutter=
{
12
}
style=
{
{
marginTop
:
8
}
}
>
{
meta
.
from
||
meta
.
to
===
false
?
(
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
className=
'mt-1 block'
>
{
MODE_DESCRIPTIONS
[
mode
]
||
''
}
</
Text
>
{
meta
.
value
?
(
<
div
className=
'mt-2'
>
<
Text
type=
'tertiary'
size=
'small'
>
from
value (JSON or plain text)
</
Text
>
<
Input
value=
{
operation
.
from
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'Authorization'
:
'model'
}
<
TextArea
value=
{
selectedOperation
.
value_text
}
autosize=
{
{
minRows
:
1
,
maxRows
:
4
}
}
placeholder=
'0.7'
onChange=
{
(
nextValue
)
=>
updateOperation
(
o
peration
.
id
,
{
from
:
nextValue
,
updateOperation
(
selectedO
peration
.
id
,
{
value_text
:
nextValue
,
})
}
/>
</
Col
>
</
div
>
)
:
null
}
{
meta
.
to
||
meta
.
to
===
false
?
(
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
to
</
Text
>
<
Input
value=
{
operation
.
to
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'X-Upstream-Auth'
:
'original_model'
}
{
meta
.
keepOrigin
?
(
<
div
className=
'mt-2'
>
<
Switch
checked=
{
Boolean
(
selectedOperation
.
keep_origin
,
)
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
operation
.
id
,
{
to
:
nextValue
})
updateOperation
(
selectedOperation
.
id
,
{
keep_origin
:
nextValue
,
})
}
/>
</
Col
>
<
Text
type=
'tertiary'
size=
'small'
className=
'ml-2'
>
keep_origin
</
Text
>
</
div
>
)
:
null
}
</
Row
>
)
:
null
}
<
div
className=
'mt-3 border rounded-lg p-2'
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Space
>
<
Text
>
{
t
(
'条件'
)
}
</
Text
>
<
Select
value=
{
operation
.
logic
||
'OR'
}
optionList=
{
[
{
label
:
'OR'
,
value
:
'OR'
},
{
label
:
'AND'
,
value
:
'AND'
},
]
}
style=
{
{
width
:
96
}
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
operation
.
id
,
{
logic
:
nextValue
,
})
}
/>
</
Space
>
<
Button
icon=
{
<
IconPlus
/>
}
size=
'small'
onClick=
{
()
=>
addCondition
(
operation
.
id
)
}
>
{
t
(
'新增条件'
)
}
</
Button
>
</
div
>
{
conditions
.
length
===
0
?
(
<
Text
type=
'tertiary'
size=
'small'
>
{
t
(
'没有条件时,默认总是执行该操作。'
)
}
</
Text
>
)
:
(
<
Space
vertical
spacing=
{
8
}
style=
{
{
width
:
'100%'
}
}
>
{
conditions
.
map
((
condition
,
conditionIndex
)
=>
(
<
Card
key=
{
condition
.
id
}
bodyStyle=
{
{
padding
:
10
}
}
className=
'!rounded-lg'
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Tag
size=
'small'
>
{
`C${conditionIndex + 1}`
}
</
Tag
>
<
Button
theme=
'borderless'
type=
'danger'
icon=
{
<
IconDelete
/>
}
size=
'small'
onClick=
{
()
=>
removeCondition
(
operation
.
id
,
condition
.
id
)
}
/>
</
div
>
<
Row
gutter=
{
12
}
>
<
Col
xs=
{
24
}
md=
{
10
}
>
<
Text
type=
'tertiary'
size=
'small'
>
path
</
Text
>
{
mode
===
'sync_fields'
?
(
<
div
className=
'mt-2'
>
<
Text
type=
'tertiary'
size=
'small'
>
sync endpoints
</
Text
>
<
Row
gutter=
{
12
}
style=
{
{
marginTop
:
6
}
}
>
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
from endpoint
</
Text
>
<
div
className=
'flex gap-2'
>
<
Select
value=
{
syncFromTarget
?.
type
||
'json'
}
optionList=
{
SYNC_TARGET_TYPE_OPTIONS
}
style=
{
{
width
:
120
}
}
onChange=
{
(
nextType
)
=>
updateOperation
(
selectedOperation
.
id
,
{
from
:
buildSyncTargetSpec
(
nextType
,
syncFromTarget
?.
key
||
''
,
),
},
)
}
/>
<
Input
value=
{
condition
.
path
}
placeholder=
'model'
onChange=
{
(
nextValue
)
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
path
:
nextValue
},
value=
{
syncFromTarget
?.
key
||
''
}
placeholder=
'session_id'
onChange=
{
(
nextKey
)
=>
updateOperation
(
selectedOperation
.
id
,
{
from
:
buildSyncTargetSpec
(
syncFromTarget
?.
type
||
'json'
,
nextKey
,
),
},
)
}
/>
<
Space
wrap
style=
{
{
marginTop
:
6
}
}
>
{
CONDITION_PATH_SUGGESTIONS
.
map
(
(
pathItem
)
=>
(
<
Tag
key=
{
`${condition.id}_${pathItem}`
}
size=
'small'
color=
'grey'
className=
'cursor-pointer'
onClick=
{
()
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
path
:
pathItem
},
)
}
>
{
pathItem
}
</
Tag
>
),
)
}
</
Space
>
</
Col
>
<
Col
xs=
{
24
}
md=
{
8
}
>
<
Text
type=
'tertiary'
size=
'small'
>
mode
</
Text
>
</
div
>
</
Col
>
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
to endpoint
</
Text
>
<
div
className=
'flex gap-2'
>
<
Select
value=
{
condition
.
mode
}
optionList=
{
CONDITION_MODE_OPTIONS
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
mode
:
nextValue
},
value=
{
syncToTarget
?.
type
||
'json'
}
optionList=
{
SYNC_TARGET_TYPE_OPTIONS
}
style=
{
{
width
:
120
}
}
onChange=
{
(
nextType
)
=>
updateOperation
(
selectedOperation
.
id
,
{
to
:
buildSyncTargetSpec
(
nextType
,
syncToTarget
?.
key
||
''
,
),
},
)
}
style=
{
{
width
:
'100%'
}
}
/>
</
Col
>
<
Col
xs=
{
24
}
md=
{
6
}
>
<
Text
type=
'tertiary'
size=
'small'
>
value
</
Text
>
<
Input
value=
{
condition
.
value_text
}
placeholder=
'gpt'
onChange=
{
(
nextValue
)
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
value_text
:
nextValue
},
value=
{
syncToTarget
?.
key
||
''
}
placeholder=
'prompt_cache_key'
onChange=
{
(
nextKey
)
=>
updateOperation
(
selectedOperation
.
id
,
{
to
:
buildSyncTargetSpec
(
syncToTarget
?.
type
||
'json'
,
nextKey
,
),
},
)
}
/>
</
Col
>
</
Row
>
<
Space
style=
{
{
marginTop
:
8
}
}
>
<
Switch
checked=
{
condition
.
invert
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
</
div
>
</
Col
>
</
Row
>
<
Space
wrap
style=
{
{
marginTop
:
8
}
}
>
<
Tag
size=
'small'
color=
'cyan'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
selectedOperation
.
id
,
{
from
:
'header:session_id'
,
to
:
'json:prompt_cache_key'
,
})
}
>
{
'header:session_id -> json:prompt_cache_key'
}
</
Tag
>
<
Tag
size=
'small'
color=
'cyan'
className=
'cursor-pointer'
onClick=
{
()
=>
updateOperation
(
selectedOperation
.
id
,
{
from
:
'json:prompt_cache_key'
,
to
:
'header:session_id'
,
})
}
>
{
'json:prompt_cache_key -> header:session_id'
}
</
Tag
>
</
Space
>
</
div
>
)
:
meta
.
from
||
meta
.
to
===
false
||
meta
.
to
?
(
<
Row
gutter=
{
12
}
style=
{
{
marginTop
:
8
}
}
>
{
meta
.
from
||
meta
.
to
===
false
?
(
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
from
</
Text
>
<
Input
value=
{
selectedOperation
.
from
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'Authorization'
:
'model'
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
invert
:
nextValue
},
)
updateOperation
(
selectedOperation
.
id
,
{
from
:
nextValue
,
})
}
/>
</
Col
>
)
:
null
}
{
meta
.
to
||
meta
.
to
===
false
?
(
<
Col
xs=
{
24
}
md=
{
12
}
>
<
Text
type=
'tertiary'
size=
'small'
>
invert
to
</
Text
>
<
Switch
checked=
{
condition
.
pass_missing_key
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
<
Input
value=
{
selectedOperation
.
to
}
placeholder=
{
mode
.
includes
(
'header'
)
?
'X-Upstream-Auth'
:
'original_model'
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
operation
.
id
,
condition
.
id
,
{
pass_missing_key
:
nextValue
},
)
updateOperation
(
selectedOperation
.
id
,
{
to
:
nextValue
,
})
}
/>
<
Text
type=
'tertiary'
size=
'small'
>
pass_missing_key
</
Text
>
</
Space
>
</
Card
>
))
}
</
Space
>
)
}
</
div
>
</
Col
>
)
:
null
}
</
Row
>
)
:
null
}
<
div
className=
'mt-3 rounded-xl p-2'
style=
{
{
background
:
'rgba(127, 127, 127, 0.08)'
,
}
}
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Space
>
<
Text
>
{
t
(
'条件'
)
}
</
Text
>
<
Select
value=
{
selectedOperation
.
logic
||
'OR'
}
optionList=
{
[
{
label
:
'OR'
,
value
:
'OR'
},
{
label
:
'AND'
,
value
:
'AND'
},
]
}
style=
{
{
width
:
96
}
}
onChange=
{
(
nextValue
)
=>
updateOperation
(
selectedOperation
.
id
,
{
logic
:
nextValue
,
})
}
/>
</
Space
>
<
Space
spacing=
{
6
}
>
<
Button
size=
'small'
type=
'tertiary'
onClick=
{
expandAllSelectedConditions
}
>
{
t
(
'全部展开'
)
}
</
Button
>
<
Button
size=
'small'
type=
'tertiary'
onClick=
{
collapseAllSelectedConditions
}
>
{
t
(
'全部收起'
)
}
</
Button
>
<
Button
icon=
{
<
IconPlus
/>
}
size=
'small'
onClick=
{
()
=>
addCondition
(
selectedOperation
.
id
)
}
>
{
t
(
'新增条件'
)
}
</
Button
>
</
Space
>
</
div
>
{
conditions
.
length
===
0
?
(
<
Text
type=
'tertiary'
size=
'small'
>
{
t
(
'没有条件时,默认总是执行该操作。'
)
}
</
Text
>
)
:
(
<
Collapse
keepDOM
activeKey=
{
selectedConditionKeys
}
onChange=
{
(
activeKeys
)
=>
handleConditionCollapseChange
(
selectedOperation
.
id
,
activeKeys
,
)
}
>
{
conditions
.
map
(
(
condition
,
conditionIndex
)
=>
(
<
Collapse
.
Panel
key=
{
condition
.
id
}
itemKey=
{
condition
.
id
}
header=
{
<
Space
spacing=
{
8
}
>
<
Tag
size=
'small'
>
{
`C${conditionIndex + 1}`
}
</
Tag
>
<
Text
type=
'tertiary'
size=
'small'
>
{
condition
.
path
||
t
(
'未设置 path'
)
}
</
Text
>
</
Space
>
}
>
<
div
>
<
div
className=
'flex justify-end mb-2'
>
<
Button
theme=
'borderless'
type=
'danger'
icon=
{
<
IconDelete
/>
}
size=
'small'
onClick=
{
()
=>
removeCondition
(
selectedOperation
.
id
,
condition
.
id
,
)
}
/>
</
div
>
<
Row
gutter=
{
12
}
>
<
Col
xs=
{
24
}
md=
{
10
}
>
<
Text
type=
'tertiary'
size=
'small'
>
path
</
Text
>
<
Input
value=
{
condition
.
path
}
placeholder=
'model'
onChange=
{
(
nextValue
)
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
path
:
nextValue
},
)
}
/>
<
Space
wrap
style=
{
{
marginTop
:
6
}
}
>
{
CONDITION_PATH_SUGGESTIONS
.
map
(
(
pathItem
)
=>
(
<
Tag
key=
{
`${condition.id}_${pathItem}`
}
size=
'small'
color=
'grey'
className=
'cursor-pointer'
onClick=
{
()
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
path
:
pathItem
},
)
}
>
{
pathItem
}
</
Tag
>
),
)
}
</
Space
>
</
Col
>
<
Col
xs=
{
24
}
md=
{
8
}
>
<
Text
type=
'tertiary'
size=
'small'
>
mode
</
Text
>
<
Select
value=
{
condition
.
mode
}
optionList=
{
CONDITION_MODE_OPTIONS
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
mode
:
nextValue
},
)
}
style=
{
{
width
:
'100%'
}
}
/>
</
Col
>
<
Col
xs=
{
24
}
md=
{
6
}
>
<
Text
type=
'tertiary'
size=
'small'
>
value
</
Text
>
<
Input
value=
{
condition
.
value_text
}
placeholder=
'gpt'
onChange=
{
(
nextValue
)
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
value_text
:
nextValue
},
)
}
/>
</
Col
>
</
Row
>
<
Space
style=
{
{
marginTop
:
8
}
}
>
<
Switch
checked=
{
Boolean
(
condition
.
invert
,
)
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
invert
:
nextValue
},
)
}
/>
<
Text
type=
'tertiary'
size=
'small'
>
invert
</
Text
>
<
Switch
checked=
{
Boolean
(
condition
.
pass_missing_key
,
)
}
checkedText=
{
t
(
'开'
)
}
uncheckedText=
{
t
(
'关'
)
}
onChange=
{
(
nextValue
)
=>
updateCondition
(
selectedOperation
.
id
,
condition
.
id
,
{
pass_missing_key
:
nextValue
,
},
)
}
/>
<
Text
type=
'tertiary'
size=
'small'
>
pass_missing_key
</
Text
>
</
Space
>
</
div
>
</
Collapse
.
Panel
>
),
)
}
</
Collapse
>
)
}
</
div
>
</
Card
>
);
})()
)
:
(
<
Card
className=
'!rounded-2xl !border-0'
bodyStyle=
{
{
padding
:
14
,
background
:
'var(--semi-color-fill-0)'
,
}
}
>
<
Text
type=
'tertiary'
>
{
t
(
'请选择一条规则进行编辑。'
)
}
</
Text
>
</
Card
>
);
})
}
</
Space
>
<
Card
className=
'!rounded-xl border mt-3'
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Text
>
{
t
(
'实时 JSON 预览'
)
}
</
Text
>
<
Tag
color=
'grey'
>
{
t
(
'预览'
)
}
</
Tag
>
</
div
>
<
pre
className=
'mb-0 text-xs leading-5 whitespace-pre-wrap break-all max-h-64 overflow-auto'
>
{
visualPreview
||
'
{}
'
}
</
pre
>
</
Card
>
)
}
<
Card
className=
'!rounded-2xl !border-0 mt-3'
bodyStyle=
{
{
padding
:
12
,
background
:
'var(--semi-color-fill-0)'
,
}
}
>
<
div
className=
'flex items-center justify-between mb-2'
>
<
Text
>
{
t
(
'实时 JSON 预览'
)
}
</
Text
>
<
Tag
color=
'grey'
>
{
t
(
'预览'
)
}
</
Tag
>
</
div
>
<
pre
className=
'mb-0 text-xs leading-5 whitespace-pre-wrap break-all max-h-64 overflow-auto'
>
{
visualPreview
||
'
{}
'
}
</
pre
>
</
Card
>
</
Col
>
</
Row
>
</
div
>
)
}
</
div
>
...
...
@@ -1295,7 +1734,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<
Button
onClick=
{
formatJson
}
>
{
t
(
'格式化'
)
}
</
Button
>
<
Tag
color=
'grey'
>
{
t
(
'普通编辑'
)
}
</
Tag
>
</
Space
>
<
Input
.
TextArea
<
TextArea
value=
{
jsonText
}
autosize=
{
{
minRows
:
18
,
maxRows
:
28
}
}
onChange=
{
(
nextValue
)
=>
handleJsonChange
(
nextValue
??
''
)
}
...
...
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