Commit f0886c8a by Seefs

fix

parent 6b58648d
...@@ -20,10 +20,10 @@ For commercial licensing, please contact support@quantumnous.com ...@@ -20,10 +20,10 @@ For commercial licensing, please contact support@quantumnous.com
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Banner,
Button, Button,
Card, Card,
Col, Col,
Collapse,
Input, Input,
Modal, Modal,
Row, Row,
...@@ -31,6 +31,7 @@ import { ...@@ -31,6 +31,7 @@ import {
Space, Space,
Switch, Switch,
Tag, Tag,
TextArea,
Typography, Typography,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus } from '@douyinfe/semi-icons'; import { IconDelete, IconPlus } from '@douyinfe/semi-icons';
...@@ -204,6 +205,11 @@ const OPERATION_TEMPLATE = { ...@@ -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; let localIdSeed = 0;
const nextLocalId = () => `param_override_${Date.now()}_${localIdSeed++}`; const nextLocalId = () => `param_override_${Date.now()}_${localIdSeed++}`;
...@@ -274,6 +280,28 @@ const normalizeOperation = (operation = {}) => ({ ...@@ -274,6 +280,28 @@ const normalizeOperation = (operation = {}) => ({
const createDefaultOperation = () => normalizeOperation({ mode: 'set' }); 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 parseInitialState = (rawValue) => {
const text = typeof rawValue === 'string' ? rawValue : ''; const text = typeof rawValue === 'string' ? rawValue : '';
const trimmed = text.trim(); const trimmed = text.trim();
...@@ -439,6 +467,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -439,6 +467,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
const [operations, setOperations] = useState([createDefaultOperation()]); const [operations, setOperations] = useState([createDefaultOperation()]);
const [jsonText, setJsonText] = useState(''); const [jsonText, setJsonText] = useState('');
const [jsonError, setJsonError] = useState(''); const [jsonError, setJsonError] = useState('');
const [operationSearch, setOperationSearch] = useState('');
const [selectedOperationId, setSelectedOperationId] = useState('');
const [expandedConditionMap, setExpandedConditionMap] = useState({});
const [templateLibraryKey, setTemplateLibraryKey] = useState('operations');
useEffect(() => { useEffect(() => {
if (!visible) return; if (!visible) return;
...@@ -449,13 +481,73 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -449,13 +481,73 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
setOperations(nextState.operations); setOperations(nextState.operations);
setJsonText(nextState.jsonText); setJsonText(nextState.jsonText);
setJsonError(nextState.jsonError); setJsonError(nextState.jsonError);
setOperationSearch('');
setSelectedOperationId(nextState.operations[0]?.id || '');
setExpandedConditionMap({});
setTemplateLibraryKey(
nextState.visualMode === 'legacy' ? 'legacy' : 'operations',
);
}, [visible, value]); }, [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( const operationCount = useMemo(
() => operations.filter((item) => !isOperationBlank(item)).length, () => operations.filter((item) => !isOperationBlank(item)).length,
[operations], [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(() => { const buildVisualJson = useCallback(() => {
if (visualMode === 'legacy') { if (visualMode === 'legacy') {
const trimmed = legacyValue.trim(); const trimmed = legacyValue.trim();
...@@ -545,8 +637,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -545,8 +637,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if (editMode === 'visual') return; if (editMode === 'visual') return;
const trimmed = jsonText.trim(); const trimmed = jsonText.trim();
if (!trimmed) { if (!trimmed) {
const fallback = createDefaultOperation();
setVisualMode('operations'); setVisualMode('operations');
setOperations([createDefaultOperation()]); setOperations([fallback]);
setSelectedOperationId(fallback.id);
setLegacyValue(''); setLegacyValue('');
setJsonError(''); setJsonError('');
setEditMode('visual'); setEditMode('visual');
...@@ -563,21 +657,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -563,21 +657,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
!Array.isArray(parsed) && !Array.isArray(parsed) &&
Array.isArray(parsed.operations) Array.isArray(parsed.operations)
) { ) {
setVisualMode('operations'); const nextOperations =
setOperations(
parsed.operations.length > 0 parsed.operations.length > 0
? parsed.operations.map(normalizeOperation) ? parsed.operations.map(normalizeOperation)
: [createDefaultOperation()], : [createDefaultOperation()];
); setVisualMode('operations');
setOperations(nextOperations);
setSelectedOperationId(nextOperations[0]?.id || '');
setLegacyValue(''); setLegacyValue('');
setJsonError(''); setJsonError('');
setEditMode('visual'); setEditMode('visual');
return; return;
} }
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
const fallback = createDefaultOperation();
setVisualMode('legacy'); setVisualMode('legacy');
setLegacyValue(JSON.stringify(parsed, null, 2)); setLegacyValue(JSON.stringify(parsed, null, 2));
setOperations([createDefaultOperation()]); setOperations([fallback]);
setSelectedOperationId(fallback.id);
setJsonError(''); setJsonError('');
setEditMode('visual'); setEditMode('visual');
return; return;
...@@ -587,27 +684,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -587,27 +684,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
const setOldTemplate = () => { const setOldTemplate = () => {
const text = JSON.stringify(LEGACY_TEMPLATE, null, 2); const text = JSON.stringify(LEGACY_TEMPLATE, null, 2);
const fallback = createDefaultOperation();
setVisualMode('legacy'); setVisualMode('legacy');
setLegacyValue(text); setLegacyValue(text);
setOperations([fallback]);
setSelectedOperationId(fallback.id);
setExpandedConditionMap({});
setJsonText(text); setJsonText(text);
setJsonError(''); setJsonError('');
setEditMode('visual'); setEditMode('visual');
setTemplateLibraryKey('legacy');
}; };
const setNewTemplate = () => { const setNewTemplate = () => {
const nextOperations =
OPERATION_TEMPLATE.operations.map(normalizeOperation);
setVisualMode('operations'); setVisualMode('operations');
setOperations(OPERATION_TEMPLATE.operations.map(normalizeOperation)); setOperations(nextOperations);
setSelectedOperationId(nextOperations[0]?.id || '');
setExpandedConditionMap({});
setJsonText(JSON.stringify(OPERATION_TEMPLATE, null, 2)); setJsonText(JSON.stringify(OPERATION_TEMPLATE, null, 2));
setJsonError(''); setJsonError('');
setEditMode('visual'); setEditMode('visual');
setTemplateLibraryKey('operations');
}; };
const clearValue = () => { const clearValue = () => {
const fallback = createDefaultOperation();
setVisualMode('operations'); setVisualMode('operations');
setLegacyValue(''); setLegacyValue('');
setOperations([createDefaultOperation()]); setOperations([fallback]);
setSelectedOperationId(fallback.id);
setExpandedConditionMap({});
setJsonText(''); setJsonText('');
setJsonError(''); setJsonError('');
setTemplateLibraryKey('operations');
};
const applyTemplateFromLibrary = () => {
if (templateLibraryKey === 'legacy') {
setOldTemplate();
return;
}
setNewTemplate();
};
const resetEditorState = () => {
clearValue();
setEditMode('visual');
}; };
const updateOperation = (operationId, patch) => { const updateOperation = (operationId, patch) => {
...@@ -619,10 +743,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -619,10 +743,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
}; };
const addOperation = () => { const addOperation = () => {
setOperations((prev) => [...prev, createDefaultOperation()]); const created = createDefaultOperation();
setOperations((prev) => [...prev, created]);
setSelectedOperationId(created.id);
}; };
const duplicateOperation = (operationId) => { const duplicateOperation = (operationId) => {
let insertedId = '';
setOperations((prev) => { setOperations((prev) => {
const index = prev.findIndex((item) => item.id === operationId); const index = prev.findIndex((item) => item.id === operationId);
if (index < 0) return prev; if (index < 0) return prev;
...@@ -643,10 +770,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -643,10 +770,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
pass_missing_key: condition.pass_missing_key, pass_missing_key: condition.pass_missing_key,
})), })),
}); });
insertedId = cloned.id;
const next = [...prev]; const next = [...prev];
next.splice(index + 1, 0, cloned); next.splice(index + 1, 0, cloned);
return next; return next;
}); });
if (insertedId) {
setSelectedOperationId(insertedId);
}
}; };
const removeOperation = (operationId) => { const removeOperation = (operationId) => {
...@@ -654,19 +785,32 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -654,19 +785,32 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
if (prev.length <= 1) return [createDefaultOperation()]; if (prev.length <= 1) return [createDefaultOperation()];
return prev.filter((item) => item.id !== operationId); 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 addCondition = (operationId) => {
const createdCondition = createDefaultCondition();
setOperations((prev) => setOperations((prev) =>
prev.map((operation) => prev.map((operation) =>
operation.id === operationId operation.id === operationId
? { ? {
...operation, ...operation,
conditions: [...(operation.conditions || []), createDefaultCondition()], conditions: [...(operation.conditions || []), createdCondition],
} }
: operation, : operation,
), ),
); );
setExpandedConditionMap((prev) => ({
...prev,
[operationId]: [...(prev[operationId] || []), createdCondition.id],
}));
}; };
const updateCondition = (operationId, conditionId, patch) => { const updateCondition = (operationId, conditionId, patch) => {
...@@ -697,8 +841,50 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -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) => { const handleJsonChange = (nextValue) => {
setJsonText(nextValue); setJsonText(nextValue);
const trimmed = String(nextValue || '').trim(); const trimmed = String(nextValue || '').trim();
...@@ -761,21 +947,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -761,21 +947,13 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<Modal <Modal
title={t('参数覆盖')} title={t('参数覆盖')}
visible={visible} visible={visible}
width={980} width={1120}
onCancel={onCancel} onCancel={onCancel}
onOk={handleSave} onOk={handleSave}
okText={t('保存')} okText={t('保存')}
cancelText={t('取消')} cancelText={t('取消')}
> >
<Space vertical align='start' spacing={12} style={{ width: '100%' }}> <Space vertical align='start' spacing={12} style={{ width: '100%' }}>
<Banner
fullMode={false}
type='info'
description={t(
'支持旧格式直接覆盖,也支持新格式 operations 条件编辑;可在可视化和 JSON 之间双向切换。',
)}
/>
<Space wrap> <Space wrap>
<Button <Button
type={editMode === 'visual' ? 'primary' : 'tertiary'} type={editMode === 'visual' ? 'primary' : 'tertiary'}
...@@ -789,32 +967,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -789,32 +967,24 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
> >
{t('JSON 模式')} {t('JSON 模式')}
</Button> </Button>
<Button onClick={setOldTemplate}>{t('旧格式模板')}</Button> <Select
<Button onClick={setNewTemplate}>{t('新格式模板')}</Button> value={templateLibraryKey}
<Button onClick={clearValue}>{t('不更改')}</Button> optionList={TEMPLATE_LIBRARY_OPTIONS}
onChange={(nextValue) =>
setTemplateLibraryKey(nextValue || 'operations')
}
style={{ width: 240 }}
/>
<Button onClick={applyTemplateFromLibrary}>{t('应用模板')}</Button>
<Button onClick={resetEditorState}>{t('重置')}</Button>
</Space> </Space>
{editMode === 'visual' ? ( {editMode === 'visual' ? (
<div style={{ width: '100%' }}> <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' ? ( {visualMode === 'legacy' ? (
<div> <div>
<Text className='mb-2 block'>{t('旧格式(直接覆盖):')}</Text> <Text className='mb-2 block'>{t('旧格式(直接覆盖):')}</Text>
<Input.TextArea <TextArea
value={legacyValue} value={legacyValue}
autosize={{ minRows: 10, maxRows: 20 }} autosize={{ minRows: 10, maxRows: 20 }}
placeholder={JSON.stringify(LEGACY_TEMPLATE, null, 2)} placeholder={JSON.stringify(LEGACY_TEMPLATE, null, 2)}
...@@ -830,48 +1000,190 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -830,48 +1000,190 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<div className='flex items-center justify-between mb-3'> <div className='flex items-center justify-between mb-3'>
<Space> <Space>
<Text>{t('新格式(支持条件判断与json自定义):')}</Text> <Text>{t('新格式(支持条件判断与json自定义):')}</Text>
<Tag color='cyan'> <Tag color='cyan'>{`${t('规则')}: ${operationCount}`}</Tag>
{`${t('规则')}: ${operationCount}`}
</Tag>
</Space> </Space>
<Button icon={<IconPlus />} onClick={addOperation}> <Button icon={<IconPlus />} onClick={addOperation}>
{t('新增规则')} {t('新增规则')}
</Button> </Button>
</div> </div>
<Space vertical spacing={8} style={{ width: '100%' }}> <Row gutter={12}>
{operations.map((operation, index) => { <Col xs={24} md={8}>
const mode = operation.mode || 'set'; <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'
color={getOperationModeTagColor(mode)}
>
{`${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);
}
}}
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>
)}
</div>
</Card>
</Col>
<Col xs={24} md={16}>
{selectedOperation ? (
(() => {
const mode = selectedOperation.mode || 'set';
const meta = MODE_META[mode] || MODE_META.set; const meta = MODE_META[mode] || MODE_META.set;
const conditions = operation.conditions || []; const conditions = selectedOperation.conditions || [];
const syncFromTarget = const syncFromTarget =
mode === 'sync_fields' mode === 'sync_fields'
? parseSyncTargetSpec(operation.from) ? parseSyncTargetSpec(selectedOperation.from)
: null; : null;
const syncToTarget = const syncToTarget =
mode === 'sync_fields' mode === 'sync_fields'
? parseSyncTargetSpec(operation.to) ? parseSyncTargetSpec(selectedOperation.to)
: null; : null;
return ( return (
<Card key={operation.id} className='!rounded-xl border'> <Card
<div className='flex items-center justify-between mb-2'> className='!rounded-2xl !border-0'
bodyStyle={{
padding: 14,
background: 'var(--semi-color-fill-0)',
}}
>
<div className='flex items-center justify-between mb-3'>
<Space> <Space>
<Tag>{`#${index + 1}`}</Tag> <Tag color='blue'>{`#${selectedOperationIndex + 1}`}</Tag>
<Text>{mode}</Text> <Text strong>
{getOperationSummary(
selectedOperation,
selectedOperationIndex,
)}
</Text>
</Space> </Space>
<Space> <Space>
<Button <Button
size='small' size='small'
type='tertiary' type='tertiary'
onClick={() => duplicateOperation(operation.id)} onClick={() =>
duplicateOperation(selectedOperation.id)
}
> >
{t('复制')} {t('复制')}
</Button> </Button>
<Button <Button
theme='borderless' size='small'
type='danger' type='danger'
theme='borderless'
icon={<IconDelete />} icon={<IconDelete />}
onClick={() => removeOperation(operation.id)} onClick={() =>
removeOperation(selectedOperation.id)
}
/> />
</Space> </Space>
</div> </div>
...@@ -885,7 +1197,9 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -885,7 +1197,9 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
value={mode} value={mode}
optionList={OPERATION_MODE_OPTIONS} optionList={OPERATION_MODE_OPTIONS}
onChange={(nextMode) => onChange={(nextMode) =>
updateOperation(operation.id, { mode: nextMode }) updateOperation(selectedOperation.id, {
mode: nextMode,
})
} }
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
...@@ -893,10 +1207,12 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -893,10 +1207,12 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
{meta.path || meta.pathOptional ? ( {meta.path || meta.pathOptional ? (
<Col xs={24} md={16}> <Col xs={24} md={16}>
<Text type='tertiary' size='small'> <Text type='tertiary' size='small'>
{meta.pathOptional ? 'path (optional)' : 'path'} {meta.pathOptional
? 'path (optional)'
: 'path'}
</Text> </Text>
<Input <Input
value={operation.path} value={selectedOperation.path}
placeholder={ placeholder={
mode.includes('header') mode.includes('header')
? 'X-Debug-Mode' ? 'X-Debug-Mode'
...@@ -905,32 +1221,42 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -905,32 +1221,42 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
: 'temperature' : 'temperature'
} }
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
path: nextValue, path: nextValue,
}) })
} }
/> />
<Space wrap style={{ marginTop: 6 }}> <Space wrap style={{ marginTop: 6 }}>
{OPERATION_PATH_SUGGESTIONS.map((pathItem) => ( {OPERATION_PATH_SUGGESTIONS.map(
(pathItem) => (
<Tag <Tag
key={`${operation.id}_${pathItem}`} key={`${selectedOperation.id}_${pathItem}`}
size='small' size='small'
color='grey' color='grey'
className='cursor-pointer' className='cursor-pointer'
onClick={() => onClick={() =>
updateOperation(operation.id, { updateOperation(
selectedOperation.id,
{
path: pathItem, path: pathItem,
}) },
)
} }
> >
{pathItem} {pathItem}
</Tag> </Tag>
))} ),
)}
</Space> </Space>
</Col> </Col>
) : null} ) : null}
</Row> </Row>
<Text type='tertiary' size='small' className='mt-1 block'>
<Text
type='tertiary'
size='small'
className='mt-1 block'
>
{MODE_DESCRIPTIONS[mode] || ''} {MODE_DESCRIPTIONS[mode] || ''}
</Text> </Text>
...@@ -939,12 +1265,12 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -939,12 +1265,12 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<Text type='tertiary' size='small'> <Text type='tertiary' size='small'>
value (JSON or plain text) value (JSON or plain text)
</Text> </Text>
<Input.TextArea <TextArea
value={operation.value_text} value={selectedOperation.value_text}
autosize={{ minRows: 1, maxRows: 4 }} autosize={{ minRows: 1, maxRows: 4 }}
placeholder='0.7' placeholder='0.7'
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
value_text: nextValue, value_text: nextValue,
}) })
} }
...@@ -955,16 +1281,22 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -955,16 +1281,22 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
{meta.keepOrigin ? ( {meta.keepOrigin ? (
<div className='mt-2'> <div className='mt-2'>
<Switch <Switch
checked={operation.keep_origin} checked={Boolean(
selectedOperation.keep_origin,
)}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('关')}
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
keep_origin: nextValue, keep_origin: nextValue,
}) })
} }
/> />
<Text type='tertiary' size='small' className='ml-2'> <Text
type='tertiary'
size='small'
className='ml-2'
>
keep_origin keep_origin
</Text> </Text>
</div> </div>
...@@ -986,24 +1318,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -986,24 +1318,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
optionList={SYNC_TARGET_TYPE_OPTIONS} optionList={SYNC_TARGET_TYPE_OPTIONS}
style={{ width: 120 }} style={{ width: 120 }}
onChange={(nextType) => onChange={(nextType) =>
updateOperation(operation.id, { updateOperation(
selectedOperation.id,
{
from: buildSyncTargetSpec( from: buildSyncTargetSpec(
nextType, nextType,
syncFromTarget?.key || '', syncFromTarget?.key || '',
), ),
}) },
)
} }
/> />
<Input <Input
value={syncFromTarget?.key || ''} value={syncFromTarget?.key || ''}
placeholder='session_id' placeholder='session_id'
onChange={(nextKey) => onChange={(nextKey) =>
updateOperation(operation.id, { updateOperation(
selectedOperation.id,
{
from: buildSyncTargetSpec( from: buildSyncTargetSpec(
syncFromTarget?.type || 'json', syncFromTarget?.type || 'json',
nextKey, nextKey,
), ),
}) },
)
} }
/> />
</div> </div>
...@@ -1018,24 +1356,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1018,24 +1356,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
optionList={SYNC_TARGET_TYPE_OPTIONS} optionList={SYNC_TARGET_TYPE_OPTIONS}
style={{ width: 120 }} style={{ width: 120 }}
onChange={(nextType) => onChange={(nextType) =>
updateOperation(operation.id, { updateOperation(
selectedOperation.id,
{
to: buildSyncTargetSpec( to: buildSyncTargetSpec(
nextType, nextType,
syncToTarget?.key || '', syncToTarget?.key || '',
), ),
}) },
)
} }
/> />
<Input <Input
value={syncToTarget?.key || ''} value={syncToTarget?.key || ''}
placeholder='prompt_cache_key' placeholder='prompt_cache_key'
onChange={(nextKey) => onChange={(nextKey) =>
updateOperation(operation.id, { updateOperation(
selectedOperation.id,
{
to: buildSyncTargetSpec( to: buildSyncTargetSpec(
syncToTarget?.type || 'json', syncToTarget?.type || 'json',
nextKey, nextKey,
), ),
}) },
)
} }
/> />
</div> </div>
...@@ -1047,26 +1391,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1047,26 +1391,30 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
color='cyan' color='cyan'
className='cursor-pointer' className='cursor-pointer'
onClick={() => onClick={() =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
from: 'header:session_id', from: 'header:session_id',
to: 'json:prompt_cache_key', to: 'json:prompt_cache_key',
}) })
} }
> >
{'header:session_id -> json:prompt_cache_key'} {
'header:session_id -> json:prompt_cache_key'
}
</Tag> </Tag>
<Tag <Tag
size='small' size='small'
color='cyan' color='cyan'
className='cursor-pointer' className='cursor-pointer'
onClick={() => onClick={() =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
from: 'json:prompt_cache_key', from: 'json:prompt_cache_key',
to: 'header:session_id', to: 'header:session_id',
}) })
} }
> >
{'json:prompt_cache_key -> header:session_id'} {
'json:prompt_cache_key -> header:session_id'
}
</Tag> </Tag>
</Space> </Space>
</div> </div>
...@@ -1078,14 +1426,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1078,14 +1426,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
from from
</Text> </Text>
<Input <Input
value={operation.from} value={selectedOperation.from}
placeholder={ placeholder={
mode.includes('header') mode.includes('header')
? 'Authorization' ? 'Authorization'
: 'model' : 'model'
} }
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
from: nextValue, from: nextValue,
}) })
} }
...@@ -1098,14 +1446,16 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1098,14 +1446,16 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
to to
</Text> </Text>
<Input <Input
value={operation.to} value={selectedOperation.to}
placeholder={ placeholder={
mode.includes('header') mode.includes('header')
? 'X-Upstream-Auth' ? 'X-Upstream-Auth'
: 'original_model' : 'original_model'
} }
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { to: nextValue }) updateOperation(selectedOperation.id, {
to: nextValue,
})
} }
/> />
</Col> </Col>
...@@ -1113,31 +1463,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1113,31 +1463,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
</Row> </Row>
) : null} ) : null}
<div className='mt-3 border rounded-lg p-2'> <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'> <div className='flex items-center justify-between mb-2'>
<Space> <Space>
<Text>{t('条件')}</Text> <Text>{t('条件')}</Text>
<Select <Select
value={operation.logic || 'OR'} value={selectedOperation.logic || 'OR'}
optionList={[ optionList={[
{ label: 'OR', value: 'OR' }, { label: 'OR', value: 'OR' },
{ label: 'AND', value: 'AND' }, { label: 'AND', value: 'AND' },
]} ]}
style={{ width: 96 }} style={{ width: 96 }}
onChange={(nextValue) => onChange={(nextValue) =>
updateOperation(operation.id, { updateOperation(selectedOperation.id, {
logic: nextValue, logic: nextValue,
}) })
} }
/> />
</Space> </Space>
<Space spacing={6}>
<Button
size='small'
type='tertiary'
onClick={expandAllSelectedConditions}
>
{t('全部展开')}
</Button>
<Button
size='small'
type='tertiary'
onClick={collapseAllSelectedConditions}
>
{t('全部收起')}
</Button>
<Button <Button
icon={<IconPlus />} icon={<IconPlus />}
size='small' size='small'
onClick={() => addCondition(operation.id)} onClick={() =>
addCondition(selectedOperation.id)
}
> >
{t('新增条件')} {t('新增条件')}
</Button> </Button>
</Space>
</div> </div>
{conditions.length === 0 ? ( {conditions.length === 0 ? (
...@@ -1145,28 +1518,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1145,28 +1518,54 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
{t('没有条件时,默认总是执行该操作。')} {t('没有条件时,默认总是执行该操作。')}
</Text> </Text>
) : ( ) : (
<Space vertical spacing={8} style={{ width: '100%' }}> <Collapse
{conditions.map((condition, conditionIndex) => ( keepDOM
<Card activeKey={selectedConditionKeys}
onChange={(activeKeys) =>
handleConditionCollapseChange(
selectedOperation.id,
activeKeys,
)
}
>
{conditions.map(
(condition, conditionIndex) => (
<Collapse.Panel
key={condition.id} key={condition.id}
bodyStyle={{ padding: 10 }} itemKey={condition.id}
className='!rounded-lg' header={
<Space spacing={8}>
<Tag size='small'>
{`C${conditionIndex + 1}`}
</Tag>
<Text type='tertiary' size='small'>
{condition.path ||
t('未设置 path')}
</Text>
</Space>
}
> >
<div className='flex items-center justify-between mb-2'> <div>
<Tag size='small'>{`C${conditionIndex + 1}`}</Tag> <div className='flex justify-end mb-2'>
<Button <Button
theme='borderless' theme='borderless'
type='danger' type='danger'
icon={<IconDelete />} icon={<IconDelete />}
size='small' size='small'
onClick={() => onClick={() =>
removeCondition(operation.id, condition.id) removeCondition(
selectedOperation.id,
condition.id,
)
} }
/> />
</div> </div>
<Row gutter={12}> <Row gutter={12}>
<Col xs={24} md={10}> <Col xs={24} md={10}>
<Text type='tertiary' size='small'> <Text
type='tertiary'
size='small'
>
path path
</Text> </Text>
<Input <Input
...@@ -1174,13 +1573,16 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1174,13 +1573,16 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
placeholder='model' placeholder='model'
onChange={(nextValue) => onChange={(nextValue) =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ path: nextValue }, { path: nextValue },
) )
} }
/> />
<Space wrap style={{ marginTop: 6 }}> <Space
wrap
style={{ marginTop: 6 }}
>
{CONDITION_PATH_SUGGESTIONS.map( {CONDITION_PATH_SUGGESTIONS.map(
(pathItem) => ( (pathItem) => (
<Tag <Tag
...@@ -1190,7 +1592,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1190,7 +1592,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
className='cursor-pointer' className='cursor-pointer'
onClick={() => onClick={() =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ path: pathItem }, { path: pathItem },
) )
...@@ -1203,15 +1605,20 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1203,15 +1605,20 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
</Space> </Space>
</Col> </Col>
<Col xs={24} md={8}> <Col xs={24} md={8}>
<Text type='tertiary' size='small'> <Text
type='tertiary'
size='small'
>
mode mode
</Text> </Text>
<Select <Select
value={condition.mode} value={condition.mode}
optionList={CONDITION_MODE_OPTIONS} optionList={
CONDITION_MODE_OPTIONS
}
onChange={(nextValue) => onChange={(nextValue) =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ mode: nextValue }, { mode: nextValue },
) )
...@@ -1220,7 +1627,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1220,7 +1627,10 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
/> />
</Col> </Col>
<Col xs={24} md={6}> <Col xs={24} md={6}>
<Text type='tertiary' size='small'> <Text
type='tertiary'
size='small'
>
value value
</Text> </Text>
<Input <Input
...@@ -1228,7 +1638,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1228,7 +1638,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
placeholder='gpt' placeholder='gpt'
onChange={(nextValue) => onChange={(nextValue) =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ value_text: nextValue }, { value_text: nextValue },
) )
...@@ -1238,12 +1648,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1238,12 +1648,14 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
</Row> </Row>
<Space style={{ marginTop: 8 }}> <Space style={{ marginTop: 8 }}>
<Switch <Switch
checked={condition.invert} checked={Boolean(
condition.invert,
)}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('关')}
onChange={(nextValue) => onChange={(nextValue) =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ invert: nextValue }, { invert: nextValue },
) )
...@@ -1253,14 +1665,18 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1253,14 +1665,18 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
invert invert
</Text> </Text>
<Switch <Switch
checked={condition.pass_missing_key} checked={Boolean(
condition.pass_missing_key,
)}
checkedText={t('开')} checkedText={t('开')}
uncheckedText={t('关')} uncheckedText={t('关')}
onChange={(nextValue) => onChange={(nextValue) =>
updateCondition( updateCondition(
operation.id, selectedOperation.id,
condition.id, condition.id,
{ pass_missing_key: nextValue }, {
pass_missing_key: nextValue,
},
) )
} }
/> />
...@@ -1268,16 +1684,37 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1268,16 +1684,37 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
pass_missing_key pass_missing_key
</Text> </Text>
</Space> </Space>
</Card> </div>
))} </Collapse.Panel>
</Space> ),
)}
</Collapse>
)} )}
</div> </div>
</Card> </Card>
); );
})} })()
</Space> ) : (
<Card className='!rounded-xl border mt-3'> <Card
className='!rounded-2xl !border-0'
bodyStyle={{
padding: 14,
background: 'var(--semi-color-fill-0)',
}}
>
<Text type='tertiary'>
{t('请选择一条规则进行编辑。')}
</Text>
</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'> <div className='flex items-center justify-between mb-2'>
<Text>{t('实时 JSON 预览')}</Text> <Text>{t('实时 JSON 预览')}</Text>
<Tag color='grey'>{t('预览')}</Tag> <Tag color='grey'>{t('预览')}</Tag>
...@@ -1286,6 +1723,8 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1286,6 +1723,8 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
{visualPreview || '{}'} {visualPreview || '{}'}
</pre> </pre>
</Card> </Card>
</Col>
</Row>
</div> </div>
)} )}
</div> </div>
...@@ -1295,7 +1734,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ...@@ -1295,7 +1734,7 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => {
<Button onClick={formatJson}>{t('格式化')}</Button> <Button onClick={formatJson}>{t('格式化')}</Button>
<Tag color='grey'>{t('普通编辑')}</Tag> <Tag color='grey'>{t('普通编辑')}</Tag>
</Space> </Space>
<Input.TextArea <TextArea
value={jsonText} value={jsonText}
autosize={{ minRows: 18, maxRows: 28 }} autosize={{ minRows: 18, maxRows: 28 }}
onChange={(nextValue) => handleJsonChange(nextValue ?? '')} onChange={(nextValue) => handleJsonChange(nextValue ?? '')}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment