Form.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { useEffect, useState } from 'react'
  2. import type { Dispatch, FC, SetStateAction } from 'react'
  3. import { useContext } from 'use-context-selector'
  4. import { type Field, type FormValue, type ProviderConfigModal, ProviderEnum } from '../declarations'
  5. import { useValidate } from '../../key-validator/hooks'
  6. import { ValidatingTip } from '../../key-validator/ValidateStatus'
  7. import { validateModelProviderFn } from '../utils'
  8. import Input from './Input'
  9. import I18n from '@/context/i18n'
  10. import { SimpleSelect } from '@/app/components/base/select'
  11. type FormProps = {
  12. modelModal?: ProviderConfigModal
  13. initValue?: FormValue
  14. fields: Field[]
  15. onChange: (v: FormValue) => void
  16. onValidatedError: (v: string) => void
  17. mode: string
  18. cleared: boolean
  19. onClearedChange: Dispatch<SetStateAction<boolean>>
  20. onValidating: (validating: boolean) => void
  21. }
  22. const nameClassName = `
  23. py-2 text-sm text-gray-900
  24. `
  25. const Form: FC<FormProps> = ({
  26. modelModal,
  27. initValue = {},
  28. fields,
  29. onChange,
  30. onValidatedError,
  31. mode,
  32. cleared,
  33. onClearedChange,
  34. onValidating,
  35. }) => {
  36. const { locale } = useContext(I18n)
  37. const [value, setValue] = useState(initValue)
  38. const [validate, validating, validatedStatusState] = useValidate(value)
  39. const [changeKey, setChangeKey] = useState('')
  40. useEffect(() => {
  41. onValidatedError(validatedStatusState.message || '')
  42. }, [validatedStatusState, onValidatedError])
  43. useEffect(() => {
  44. onValidating(validating)
  45. }, [validating, onValidating])
  46. const updateValue = (v: FormValue) => {
  47. setValue(v)
  48. onChange(v)
  49. }
  50. const handleMultiFormChange = (v: FormValue, newChangeKey: string) => {
  51. updateValue(v)
  52. setChangeKey(newChangeKey)
  53. const validateKeys = (typeof modelModal?.validateKeys === 'function' ? modelModal?.validateKeys(v) : modelModal?.validateKeys) || []
  54. if (validateKeys.length) {
  55. validate({
  56. before: () => {
  57. for (let i = 0; i < validateKeys.length; i++) {
  58. if (!v[validateKeys[i]])
  59. return false
  60. }
  61. return true
  62. },
  63. run: () => {
  64. return validateModelProviderFn(modelModal!.key, modelModal?.filterValue ? modelModal?.filterValue(v) : v)
  65. },
  66. })
  67. }
  68. }
  69. const handleClear = (saveValue?: FormValue) => {
  70. const needClearFields = modelModal?.fields.filter(field => field.type !== 'radio')
  71. const newValue: Record<string, string> = {}
  72. needClearFields?.forEach((field) => {
  73. newValue[field.key] = ''
  74. })
  75. updateValue({ ...value, ...newValue, ...saveValue })
  76. onClearedChange(true)
  77. }
  78. const handleFormChange = (k: string, v: string) => {
  79. if (mode === 'edit' && !cleared) {
  80. handleClear({ [k]: v })
  81. }
  82. else {
  83. const extraValue: Record<string, string> = {}
  84. if (
  85. (
  86. (k === 'model_type' && v === 'embeddings' && value.huggingfacehub_api_type === 'inference_endpoints')
  87. || (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'embeddings')
  88. )
  89. && modelModal?.key === ProviderEnum.huggingface_hub
  90. )
  91. extraValue.task_type = 'feature-extraction'
  92. if (
  93. (
  94. (k === 'model_type' && v === 'text-generation' && value.huggingfacehub_api_type === 'inference_endpoints')
  95. || (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'text-generation')
  96. )
  97. && modelModal?.key === ProviderEnum.huggingface_hub
  98. )
  99. extraValue.task_type = 'text-generation'
  100. handleMultiFormChange({ ...value, [k]: v, ...extraValue }, k)
  101. }
  102. }
  103. const handleFocus = () => {
  104. if (mode === 'edit' && !cleared)
  105. handleClear()
  106. }
  107. const renderField = (field: Field) => {
  108. const hidden = typeof field.hidden === 'function' ? field.hidden(value) : field.hidden
  109. if (hidden)
  110. return null
  111. if (field.type === 'text') {
  112. return (
  113. <div key={field.key} className='py-3'>
  114. <div className={nameClassName}>{field.label[locale]}</div>
  115. <Input
  116. field={field}
  117. value={value}
  118. onChange={v => handleMultiFormChange(v, field.key)}
  119. onFocus={handleFocus}
  120. validatedStatusState={validatedStatusState}
  121. />
  122. {validating && changeKey === field.key && <ValidatingTip />}
  123. </div>
  124. )
  125. }
  126. if (field.type === 'radio') {
  127. const options = typeof field.options === 'function' ? field.options(value) : field.options
  128. return (
  129. <div key={field.key} className='py-3'>
  130. <div className={nameClassName}>{field.label[locale]}</div>
  131. <div className='grid grid-cols-2 gap-3'>
  132. {
  133. options?.map(option => (
  134. <div
  135. className={`
  136. flex items-center px-3 h-9 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
  137. ${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
  138. `}
  139. onClick={() => handleFormChange(field.key, option.key)}
  140. key={`${field.key}-${option.key}`}
  141. >
  142. <div className={`
  143. flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
  144. ${value?.[field.key] === option.key && 'border-[5px] border-primary-600'}
  145. `} />
  146. <div className='text-sm text-gray-900'>{option.label[locale]}</div>
  147. </div>
  148. ))
  149. }
  150. </div>
  151. {validating && changeKey === field.key && <ValidatingTip />}
  152. </div>
  153. )
  154. }
  155. if (field.type === 'select') {
  156. const options = typeof field.options === 'function' ? field.options(value) : field.options
  157. return (
  158. <div key={field.key} className='py-3'>
  159. <div className={nameClassName}>{field.label[locale]}</div>
  160. <SimpleSelect
  161. defaultValue={value[field.key]}
  162. items={options!.map(option => ({ value: option.key, name: option.label[locale] }))}
  163. onSelect={item => handleFormChange(field.key, item.value as string)}
  164. />
  165. {validating && changeKey === field.key && <ValidatingTip />}
  166. </div>
  167. )
  168. }
  169. }
  170. return (
  171. <div>
  172. {
  173. fields.map(field => renderField(field))
  174. }
  175. </div>
  176. )
  177. }
  178. export default Form