mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-06-02 10:59:36 +08:00
297 lines
9.5 KiB
TypeScript
297 lines
9.5 KiB
TypeScript
import type { FC } from 'react'
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import type { ModelParameterRule } from '../declarations'
|
|
import { useLanguage } from '../hooks'
|
|
import { isNullOrUndefined } from '../utils'
|
|
import cn from '@/utils/classnames'
|
|
import Switch from '@/app/components/base/switch'
|
|
import Tooltip from '@/app/components/base/tooltip'
|
|
import Slider from '@/app/components/base/slider'
|
|
import Radio from '@/app/components/base/radio'
|
|
import { SimpleSelect } from '@/app/components/base/select'
|
|
import TagInput from '@/app/components/base/tag-input'
|
|
|
|
export type ParameterValue = number | string | string[] | boolean | undefined
|
|
|
|
type ParameterItemProps = {
|
|
parameterRule: ModelParameterRule
|
|
value?: ParameterValue
|
|
onChange?: (value: ParameterValue) => void
|
|
className?: string
|
|
onSwitch?: (checked: boolean, assignValue: ParameterValue) => void
|
|
isInWorkflow?: boolean
|
|
}
|
|
const ParameterItem: FC<ParameterItemProps> = ({
|
|
parameterRule,
|
|
value,
|
|
onChange,
|
|
className,
|
|
onSwitch,
|
|
isInWorkflow,
|
|
}) => {
|
|
const language = useLanguage()
|
|
const [localValue, setLocalValue] = useState(value)
|
|
const numberInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
const getDefaultValue = () => {
|
|
let defaultValue: ParameterValue
|
|
|
|
if (parameterRule.type === 'int' || parameterRule.type === 'float')
|
|
defaultValue = isNullOrUndefined(parameterRule.default) ? (parameterRule.min || 0) : parameterRule.default
|
|
else if (parameterRule.type === 'string' || parameterRule.type === 'text')
|
|
defaultValue = parameterRule.options?.length ? (parameterRule.default || '') : (parameterRule.default || '')
|
|
else if (parameterRule.type === 'boolean')
|
|
defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : false
|
|
else if (parameterRule.type === 'tag')
|
|
defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : []
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
const renderValue = value ?? localValue ?? getDefaultValue()
|
|
|
|
const handleInputChange = (newValue: ParameterValue) => {
|
|
setLocalValue(newValue)
|
|
|
|
if (onChange && (parameterRule.name === 'stop' || !isNullOrUndefined(value) || parameterRule.required))
|
|
onChange(newValue)
|
|
}
|
|
|
|
const handleNumberInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
let num = +e.target.value
|
|
|
|
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
|
|
num = parameterRule.max as number
|
|
numberInputRef.current!.value = `${num}`
|
|
}
|
|
|
|
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!)
|
|
num = parameterRule.min as number
|
|
|
|
handleInputChange(num)
|
|
}
|
|
|
|
const handleNumberInputBlur = () => {
|
|
if (numberInputRef.current)
|
|
numberInputRef.current.value = renderValue as string
|
|
}
|
|
|
|
const handleSlideChange = (num: number) => {
|
|
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
|
|
handleInputChange(parameterRule.max)
|
|
numberInputRef.current!.value = `${parameterRule.max}`
|
|
return
|
|
}
|
|
|
|
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) {
|
|
handleInputChange(parameterRule.min)
|
|
numberInputRef.current!.value = `${parameterRule.min}`
|
|
return
|
|
}
|
|
|
|
handleInputChange(num)
|
|
numberInputRef.current!.value = `${num}`
|
|
}
|
|
|
|
const handleRadioChange = (v: number) => {
|
|
handleInputChange(v === 1)
|
|
}
|
|
|
|
const handleStringInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
handleInputChange(e.target.value)
|
|
}
|
|
|
|
const handleSelect = (option: { value: string | number; name: string }) => {
|
|
handleInputChange(option.value)
|
|
}
|
|
|
|
const handleTagChange = (newSequences: string[]) => {
|
|
handleInputChange(newSequences)
|
|
}
|
|
|
|
const handleSwitch = (checked: boolean) => {
|
|
if (onSwitch) {
|
|
const assignValue: ParameterValue = localValue || getDefaultValue()
|
|
|
|
onSwitch(checked, assignValue)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if ((parameterRule.type === 'int' || parameterRule.type === 'float') && numberInputRef.current)
|
|
numberInputRef.current.value = `${renderValue}`
|
|
}, [value])
|
|
|
|
const renderInput = () => {
|
|
const numberInputWithSlide = (parameterRule.type === 'int' || parameterRule.type === 'float')
|
|
&& !isNullOrUndefined(parameterRule.min)
|
|
&& !isNullOrUndefined(parameterRule.max)
|
|
|
|
if (parameterRule.type === 'int') {
|
|
let step = 100
|
|
if (parameterRule.max) {
|
|
if (parameterRule.max < 100)
|
|
step = 1
|
|
else if (parameterRule.max < 1000)
|
|
step = 10
|
|
else if (parameterRule.max < 10000)
|
|
step = 100
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{numberInputWithSlide && <Slider
|
|
className='w-[120px]'
|
|
value={renderValue as number}
|
|
min={parameterRule.min}
|
|
max={parameterRule.max}
|
|
step={step}
|
|
onChange={handleSlideChange}
|
|
/>}
|
|
<input
|
|
ref={numberInputRef}
|
|
className='shrink-0 block ml-4 pl-3 w-16 h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900'
|
|
type='number'
|
|
max={parameterRule.max}
|
|
min={parameterRule.min}
|
|
step={numberInputWithSlide ? step : +`0.${parameterRule.precision || 0}`}
|
|
onChange={handleNumberInputChange}
|
|
onBlur={handleNumberInputBlur}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'float') {
|
|
return (
|
|
<>
|
|
{numberInputWithSlide && <Slider
|
|
className='w-[120px]'
|
|
value={renderValue as number}
|
|
min={parameterRule.min}
|
|
max={parameterRule.max}
|
|
step={0.1}
|
|
onChange={handleSlideChange}
|
|
/>}
|
|
<input
|
|
ref={numberInputRef}
|
|
className='shrink-0 block ml-4 pl-3 w-16 h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900'
|
|
type='number'
|
|
max={parameterRule.max}
|
|
min={parameterRule.min}
|
|
step={numberInputWithSlide ? 0.1 : +`0.${parameterRule.precision || 0}`}
|
|
onChange={handleNumberInputChange}
|
|
onBlur={handleNumberInputBlur}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'boolean') {
|
|
return (
|
|
<Radio.Group
|
|
className='w-[200px] flex items-center'
|
|
value={renderValue ? 1 : 0}
|
|
onChange={handleRadioChange}
|
|
>
|
|
<Radio value={1} className='!mr-1 w-[94px]'>True</Radio>
|
|
<Radio value={0} className='w-[94px]'>False</Radio>
|
|
</Radio.Group>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'string' && !parameterRule.options?.length) {
|
|
return (
|
|
<input
|
|
className={cn(isInWorkflow ? 'w-[200px]' : 'w-full', 'ml-4 flex items-center px-3 h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900')}
|
|
value={renderValue as string}
|
|
onChange={handleStringInputChange}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'text') {
|
|
return (
|
|
<textarea
|
|
className='w-full h-20 ml-4 px-1 rounded-lg bg-gray-100 outline-none text-[12px] text-gray-900'
|
|
value={renderValue as string}
|
|
onChange={handleStringInputChange}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'string' && !!parameterRule?.options?.length) {
|
|
return (
|
|
<SimpleSelect
|
|
className='!py-0'
|
|
wrapperClassName={cn(isInWorkflow ? '!w-[200px]' : 'w-full', 'ml-4 !h-8')}
|
|
defaultValue={renderValue as string}
|
|
onSelect={handleSelect}
|
|
items={parameterRule.options.map(option => ({ value: option, name: option }))}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (parameterRule.type === 'tag') {
|
|
return (
|
|
<div className={cn(isInWorkflow ? 'w-[200px]' : 'w-full', 'ml-4')}>
|
|
<TagInput
|
|
items={renderValue as string[]}
|
|
onChange={handleTagChange}
|
|
customizedConfirmKey='Tab'
|
|
isInWorkflow={isInWorkflow}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className={`flex items-center justify-between ${className}`}>
|
|
<div>
|
|
<div className={cn(isInWorkflow ? 'w-[140px]' : 'w-full', 'ml-4 shrink-0 flex items-center')}>
|
|
<div
|
|
className='mr-0.5 text-[13px] font-medium text-gray-700 truncate'
|
|
title={parameterRule.label[language] || parameterRule.label.en_US}
|
|
>
|
|
{parameterRule.label[language] || parameterRule.label.en_US}
|
|
</div>
|
|
{
|
|
parameterRule.help && (
|
|
<Tooltip
|
|
popupContent={(
|
|
<div className='w-[200px] whitespace-pre-wrap'>{parameterRule.help[language] || parameterRule.help.en_US}</div>
|
|
)}
|
|
popupClassName='mr-1'
|
|
triggerClassName='mr-1 w-4 h-4 shrink-0'
|
|
/>
|
|
)
|
|
}
|
|
{
|
|
!parameterRule.required && parameterRule.name !== 'stop' && (
|
|
<Switch
|
|
className='mr-1'
|
|
defaultValue={!isNullOrUndefined(value)}
|
|
onChange={handleSwitch}
|
|
size='md'
|
|
/>
|
|
)
|
|
}
|
|
</div>
|
|
{
|
|
parameterRule.type === 'tag' && (
|
|
<div className={cn(!isInWorkflow && 'w-[200px]', 'text-gray-400 text-xs font-normal')}>
|
|
{parameterRule?.tagPlaceholder?.[language]}
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
{renderInput()}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ParameterItem
|