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.

index.tsx 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. 'use client'
  2. import { useMemo, useRef, useState } from 'react'
  3. import { useRouter } from 'next/navigation'
  4. import { useContext } from 'use-context-selector'
  5. import { useTranslation } from 'react-i18next'
  6. import { useDebounceFn, useKeyPress } from 'ahooks'
  7. import Button from '@/app/components/base/button'
  8. import Input from '@/app/components/base/input'
  9. import Modal from '@/app/components/base/modal'
  10. import { ToastContext } from '@/app/components/base/toast'
  11. import {
  12. DSLImportMode,
  13. DSLImportStatus,
  14. } from '@/models/app'
  15. import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
  16. import { noop } from 'lodash-es'
  17. import Uploader from './uploader'
  18. import Header from './header'
  19. import Tab from './tab'
  20. import { useImportPipelineDSL, useImportPipelineDSLConfirm } from '@/service/use-pipeline'
  21. type CreateFromDSLModalProps = {
  22. show: boolean
  23. onSuccess?: () => void
  24. onClose: () => void
  25. activeTab?: CreateFromDSLModalTab
  26. dslUrl?: string
  27. }
  28. export enum CreateFromDSLModalTab {
  29. FROM_FILE = 'from-file',
  30. FROM_URL = 'from-url',
  31. }
  32. const CreateFromDSLModal = ({
  33. show,
  34. onSuccess,
  35. onClose,
  36. activeTab = CreateFromDSLModalTab.FROM_FILE,
  37. dslUrl = '',
  38. }: CreateFromDSLModalProps) => {
  39. const { push } = useRouter()
  40. const { t } = useTranslation()
  41. const { notify } = useContext(ToastContext)
  42. const [currentFile, setDSLFile] = useState<File>()
  43. const [fileContent, setFileContent] = useState<string>()
  44. const [currentTab, setCurrentTab] = useState(activeTab)
  45. const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
  46. const [showErrorModal, setShowErrorModal] = useState(false)
  47. const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
  48. const [importId, setImportId] = useState<string>()
  49. const { handleCheckPluginDependencies } = usePluginDependencies()
  50. const readFile = (file: File) => {
  51. const reader = new FileReader()
  52. reader.onload = function (event) {
  53. const content = event.target?.result
  54. setFileContent(content as string)
  55. }
  56. reader.readAsText(file)
  57. }
  58. const handleFile = (file?: File) => {
  59. setDSLFile(file)
  60. if (file)
  61. readFile(file)
  62. if (!file)
  63. setFileContent('')
  64. }
  65. const isCreatingRef = useRef(false)
  66. const { mutateAsync: importDSL } = useImportPipelineDSL()
  67. const onCreate = async () => {
  68. if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
  69. return
  70. if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
  71. return
  72. if (isCreatingRef.current)
  73. return
  74. isCreatingRef.current = true
  75. let response
  76. if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
  77. response = await importDSL({
  78. mode: DSLImportMode.YAML_CONTENT,
  79. yaml_content: fileContent || '',
  80. })
  81. }
  82. if (currentTab === CreateFromDSLModalTab.FROM_URL) {
  83. response = await importDSL({
  84. mode: DSLImportMode.YAML_URL,
  85. yaml_url: dslUrlValue || '',
  86. })
  87. }
  88. if (!response) {
  89. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  90. isCreatingRef.current = false
  91. return
  92. }
  93. const { id, status, pipeline_id, dataset_id, imported_dsl_version, current_dsl_version } = response
  94. if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
  95. if (onSuccess)
  96. onSuccess()
  97. if (onClose)
  98. onClose()
  99. notify({
  100. type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
  101. message: t(status === DSLImportStatus.COMPLETED ? 'datasetPipeline.creation.successTip' : 'datasetPipeline.creation.caution'),
  102. children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
  103. })
  104. if (pipeline_id)
  105. await handleCheckPluginDependencies(pipeline_id, true)
  106. push(`/datasets/${dataset_id}/pipeline`)
  107. isCreatingRef.current = false
  108. }
  109. else if (status === DSLImportStatus.PENDING) {
  110. setVersions({
  111. importedVersion: imported_dsl_version ?? '',
  112. systemVersion: current_dsl_version ?? '',
  113. })
  114. if (onClose)
  115. onClose()
  116. setTimeout(() => {
  117. setShowErrorModal(true)
  118. }, 300)
  119. setImportId(id)
  120. isCreatingRef.current = false
  121. }
  122. else {
  123. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  124. isCreatingRef.current = false
  125. }
  126. }
  127. const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
  128. useKeyPress('esc', () => {
  129. if (show && !showErrorModal)
  130. onClose()
  131. })
  132. const { mutateAsync: importDSLConfirm } = useImportPipelineDSLConfirm()
  133. const onDSLConfirm = async () => {
  134. if (!importId)
  135. return
  136. const response = await importDSLConfirm(importId)
  137. if (!response) {
  138. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  139. return
  140. }
  141. const { status, pipeline_id, dataset_id } = response
  142. if (status === DSLImportStatus.COMPLETED) {
  143. if (onSuccess)
  144. onSuccess()
  145. if (onClose)
  146. onClose()
  147. notify({
  148. type: 'success',
  149. message: t('datasetPipeline.creation.successTip'),
  150. })
  151. if (pipeline_id)
  152. await handleCheckPluginDependencies(pipeline_id, true)
  153. push(`datasets/${dataset_id}/pipeline`)
  154. }
  155. else if (status === DSLImportStatus.FAILED) {
  156. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  157. }
  158. }
  159. const buttonDisabled = useMemo(() => {
  160. if (currentTab === CreateFromDSLModalTab.FROM_FILE)
  161. return !currentFile
  162. if (currentTab === CreateFromDSLModalTab.FROM_URL)
  163. return !dslUrlValue
  164. return false
  165. }, [currentTab, currentFile, dslUrlValue])
  166. return (
  167. <>
  168. <Modal
  169. className='w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl'
  170. isShow={show}
  171. onClose={noop}
  172. >
  173. <Header onClose={onClose} />
  174. <Tab
  175. currentTab={currentTab}
  176. setCurrentTab={setCurrentTab}
  177. />
  178. <div className='px-6 py-4'>
  179. {
  180. currentTab === CreateFromDSLModalTab.FROM_FILE && (
  181. <Uploader
  182. className='mt-0'
  183. file={currentFile}
  184. updateFile={handleFile}
  185. />
  186. )
  187. }
  188. {
  189. currentTab === CreateFromDSLModalTab.FROM_URL && (
  190. <div>
  191. <div className='system-md-semibold leading6 mb-1 text-text-secondary'>
  192. DSL URL
  193. </div>
  194. <Input
  195. placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
  196. value={dslUrlValue}
  197. onChange={e => setDslUrlValue(e.target.value)}
  198. />
  199. </div>
  200. )
  201. }
  202. </div>
  203. <div className='flex justify-end gap-x-2 p-6 pt-5'>
  204. <Button onClick={onClose}>
  205. {t('app.newApp.Cancel')}
  206. </Button>
  207. <Button
  208. disabled={buttonDisabled}
  209. variant='primary'
  210. onClick={handleCreateApp}
  211. className='gap-1'
  212. >
  213. <span>{t('app.newApp.import')}</span>
  214. </Button>
  215. </div>
  216. </Modal>
  217. <Modal
  218. isShow={showErrorModal}
  219. onClose={() => setShowErrorModal(false)}
  220. className='w-[480px]'
  221. >
  222. <div className='flex flex-col items-start gap-2 self-stretch pb-4'>
  223. <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
  224. <div className='system-md-regular flex grow flex-col text-text-secondary'>
  225. <div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
  226. <div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
  227. <br />
  228. <div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
  229. <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
  230. </div>
  231. </div>
  232. <div className='flex items-start justify-end gap-2 self-stretch pt-6'>
  233. <Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
  234. <Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>
  235. </div>
  236. </Modal>
  237. </>
  238. )
  239. }
  240. export default CreateFromDSLModal