Form.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { Fragment, useState } from 'react'
  2. import type { FC } from 'react'
  3. import { RiQuestionLine } from '@remixicon/react'
  4. import { ValidatingTip } from '../../key-validator/ValidateStatus'
  5. import type {
  6. CredentialFormSchema,
  7. CredentialFormSchemaNumberInput,
  8. CredentialFormSchemaRadio,
  9. CredentialFormSchemaSecretInput,
  10. CredentialFormSchemaSelect,
  11. CredentialFormSchemaTextInput,
  12. FormValue,
  13. } from '../declarations'
  14. import { FormTypeEnum } from '../declarations'
  15. import { useLanguage } from '../hooks'
  16. import Input from './Input'
  17. import cn from '@/utils/classnames'
  18. import { SimpleSelect } from '@/app/components/base/select'
  19. import Tooltip from '@/app/components/base/tooltip'
  20. import Radio from '@/app/components/base/radio'
  21. type FormProps = {
  22. className?: string
  23. itemClassName?: string
  24. fieldLabelClassName?: string
  25. value: FormValue
  26. onChange: (val: FormValue) => void
  27. formSchemas: CredentialFormSchema[]
  28. validating: boolean
  29. validatedSuccess?: boolean
  30. showOnVariableMap: Record<string, string[]>
  31. isEditMode: boolean
  32. readonly?: boolean
  33. inputClassName?: string
  34. isShowDefaultValue?: boolean
  35. fieldMoreInfo?: (payload: CredentialFormSchema) => JSX.Element | null
  36. }
  37. const Form: FC<FormProps> = ({
  38. className,
  39. itemClassName,
  40. fieldLabelClassName,
  41. value,
  42. onChange,
  43. formSchemas,
  44. validating,
  45. validatedSuccess,
  46. showOnVariableMap,
  47. isEditMode,
  48. readonly,
  49. inputClassName,
  50. isShowDefaultValue = false,
  51. fieldMoreInfo,
  52. }) => {
  53. const language = useLanguage()
  54. const [changeKey, setChangeKey] = useState('')
  55. const handleFormChange = (key: string, val: string | boolean) => {
  56. if (isEditMode && (key === '__model_type' || key === '__model_name'))
  57. return
  58. setChangeKey(key)
  59. const shouldClearVariable: Record<string, string | undefined> = {}
  60. if (showOnVariableMap[key]?.length) {
  61. showOnVariableMap[key].forEach((clearVariable) => {
  62. shouldClearVariable[clearVariable] = undefined
  63. })
  64. }
  65. onChange({ ...value, [key]: val, ...shouldClearVariable })
  66. }
  67. // convert tooltip '\n' to <br />
  68. const renderTooltipContent = (content: string) => {
  69. return content.split('\n').map((line, index, array) => (
  70. <Fragment key={index}>
  71. {line}
  72. {index < array.length - 1 && <br />}
  73. </Fragment>
  74. ))
  75. }
  76. const renderField = (formSchema: CredentialFormSchema) => {
  77. const tooltip = formSchema.tooltip
  78. const tooltipContent = (tooltip && (
  79. <span className='ml-1 pt-1.5'>
  80. <Tooltip popupContent={
  81. // w-[100px] caused problem
  82. <div className=''>
  83. {renderTooltipContent(tooltip[language] || tooltip.en_US)}
  84. </div>
  85. } >
  86. <RiQuestionLine className='w-3 h-3 text-gray-500' />
  87. </Tooltip>
  88. </span>))
  89. if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) {
  90. const {
  91. variable,
  92. label,
  93. placeholder,
  94. required,
  95. show_on,
  96. } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
  97. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  98. return null
  99. const disabed = readonly || (isEditMode && (variable === '__model_type' || variable === '__model_name'))
  100. return (
  101. <div key={variable} className={cn(itemClassName, 'py-3')}>
  102. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  103. {label[language] || label.en_US}
  104. {
  105. required && (
  106. <span className='ml-1 text-red-500'>*</span>
  107. )
  108. }
  109. {tooltipContent}
  110. </div>
  111. <Input
  112. className={cn(inputClassName, `${disabed && 'cursor-not-allowed opacity-60'}`)}
  113. value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
  114. onChange={val => handleFormChange(variable, val)}
  115. validated={validatedSuccess}
  116. placeholder={placeholder?.[language] || placeholder?.en_US}
  117. disabled={disabed}
  118. type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'}
  119. {...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})}
  120. />
  121. {fieldMoreInfo?.(formSchema)}
  122. {validating && changeKey === variable && <ValidatingTip />}
  123. </div>
  124. )
  125. }
  126. if (formSchema.type === FormTypeEnum.radio) {
  127. const {
  128. options,
  129. variable,
  130. label,
  131. show_on,
  132. required,
  133. } = formSchema as CredentialFormSchemaRadio
  134. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  135. return null
  136. const disabed = isEditMode && (variable === '__model_type' || variable === '__model_name')
  137. return (
  138. <div key={variable} className={cn(itemClassName, 'py-3')}>
  139. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  140. {label[language] || label.en_US}
  141. {
  142. required && (
  143. <span className='ml-1 text-red-500'>*</span>
  144. )
  145. }
  146. {tooltipContent}
  147. </div>
  148. <div className={`grid grid-cols-${options?.length} gap-3`}>
  149. {
  150. options.filter((option) => {
  151. if (option.show_on.length)
  152. return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
  153. return true
  154. }).map(option => (
  155. <div
  156. className={`
  157. flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
  158. ${value[variable] === option.value && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
  159. ${disabed && '!cursor-not-allowed opacity-60'}
  160. `}
  161. onClick={() => handleFormChange(variable, option.value)}
  162. key={`${variable}-${option.value}`}
  163. >
  164. <div className={`
  165. flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
  166. ${value[variable] === option.value && 'border-[5px] border-primary-600'}
  167. `} />
  168. <div className='text-sm text-gray-900'>{option.label[language] || option.label.en_US}</div>
  169. </div>
  170. ))
  171. }
  172. </div>
  173. {fieldMoreInfo?.(formSchema)}
  174. {validating && changeKey === variable && <ValidatingTip />}
  175. </div>
  176. )
  177. }
  178. if (formSchema.type === 'select') {
  179. const {
  180. options,
  181. variable,
  182. label,
  183. show_on,
  184. required,
  185. placeholder,
  186. } = formSchema as CredentialFormSchemaSelect
  187. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  188. return null
  189. return (
  190. <div key={variable} className={cn(itemClassName, 'py-3')}>
  191. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  192. {label[language] || label.en_US}
  193. {
  194. required && (
  195. <span className='ml-1 text-red-500'>*</span>
  196. )
  197. }
  198. {tooltipContent}
  199. </div>
  200. <SimpleSelect
  201. className={cn(inputClassName)}
  202. disabled={readonly}
  203. defaultValue={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
  204. items={options.filter((option) => {
  205. if (option.show_on.length)
  206. return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
  207. return true
  208. }).map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
  209. onSelect={item => handleFormChange(variable, item.value as string)}
  210. placeholder={placeholder?.[language] || placeholder?.en_US}
  211. />
  212. {fieldMoreInfo?.(formSchema)}
  213. {validating && changeKey === variable && <ValidatingTip />}
  214. </div>
  215. )
  216. }
  217. if (formSchema.type === 'boolean') {
  218. const {
  219. variable,
  220. label,
  221. show_on,
  222. required,
  223. } = formSchema as CredentialFormSchemaRadio
  224. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  225. return null
  226. return (
  227. <div key={variable} className={cn(itemClassName, 'py-3')}>
  228. <div className='flex items-center justify-between py-2 text-sm text-gray-900'>
  229. <div className='flex items-center space-x-2'>
  230. <span className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>{label[language] || label.en_US}</span>
  231. {
  232. required && (
  233. <span className='ml-1 text-red-500'>*</span>
  234. )
  235. }
  236. {tooltipContent}
  237. </div>
  238. <Radio.Group
  239. className='flex items-center'
  240. value={value[variable] === null ? undefined : (value[variable] ? 1 : 0)}
  241. onChange={val => handleFormChange(variable, val === 1)}
  242. >
  243. <Radio value={1} className='!mr-1'>True</Radio>
  244. <Radio value={0}>False</Radio>
  245. </Radio.Group>
  246. </div>
  247. {fieldMoreInfo?.(formSchema)}
  248. </div>
  249. )
  250. }
  251. }
  252. return (
  253. <div className={className}>
  254. {
  255. formSchemas.map(formSchema => renderField(formSchema))
  256. }
  257. </div>
  258. )
  259. }
  260. export default Form