You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parameter-item.tsx 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. import type { FC } from 'react'
  2. import { useEffect, useRef, useState } from 'react'
  3. import type { ModelParameterRule } from '../declarations'
  4. import { isNullOrUndefined } from '../utils'
  5. import cn from '@/utils/classnames'
  6. import Switch from '@/app/components/base/switch'
  7. import Tooltip from '@/app/components/base/tooltip'
  8. import Slider from '@/app/components/base/slider'
  9. import Radio from '@/app/components/base/radio'
  10. import { SimpleSelect } from '@/app/components/base/select'
  11. import TagInput from '@/app/components/base/tag-input'
  12. export type ParameterValue = number | string | string[] | boolean | undefined
  13. type ParameterItemProps = {
  14. parameterRule: ModelParameterRule
  15. value?: ParameterValue
  16. onChange?: (value: ParameterValue) => void
  17. onSwitch?: (checked: boolean, assignValue: ParameterValue) => void
  18. isInWorkflow?: boolean
  19. }
  20. const ParameterItem: FC<ParameterItemProps> = ({
  21. parameterRule,
  22. value,
  23. onChange,
  24. onSwitch,
  25. isInWorkflow,
  26. }) => {
  27. const [localValue, setLocalValue] = useState(value)
  28. const numberInputRef = useRef<HTMLInputElement>(null)
  29. const getDefaultValue = () => {
  30. let defaultValue: ParameterValue
  31. if (parameterRule.type === 'int' || parameterRule.type === 'float')
  32. defaultValue = isNullOrUndefined(parameterRule.default) ? (parameterRule.min || 0) : parameterRule.default
  33. else if (parameterRule.type === 'string' || parameterRule.type === 'text')
  34. defaultValue = parameterRule.default || ''
  35. else if (parameterRule.type === 'boolean')
  36. defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : false
  37. else if (parameterRule.type === 'tag')
  38. defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : []
  39. return defaultValue
  40. }
  41. const renderValue = value ?? localValue ?? getDefaultValue()
  42. const handleInputChange = (newValue: ParameterValue) => {
  43. setLocalValue(newValue)
  44. if (onChange && (parameterRule.name === 'stop' || !isNullOrUndefined(value) || parameterRule.required))
  45. onChange(newValue)
  46. }
  47. const handleNumberInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  48. let num = +e.target.value
  49. if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
  50. num = parameterRule.max as number
  51. numberInputRef.current!.value = `${num}`
  52. }
  53. if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!)
  54. num = parameterRule.min as number
  55. handleInputChange(num)
  56. }
  57. const handleNumberInputBlur = () => {
  58. if (numberInputRef.current)
  59. numberInputRef.current.value = renderValue as string
  60. }
  61. const handleSlideChange = (num: number) => {
  62. if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
  63. handleInputChange(parameterRule.max)
  64. numberInputRef.current!.value = `${parameterRule.max}`
  65. return
  66. }
  67. if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) {
  68. handleInputChange(parameterRule.min)
  69. numberInputRef.current!.value = `${parameterRule.min}`
  70. return
  71. }
  72. handleInputChange(num)
  73. numberInputRef.current!.value = `${num}`
  74. }
  75. const handleRadioChange = (v: number) => {
  76. handleInputChange(v === 1)
  77. }
  78. const handleStringInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
  79. handleInputChange(e.target.value)
  80. }
  81. const handleSelect = (option: { value: string | number; name: string }) => {
  82. handleInputChange(option.value)
  83. }
  84. const handleTagChange = (newSequences: string[]) => {
  85. handleInputChange(newSequences)
  86. }
  87. const handleSwitch = (checked: boolean) => {
  88. if (onSwitch) {
  89. const assignValue: ParameterValue = localValue || getDefaultValue()
  90. onSwitch(checked, assignValue)
  91. }
  92. }
  93. useEffect(() => {
  94. if ((parameterRule.type === 'int' || parameterRule.type === 'float') && numberInputRef.current)
  95. numberInputRef.current.value = `${renderValue}`
  96. }, [value])
  97. const renderInput = () => {
  98. const numberInputWithSlide = (parameterRule.type === 'int' || parameterRule.type === 'float')
  99. && !isNullOrUndefined(parameterRule.min)
  100. && !isNullOrUndefined(parameterRule.max)
  101. if (parameterRule.type === 'int') {
  102. let step = 100
  103. if (parameterRule.max) {
  104. if (parameterRule.max < 100)
  105. step = 1
  106. else if (parameterRule.max < 1000)
  107. step = 10
  108. }
  109. return (
  110. <>
  111. {numberInputWithSlide && <Slider
  112. className='w-[120px]'
  113. value={renderValue as number}
  114. min={parameterRule.min}
  115. max={parameterRule.max}
  116. step={step}
  117. onChange={handleSlideChange}
  118. />}
  119. <input
  120. ref={numberInputRef}
  121. className='system-sm-regular ml-4 block h-8 w-16 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-components-input-text-filled outline-none'
  122. type='number'
  123. max={parameterRule.max}
  124. min={parameterRule.min}
  125. step={numberInputWithSlide ? step : +`0.${parameterRule.precision || 0}`}
  126. onChange={handleNumberInputChange}
  127. onBlur={handleNumberInputBlur}
  128. />
  129. </>
  130. )
  131. }
  132. if (parameterRule.type === 'float') {
  133. return (
  134. <>
  135. {numberInputWithSlide && <Slider
  136. className='w-[120px]'
  137. value={renderValue as number}
  138. min={parameterRule.min}
  139. max={parameterRule.max}
  140. step={0.1}
  141. onChange={handleSlideChange}
  142. />}
  143. <input
  144. ref={numberInputRef}
  145. className='system-sm-regular ml-4 block h-8 w-16 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-components-input-text-filled outline-none'
  146. type='number'
  147. max={parameterRule.max}
  148. min={parameterRule.min}
  149. step={numberInputWithSlide ? 0.1 : +`0.${parameterRule.precision || 0}`}
  150. onChange={handleNumberInputChange}
  151. onBlur={handleNumberInputBlur}
  152. />
  153. </>
  154. )
  155. }
  156. if (parameterRule.type === 'boolean') {
  157. return (
  158. <Radio.Group
  159. className='flex w-[178px] items-center'
  160. value={renderValue ? 1 : 0}
  161. onChange={handleRadioChange}
  162. >
  163. <Radio value={1} className='w-[83px]'>True</Radio>
  164. <Radio value={0} className='w-[83px]'>False</Radio>
  165. </Radio.Group>
  166. )
  167. }
  168. if (parameterRule.type === 'string' && !parameterRule.options?.length) {
  169. return (
  170. <input
  171. className={cn(isInWorkflow ? 'w-[178px]' : 'w-full', 'system-sm-regular ml-4 flex h-8 appearance-none items-center rounded-lg bg-components-input-bg-normal px-3 text-components-input-text-filled outline-none')}
  172. value={renderValue as string}
  173. onChange={handleStringInputChange}
  174. />
  175. )
  176. }
  177. if (parameterRule.type === 'text') {
  178. return (
  179. <textarea
  180. className='system-sm-regular ml-4 h-20 w-full rounded-lg bg-components-input-bg-normal px-1 text-components-input-text-filled'
  181. value={renderValue as string}
  182. onChange={handleStringInputChange}
  183. />
  184. )
  185. }
  186. if (parameterRule.type === 'string' && !!parameterRule?.options?.length) {
  187. return (
  188. <SimpleSelect
  189. className='!py-0'
  190. wrapperClassName={cn('!h-8 w-full')}
  191. defaultValue={renderValue as string}
  192. onSelect={handleSelect}
  193. items={parameterRule.options.map(option => ({ value: option, name: option }))}
  194. />
  195. )
  196. }
  197. if (parameterRule.type === 'tag') {
  198. return (
  199. <div className={cn('!h-8 w-full')}>
  200. <TagInput
  201. items={renderValue as string[]}
  202. onChange={handleTagChange}
  203. customizedConfirmKey='Tab'
  204. isInWorkflow={isInWorkflow}
  205. />
  206. </div>
  207. )
  208. }
  209. return null
  210. }
  211. return (
  212. <div className='mb-2 flex items-center justify-between'>
  213. <div className='shrink-0 basis-1/2'>
  214. <div className={cn('flex w-full shrink-0 items-center')}>
  215. {
  216. !parameterRule.required && parameterRule.name !== 'stop' && (
  217. <div className='mr-2 w-7'>
  218. <Switch
  219. defaultValue={!isNullOrUndefined(value)}
  220. onChange={handleSwitch}
  221. size='md'
  222. />
  223. </div>
  224. )
  225. }
  226. <div
  227. className='system-xs-regular mr-0.5 truncate text-text-secondary'
  228. title={parameterRule.label[language] || parameterRule.label.en_US}
  229. >
  230. {parameterRule.label[language] || parameterRule.label.en_US}
  231. </div>
  232. {
  233. parameterRule.help && (
  234. <Tooltip
  235. popupContent={(
  236. <div className='w-[178px] whitespace-pre-wrap'>{parameterRule.help[language] || parameterRule.help.en_US}</div>
  237. )}
  238. popupClassName='mr-1'
  239. triggerClassName='mr-1 w-4 h-4 shrink-0'
  240. />
  241. )
  242. }
  243. </div>
  244. {
  245. parameterRule.type === 'tag' && (
  246. <div className={cn(!isInWorkflow && 'w-[178px]', 'system-xs-regular text-text-tertiary')}>
  247. {parameterRule?.tagPlaceholder?.[language]}
  248. </div>
  249. )
  250. }
  251. </div>
  252. {renderInput()}
  253. </div>
  254. )
  255. }
  256. export default ParameterItem