form.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import React, { useEffect, useState } from 'react'
  2. import Button from '@/app/components/base/button'
  3. import Input from '@/app/components/base/input'
  4. import Textarea from '@/app/components/base/textarea'
  5. import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
  6. import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
  7. import Checkbox from '@/app/components/base/checkbox'
  8. import Select from '@/app/components/base/select'
  9. import { useChatContext } from '@/app/components/base/chat/chat/context'
  10. enum DATA_FORMAT {
  11. TEXT = 'text',
  12. JSON = 'json',
  13. }
  14. enum SUPPORTED_TAGS {
  15. LABEL = 'label',
  16. INPUT = 'input',
  17. TEXTAREA = 'textarea',
  18. BUTTON = 'button',
  19. }
  20. enum SUPPORTED_TYPES {
  21. TEXT = 'text',
  22. PASSWORD = 'password',
  23. EMAIL = 'email',
  24. NUMBER = 'number',
  25. DATE = 'date',
  26. TIME = 'time',
  27. DATETIME = 'datetime',
  28. CHECKBOX = 'checkbox',
  29. SELECT = 'select',
  30. }
  31. const MarkdownForm = ({ node }: any) => {
  32. const { onSend } = useChatContext()
  33. const [formValues, setFormValues] = useState<{ [key: string]: any }>({})
  34. useEffect(() => {
  35. const initialValues: { [key: string]: any } = {}
  36. node.children.forEach((child: any) => {
  37. if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName))
  38. initialValues[child.properties.name] = child.properties.value
  39. })
  40. setFormValues(initialValues)
  41. }, [node.children])
  42. const getFormValues = (children: any) => {
  43. const values: { [key: string]: any } = {}
  44. children.forEach((child: any) => {
  45. if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName))
  46. values[child.properties.name] = formValues[child.properties.name]
  47. })
  48. return values
  49. }
  50. const onSubmit = (e: any) => {
  51. e.preventDefault()
  52. const format = node.properties.dataFormat || DATA_FORMAT.TEXT
  53. const result = getFormValues(node.children)
  54. if (format === DATA_FORMAT.JSON) {
  55. onSend?.(JSON.stringify(result))
  56. }
  57. else {
  58. const textResult = Object.entries(result)
  59. .map(([key, value]) => `${key}: ${value}`)
  60. .join('\n')
  61. onSend?.(textResult)
  62. }
  63. }
  64. return (
  65. <form
  66. autoComplete="off"
  67. className='flex flex-col self-stretch'
  68. onSubmit={(e: any) => {
  69. e.preventDefault()
  70. e.stopPropagation()
  71. }}
  72. >
  73. {node.children.filter((i: any) => i.type === 'element').map((child: any, index: number) => {
  74. if (child.tagName === SUPPORTED_TAGS.LABEL) {
  75. return (
  76. <label
  77. key={index}
  78. htmlFor={child.properties.for}
  79. className="system-md-semibold my-2 text-text-secondary"
  80. >
  81. {child.children[0]?.value || ''}
  82. </label>
  83. )
  84. }
  85. if (child.tagName === SUPPORTED_TAGS.INPUT && Object.values(SUPPORTED_TYPES).includes(child.properties.type)) {
  86. if (child.properties.type === SUPPORTED_TYPES.DATE || child.properties.type === SUPPORTED_TYPES.DATETIME) {
  87. return (
  88. <DatePicker
  89. key={index}
  90. value={formValues[child.properties.name]}
  91. needTimePicker={child.properties.type === SUPPORTED_TYPES.DATETIME}
  92. onChange={(date) => {
  93. setFormValues(prevValues => ({
  94. ...prevValues,
  95. [child.properties.name]: date,
  96. }))
  97. }}
  98. onClear={() => {
  99. setFormValues(prevValues => ({
  100. ...prevValues,
  101. [child.properties.name]: undefined,
  102. }))
  103. }}
  104. />
  105. )
  106. }
  107. if (child.properties.type === SUPPORTED_TYPES.TIME) {
  108. return (
  109. <TimePicker
  110. key={index}
  111. value={formValues[child.properties.name]}
  112. onChange={(time) => {
  113. setFormValues(prevValues => ({
  114. ...prevValues,
  115. [child.properties.name]: time,
  116. }))
  117. }}
  118. onClear={() => {
  119. setFormValues(prevValues => ({
  120. ...prevValues,
  121. [child.properties.name]: undefined,
  122. }))
  123. }}
  124. />
  125. )
  126. }
  127. if (child.properties.type === SUPPORTED_TYPES.CHECKBOX) {
  128. return (
  129. <div className='mt-2 flex h-6 items-center space-x-2' key={index}>
  130. <Checkbox
  131. key={index}
  132. checked={formValues[child.properties.name]}
  133. onCheck={() => {
  134. setFormValues(prevValues => ({
  135. ...prevValues,
  136. [child.properties.name]: !prevValues[child.properties.name],
  137. }))
  138. }}
  139. />
  140. <span>{child.properties.dataTip || child.properties['data-tip'] || ''}</span>
  141. </div>
  142. )
  143. }
  144. if (child.properties.type === SUPPORTED_TYPES.SELECT) {
  145. return (
  146. <Select
  147. key={index}
  148. allowSearch={false}
  149. className="w-full"
  150. items={(() => {
  151. let options = child.properties.dataOptions || child.properties['data-options'] || []
  152. if (typeof options === 'string') {
  153. try {
  154. options = JSON.parse(options)
  155. }
  156. catch (e) {
  157. console.error('Failed to parse options:', e)
  158. options = []
  159. }
  160. }
  161. return options.map((option: string) => ({
  162. name: option,
  163. value: option,
  164. }))
  165. })()}
  166. defaultValue={formValues[child.properties.name]}
  167. onSelect={(item) => {
  168. setFormValues(prevValues => ({
  169. ...prevValues,
  170. [child.properties.name]: item.value,
  171. }))
  172. }}
  173. />
  174. )
  175. }
  176. return (
  177. <Input
  178. key={index}
  179. type={child.properties.type}
  180. name={child.properties.name}
  181. placeholder={child.properties.placeholder}
  182. value={formValues[child.properties.name]}
  183. onChange={(e) => {
  184. setFormValues(prevValues => ({
  185. ...prevValues,
  186. [child.properties.name]: e.target.value,
  187. }))
  188. }}
  189. />
  190. )
  191. }
  192. if (child.tagName === SUPPORTED_TAGS.TEXTAREA) {
  193. return (
  194. <Textarea
  195. key={index}
  196. name={child.properties.name}
  197. placeholder={child.properties.placeholder}
  198. value={formValues[child.properties.name]}
  199. onChange={(e) => {
  200. setFormValues(prevValues => ({
  201. ...prevValues,
  202. [child.properties.name]: e.target.value,
  203. }))
  204. }}
  205. />
  206. )
  207. }
  208. if (child.tagName === SUPPORTED_TAGS.BUTTON) {
  209. const variant = child.properties.dataVariant
  210. const size = child.properties.dataSize
  211. return (
  212. <Button
  213. variant={variant}
  214. size={size}
  215. className='mt-4'
  216. key={index}
  217. onClick={onSubmit}
  218. >
  219. <span className='text-[13px]'>{child.children[0]?.value || ''}</span>
  220. </Button>
  221. )
  222. }
  223. return <p key={index}>Unsupported tag: {child.tagName}</p>
  224. })}
  225. </form>
  226. )
  227. }
  228. MarkdownForm.displayName = 'MarkdownForm'
  229. export default MarkdownForm