Form.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import { Fragment, 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. // convert tooltip '\n' to <br />
  70. const renderTooltipContent = (content: string) => {
  71. return content.split('\n').map((line, index, array) => (
  72. <Fragment key={index}>
  73. {line}
  74. {index < array.length - 1 && <br />}
  75. </Fragment>
  76. ))
  77. }
  78. const renderField = (formSchema: CredentialFormSchema) => {
  79. const tooltip = formSchema.tooltip
  80. const tooltipContent = (tooltip && (
  81. <span className='ml-1 pt-1.5'>
  82. <Tooltip popupContent={
  83. // w-[100px] caused problem
  84. <div className=''>
  85. {renderTooltipContent(tooltip[language] || tooltip.en_US)}
  86. </div>
  87. } >
  88. <RiQuestionLine className='w-3 h-3 text-gray-500' />
  89. </Tooltip>
  90. </span>))
  91. if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) {
  92. const {
  93. variable,
  94. label,
  95. placeholder,
  96. required,
  97. show_on,
  98. } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
  99. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  100. return null
  101. const disabed = readonly || (isEditMode && (variable === '__model_type' || variable === '__model_name'))
  102. return (
  103. <div key={variable} className={cn(itemClassName, 'py-3')}>
  104. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  105. {label[language] || label.en_US}
  106. {
  107. required && (
  108. <span className='ml-1 text-red-500'>*</span>
  109. )
  110. }
  111. {tooltipContent}
  112. </div>
  113. <Input
  114. className={cn(inputClassName, `${disabed && 'cursor-not-allowed opacity-60'}`)}
  115. value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
  116. onChange={val => handleFormChange(variable, val)}
  117. validated={validatedSuccess}
  118. placeholder={placeholder?.[language] || placeholder?.en_US}
  119. disabled={disabed}
  120. type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'}
  121. {...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})}
  122. />
  123. {fieldMoreInfo?.(formSchema)}
  124. {validating && changeKey === variable && <ValidatingTip />}
  125. </div>
  126. )
  127. }
  128. if (formSchema.type === FormTypeEnum.radio) {
  129. const {
  130. options,
  131. variable,
  132. label,
  133. show_on,
  134. required,
  135. } = formSchema as CredentialFormSchemaRadio
  136. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  137. return null
  138. const disabed = isEditMode && (variable === '__model_type' || variable === '__model_name')
  139. return (
  140. <div key={variable} className={cn(itemClassName, 'py-3')}>
  141. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  142. {label[language] || label.en_US}
  143. {
  144. required && (
  145. <span className='ml-1 text-red-500'>*</span>
  146. )
  147. }
  148. {tooltipContent}
  149. </div>
  150. <div className={`grid grid-cols-${options?.length} gap-3`}>
  151. {
  152. options.filter((option) => {
  153. if (option.show_on.length)
  154. return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
  155. return true
  156. }).map(option => (
  157. <div
  158. className={`
  159. flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
  160. ${value[variable] === option.value && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
  161. ${disabed && '!cursor-not-allowed opacity-60'}
  162. `}
  163. onClick={() => handleFormChange(variable, option.value)}
  164. key={`${variable}-${option.value}`}
  165. >
  166. <div className={`
  167. flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
  168. ${value[variable] === option.value && 'border-[5px] border-primary-600'}
  169. `} />
  170. <div className='text-sm text-gray-900'>{option.label[language] || option.label.en_US}</div>
  171. </div>
  172. ))
  173. }
  174. </div>
  175. {fieldMoreInfo?.(formSchema)}
  176. {validating && changeKey === variable && <ValidatingTip />}
  177. </div>
  178. )
  179. }
  180. if (formSchema.type === 'select') {
  181. const {
  182. options,
  183. variable,
  184. label,
  185. show_on,
  186. required,
  187. placeholder,
  188. } = formSchema as CredentialFormSchemaSelect
  189. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  190. return null
  191. return (
  192. <div key={variable} className={cn(itemClassName, 'py-3')}>
  193. <div className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>
  194. {label[language] || label.en_US}
  195. {
  196. required && (
  197. <span className='ml-1 text-red-500'>*</span>
  198. )
  199. }
  200. {tooltipContent}
  201. </div>
  202. <SimpleSelect
  203. className={cn(inputClassName)}
  204. disabled={readonly}
  205. defaultValue={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
  206. items={options.filter((option) => {
  207. if (option.show_on.length)
  208. return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
  209. return true
  210. }).map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
  211. onSelect={item => handleFormChange(variable, item.value as string)}
  212. placeholder={placeholder?.[language] || placeholder?.en_US}
  213. />
  214. {fieldMoreInfo?.(formSchema)}
  215. {validating && changeKey === variable && <ValidatingTip />}
  216. </div>
  217. )
  218. }
  219. if (formSchema.type === 'boolean') {
  220. const {
  221. variable,
  222. label,
  223. show_on,
  224. required,
  225. } = formSchema as CredentialFormSchemaRadio
  226. if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
  227. return null
  228. return (
  229. <div key={variable} className={cn(itemClassName, 'py-3')}>
  230. <div className='flex items-center justify-between py-2 text-sm text-gray-900'>
  231. <div className='flex items-center space-x-2'>
  232. <span className={cn(fieldLabelClassName, 'py-2 text-sm text-gray-900')}>{label[language] || label.en_US}</span>
  233. {
  234. required && (
  235. <span className='ml-1 text-red-500'>*</span>
  236. )
  237. }
  238. {tooltipContent}
  239. </div>
  240. <Radio.Group
  241. className='flex items-center'
  242. value={value[variable] === null ? undefined : (value[variable] ? 1 : 0)}
  243. onChange={val => handleFormChange(variable, val === 1)}
  244. >
  245. <Radio value={1} className='!mr-1'>True</Radio>
  246. <Radio value={0}>False</Radio>
  247. </Radio.Group>
  248. </div>
  249. {fieldMoreInfo?.(formSchema)}
  250. </div>
  251. )
  252. }
  253. }
  254. return (
  255. <div className={className}>
  256. {
  257. formSchemas.map(formSchema => renderField(formSchema))
  258. }
  259. </div>
  260. )
  261. }
  262. export default Form