mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
feat(frontend): workflow import dsl from url (#6286)
This commit is contained in:
parent
46a5294d94
commit
9a536979ab
|
@ -1,10 +1,14 @@
|
|||
'use client'
|
||||
|
||||
import { forwardRef, useState } from 'react'
|
||||
import { forwardRef, useMemo, useState } from 'react'
|
||||
import {
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
} from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||
import CreateFromDSLModal, { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
|
||||
|
@ -16,10 +20,21 @@ export type CreateAppCardProps = {
|
|||
const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuccess }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { onPlanInfoChanged } = useProviderContext()
|
||||
const searchParams = useSearchParams()
|
||||
const { replace } = useRouter()
|
||||
const dslUrl = searchParams.get('remoteInstallUrl') || undefined
|
||||
|
||||
const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
|
||||
const [showNewAppModal, setShowNewAppModal] = useState(false)
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(!!dslUrl)
|
||||
|
||||
const activeTab = useMemo(() => {
|
||||
if (dslUrl)
|
||||
return CreateFromDSLModalTab.FROM_URL
|
||||
|
||||
return undefined
|
||||
}, [dslUrl])
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
|
@ -65,7 +80,14 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
|
|||
/>
|
||||
<CreateFromDSLModal
|
||||
show={showCreateFromDSLModal}
|
||||
onClose={() => setShowCreateFromDSLModal(false)}
|
||||
onClose={() => {
|
||||
setShowCreateFromDSLModal(false)
|
||||
|
||||
if (dslUrl)
|
||||
replace('/')
|
||||
}}
|
||||
activeTab={activeTab}
|
||||
dslUrl={dslUrl}
|
||||
onSuccess={() => {
|
||||
onPlanInfoChanged()
|
||||
if (onSuccess)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import type { MouseEventHandler } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -10,25 +10,38 @@ import Uploader from './uploader'
|
|||
import Button from '@/app/components/base/button'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { importApp } from '@/service/apps'
|
||||
import {
|
||||
importApp,
|
||||
importAppFromUrl,
|
||||
} 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 { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { getRedirection } from '@/utils/app-redirection'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type CreateFromDSLModalProps = {
|
||||
show: boolean
|
||||
onSuccess?: () => void
|
||||
onClose: () => void
|
||||
activeTab?: string
|
||||
dslUrl?: string
|
||||
}
|
||||
|
||||
const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProps) => {
|
||||
export enum CreateFromDSLModalTab {
|
||||
FROM_FILE = 'from-file',
|
||||
FROM_URL = 'from-url',
|
||||
}
|
||||
|
||||
const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDSLModalTab.FROM_FILE, dslUrl = '' }: CreateFromDSLModalProps) => {
|
||||
const { push } = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const [currentFile, setDSLFile] = useState<File>()
|
||||
const [fileContent, setFileContent] = useState<string>()
|
||||
const [currentTab, setCurrentTab] = useState(activeTab)
|
||||
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
|
||||
|
||||
const readFile = (file: File) => {
|
||||
const reader = new FileReader()
|
||||
|
@ -53,15 +66,26 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
|
|||
|
||||
const isCreatingRef = useRef(false)
|
||||
const onCreate: MouseEventHandler = async () => {
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
|
||||
return
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
|
||||
return
|
||||
if (isCreatingRef.current)
|
||||
return
|
||||
isCreatingRef.current = true
|
||||
if (!currentFile)
|
||||
return
|
||||
try {
|
||||
const app = await importApp({
|
||||
data: fileContent || '',
|
||||
})
|
||||
let app
|
||||
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
|
||||
app = await importApp({
|
||||
data: fileContent || '',
|
||||
})
|
||||
}
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
|
||||
app = await importAppFromUrl({
|
||||
url: dslUrlValue || '',
|
||||
})
|
||||
}
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
if (onClose)
|
||||
|
@ -76,24 +100,95 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
|
|||
isCreatingRef.current = false
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: CreateFromDSLModalTab.FROM_FILE,
|
||||
label: t('app.importFromDSLFile'),
|
||||
},
|
||||
{
|
||||
key: CreateFromDSLModalTab.FROM_URL,
|
||||
label: t('app.importFromDSLUrl'),
|
||||
},
|
||||
]
|
||||
|
||||
const buttonDisabled = useMemo(() => {
|
||||
if (isAppsFull)
|
||||
return true
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_FILE)
|
||||
return !currentFile
|
||||
if (currentTab === CreateFromDSLModalTab.FROM_URL)
|
||||
return !dslUrlValue
|
||||
return false
|
||||
}, [isAppsFull, currentTab, currentFile, dslUrlValue])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='px-8 py-6 max-w-[520px] w-[520px] rounded-xl'
|
||||
className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
>
|
||||
<div className='relative pb-2 text-xl font-medium leading-[30px] text-gray-900'>{t('app.createFromConfigFile')}</div>
|
||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
|
||||
{t('app.importFromDSL')}
|
||||
<div
|
||||
className='flex items-center w-8 h-8 cursor-pointer'
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
<Uploader
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
{isAppsFull && <AppsFull loc='app-create-dsl' />}
|
||||
<div className='pt-6 flex justify-end'>
|
||||
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative flex items-center h-full cursor-pointer',
|
||||
currentTab === tab.key && 'text-text-primary',
|
||||
)}
|
||||
onClick={() => setCurrentTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
{
|
||||
currentTab === tab.key && (
|
||||
<div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='px-6 py-4'>
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_FILE && (
|
||||
<Uploader
|
||||
className='mt-0'
|
||||
file={currentFile}
|
||||
updateFile={handleFile}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentTab === CreateFromDSLModalTab.FROM_URL && (
|
||||
<div>
|
||||
<div className='mb-1 system-md-semibold leading6'>DSL URL</div>
|
||||
<input
|
||||
placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
|
||||
className='px-2 w-full h-8 border border-components-input-border-active bg-components-input-bg-active rounded-lg outline-none appearance-none placeholder:text-components-input-text-placeholder system-sm-regular'
|
||||
value={dslUrlValue}
|
||||
onChange={e => setDslUrlValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{isAppsFull && (
|
||||
<div className='px-6'>
|
||||
<AppsFull className='mt-0' loc='app-create-dsl' />
|
||||
</div>
|
||||
)}
|
||||
<div className='flex justify-end px-6 py-5'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
|
||||
<Button disabled={isAppsFull || !currentFile} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
<Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
|
|
@ -8,14 +8,18 @@ import s from './style.module.css'
|
|||
import cn from '@/utils/classnames'
|
||||
import GridMask from '@/app/components/base/grid-mask'
|
||||
|
||||
const AppsFull: FC<{ loc: string }> = ({
|
||||
const AppsFull: FC<{ loc: string; className?: string }> = ({
|
||||
loc,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
|
||||
<div className='mt-6 px-3.5 py-4 border-2 border-solid border-transparent rounded-lg shadow-md flex flex-col transition-all duration-200 ease-in-out cursor-pointer'>
|
||||
<div className={cn(
|
||||
'mt-6 px-3.5 py-4 border-2 border-solid border-transparent rounded-lg shadow-md flex flex-col transition-all duration-200 ease-in-out cursor-pointer',
|
||||
className,
|
||||
)}>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
||||
<div>{t('billing.apps.fullTipLine1')}</div>
|
||||
|
|
|
@ -13,6 +13,10 @@ const translation = {
|
|||
exportFailed: 'Export DSL failed.',
|
||||
importDSL: 'Import DSL file',
|
||||
createFromConfigFile: 'Create from DSL file',
|
||||
importFromDSL: 'Import from DSL',
|
||||
importFromDSLFile: 'From DSL file',
|
||||
importFromDSLUrl: 'From URL',
|
||||
importFromDSLUrlPlaceholder: 'Paste DSL link here',
|
||||
deleteAppConfirmTitle: 'Delete this app?',
|
||||
deleteAppConfirmContent:
|
||||
'Deleting the app is irreversible. Users will no longer be able to access your app, and all prompt configurations and logs will be permanently deleted.',
|
||||
|
|
|
@ -13,6 +13,10 @@ const translation = {
|
|||
exportFailed: '导出 DSL 失败',
|
||||
importDSL: '导入 DSL 文件',
|
||||
createFromConfigFile: '通过 DSL 文件创建',
|
||||
importFromDSL: '导入 DSL',
|
||||
importFromDSLFile: '文件',
|
||||
importFromDSLUrl: 'URL',
|
||||
importFromDSLUrlPlaceholder: '输入 DSL 文件的 URL',
|
||||
deleteAppConfirmTitle: '确认删除应用?',
|
||||
deleteAppConfirmContent:
|
||||
'删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。',
|
||||
|
|
|
@ -37,6 +37,10 @@ export const importApp: Fetcher<AppDetailResponse, { data: string; name?: string
|
|||
return post<AppDetailResponse>('apps/import', { body: { data, name, description, icon, icon_background } })
|
||||
}
|
||||
|
||||
export const importAppFromUrl: Fetcher<AppDetailResponse, { url: string; name?: string; description?: string; icon?: string; icon_background?: string }> = ({ url, name, description, icon, icon_background }) => {
|
||||
return post<AppDetailResponse>('apps/import/url', { body: { url, name, description, icon, icon_background } })
|
||||
}
|
||||
|
||||
export const switchApp: Fetcher<{ new_app_id: string }, { appID: string; name: string; icon: string; icon_background: string }> = ({ appID, name, icon, icon_background }) => {
|
||||
return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon, icon_background } })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user