index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useMemo } from 'react'
  4. import produce from 'immer'
  5. import { uniqueId } from 'lodash-es'
  6. import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types'
  7. import { BodyPayloadValueType, BodyType } from '../../types'
  8. import KeyValue from '../key-value'
  9. import useAvailableVarList from '../../../_base/hooks/use-available-var-list'
  10. import VarReferencePicker from '../../../_base/components/variable/var-reference-picker'
  11. import cn from '@/utils/classnames'
  12. import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor'
  13. import type { ValueSelector, Var } from '@/app/components/workflow/types'
  14. import { VarType } from '@/app/components/workflow/types'
  15. const UNIQUE_ID_PREFIX = 'key-value-'
  16. type Props = {
  17. readonly: boolean
  18. nodeId: string
  19. payload: Body
  20. onChange: (payload: Body) => void
  21. }
  22. const allTypes = [
  23. BodyType.none,
  24. BodyType.formData,
  25. BodyType.xWwwFormUrlencoded,
  26. BodyType.json,
  27. BodyType.rawText,
  28. BodyType.binary,
  29. ]
  30. const bodyTextMap = {
  31. [BodyType.none]: 'none',
  32. [BodyType.formData]: 'form-data',
  33. [BodyType.xWwwFormUrlencoded]: 'x-www-form-urlencoded',
  34. [BodyType.rawText]: 'raw',
  35. [BodyType.json]: 'JSON',
  36. [BodyType.binary]: 'binary',
  37. }
  38. const EditBody: FC<Props> = ({
  39. readonly,
  40. nodeId,
  41. payload,
  42. onChange,
  43. }) => {
  44. const { type, data } = payload
  45. const bodyPayload = useMemo(() => {
  46. if (typeof data === 'string') { // old data
  47. return []
  48. }
  49. return data
  50. }, [data])
  51. const stringValue = [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(type) ? '' : (bodyPayload[0]?.value || '')
  52. const { availableVars, availableNodes } = useAvailableVarList(nodeId, {
  53. onlyLeafNodeVar: false,
  54. filterVar: (varPayload: Var) => {
  55. return [VarType.string, VarType.number, VarType.secret, VarType.arrayNumber, VarType.arrayString].includes(varPayload.type)
  56. },
  57. })
  58. const handleTypeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  59. const newType = e.target.value as BodyType
  60. const hasKeyValue = [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(newType)
  61. onChange({
  62. type: newType,
  63. data: hasKeyValue
  64. ? [
  65. {
  66. id: uniqueId(UNIQUE_ID_PREFIX),
  67. type: BodyPayloadValueType.text,
  68. key: '',
  69. value: '',
  70. },
  71. ]
  72. : [],
  73. })
  74. }, [onChange])
  75. const handleAddBody = useCallback(() => {
  76. const newPayload = produce(payload, (draft) => {
  77. (draft.data as BodyPayload).push({
  78. id: uniqueId(UNIQUE_ID_PREFIX),
  79. type: BodyPayloadValueType.text,
  80. key: '',
  81. value: '',
  82. })
  83. })
  84. onChange(newPayload)
  85. }, [onChange, payload])
  86. const handleBodyPayloadChange = useCallback((newList: KeyValueType[]) => {
  87. const newPayload = produce(payload, (draft) => {
  88. draft.data = newList as BodyPayload
  89. })
  90. onChange(newPayload)
  91. }, [onChange, payload])
  92. const filterOnlyFileVariable = (varPayload: Var) => {
  93. return [VarType.file, VarType.arrayFile].includes(varPayload.type)
  94. }
  95. const handleBodyValueChange = useCallback((value: string) => {
  96. const newBody = produce(payload, (draft: Body) => {
  97. if ((draft.data as BodyPayload).length === 0) {
  98. (draft.data as BodyPayload).push({
  99. id: uniqueId(UNIQUE_ID_PREFIX),
  100. type: BodyPayloadValueType.text,
  101. key: '',
  102. value: '',
  103. })
  104. }
  105. (draft.data as BodyPayload)[0].value = value
  106. })
  107. onChange(newBody)
  108. }, [onChange, payload])
  109. const handleFileChange = useCallback((value: ValueSelector | string) => {
  110. const newBody = produce(payload, (draft: Body) => {
  111. if ((draft.data as BodyPayload).length === 0) {
  112. (draft.data as BodyPayload).push({
  113. id: uniqueId(UNIQUE_ID_PREFIX),
  114. type: BodyPayloadValueType.file,
  115. })
  116. }
  117. (draft.data as BodyPayload)[0].file = value as ValueSelector
  118. })
  119. onChange(newBody)
  120. }, [onChange, payload])
  121. return (
  122. <div>
  123. {/* body type */}
  124. <div className='flex flex-wrap'>
  125. {allTypes.map(t => (
  126. <label key={t} htmlFor={`body-type-${t}`} className='mr-4 flex h-7 items-center space-x-2'>
  127. <input
  128. type="radio"
  129. id={`body-type-${t}`}
  130. value={t}
  131. checked={type === t}
  132. onChange={handleTypeChange}
  133. disabled={readonly}
  134. />
  135. <div className='text-[13px] font-normal leading-[18px] text-gray-700'>{bodyTextMap[t]}</div>
  136. </label>
  137. ))}
  138. </div>
  139. {/* body value */}
  140. <div className={cn(type !== BodyType.none && 'mt-1')}>
  141. {type === BodyType.none && null}
  142. {(type === BodyType.formData || type === BodyType.xWwwFormUrlencoded) && (
  143. <KeyValue
  144. readonly={readonly}
  145. nodeId={nodeId}
  146. list={bodyPayload as KeyValueType[]}
  147. onChange={handleBodyPayloadChange}
  148. onAdd={handleAddBody}
  149. isSupportFile={type === BodyType.formData}
  150. />
  151. )}
  152. {type === BodyType.rawText && (
  153. <InputWithVar
  154. instanceId={'http-body-raw'}
  155. title={<div className='uppercase'>Raw text</div>}
  156. onChange={handleBodyValueChange}
  157. value={stringValue}
  158. justVar
  159. nodesOutputVars={availableVars}
  160. availableNodes={availableNodes}
  161. readOnly={readonly}
  162. />
  163. )}
  164. {type === BodyType.json && (
  165. <InputWithVar
  166. instanceId={'http-body-json'}
  167. title='JSON'
  168. value={stringValue}
  169. onChange={handleBodyValueChange}
  170. justVar
  171. nodesOutputVars={availableVars}
  172. availableNodes={availableNodes}
  173. readOnly={readonly}
  174. />
  175. )}
  176. {type === BodyType.binary && (
  177. <VarReferencePicker
  178. nodeId={nodeId}
  179. readonly={readonly}
  180. value={bodyPayload[0]?.file || []}
  181. onChange={handleFileChange}
  182. filterVar={filterOnlyFileVariable}
  183. />
  184. )}
  185. </div>
  186. </div>
  187. )
  188. }
  189. export default React.memo(EditBody)