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.

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