mirror of
https://gitee.com/dify_ai/dify.git
synced 2024-12-02 19:27:48 +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'
|
'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 { useTranslation } from 'react-i18next'
|
||||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
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 { useProviderContext } from '@/context/provider-context'
|
||||||
import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
|
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 CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuccess }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { onPlanInfoChanged } = useProviderContext()
|
const { onPlanInfoChanged } = useProviderContext()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const { replace } = useRouter()
|
||||||
|
const dslUrl = searchParams.get('remoteInstallUrl') || undefined
|
||||||
|
|
||||||
const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
|
const [showNewAppTemplateDialog, setShowNewAppTemplateDialog] = useState(false)
|
||||||
const [showNewAppModal, setShowNewAppModal] = 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 (
|
return (
|
||||||
<a
|
<a
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -65,7 +80,14 @@ const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuc
|
|||||||
/>
|
/>
|
||||||
<CreateFromDSLModal
|
<CreateFromDSLModal
|
||||||
show={showCreateFromDSLModal}
|
show={showCreateFromDSLModal}
|
||||||
onClose={() => setShowCreateFromDSLModal(false)}
|
onClose={() => {
|
||||||
|
setShowCreateFromDSLModal(false)
|
||||||
|
|
||||||
|
if (dslUrl)
|
||||||
|
replace('/')
|
||||||
|
}}
|
||||||
|
activeTab={activeTab}
|
||||||
|
dslUrl={dslUrl}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
onPlanInfoChanged()
|
onPlanInfoChanged()
|
||||||
if (onSuccess)
|
if (onSuccess)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { useRef, useState } from 'react'
|
import { useMemo, useRef, useState } from 'react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -10,25 +10,38 @@ import Uploader from './uploader'
|
|||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import Modal from '@/app/components/base/modal'
|
import Modal from '@/app/components/base/modal'
|
||||||
import { ToastContext } from '@/app/components/base/toast'
|
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 { useAppContext } from '@/context/app-context'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||||
import { getRedirection } from '@/utils/app-redirection'
|
import { getRedirection } from '@/utils/app-redirection'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type CreateFromDSLModalProps = {
|
type CreateFromDSLModalProps = {
|
||||||
show: boolean
|
show: boolean
|
||||||
onSuccess?: () => void
|
onSuccess?: () => void
|
||||||
onClose: () => 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 { push } = useRouter()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { notify } = useContext(ToastContext)
|
const { notify } = useContext(ToastContext)
|
||||||
const [currentFile, setDSLFile] = useState<File>()
|
const [currentFile, setDSLFile] = useState<File>()
|
||||||
const [fileContent, setFileContent] = useState<string>()
|
const [fileContent, setFileContent] = useState<string>()
|
||||||
|
const [currentTab, setCurrentTab] = useState(activeTab)
|
||||||
|
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
|
||||||
|
|
||||||
const readFile = (file: File) => {
|
const readFile = (file: File) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
@ -53,15 +66,26 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
|
|||||||
|
|
||||||
const isCreatingRef = useRef(false)
|
const isCreatingRef = useRef(false)
|
||||||
const onCreate: MouseEventHandler = async () => {
|
const onCreate: MouseEventHandler = async () => {
|
||||||
|
if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
|
||||||
|
return
|
||||||
|
if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
|
||||||
|
return
|
||||||
if (isCreatingRef.current)
|
if (isCreatingRef.current)
|
||||||
return
|
return
|
||||||
isCreatingRef.current = true
|
isCreatingRef.current = true
|
||||||
if (!currentFile)
|
|
||||||
return
|
|
||||||
try {
|
try {
|
||||||
const app = await importApp({
|
let app
|
||||||
data: fileContent || '',
|
|
||||||
})
|
if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
|
||||||
|
app = await importApp({
|
||||||
|
data: fileContent || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
|
||||||
|
app = await importAppFromUrl({
|
||||||
|
url: dslUrlValue || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
if (onSuccess)
|
if (onSuccess)
|
||||||
onSuccess()
|
onSuccess()
|
||||||
if (onClose)
|
if (onClose)
|
||||||
@ -76,24 +100,95 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose }: CreateFromDSLModalProp
|
|||||||
isCreatingRef.current = false
|
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 (
|
return (
|
||||||
<Modal
|
<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}
|
isShow={show}
|
||||||
onClose={() => { }}
|
onClose={() => { }}
|
||||||
>
|
>
|
||||||
<div className='relative pb-2 text-xl font-medium leading-[30px] text-gray-900'>{t('app.createFromConfigFile')}</div>
|
<div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
|
||||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}>
|
{t('app.importFromDSL')}
|
||||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
<div
|
||||||
|
className='flex items-center w-8 h-8 cursor-pointer'
|
||||||
|
onClick={() => onClose()}
|
||||||
|
>
|
||||||
|
<RiCloseLine className='w-5 h-5 text-text-tertiary' />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Uploader
|
<div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
|
||||||
file={currentFile}
|
{
|
||||||
updateFile={handleFile}
|
tabs.map(tab => (
|
||||||
/>
|
<div
|
||||||
{isAppsFull && <AppsFull loc='app-create-dsl' />}
|
key={tab.key}
|
||||||
<div className='pt-6 flex justify-end'>
|
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 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>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
@ -8,14 +8,18 @@ import s from './style.module.css'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import GridMask from '@/app/components/base/grid-mask'
|
import GridMask from '@/app/components/base/grid-mask'
|
||||||
|
|
||||||
const AppsFull: FC<{ loc: string }> = ({
|
const AppsFull: FC<{ loc: string; className?: string }> = ({
|
||||||
loc,
|
loc,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
|
<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='flex justify-between items-center'>
|
||||||
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
<div className={cn(s.textGradient, 'leading-[24px] text-base font-semibold')}>
|
||||||
<div>{t('billing.apps.fullTipLine1')}</div>
|
<div>{t('billing.apps.fullTipLine1')}</div>
|
||||||
|
@ -13,6 +13,10 @@ const translation = {
|
|||||||
exportFailed: 'Export DSL failed.',
|
exportFailed: 'Export DSL failed.',
|
||||||
importDSL: 'Import DSL file',
|
importDSL: 'Import DSL file',
|
||||||
createFromConfigFile: 'Create from 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?',
|
deleteAppConfirmTitle: 'Delete this app?',
|
||||||
deleteAppConfirmContent:
|
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.',
|
'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 失败',
|
exportFailed: '导出 DSL 失败',
|
||||||
importDSL: '导入 DSL 文件',
|
importDSL: '导入 DSL 文件',
|
||||||
createFromConfigFile: '通过 DSL 文件创建',
|
createFromConfigFile: '通过 DSL 文件创建',
|
||||||
|
importFromDSL: '导入 DSL',
|
||||||
|
importFromDSLFile: '文件',
|
||||||
|
importFromDSLUrl: 'URL',
|
||||||
|
importFromDSLUrlPlaceholder: '输入 DSL 文件的 URL',
|
||||||
deleteAppConfirmTitle: '确认删除应用?',
|
deleteAppConfirmTitle: '确认删除应用?',
|
||||||
deleteAppConfirmContent:
|
deleteAppConfirmContent:
|
||||||
'删除应用将无法撤销。用户将不能访问你的应用,所有 Prompt 编排配置和日志均将一并被删除。',
|
'删除应用将无法撤销。用户将不能访问你的应用,所有 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 } })
|
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 }) => {
|
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 } })
|
return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon, icon_background } })
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user