mirror of https://github.com/langgenius/dify.git
create from DSL
This commit is contained in:
parent
117b84116e
commit
f607a334ac
|
|
@ -3,6 +3,7 @@
|
|||
import { forwardRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NewAppDialog from './NewAppDialog'
|
||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
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 [showNewAppDialog, setShowNewAppDialog] = useState(false)
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||
return (
|
||||
<a
|
||||
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'
|
||||
>
|
||||
<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'>
|
||||
<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'/>
|
||||
|
|
@ -33,10 +34,22 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
|
|||
</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')}
|
||||
<ArrowUpRight className='ml-1 w-3 h-3'/>
|
||||
</div>
|
||||
<CreateFromDSLModal
|
||||
show={showCreateFromDSLModal}
|
||||
onClose={() => setShowCreateFromDSLModal(false)}
|
||||
onSuccess={() => {
|
||||
onPlanInfoChanged()
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
}}
|
||||
/>
|
||||
<NewAppDialog show={showNewAppDialog} onSuccess={
|
||||
() => {
|
||||
onPlanInfoChanged()
|
||||
|
|
|
|||
|
|
@ -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 Trash03 } from './Trash03'
|
||||
export { default as Upload03 } from './Upload03'
|
||||
export { default as UploadCloud01 } from './UploadCloud01'
|
||||
export { default as XClose } from './XClose'
|
||||
export { default as X } from './X'
|
||||
|
|
|
|||
Loading…
Reference in New Issue