update-dsl-modal.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. 'use client'
  2. import type { MouseEventHandler } from 'react'
  3. import {
  4. memo,
  5. useCallback,
  6. useRef,
  7. useState,
  8. } from 'react'
  9. import { useContext } from 'use-context-selector'
  10. import { useTranslation } from 'react-i18next'
  11. import {
  12. RiAlertLine,
  13. RiCloseLine,
  14. } from '@remixicon/react'
  15. import { WORKFLOW_DATA_UPDATE } from './constants'
  16. import {
  17. SupportUploadFileTypes,
  18. } from './types'
  19. import {
  20. initialEdges,
  21. initialNodes,
  22. } from './utils'
  23. import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
  24. import Button from '@/app/components/base/button'
  25. import Modal from '@/app/components/base/modal'
  26. import { ToastContext } from '@/app/components/base/toast'
  27. import { updateWorkflowDraftFromDSL } from '@/service/workflow'
  28. import { useEventEmitterContextContext } from '@/context/event-emitter'
  29. import { useStore as useAppStore } from '@/app/components/app/store'
  30. import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
  31. type UpdateDSLModalProps = {
  32. onCancel: () => void
  33. onBackup: () => void
  34. onImport?: () => void
  35. }
  36. const UpdateDSLModal = ({
  37. onCancel,
  38. onBackup,
  39. onImport,
  40. }: UpdateDSLModalProps) => {
  41. const { t } = useTranslation()
  42. const { notify } = useContext(ToastContext)
  43. const appDetail = useAppStore(s => s.appDetail)
  44. const [currentFile, setDSLFile] = useState<File>()
  45. const [fileContent, setFileContent] = useState<string>()
  46. const [loading, setLoading] = useState(false)
  47. const { eventEmitter } = useEventEmitterContextContext()
  48. const readFile = (file: File) => {
  49. const reader = new FileReader()
  50. reader.onload = function (event) {
  51. const content = event.target?.result
  52. setFileContent(content as string)
  53. }
  54. reader.readAsText(file)
  55. }
  56. const handleFile = (file?: File) => {
  57. setDSLFile(file)
  58. if (file)
  59. readFile(file)
  60. if (!file)
  61. setFileContent('')
  62. }
  63. const isCreatingRef = useRef(false)
  64. const handleImport: MouseEventHandler = useCallback(async () => {
  65. if (isCreatingRef.current)
  66. return
  67. isCreatingRef.current = true
  68. if (!currentFile)
  69. return
  70. try {
  71. if (appDetail && fileContent) {
  72. setLoading(true)
  73. const {
  74. graph,
  75. features,
  76. hash,
  77. } = await updateWorkflowDraftFromDSL(appDetail.id, fileContent)
  78. const { nodes, edges, viewport } = graph
  79. const newFeatures = {
  80. file: {
  81. image: {
  82. enabled: !!features.file_upload?.image?.enabled,
  83. number_limits: features.file_upload?.image?.number_limits || 3,
  84. transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
  85. },
  86. enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
  87. allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
  88. allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
  89. allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
  90. number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
  91. },
  92. opening: {
  93. enabled: !!features.opening_statement,
  94. opening_statement: features.opening_statement,
  95. suggested_questions: features.suggested_questions,
  96. },
  97. suggested: features.suggested_questions_after_answer || { enabled: false },
  98. speech2text: features.speech_to_text || { enabled: false },
  99. text2speech: features.text_to_speech || { enabled: false },
  100. citation: features.retriever_resource || { enabled: false },
  101. moderation: features.sensitive_word_avoidance || { enabled: false },
  102. }
  103. eventEmitter?.emit({
  104. type: WORKFLOW_DATA_UPDATE,
  105. payload: {
  106. nodes: initialNodes(nodes, edges),
  107. edges: initialEdges(edges, nodes),
  108. viewport,
  109. features: newFeatures,
  110. hash,
  111. },
  112. } as any)
  113. if (onImport)
  114. onImport()
  115. notify({ type: 'success', message: t('workflow.common.importSuccess') })
  116. setLoading(false)
  117. onCancel()
  118. }
  119. }
  120. catch (e) {
  121. setLoading(false)
  122. notify({ type: 'error', message: t('workflow.common.importFailure') })
  123. }
  124. isCreatingRef.current = false
  125. }, [currentFile, fileContent, onCancel, notify, t, eventEmitter, appDetail, onImport])
  126. return (
  127. <Modal
  128. className='p-6 w-[520px] rounded-2xl'
  129. isShow={true}
  130. onClose={() => {}}
  131. >
  132. <div className='flex items-center justify-between mb-6'>
  133. <div className='text-2xl font-semibold text-[#101828]'>{t('workflow.common.importDSL')}</div>
  134. <div className='flex items-center justify-center w-[22px] h-[22px] cursor-pointer' onClick={onCancel}>
  135. <RiCloseLine className='w-5 h-5 text-gray-500' />
  136. </div>
  137. </div>
  138. <div className='flex mb-4 px-4 py-3 bg-[#FFFAEB] rounded-xl border border-[#FEDF89]'>
  139. <RiAlertLine className='shrink-0 mt-0.5 mr-2 w-4 h-4 text-[#F79009]' />
  140. <div>
  141. <div className='mb-2 text-sm font-medium text-[#354052]'>{t('workflow.common.importDSLTip')}</div>
  142. <Button
  143. variant='secondary-accent'
  144. onClick={onBackup}
  145. >
  146. {t('workflow.common.backupCurrentDraft')}
  147. </Button>
  148. </div>
  149. </div>
  150. <div className='mb-8'>
  151. <div className='mb-1 text-[13px] font-semibold text-[#354052]'>
  152. {t('workflow.common.chooseDSL')}
  153. </div>
  154. <Uploader
  155. file={currentFile}
  156. updateFile={handleFile}
  157. className='!mt-0'
  158. />
  159. </div>
  160. <div className='flex justify-end'>
  161. <Button className='mr-2' onClick={onCancel}>{t('app.newApp.Cancel')}</Button>
  162. <Button
  163. disabled={!currentFile || loading}
  164. variant='warning'
  165. onClick={handleImport}
  166. loading={loading}
  167. >
  168. {t('workflow.common.overwriteAndImport')}
  169. </Button>
  170. </div>
  171. </Modal>
  172. )
  173. }
  174. export default memo(UpdateDSLModal)