|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
import { |
|
|
TextArea, |
|
|
Typography, |
|
|
Button, |
|
|
Switch, |
|
|
Banner, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { Code, Edit, Check, X, AlertTriangle } from 'lucide-react'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
|
|
|
const CustomRequestEditor = ({ |
|
|
customRequestMode, |
|
|
customRequestBody, |
|
|
onCustomRequestModeChange, |
|
|
onCustomRequestBodyChange, |
|
|
defaultPayload, |
|
|
}) => { |
|
|
const { t } = useTranslation(); |
|
|
const [isValid, setIsValid] = useState(true); |
|
|
const [errorMessage, setErrorMessage] = useState(''); |
|
|
const [localValue, setLocalValue] = useState(customRequestBody || ''); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if ( |
|
|
customRequestMode && |
|
|
(!customRequestBody || customRequestBody.trim() === '') |
|
|
) { |
|
|
const defaultJson = defaultPayload |
|
|
? JSON.stringify(defaultPayload, null, 2) |
|
|
: ''; |
|
|
setLocalValue(defaultJson); |
|
|
onCustomRequestBodyChange(defaultJson); |
|
|
} |
|
|
}, [ |
|
|
customRequestMode, |
|
|
defaultPayload, |
|
|
customRequestBody, |
|
|
onCustomRequestBodyChange, |
|
|
]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (customRequestBody !== localValue) { |
|
|
setLocalValue(customRequestBody || ''); |
|
|
validateJson(customRequestBody || ''); |
|
|
} |
|
|
}, [customRequestBody]); |
|
|
|
|
|
|
|
|
const validateJson = (value) => { |
|
|
if (!value.trim()) { |
|
|
setIsValid(true); |
|
|
setErrorMessage(''); |
|
|
return true; |
|
|
} |
|
|
|
|
|
try { |
|
|
JSON.parse(value); |
|
|
setIsValid(true); |
|
|
setErrorMessage(''); |
|
|
return true; |
|
|
} catch (error) { |
|
|
setIsValid(false); |
|
|
setErrorMessage(`JSON格式错误: ${error.message}`); |
|
|
return false; |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleValueChange = (value) => { |
|
|
setLocalValue(value); |
|
|
validateJson(value); |
|
|
|
|
|
onCustomRequestBodyChange(value); |
|
|
}; |
|
|
|
|
|
const handleModeToggle = (enabled) => { |
|
|
onCustomRequestModeChange(enabled); |
|
|
if (enabled && defaultPayload) { |
|
|
const defaultJson = JSON.stringify(defaultPayload, null, 2); |
|
|
setLocalValue(defaultJson); |
|
|
onCustomRequestBodyChange(defaultJson); |
|
|
} |
|
|
}; |
|
|
|
|
|
const formatJson = () => { |
|
|
try { |
|
|
const parsed = JSON.parse(localValue); |
|
|
const formatted = JSON.stringify(parsed, null, 2); |
|
|
setLocalValue(formatted); |
|
|
onCustomRequestBodyChange(formatted); |
|
|
setIsValid(true); |
|
|
setErrorMessage(''); |
|
|
} catch (error) { |
|
|
|
|
|
} |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className='space-y-4'> |
|
|
{/* 自定义模式开关 */} |
|
|
<div className='flex items-center justify-between'> |
|
|
<div className='flex items-center gap-2'> |
|
|
<Code size={16} className='text-gray-500' /> |
|
|
<Typography.Text strong className='text-sm'> |
|
|
自定义请求体模式 |
|
|
</Typography.Text> |
|
|
</div> |
|
|
<Switch |
|
|
checked={customRequestMode} |
|
|
onChange={handleModeToggle} |
|
|
checkedText='开' |
|
|
uncheckedText='关' |
|
|
size='small' |
|
|
/> |
|
|
</div> |
|
|
|
|
|
{customRequestMode && ( |
|
|
<> |
|
|
{/* 提示信息 */} |
|
|
<Banner |
|
|
type='warning' |
|
|
description='启用此模式后,将使用您自定义的请求体发送API请求,模型配置面板的参数设置将被忽略。' |
|
|
icon={<AlertTriangle size={16} />} |
|
|
className='!rounded-lg' |
|
|
closeIcon={null} |
|
|
/> |
|
|
|
|
|
{/* JSON编辑器 */} |
|
|
<div> |
|
|
<div className='flex items-center justify-between mb-2'> |
|
|
<Typography.Text strong className='text-sm'> |
|
|
请求体 JSON |
|
|
</Typography.Text> |
|
|
<div className='flex items-center gap-2'> |
|
|
{isValid ? ( |
|
|
<div className='flex items-center gap-1 text-green-600'> |
|
|
<Check size={14} /> |
|
|
<Typography.Text className='text-xs'> |
|
|
格式正确 |
|
|
</Typography.Text> |
|
|
</div> |
|
|
) : ( |
|
|
<div className='flex items-center gap-1 text-red-600'> |
|
|
<X size={14} /> |
|
|
<Typography.Text className='text-xs'> |
|
|
格式错误 |
|
|
</Typography.Text> |
|
|
</div> |
|
|
)} |
|
|
<Button |
|
|
theme='borderless' |
|
|
type='tertiary' |
|
|
size='small' |
|
|
icon={<Edit size={14} />} |
|
|
onClick={formatJson} |
|
|
disabled={!isValid} |
|
|
className='!rounded-lg' |
|
|
> |
|
|
格式化 |
|
|
</Button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<TextArea |
|
|
value={localValue} |
|
|
onChange={handleValueChange} |
|
|
placeholder='{"model": "gpt-4o", "messages": [...], ...}' |
|
|
autosize={{ minRows: 8, maxRows: 20 }} |
|
|
className={`custom-request-textarea !rounded-lg font-mono text-sm ${!isValid ? '!border-red-500' : ''}`} |
|
|
style={{ |
|
|
fontFamily: 'Consolas, Monaco, "Courier New", monospace', |
|
|
lineHeight: '1.5', |
|
|
}} |
|
|
/> |
|
|
|
|
|
{!isValid && errorMessage && ( |
|
|
<Typography.Text type='danger' className='text-xs mt-1 block'> |
|
|
{errorMessage} |
|
|
</Typography.Text> |
|
|
)} |
|
|
|
|
|
<Typography.Text className='text-xs text-gray-500 mt-2 block'> |
|
|
请输入有效的JSON格式的请求体。您可以参考预览面板中的默认请求体格式。 |
|
|
</Typography.Text> |
|
|
</div> |
|
|
</> |
|
|
)} |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default CustomRequestEditor; |
|
|
|