You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

form.tsx 8.8KB

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