Form.tsx 9.5 KB

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