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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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. // todo: TBD billing plan
  66. // const plan = useProviderContextSelector(state => state.plan)
  67. // const enableBilling = useProviderContextSelector(state => state.enableBilling)
  68. // const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
  69. const isCreatingRef = useRef(false)
  70. const { mutateAsync: importDSL } = useImportPipelineDSL()
  71. const onCreate = async () => {
  72. if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
  73. return
  74. if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
  75. return
  76. if (isCreatingRef.current)
  77. return
  78. isCreatingRef.current = true
  79. let response
  80. if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
  81. response = await importDSL({
  82. mode: DSLImportMode.YAML_CONTENT,
  83. yaml_content: fileContent || '',
  84. })
  85. }
  86. if (currentTab === CreateFromDSLModalTab.FROM_URL) {
  87. response = await importDSL({
  88. mode: DSLImportMode.YAML_URL,
  89. yaml_url: dslUrlValue || '',
  90. })
  91. }
  92. if (!response) {
  93. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  94. isCreatingRef.current = false
  95. return
  96. }
  97. const { id, status, pipeline_id, dataset_id, imported_dsl_version, current_dsl_version } = response
  98. if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
  99. if (onSuccess)
  100. onSuccess()
  101. if (onClose)
  102. onClose()
  103. notify({
  104. type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
  105. message: t(status === DSLImportStatus.COMPLETED ? 'datasetPipeline.creation.successTip' : 'datasetPipeline.creation.caution'),
  106. children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('app.newApp.appCreateDSLWarning'),
  107. })
  108. if (pipeline_id)
  109. await handleCheckPluginDependencies(pipeline_id, true)
  110. push(`/datasets/${dataset_id}/pipeline`)
  111. isCreatingRef.current = false
  112. }
  113. else if (status === DSLImportStatus.PENDING) {
  114. setVersions({
  115. importedVersion: imported_dsl_version ?? '',
  116. systemVersion: current_dsl_version ?? '',
  117. })
  118. if (onClose)
  119. onClose()
  120. setTimeout(() => {
  121. setShowErrorModal(true)
  122. }, 300)
  123. setImportId(id)
  124. isCreatingRef.current = false
  125. }
  126. else {
  127. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  128. isCreatingRef.current = false
  129. }
  130. }
  131. const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 })
  132. useKeyPress('esc', () => {
  133. if (show && !showErrorModal)
  134. onClose()
  135. })
  136. const { mutateAsync: importDSLConfirm } = useImportPipelineDSLConfirm()
  137. const onDSLConfirm = async () => {
  138. if (!importId)
  139. return
  140. const response = await importDSLConfirm(importId)
  141. if (!response) {
  142. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  143. return
  144. }
  145. const { status, pipeline_id, dataset_id } = response
  146. if (status === DSLImportStatus.COMPLETED) {
  147. if (onSuccess)
  148. onSuccess()
  149. if (onClose)
  150. onClose()
  151. notify({
  152. type: 'success',
  153. message: t('datasetPipeline.creation.successTip'),
  154. })
  155. if (pipeline_id)
  156. await handleCheckPluginDependencies(pipeline_id, true)
  157. push(`datasets/${dataset_id}/pipeline`)
  158. }
  159. else if (status === DSLImportStatus.FAILED) {
  160. notify({ type: 'error', message: t('datasetPipeline.creation.errorTip') })
  161. }
  162. }
  163. const buttonDisabled = useMemo(() => {
  164. if (currentTab === CreateFromDSLModalTab.FROM_FILE)
  165. return !currentFile
  166. if (currentTab === CreateFromDSLModalTab.FROM_URL)
  167. return !dslUrlValue
  168. return false
  169. }, [currentTab, currentFile, dslUrlValue])
  170. return (
  171. <>
  172. <Modal
  173. className='w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0 shadow-xl'
  174. isShow={show}
  175. onClose={noop}
  176. >
  177. <Header onClose={onClose} />
  178. <Tab
  179. currentTab={currentTab}
  180. setCurrentTab={setCurrentTab}
  181. />
  182. <div className='px-6 py-4'>
  183. {
  184. currentTab === CreateFromDSLModalTab.FROM_FILE && (
  185. <Uploader
  186. className='mt-0'
  187. file={currentFile}
  188. updateFile={handleFile}
  189. />
  190. )
  191. }
  192. {
  193. currentTab === CreateFromDSLModalTab.FROM_URL && (
  194. <div>
  195. <div className='system-md-semibold leading6 mb-1'>DSL URL</div>
  196. <Input
  197. placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
  198. value={dslUrlValue}
  199. onChange={e => setDslUrlValue(e.target.value)}
  200. />
  201. </div>
  202. )
  203. }
  204. </div>
  205. <div className='flex justify-end gap-x-2 p-6 pt-5'>
  206. <Button onClick={onClose}>
  207. {t('app.newApp.Cancel')}
  208. </Button>
  209. <Button
  210. disabled={buttonDisabled}
  211. variant='primary'
  212. onClick={handleCreateApp}
  213. className='gap-1'
  214. >
  215. <span>{t('app.newApp.import')}</span>
  216. </Button>
  217. </div>
  218. </Modal>
  219. <Modal
  220. isShow={showErrorModal}
  221. onClose={() => setShowErrorModal(false)}
  222. className='w-[480px]'
  223. >
  224. <div className='flex flex-col items-start gap-2 self-stretch pb-4'>
  225. <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
  226. <div className='system-md-regular flex grow flex-col text-text-secondary'>
  227. <div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
  228. <div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
  229. <br />
  230. <div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div>
  231. <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
  232. </div>
  233. </div>
  234. <div className='flex items-start justify-end gap-2 self-stretch pt-6'>
  235. <Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
  236. <Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>
  237. </div>
  238. </Modal>
  239. </>
  240. )
  241. }
  242. export default CreateFromDSLModal