Form.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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. if (
  101. (
  102. (k === 'model_type' && v === 'chat' && value.huggingfacehub_api_type === 'inference_endpoints')
  103. || (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'chat')
  104. )
  105. && modelModal?.key === ProviderEnum.huggingface_hub
  106. )
  107. extraValue.task_type = 'question-answer'
  108. handleMultiFormChange({ ...value, [k]: v, ...extraValue }, k)
  109. }
  110. }
  111. const handleFocus = () => {
  112. if (mode === 'edit' && !cleared)
  113. handleClear()
  114. }
  115. const renderField = (field: Field) => {
  116. const hidden = typeof field.hidden === 'function' ? field.hidden(value) : field.hidden
  117. if (hidden)
  118. return null
  119. if (field.type === 'text') {
  120. return (
  121. <div key={field.key} className='py-3'>
  122. <div className={nameClassName}>{field.label[locale]}</div>
  123. <Input
  124. field={field}
  125. value={value}
  126. onChange={v => handleMultiFormChange(v, field.key)}
  127. onFocus={handleFocus}
  128. validatedStatusState={validatedStatusState}
  129. />
  130. {validating && changeKey === field.key && <ValidatingTip />}
  131. </div>
  132. )
  133. }
  134. if (field.type === 'radio') {
  135. const options = typeof field.options === 'function' ? field.options(value) : field.options
  136. return (
  137. <div key={field.key} className='py-3'>
  138. <div className={nameClassName}>{field.label[locale]}</div>
  139. <div className={`grid grid-cols-${options?.length} gap-3`}>
  140. {
  141. options?.map(option => (
  142. <div
  143. className={`
  144. flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
  145. ${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
  146. `}
  147. onClick={() => handleFormChange(field.key, option.key)}
  148. key={`${field.key}-${option.key}`}
  149. >
  150. <div className={`
  151. flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
  152. ${value?.[field.key] === option.key && 'border-[5px] border-primary-600'}
  153. `} />
  154. <div className='text-sm text-gray-900'>{option.label[locale]}</div>
  155. </div>
  156. ))
  157. }
  158. </div>
  159. {validating && changeKey === field.key && <ValidatingTip />}
  160. </div>
  161. )
  162. }
  163. if (field.type === 'select') {
  164. const options = typeof field.options === 'function' ? field.options(value) : field.options
  165. return (
  166. <div key={field.key} className='py-3'>
  167. <div className={nameClassName}>{field.label[locale]}</div>
  168. <SimpleSelect
  169. defaultValue={value[field.key]}
  170. items={options!.map(option => ({ value: option.key, name: option.label[locale] }))}
  171. onSelect={item => handleFormChange(field.key, item.value as string)}
  172. />
  173. {validating && changeKey === field.key && <ValidatingTip />}
  174. </div>
  175. )
  176. }
  177. }
  178. return (
  179. <div>
  180. {
  181. fields.map(field => renderField(field))
  182. }
  183. </div>
  184. )
  185. }
  186. export default Form