mirror of
https://github.com/langgenius/dify.git
synced 2026-04-25 01:26:57 +08:00
create from DSL
This commit is contained in:
parent
117b84116e
commit
f607a334ac
@ -3,6 +3,7 @@
|
|||||||
import { forwardRef, useState } from 'react'
|
import { forwardRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NewAppDialog from './NewAppDialog'
|
import NewAppDialog from './NewAppDialog'
|
||||||
|
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||||
@ -17,13 +18,13 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
|
|||||||
const { onPlanInfoChanged } = useProviderContext()
|
const { onPlanInfoChanged } = useProviderContext()
|
||||||
|
|
||||||
const [showNewAppDialog, setShowNewAppDialog] = useState(false)
|
const [showNewAppDialog, setShowNewAppDialog] = useState(false)
|
||||||
|
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={() => setShowNewAppDialog(true)}
|
|
||||||
className='relative col-span-1 flex flex-col justify-between min-h-[160px] bg-gray-200 rounded-xl cursor-pointer duration-200 ease-in-out hover:bg-gray-50 hover:shadow-lg transition-colors'
|
className='relative col-span-1 flex flex-col justify-between min-h-[160px] bg-gray-200 rounded-xl cursor-pointer duration-200 ease-in-out hover:bg-gray-50 hover:shadow-lg transition-colors'
|
||||||
>
|
>
|
||||||
<div className='grow rounded-t-xl group hover:bg-white'>
|
<div className='grow rounded-t-xl group hover:bg-white' onClick={() => setShowNewAppDialog(true)}>
|
||||||
<div className='flex pt-4 px-4 pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
<div className='flex pt-4 px-4 pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||||
<span className='w-10 h-10 p-3 bg-gray-100 rounded-lg border border-gray-200 group-hover:bg-primary-50 group-hover:border-primary-100'>
|
<span className='w-10 h-10 p-3 bg-gray-100 rounded-lg border border-gray-200 group-hover:bg-primary-50 group-hover:border-primary-100'>
|
||||||
<Plus className='w-4 h-4 text-gray-500 group-hover:text-primary-600'/>
|
<Plus className='w-4 h-4 text-gray-500 group-hover:text-primary-600'/>
|
||||||
@ -33,10 +34,22 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center px-4 py-3 border-t-[0.5px] border-black/[.05] rounded-b-xl text-xs leading-[18px] text-gray-500 hover:bg-white hover:text-primary-600'>
|
<div
|
||||||
|
className='flex items-center px-4 py-3 border-t-[0.5px] border-black/[.05] rounded-b-xl text-xs leading-[18px] text-gray-500 hover:bg-white hover:text-primary-600'
|
||||||
|
onClick={() => setShowCreateFromDSLModal(true)}
|
||||||
|
>
|
||||||
{t('app.createFromConfigFile')}
|
{t('app.createFromConfigFile')}
|
||||||
<ArrowUpRight className='ml-1 w-3 h-3'/>
|
<ArrowUpRight className='ml-1 w-3 h-3'/>
|
||||||
</div>
|
</div>
|
||||||
|
<CreateFromDSLModal
|
||||||
|
show={showCreateFromDSLModal}
|
||||||
|
onClose={() => setShowCreateFromDSLModal(false)}
|
||||||
|
onSuccess={() => {
|
||||||
|
onPlanInfoChanged()
|
||||||
|
if (onSuccess)
|
||||||
|
onSuccess()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<NewAppDialog show={showNewAppDialog} onSuccess={
|
<NewAppDialog show={showNewAppDialog} onSuccess={
|
||||||
() => {
|
() => {
|
||||||
onPlanInfoChanged()
|
onPlanInfoChanged()
|
||||||
|
|||||||
198
web/app/components/app/create-from-dsl-modal/index.tsx
Normal file
198
web/app/components/app/create-from-dsl-modal/index.tsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { MouseEventHandler } from 'react'
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
// import useSWR from 'swr'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useContext } from 'use-context-selector'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Button from '@/app/components/base/button'
|
||||||
|
// import Dialog from '@/app/components/base/dialog'
|
||||||
|
import Modal from '@/app/components/base/modal'
|
||||||
|
// import Input from '@/app/components/base/input'
|
||||||
|
// import type { AppMode } from '@/types/app'
|
||||||
|
import { ToastContext } from '@/app/components/base/toast'
|
||||||
|
// import { createApp, fetchAppTemplates } from '@/service/apps'
|
||||||
|
import { useAppContext } from '@/context/app-context'
|
||||||
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
|
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||||
|
import { Trash03, UploadCloud01, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
|
||||||
|
type CreateFromDSLModalProps = {
|
||||||
|
show: boolean
|
||||||
|
onSuccess?: () => void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProps) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { notify } = useContext(ToastContext)
|
||||||
|
const [currentFile, setDSLFile] = useState<File | null>()
|
||||||
|
const [dragging, setDragging] = useState(false)
|
||||||
|
const dropRef = useRef<HTMLDivElement>(null)
|
||||||
|
const dragRef = useRef<HTMLDivElement>(null)
|
||||||
|
const fileUploader = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const handleDragEnter = (e: DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
e.target !== dragRef.current && setDragging(true)
|
||||||
|
}
|
||||||
|
const handleDragOver = (e: DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
const handleDragLeave = (e: DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
e.target === dragRef.current && setDragging(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrop = useCallback((e: DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
setDragging(false)
|
||||||
|
if (!e.dataTransfer)
|
||||||
|
return
|
||||||
|
|
||||||
|
const files = [...e.dataTransfer.files] as File[]
|
||||||
|
setDSLFile(files[0])
|
||||||
|
}, [setDSLFile])
|
||||||
|
|
||||||
|
const selectHandle = () => {
|
||||||
|
if (fileUploader.current)
|
||||||
|
fileUploader.current.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = [...(e.target.files ?? [])] as File[]
|
||||||
|
console.log(files[0])
|
||||||
|
setDSLFile(files[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFile = () => {
|
||||||
|
if (fileUploader.current)
|
||||||
|
fileUploader.current.value = ''
|
||||||
|
setDSLFile(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// utils
|
||||||
|
// const getFileType = (currentFile: File) => {
|
||||||
|
// if (!currentFile)
|
||||||
|
// return ''
|
||||||
|
|
||||||
|
// const arr = currentFile.name.split('.')
|
||||||
|
// return arr[arr.length - 1]
|
||||||
|
// }
|
||||||
|
|
||||||
|
const getFileSize = (size: number) => {
|
||||||
|
if (size / 1024 < 10)
|
||||||
|
return `${(size / 1024).toFixed(2)}KB`
|
||||||
|
|
||||||
|
return `${(size / 1024 / 1024).toFixed(2)}MB`
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dropRef.current?.addEventListener('dragenter', handleDragEnter)
|
||||||
|
dropRef.current?.addEventListener('dragover', handleDragOver)
|
||||||
|
dropRef.current?.addEventListener('dragleave', handleDragLeave)
|
||||||
|
dropRef.current?.addEventListener('drop', handleDrop)
|
||||||
|
return () => {
|
||||||
|
dropRef.current?.removeEventListener('dragenter', handleDragEnter)
|
||||||
|
dropRef.current?.removeEventListener('dragover', handleDragOver)
|
||||||
|
dropRef.current?.removeEventListener('dragleave', handleDragLeave)
|
||||||
|
dropRef.current?.removeEventListener('drop', handleDrop)
|
||||||
|
}
|
||||||
|
}, [handleDrop])
|
||||||
|
|
||||||
|
const { isCurrentWorkspaceManager } = useAppContext()
|
||||||
|
const { plan, enableBilling } = useProviderContext()
|
||||||
|
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
|
||||||
|
|
||||||
|
const isCreatingRef = useRef(false)
|
||||||
|
// TODO
|
||||||
|
const onCreate: MouseEventHandler = async () => {
|
||||||
|
if (isCreatingRef.current)
|
||||||
|
return
|
||||||
|
isCreatingRef.current = true
|
||||||
|
try {
|
||||||
|
// const app = await createApp()
|
||||||
|
if (onSuccess)
|
||||||
|
onSuccess()
|
||||||
|
if (onClose)
|
||||||
|
onClose()
|
||||||
|
notify({ type: 'success', message: t('app.newApp.appCreated') })
|
||||||
|
// router.push(`/app/${app.id}/${isCurrentWorkspaceManager ? 'configuration' : 'overview'}`)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
|
||||||
|
}
|
||||||
|
isCreatingRef.current = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
wrapperClassName='z-20'
|
||||||
|
className='px-8 py-6 max-w-[520px] w-[520px] rounded-xl'
|
||||||
|
isShow={show}
|
||||||
|
onClose={() => {}}
|
||||||
|
>
|
||||||
|
<div className='relative pb-2 text-xl font-medium leading-[30px] text-gray-900'>Create from DSL file</div>
|
||||||
|
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}>
|
||||||
|
<XClose className='w-4 h-4 text-gray-500' />
|
||||||
|
</div>
|
||||||
|
<div className='pt-5 pb-7'>
|
||||||
|
<input
|
||||||
|
ref={fileUploader}
|
||||||
|
id="fileUploader"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
type="file"
|
||||||
|
onChange={fileChangeHandle}
|
||||||
|
/>
|
||||||
|
<div ref={dropRef}>
|
||||||
|
{!currentFile && (
|
||||||
|
<div className={cn(
|
||||||
|
'relative flex justify-center items-center h-20 bg-gray-50 rounded-xl border border-dashed border-gray-200',
|
||||||
|
dragging && '!bg-[#F5F8FF] !border-[#B2CCFF]',
|
||||||
|
)}>
|
||||||
|
|
||||||
|
<div className='flex justify-center items-center'>
|
||||||
|
<UploadCloud01 className='w-6 h-6 mr-2'/>
|
||||||
|
<span className='text-sm text-gray-500'>
|
||||||
|
{t('datasetCreation.stepOne.uploader.button')}
|
||||||
|
<label className='pl-1 cursor-pointer text-[#155eef]' onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{dragging && <div ref={dragRef} className='absolute top-0 left-0 w-full h-full' />}
|
||||||
|
</div>
|
||||||
|
{currentFile && (
|
||||||
|
<div className='group relative flex justify-center items-center justify-between h-20 pl-[64px] pr-6 bg-gray-50 rounded-xl border border-gray-100 cursor-pointer hover:bg-[#f5f8ff] hover:border-[#d1e0ff]'>
|
||||||
|
{/* TODO type icon */}
|
||||||
|
<div className='absolute top-[24px] left-[24px] w-8 h-8'/>
|
||||||
|
<div className='grow truncate'>
|
||||||
|
<div className='truncate text-sm leading-[20px] font-medium text-gray-800'>{currentFile.name}</div>
|
||||||
|
<div className='text-xs leading-[18px] text-gray-500'>{getFileSize(currentFile.size)}</div>
|
||||||
|
</div>
|
||||||
|
<div className='shrink-0 hidden group-hover:flex'>
|
||||||
|
<Trash03 className='w-4 h-4 text-gray-500 cursor-pointer' onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
removeFile()
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isAppsFull && <AppsFull loc='app-create' />}
|
||||||
|
<div className='pt-6 flex justify-end'>
|
||||||
|
<Button className='mr-2 text-gray-700 text-sm font-medium' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||||
|
<Button className='text-sm font-medium' disabled={isAppsFull || !currentFile} type="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateFromDSLModal
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.4" d="M4 16.2422C2.79401 15.435 2 14.0602 2 12.5C2 10.1564 3.79151 8.23129 6.07974 8.01937C6.54781 5.17213 9.02024 3 12 3C14.9798 3 17.4522 5.17213 17.9203 8.01937C20.2085 8.23129 22 10.1564 22 12.5C22 14.0602 21.206 15.435 20 16.2422" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 16L12 12M12 12L16 16M12 12L12 21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 564 B |
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"type": "element",
|
||||||
|
"isRootNode": true,
|
||||||
|
"name": "svg",
|
||||||
|
"attributes": {
|
||||||
|
"width": "24",
|
||||||
|
"height": "24",
|
||||||
|
"viewBox": "0 0 24 24",
|
||||||
|
"fill": "none",
|
||||||
|
"xmlns": "http://www.w3.org/2000/svg"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"opacity": "0.4",
|
||||||
|
"d": "M4 16.2422C2.79401 15.435 2 14.0602 2 12.5C2 10.1564 3.79151 8.23129 6.07974 8.01937C6.54781 5.17213 9.02024 3 12 3C14.9798 3 17.4522 5.17213 17.9203 8.01937C20.2085 8.23129 22 10.1564 22 12.5C22 14.0602 21.206 15.435 20 16.2422",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "element",
|
||||||
|
"name": "path",
|
||||||
|
"attributes": {
|
||||||
|
"d": "M8 16L12 12M12 12L16 16M12 12L12 21",
|
||||||
|
"stroke": "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
"stroke-linecap": "round",
|
||||||
|
"stroke-linejoin": "round"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "UploadCloud01"
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
// GENERATE BY script
|
||||||
|
// DON NOT EDIT IT MANUALLY
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import data from './UploadCloud01.json'
|
||||||
|
import IconBase from '@/app/components/base/icons/IconBase'
|
||||||
|
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||||
|
|
||||||
|
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||||
|
props,
|
||||||
|
ref,
|
||||||
|
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||||
|
|
||||||
|
Icon.displayName = 'UploadCloud01'
|
||||||
|
|
||||||
|
export default Icon
|
||||||
@ -28,5 +28,6 @@ export { default as Settings04 } from './Settings04'
|
|||||||
export { default as Target04 } from './Target04'
|
export { default as Target04 } from './Target04'
|
||||||
export { default as Trash03 } from './Trash03'
|
export { default as Trash03 } from './Trash03'
|
||||||
export { default as Upload03 } from './Upload03'
|
export { default as Upload03 } from './Upload03'
|
||||||
|
export { default as UploadCloud01 } from './UploadCloud01'
|
||||||
export { default as XClose } from './XClose'
|
export { default as XClose } from './XClose'
|
||||||
export { default as X } from './X'
|
export { default as X } from './X'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user