feat: redesign param override editing with guided modal and Monaco JSON hints

This commit is contained in:
Seefs 2026-02-22 01:17:26 +08:00
parent 7633863c96
commit b7bfa12837
3 changed files with 1531 additions and 34 deletions

View File

@ -7,6 +7,7 @@
"@douyinfe/semi-icons": "^2.63.1", "@douyinfe/semi-icons": "^2.63.1",
"@douyinfe/semi-ui": "^2.69.1", "@douyinfe/semi-ui": "^2.69.1",
"@lobehub/icons": "^2.0.0", "@lobehub/icons": "^2.0.0",
"@monaco-editor/react": "^4.7.0",
"@visactor/react-vchart": "~1.8.8", "@visactor/react-vchart": "~1.8.8",
"@visactor/vchart": "~1.8.8", "@visactor/vchart": "~1.8.8",
"@visactor/vchart-semi-theme": "~1.8.8", "@visactor/vchart-semi-theme": "~1.8.8",
@ -20,6 +21,7 @@
"lucide-react": "^0.511.0", "lucide-react": "^0.511.0",
"marked": "^4.1.1", "marked": "^4.1.1",
"mermaid": "^11.6.0", "mermaid": "^11.6.0",
"monaco-editor": "^0.55.1",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -59,6 +59,7 @@ import ModelSelectModal from './ModelSelectModal';
import SingleModelSelectModal from './SingleModelSelectModal'; import SingleModelSelectModal from './SingleModelSelectModal';
import OllamaModelModal from './OllamaModelModal'; import OllamaModelModal from './OllamaModelModal';
import CodexOAuthModal from './CodexOAuthModal'; import CodexOAuthModal from './CodexOAuthModal';
import ParamOverrideEditorModal from './ParamOverrideEditorModal';
import JSONEditor from '../../../common/ui/JSONEditor'; import JSONEditor from '../../../common/ui/JSONEditor';
import SecureVerificationModal from '../../../common/modals/SecureVerificationModal'; import SecureVerificationModal from '../../../common/modals/SecureVerificationModal';
import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay'; import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay';
@ -143,6 +144,7 @@ const EditChannelModal = (props) => {
base_url: '', base_url: '',
other: '', other: '',
model_mapping: '', model_mapping: '',
param_override: '',
status_code_mapping: '', status_code_mapping: '',
models: [], models: [],
auto_ban: 1, auto_ban: 1,
@ -224,11 +226,69 @@ const EditChannelModal = (props) => {
return []; return [];
} }
}, [inputs.model_mapping]); }, [inputs.model_mapping]);
const paramOverrideMeta = useMemo(() => {
const raw =
typeof inputs.param_override === 'string'
? inputs.param_override.trim()
: '';
if (!raw) {
return {
tagLabel: t('不更改'),
tagColor: 'grey',
preview: t(
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数',
),
};
}
if (!verifyJSON(raw)) {
return {
tagLabel: t('JSON格式错误'),
tagColor: 'red',
preview: raw,
};
}
try {
const parsed = JSON.parse(raw);
const pretty = JSON.stringify(parsed, null, 2);
if (
parsed &&
typeof parsed === 'object' &&
!Array.isArray(parsed) &&
Array.isArray(parsed.operations)
) {
return {
tagLabel: `${t('新格式模板')} (${parsed.operations.length})`,
tagColor: 'cyan',
preview: pretty,
};
}
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return {
tagLabel: `${t('旧格式模板')} (${Object.keys(parsed).length})`,
tagColor: 'blue',
preview: pretty,
};
}
return {
tagLabel: 'Custom JSON',
tagColor: 'orange',
preview: pretty,
};
} catch (error) {
return {
tagLabel: t('JSON格式错误'),
tagColor: 'red',
preview: raw,
};
}
}, [inputs.param_override, t]);
const [isIonetChannel, setIsIonetChannel] = useState(false); const [isIonetChannel, setIsIonetChannel] = useState(false);
const [ionetMetadata, setIonetMetadata] = useState(null); const [ionetMetadata, setIonetMetadata] = useState(null);
const [codexOAuthModalVisible, setCodexOAuthModalVisible] = useState(false); const [codexOAuthModalVisible, setCodexOAuthModalVisible] = useState(false);
const [codexCredentialRefreshing, setCodexCredentialRefreshing] = const [codexCredentialRefreshing, setCodexCredentialRefreshing] =
useState(false); useState(false);
const [paramOverrideEditorVisible, setParamOverrideEditorVisible] =
useState(false);
// //
const [keyDisplayState, setKeyDisplayState] = useState({ const [keyDisplayState, setKeyDisplayState] = useState({
@ -1170,6 +1230,7 @@ const EditChannelModal = (props) => {
const submit = async () => { const submit = async () => {
const formValues = formApiRef.current ? formApiRef.current.getValues() : {}; const formValues = formApiRef.current ? formApiRef.current.getValues() : {};
let localInputs = { ...formValues }; let localInputs = { ...formValues };
localInputs.param_override = inputs.param_override;
if (localInputs.type === 57) { if (localInputs.type === 57) {
if (batch) { if (batch) {
@ -3043,28 +3104,20 @@ const EditChannelModal = (props) => {
initValue={autoBan} initValue={autoBan}
/> />
<Form.TextArea <div className='mb-4'>
field='param_override' <div className='flex items-center justify-between gap-2 mb-1'>
label={t('参数覆盖')} <Text className='text-sm font-medium'>{t('参数覆盖')}</Text>
placeholder={ <Space wrap>
t( <Button
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数', size='small'
) + type='primary'
'\n' + icon={<IconCode size={14} />}
t('旧格式(直接覆盖):') + onClick={() => setParamOverrideEditorVisible(true)}
'\n{\n "temperature": 0,\n "max_tokens": 1000\n}' + >
'\n\n' + {t('可视化编辑')}
t('新格式支持条件判断与json自定义') + </Button>
'\n{\n "operations": [\n {\n "path": "temperature",\n "mode": "set",\n "value": 0.7,\n "conditions": [\n {\n "path": "model",\n "mode": "prefix",\n "value": "gpt"\n }\n ]\n }\n ]\n}' <Button
} size='small'
autosize
onChange={(value) =>
handleInputChange('param_override', value)
}
extraText={
<div className='flex gap-2 flex-wrap'>
<Text
className='!text-semi-color-primary cursor-pointer'
onClick={() => onClick={() =>
handleInputChange( handleInputChange(
'param_override', 'param_override',
@ -3073,9 +3126,9 @@ const EditChannelModal = (props) => {
} }
> >
{t('旧格式模板')} {t('旧格式模板')}
</Text> </Button>
<Text <Button
className='!text-semi-color-primary cursor-pointer' size='small'
onClick={() => onClick={() =>
handleInputChange( handleInputChange(
'param_override', 'param_override',
@ -3104,17 +3157,40 @@ const EditChannelModal = (props) => {
} }
> >
{t('新格式模板')} {t('新格式模板')}
</Text> </Button>
<Text <Button
className='!text-semi-color-primary cursor-pointer' size='small'
onClick={() => formatJsonField('param_override')} type='tertiary'
onClick={() => handleInputChange('param_override', '')}
> >
{t('格式化')} {t('不更改')}
</Text> </Button>
</Space>
</div>
<Text type='tertiary' size='small'>
{t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数')}
</Text>
<div
className='mt-2 rounded-lg border p-3'
style={{ backgroundColor: 'var(--semi-color-fill-0)' }}
>
<div className='flex items-center justify-between mb-2'>
<Tag color={paramOverrideMeta.tagColor}>
{paramOverrideMeta.tagLabel}
</Tag>
<Button
size='small'
type='tertiary'
onClick={() => setParamOverrideEditorVisible(true)}
>
{t('编辑')}
</Button>
</div> </div>
} <pre className='mb-0 text-xs leading-5 whitespace-pre-wrap break-all max-h-56 overflow-auto'>
showClear {paramOverrideMeta.preview}
/> </pre>
</div>
</div>
<Form.TextArea <Form.TextArea
field='header_override' field='header_override'
@ -3494,6 +3570,16 @@ const EditChannelModal = (props) => {
/> />
</Modal> </Modal>
<ParamOverrideEditorModal
visible={paramOverrideEditorVisible}
value={inputs.param_override || ''}
onCancel={() => setParamOverrideEditorVisible(false)}
onSave={(nextValue) => {
handleInputChange('param_override', nextValue);
setParamOverrideEditorVisible(false);
}}
/>
<ModelSelectModal <ModelSelectModal
visible={modelModalVisible} visible={modelModalVisible}
models={fetchedModels} models={fetchedModels}

File diff suppressed because it is too large Load Diff