/* 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 . For commercial licensing, please contact support@quantumnous.com */ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import MonacoEditor from '@monaco-editor/react'; import { useTranslation } from 'react-i18next'; import { Banner, Button, Card, Col, Input, Modal, Row, Select, Space, Switch, Tag, Typography, } from '@douyinfe/semi-ui'; import { IconDelete, IconPlus } from '@douyinfe/semi-icons'; import { showError, verifyJSON } from '../../../../helpers'; import JSONEditor from '../../../common/ui/JSONEditor'; const { Text } = Typography; const OPERATION_MODE_OPTIONS = [ { label: 'JSON · set', value: 'set' }, { label: 'JSON · delete', value: 'delete' }, { label: 'JSON · append', value: 'append' }, { label: 'JSON · prepend', value: 'prepend' }, { label: 'JSON · copy', value: 'copy' }, { label: 'JSON · move', value: 'move' }, { label: 'JSON · replace', value: 'replace' }, { label: 'JSON · regex_replace', value: 'regex_replace' }, { label: 'JSON · trim_prefix', value: 'trim_prefix' }, { label: 'JSON · trim_suffix', value: 'trim_suffix' }, { label: 'JSON · ensure_prefix', value: 'ensure_prefix' }, { label: 'JSON · ensure_suffix', value: 'ensure_suffix' }, { label: 'JSON · trim_space', value: 'trim_space' }, { label: 'JSON · to_lower', value: 'to_lower' }, { label: 'JSON · to_upper', value: 'to_upper' }, { label: 'Control · return_error', value: 'return_error' }, { label: 'Control · prune_objects', value: 'prune_objects' }, { label: 'Header · set_header', value: 'set_header' }, { label: 'Header · delete_header', value: 'delete_header' }, { label: 'Header · copy_header', value: 'copy_header' }, { label: 'Header · move_header', value: 'move_header' }, ]; const OPERATION_MODE_VALUES = new Set( OPERATION_MODE_OPTIONS.map((item) => item.value), ); const CONDITION_MODE_OPTIONS = [ { label: 'full', value: 'full' }, { label: 'prefix', value: 'prefix' }, { label: 'suffix', value: 'suffix' }, { label: 'contains', value: 'contains' }, { label: 'gt', value: 'gt' }, { label: 'gte', value: 'gte' }, { label: 'lt', value: 'lt' }, { label: 'lte', value: 'lte' }, ]; const CONDITION_MODE_VALUES = new Set( CONDITION_MODE_OPTIONS.map((item) => item.value), ); const MODE_META = { delete: { path: true }, set: { path: true, value: true, keepOrigin: true }, append: { path: true, value: true, keepOrigin: true }, prepend: { path: true, value: true, keepOrigin: true }, copy: { from: true, to: true }, move: { from: true, to: true }, replace: { path: true, from: true, to: false }, regex_replace: { path: true, from: true, to: false }, trim_prefix: { path: true, value: true }, trim_suffix: { path: true, value: true }, ensure_prefix: { path: true, value: true }, ensure_suffix: { path: true, value: true }, trim_space: { path: true }, to_lower: { path: true }, to_upper: { path: true }, return_error: { value: true }, prune_objects: { pathOptional: true, value: true }, set_header: { path: true, value: true, keepOrigin: true }, delete_header: { path: true }, copy_header: { from: true, to: true, keepOrigin: true, pathAlias: true }, move_header: { from: true, to: true, keepOrigin: true, pathAlias: true }, }; const VALUE_REQUIRED_MODES = new Set([ 'trim_prefix', 'trim_suffix', 'ensure_prefix', 'ensure_suffix', 'set_header', 'return_error', 'prune_objects', ]); const FROM_REQUIRED_MODES = new Set([ 'copy', 'move', 'replace', 'regex_replace', 'copy_header', 'move_header', ]); const TO_REQUIRED_MODES = new Set(['copy', 'move', 'copy_header', 'move_header']); const MODE_DESCRIPTIONS = { set: 'Set JSON value at path', delete: 'Delete JSON field at path', append: 'Append value to array/string/object', prepend: 'Prepend value to array/string/object', copy: 'Copy JSON value from from -> to', move: 'Move JSON value from from -> to', replace: 'String replace on target path', regex_replace: 'Regex replace on target path', trim_prefix: 'Trim prefix on string value', trim_suffix: 'Trim suffix on string value', ensure_prefix: 'Ensure string starts with prefix', ensure_suffix: 'Ensure string ends with suffix', trim_space: 'Trim spaces/newlines on string value', to_lower: 'Convert string to lower case', to_upper: 'Convert string to upper case', return_error: 'Stop processing and return custom error', prune_objects: 'Remove objects matching conditions', set_header: 'Set runtime override header', delete_header: 'Delete runtime override header', copy_header: 'Copy header from from -> to', move_header: 'Move header from from -> to', }; const OPERATION_PATH_SUGGESTIONS = [ 'model', 'temperature', 'max_tokens', 'messages.-1.content', 'metadata.conversation_id', ]; const CONDITION_PATH_SUGGESTIONS = [ 'model', 'retry.is_retry', 'last_error.code', 'request_headers.authorization', 'header_override_normalized.x_debug_mode', ]; const LEGACY_TEMPLATE = { temperature: 0, max_tokens: 1000, }; const OPERATION_TEMPLATE = { operations: [ { path: 'temperature', mode: 'set', value: 0.7, conditions: [ { path: 'model', mode: 'prefix', value: 'gpt', }, ], logic: 'AND', }, ], }; const MONACO_SCHEMA_URI = 'https://new-api.local/schemas/param-override.schema.json'; const MONACO_MODEL_URI = 'inmemory://new-api/param-override.json'; const JSON_SCALAR_SCHEMA = { oneOf: [ { type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'null' }, { type: 'array' }, { type: 'object' }, ], }; const PARAM_OVERRIDE_JSON_SCHEMA = { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { operations: { type: 'array', description: 'Operation pipeline for new param override format.', items: { type: 'object', properties: { mode: { type: 'string', enum: OPERATION_MODE_OPTIONS.map((item) => item.value), }, path: { type: 'string' }, from: { type: 'string' }, to: { type: 'string' }, keep_origin: { type: 'boolean' }, value: JSON_SCALAR_SCHEMA, logic: { type: 'string', enum: ['AND', 'OR'] }, conditions: { oneOf: [ { type: 'array', items: { type: 'object', properties: { path: { type: 'string' }, mode: { type: 'string', enum: CONDITION_MODE_OPTIONS.map((item) => item.value), }, value: JSON_SCALAR_SCHEMA, invert: { type: 'boolean' }, pass_missing_key: { type: 'boolean' }, }, required: ['path', 'mode'], additionalProperties: false, }, }, { type: 'object', additionalProperties: JSON_SCALAR_SCHEMA, }, ], }, }, required: ['mode'], additionalProperties: false, allOf: [ { if: { properties: { mode: { const: 'set' } }, required: ['mode'] }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'delete' } }, required: ['mode'] }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'append' } }, required: ['mode'] }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'prepend' } }, required: ['mode'] }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'copy' } }, required: ['mode'] }, then: { required: ['from', 'to'] }, }, { if: { properties: { mode: { const: 'move' } }, required: ['mode'] }, then: { required: ['from', 'to'] }, }, { if: { properties: { mode: { const: 'replace' } }, required: ['mode'] }, then: { required: ['path', 'from'] }, }, { if: { properties: { mode: { const: 'regex_replace' } }, required: ['mode'], }, then: { required: ['path', 'from'] }, }, { if: { properties: { mode: { const: 'trim_prefix' } }, required: ['mode'], }, then: { required: ['path', 'value'] }, }, { if: { properties: { mode: { const: 'trim_suffix' } }, required: ['mode'], }, then: { required: ['path', 'value'] }, }, { if: { properties: { mode: { const: 'ensure_prefix' } }, required: ['mode'], }, then: { required: ['path', 'value'] }, }, { if: { properties: { mode: { const: 'ensure_suffix' } }, required: ['mode'], }, then: { required: ['path', 'value'] }, }, { if: { properties: { mode: { const: 'trim_space' } }, required: ['mode'], }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'to_lower' } }, required: ['mode'], }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'to_upper' } }, required: ['mode'], }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'return_error' } }, required: ['mode'], }, then: { required: ['value'] }, }, { if: { properties: { mode: { const: 'prune_objects' } }, required: ['mode'], }, then: { required: ['value'] }, }, { if: { properties: { mode: { const: 'set_header' } }, required: ['mode'], }, then: { required: ['path', 'value'] }, }, { if: { properties: { mode: { const: 'delete_header' } }, required: ['mode'], }, then: { required: ['path'] }, }, { if: { properties: { mode: { const: 'copy_header' } }, required: ['mode'], }, then: { anyOf: [{ required: ['path'] }, { required: ['from', 'to'] }], }, }, { if: { properties: { mode: { const: 'move_header' } }, required: ['mode'], }, then: { anyOf: [{ required: ['path'] }, { required: ['from', 'to'] }], }, }, ], }, }, }, additionalProperties: true, }; let localIdSeed = 0; const nextLocalId = () => `param_override_${Date.now()}_${localIdSeed++}`; const toValueText = (value) => { if (value === undefined) return ''; if (typeof value === 'string') return value; try { return JSON.stringify(value); } catch (error) { return String(value); } }; const parseLooseValue = (valueText) => { const raw = String(valueText ?? ''); if (raw.trim() === '') return ''; try { return JSON.parse(raw); } catch (error) { return raw; } }; const normalizeCondition = (condition = {}) => ({ id: nextLocalId(), path: typeof condition.path === 'string' ? condition.path : '', mode: CONDITION_MODE_VALUES.has(condition.mode) ? condition.mode : 'full', value_text: toValueText(condition.value), invert: condition.invert === true, pass_missing_key: condition.pass_missing_key === true, }); const createDefaultCondition = () => normalizeCondition({}); const normalizeOperation = (operation = {}) => ({ id: nextLocalId(), path: typeof operation.path === 'string' ? operation.path : '', mode: OPERATION_MODE_VALUES.has(operation.mode) ? operation.mode : 'set', value_text: toValueText(operation.value), keep_origin: operation.keep_origin === true, from: typeof operation.from === 'string' ? operation.from : '', to: typeof operation.to === 'string' ? operation.to : '', logic: String(operation.logic || 'OR').toUpperCase() === 'AND' ? 'AND' : 'OR', conditions: Array.isArray(operation.conditions) ? operation.conditions.map(normalizeCondition) : [], }); const createDefaultOperation = () => normalizeOperation({ mode: 'set' }); const parseInitialState = (rawValue) => { const text = typeof rawValue === 'string' ? rawValue : ''; const trimmed = text.trim(); if (!trimmed) { return { editMode: 'visual', visualMode: 'operations', legacyValue: '', operations: [createDefaultOperation()], jsonText: '', jsonError: '', }; } if (!verifyJSON(trimmed)) { return { editMode: 'json', visualMode: 'operations', legacyValue: '', operations: [createDefaultOperation()], jsonText: text, jsonError: 'JSON format is invalid', }; } const parsed = JSON.parse(trimmed); const pretty = JSON.stringify(parsed, null, 2); if ( parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.operations) ) { return { editMode: 'visual', visualMode: 'operations', legacyValue: '', operations: parsed.operations.length > 0 ? parsed.operations.map(normalizeOperation) : [createDefaultOperation()], jsonText: pretty, jsonError: '', }; } if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { return { editMode: 'visual', visualMode: 'legacy', legacyValue: pretty, operations: [createDefaultOperation()], jsonText: pretty, jsonError: '', }; } return { editMode: 'json', visualMode: 'operations', legacyValue: '', operations: [createDefaultOperation()], jsonText: pretty, jsonError: '', }; }; const isOperationBlank = (operation) => { const hasCondition = (operation.conditions || []).some( (condition) => condition.path.trim() || String(condition.value_text ?? '').trim() || condition.mode !== 'full' || condition.invert || condition.pass_missing_key, ); return ( operation.mode === 'set' && !operation.path.trim() && !operation.from.trim() && !operation.to.trim() && String(operation.value_text ?? '').trim() === '' && !operation.keep_origin && !hasCondition ); }; const buildConditionPayload = (condition) => { const path = condition.path.trim(); if (!path) return null; const payload = { path, mode: condition.mode || 'full', value: parseLooseValue(condition.value_text), }; if (condition.invert) payload.invert = true; if (condition.pass_missing_key) payload.pass_missing_key = true; return payload; }; const validateOperations = (operations, t) => { for (let i = 0; i < operations.length; i++) { const op = operations[i]; const mode = op.mode || 'set'; const meta = MODE_META[mode] || MODE_META.set; const line = i + 1; const pathValue = op.path.trim(); const fromValue = op.from.trim(); const toValue = op.to.trim(); if (meta.path && !pathValue) { return t('第 {{line}} 条操作缺少 path', { line }); } if (FROM_REQUIRED_MODES.has(mode) && !fromValue) { if (!(meta.pathAlias && pathValue)) { return t('第 {{line}} 条操作缺少 from', { line }); } } if (TO_REQUIRED_MODES.has(mode) && !toValue) { if (!(meta.pathAlias && pathValue)) { return t('第 {{line}} 条操作缺少 to', { line }); } } if (meta.from && !fromValue) { return t('第 {{line}} 条操作缺少 from', { line }); } if (meta.to && !toValue) { return t('第 {{line}} 条操作缺少 to', { line }); } if ( VALUE_REQUIRED_MODES.has(mode) && String(op.value_text ?? '').trim() === '' ) { return t('第 {{line}} 条操作缺少 value', { line }); } if (mode === 'return_error') { const raw = String(op.value_text ?? '').trim(); if (!raw) { return t('第 {{line}} 条操作缺少 value', { line }); } try { const parsed = JSON.parse(raw); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { if (!String(parsed.message || '').trim()) { return t('第 {{line}} 条 return_error 需要 message', { line }); } } } catch (error) { // plain string value is allowed } } } return ''; }; const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { const { t } = useTranslation(); const [editMode, setEditMode] = useState('visual'); const [visualMode, setVisualMode] = useState('operations'); const [legacyValue, setLegacyValue] = useState(''); const [operations, setOperations] = useState([createDefaultOperation()]); const [jsonText, setJsonText] = useState(''); const [jsonError, setJsonError] = useState(''); const monacoConfiguredRef = useRef(false); const configureMonaco = useCallback((monaco) => { if (monacoConfiguredRef.current) return; monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ validate: true, allowComments: false, enableSchemaRequest: false, schemas: [ { uri: MONACO_SCHEMA_URI, fileMatch: [MONACO_MODEL_URI, '*param-override*.json'], schema: PARAM_OVERRIDE_JSON_SCHEMA, }, ], }); monacoConfiguredRef.current = true; }, []); useEffect(() => { if (!visible) return; const nextState = parseInitialState(value); setEditMode(nextState.editMode); setVisualMode(nextState.visualMode); setLegacyValue(nextState.legacyValue); setOperations(nextState.operations); setJsonText(nextState.jsonText); setJsonError(nextState.jsonError); }, [visible, value]); const operationCount = useMemo( () => operations.filter((item) => !isOperationBlank(item)).length, [operations], ); const buildVisualJson = useCallback(() => { if (visualMode === 'legacy') { const trimmed = legacyValue.trim(); if (!trimmed) return ''; if (!verifyJSON(trimmed)) { throw new Error(t('参数覆盖必须是合法的 JSON 格式!')); } const parsed = JSON.parse(trimmed); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(t('旧格式必须是 JSON 对象')); } return JSON.stringify(parsed, null, 2); } const filteredOps = operations.filter((item) => !isOperationBlank(item)); if (filteredOps.length === 0) return ''; const message = validateOperations(filteredOps, t); if (message) { throw new Error(message); } const payloadOps = filteredOps.map((operation) => { const mode = operation.mode || 'set'; const meta = MODE_META[mode] || MODE_META.set; const pathValue = operation.path.trim(); const fromValue = operation.from.trim(); const toValue = operation.to.trim(); const payload = { mode }; if (meta.path) { payload.path = pathValue; } if (meta.pathOptional && pathValue) { payload.path = pathValue; } if (meta.value) { payload.value = parseLooseValue(operation.value_text); } if (meta.keepOrigin && operation.keep_origin) { payload.keep_origin = true; } if (meta.from) { payload.from = fromValue; } if (!meta.to && operation.to.trim()) { payload.to = toValue; } if (meta.to) { payload.to = toValue; } if (meta.pathAlias) { if (!payload.from && pathValue) { payload.from = pathValue; } if (!payload.to && pathValue) { payload.to = pathValue; } } const conditions = (operation.conditions || []) .map(buildConditionPayload) .filter(Boolean); if (conditions.length > 0) { payload.conditions = conditions; payload.logic = operation.logic === 'AND' ? 'AND' : 'OR'; } return payload; }); return JSON.stringify({ operations: payloadOps }, null, 2); }, [legacyValue, operations, t, visualMode]); const switchToJsonMode = () => { if (editMode === 'json') return; try { setJsonText(buildVisualJson()); setJsonError(''); setEditMode('json'); } catch (error) { showError(error.message); } }; const switchToVisualMode = () => { if (editMode === 'visual') return; const trimmed = jsonText.trim(); if (!trimmed) { setVisualMode('operations'); setOperations([createDefaultOperation()]); setLegacyValue(''); setJsonError(''); setEditMode('visual'); return; } if (!verifyJSON(trimmed)) { showError(t('参数覆盖必须是合法的 JSON 格式!')); return; } const parsed = JSON.parse(trimmed); if ( parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.operations) ) { setVisualMode('operations'); setOperations( parsed.operations.length > 0 ? parsed.operations.map(normalizeOperation) : [createDefaultOperation()], ); setLegacyValue(''); setJsonError(''); setEditMode('visual'); return; } if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { setVisualMode('legacy'); setLegacyValue(JSON.stringify(parsed, null, 2)); setOperations([createDefaultOperation()]); setJsonError(''); setEditMode('visual'); return; } showError(t('参数覆盖必须是合法的 JSON 对象')); }; const setOldTemplate = () => { const text = JSON.stringify(LEGACY_TEMPLATE, null, 2); setVisualMode('legacy'); setLegacyValue(text); setJsonText(text); setJsonError(''); setEditMode('visual'); }; const setNewTemplate = () => { setVisualMode('operations'); setOperations(OPERATION_TEMPLATE.operations.map(normalizeOperation)); setJsonText(JSON.stringify(OPERATION_TEMPLATE, null, 2)); setJsonError(''); setEditMode('visual'); }; const clearValue = () => { setVisualMode('operations'); setLegacyValue(''); setOperations([createDefaultOperation()]); setJsonText(''); setJsonError(''); }; const updateOperation = (operationId, patch) => { setOperations((prev) => prev.map((item) => item.id === operationId ? { ...item, ...patch } : item, ), ); }; const addOperation = () => { setOperations((prev) => [...prev, createDefaultOperation()]); }; const duplicateOperation = (operationId) => { setOperations((prev) => { const index = prev.findIndex((item) => item.id === operationId); if (index < 0) return prev; const source = prev[index]; const cloned = normalizeOperation({ path: source.path, mode: source.mode, value: parseLooseValue(source.value_text), keep_origin: source.keep_origin, from: source.from, to: source.to, logic: source.logic, conditions: (source.conditions || []).map((condition) => ({ path: condition.path, mode: condition.mode, value: parseLooseValue(condition.value_text), invert: condition.invert, pass_missing_key: condition.pass_missing_key, })), }); const next = [...prev]; next.splice(index + 1, 0, cloned); return next; }); }; const removeOperation = (operationId) => { setOperations((prev) => { if (prev.length <= 1) return [createDefaultOperation()]; return prev.filter((item) => item.id !== operationId); }); }; const addCondition = (operationId) => { setOperations((prev) => prev.map((operation) => operation.id === operationId ? { ...operation, conditions: [...(operation.conditions || []), createDefaultCondition()], } : operation, ), ); }; const updateCondition = (operationId, conditionId, patch) => { setOperations((prev) => prev.map((operation) => { if (operation.id !== operationId) return operation; return { ...operation, conditions: (operation.conditions || []).map((condition) => condition.id === conditionId ? { ...condition, ...patch } : condition, ), }; }), ); }; const removeCondition = (operationId, conditionId) => { setOperations((prev) => prev.map((operation) => { if (operation.id !== operationId) return operation; return { ...operation, conditions: (operation.conditions || []).filter( (condition) => condition.id !== conditionId, ), }; }), ); }; const handleJsonChange = (nextValue) => { setJsonText(nextValue); const trimmed = String(nextValue || '').trim(); if (!trimmed) { setJsonError(''); return; } if (!verifyJSON(trimmed)) { setJsonError(t('JSON格式错误')); return; } setJsonError(''); }; const formatJson = () => { const trimmed = jsonText.trim(); if (!trimmed) return; if (!verifyJSON(trimmed)) { showError(t('参数覆盖必须是合法的 JSON 格式!')); return; } setJsonText(JSON.stringify(JSON.parse(trimmed), null, 2)); setJsonError(''); }; const visualPreview = useMemo(() => { if (editMode !== 'visual' || visualMode !== 'operations') { return ''; } try { return buildVisualJson() || ''; } catch (error) { return `// ${error.message}`; } }, [buildVisualJson, editMode, visualMode]); const handleSave = () => { try { let result = ''; if (editMode === 'json') { const trimmed = jsonText.trim(); if (!trimmed) { result = ''; } else { if (!verifyJSON(trimmed)) { throw new Error(t('参数覆盖必须是合法的 JSON 格式!')); } result = JSON.stringify(JSON.parse(trimmed), null, 2); } } else { result = buildVisualJson(); } onSave?.(result); } catch (error) { showError(error.message); } }; return ( {t('可视化')} {t('JSON 模式')} {t('旧格式模板')} {t('新格式模板')} {t('不更改')} {editMode === 'visual' ? ( setVisualMode('operations')} > {t('新格式模板')} setVisualMode('legacy')} > {t('旧格式模板')} {visualMode === 'legacy' ? ( ) : ( {t('新格式(支持条件判断与json自定义):')} {`${t('规则')}: ${operationCount}`} } onClick={addOperation}> {t('新增规则')} {operations.map((operation, index) => { const mode = operation.mode || 'set'; const meta = MODE_META[mode] || MODE_META.set; const conditions = operation.conditions || []; return ( {`#${index + 1}`} {mode} duplicateOperation(operation.id)} > {t('复制')} } onClick={() => removeOperation(operation.id)} /> mode updateOperation(operation.id, { mode: nextMode }) } style={{ width: '100%' }} /> {meta.path || meta.pathOptional ? ( {meta.pathOptional ? 'path (optional)' : 'path'} updateOperation(operation.id, { path: nextValue, }) } /> {OPERATION_PATH_SUGGESTIONS.map((pathItem) => ( updateOperation(operation.id, { path: pathItem, }) } > {pathItem} ))} ) : null} {MODE_DESCRIPTIONS[mode] || ''} {meta.value ? ( value (JSON or plain text) updateOperation(operation.id, { value_text: nextValue, }) } /> ) : null} {meta.keepOrigin ? ( updateOperation(operation.id, { keep_origin: nextValue, }) } /> keep_origin ) : null} {meta.from || meta.to === false || meta.to ? ( {meta.from || meta.to === false ? ( from updateOperation(operation.id, { from: nextValue, }) } /> ) : null} {meta.to || meta.to === false ? ( to updateOperation(operation.id, { to: nextValue }) } /> ) : null} ) : null} {t('条件')} updateOperation(operation.id, { logic: nextValue, }) } /> } size='small' onClick={() => addCondition(operation.id)} > {t('新增条件')} {conditions.length === 0 ? ( {t('没有条件时,默认总是执行该操作。')} ) : ( {conditions.map((condition, conditionIndex) => ( {`C${conditionIndex + 1}`} } size='small' onClick={() => removeCondition(operation.id, condition.id) } /> path updateCondition( operation.id, condition.id, { path: nextValue }, ) } /> {CONDITION_PATH_SUGGESTIONS.map( (pathItem) => ( updateCondition( operation.id, condition.id, { path: pathItem }, ) } > {pathItem} ), )} mode updateCondition( operation.id, condition.id, { mode: nextValue }, ) } style={{ width: '100%' }} /> value updateCondition( operation.id, condition.id, { value_text: nextValue }, ) } /> updateCondition( operation.id, condition.id, { invert: nextValue }, ) } /> invert updateCondition( operation.id, condition.id, { pass_missing_key: nextValue }, ) } /> pass_missing_key ))} )} ); })} {t('实时 JSON 预览')} {t('预览')} {visualPreview || '{}'} )} ) : ( {t('格式化')} {t('JSON 智能提示')} handleJsonChange(nextValue ?? '')} height='460px' options={{ minimap: { enabled: false }, fontSize: 13, lineNumbers: 'on', automaticLayout: true, scrollBeyondLastLine: false, tabSize: 2, insertSpaces: true, wordWrap: 'on', formatOnPaste: true, formatOnType: true, }} /> {t('支持 mode/conditions 字段补全与 JSON Schema 校验')} {jsonError ? ( {jsonError} ) : null} )} ); }; export default ParamOverrideEditorModal;
{visualPreview || '{}'}