mirror of
https://github.com/langgenius/dify.git
synced 2026-04-20 18:57:19 +08:00
Merge remote-tracking branch 'origin/main' into feat/trigger
This commit is contained in:
@ -7,10 +7,9 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types'
|
||||
import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
|
||||
import { START_INITIAL_POSITION } from '@/app/components/workflow/constants'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import { useNodesInitialData } from '@/app/components/workflow/hooks'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import PluginDependency from '@/app/components/workflow/plugin-dependency'
|
||||
import PluginDependency from '../../workflow/plugin-dependency'
|
||||
import {
|
||||
useDSL,
|
||||
usePanelInteractions,
|
||||
@ -23,7 +22,11 @@ import dynamic from 'next/dynamic'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
import { useAutoOnboarding } from '../hooks/use-auto-onboarding'
|
||||
import { useAvailableNodesMetaData } from '../hooks'
|
||||
|
||||
const Features = dynamic(() => import('@/app/components/workflow/features'), {
|
||||
ssr: false,
|
||||
})
|
||||
const UpdateDSLModal = dynamic(() => import('@/app/components/workflow/update-dsl-modal'), {
|
||||
ssr: false,
|
||||
})
|
||||
@ -37,13 +40,14 @@ const WorkflowOnboardingModal = dynamic(() => import('./workflow-onboarding-moda
|
||||
const WorkflowChildren = () => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const showFeaturesPanel = useStore(s => s.showFeaturesPanel)
|
||||
const showImportDSLModal = useStore(s => s.showImportDSLModal)
|
||||
const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
|
||||
const showOnboarding = useStore(s => s.showOnboarding)
|
||||
const setShowOnboarding = useStore(s => s.setShowOnboarding)
|
||||
const setHasSelectedStartNode = useStore(s => s.setHasSelectedStartNode)
|
||||
const reactFlowStore = useStoreApi()
|
||||
const nodesInitialData = useNodesInitialData()
|
||||
const availableNodesMetaData = useAvailableNodesMetaData()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { handleOnboardingClose } = useAutoOnboarding()
|
||||
const {
|
||||
@ -65,13 +69,13 @@ const WorkflowChildren = () => {
|
||||
|
||||
const handleSelectStartNode = useCallback((nodeType: BlockEnum, toolConfig?: ToolDefaultValue) => {
|
||||
const nodeData = nodeType === BlockEnum.Start
|
||||
? nodesInitialData.start
|
||||
: { ...nodesInitialData[nodeType], ...toolConfig }
|
||||
? availableNodesMetaData.nodesMap?.[BlockEnum.Start]
|
||||
: { ...availableNodesMetaData.nodesMap?.[nodeType], ...toolConfig }
|
||||
|
||||
const { newNode } = generateNewNode({
|
||||
data: {
|
||||
...nodeData,
|
||||
},
|
||||
} as any,
|
||||
position: START_INITIAL_POSITION,
|
||||
})
|
||||
|
||||
@ -79,8 +83,8 @@ const WorkflowChildren = () => {
|
||||
setNodes([newNode])
|
||||
setEdges([])
|
||||
|
||||
setShowOnboarding(false)
|
||||
setHasSelectedStartNode(true)
|
||||
setShowOnboarding?.(false)
|
||||
setHasSelectedStartNode?.(true)
|
||||
|
||||
handleSyncWorkflowDraft(true, false, {
|
||||
onSuccess: () => {
|
||||
@ -90,11 +94,14 @@ const WorkflowChildren = () => {
|
||||
console.error('Failed to save node to draft')
|
||||
},
|
||||
})
|
||||
}, [nodesInitialData, setShowOnboarding, setHasSelectedStartNode, reactFlowStore, handleSyncWorkflowDraft])
|
||||
}, [availableNodesMetaData, setShowOnboarding, setHasSelectedStartNode, reactFlowStore, handleSyncWorkflowDraft])
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginDependency />
|
||||
{
|
||||
showFeaturesPanel && <Features />
|
||||
}
|
||||
{
|
||||
showOnboarding && (
|
||||
<WorkflowOnboardingModal
|
||||
@ -108,7 +115,7 @@ const WorkflowChildren = () => {
|
||||
showImportDSLModal && (
|
||||
<UpdateDSLModal
|
||||
onCancel={() => setShowImportDSLModal(false)}
|
||||
onBackup={exportCheck}
|
||||
onBackup={exportCheck!}
|
||||
onImport={handlePaneContextmenuCancel}
|
||||
/>
|
||||
)
|
||||
@ -117,7 +124,7 @@ const WorkflowChildren = () => {
|
||||
secretEnvList.length > 0 && (
|
||||
<DSLExportConfirmModal
|
||||
envList={secretEnvList}
|
||||
onConfirm={handleExportDSL}
|
||||
onConfirm={handleExportDSL!}
|
||||
onClose={() => setSecretEnvList([])}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -3,20 +3,27 @@ import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useStore as useReactflowStore } from 'reactflow'
|
||||
import { useEdges, useNodes, useStore as useReactflowStore } from 'reactflow'
|
||||
import { RiApps2AddLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import {
|
||||
useChecklist,
|
||||
useChecklistBeforePublish,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowRunValidation,
|
||||
// useWorkflowRunValidation,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import Button from '@/app/components/base/button'
|
||||
import AppPublisher from '@/app/components/app/app-publisher'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import type {
|
||||
CommonEdgeType,
|
||||
CommonNodeType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
BlockEnum,
|
||||
InputVarType,
|
||||
@ -27,14 +34,17 @@ import { useInvalidateAppTriggers } from '@/service/use-tools'
|
||||
import type { PublishWorkflowParams } from '@/types/workflow'
|
||||
import { fetchAppDetail } from '@/service/apps'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const AppPublisherTrigger = () => {
|
||||
const FeaturesTrigger = () => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appID = appDetail?.id
|
||||
const setAppDetail = useAppStore(s => s.setAppDetail)
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const toolPublished = useStore(s => s.toolPublished)
|
||||
@ -63,9 +73,20 @@ const AppPublisherTrigger = () => {
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { notify } = useToastContext()
|
||||
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory()
|
||||
const invalidateAppTriggers = useInvalidateAppTriggers()
|
||||
|
||||
const handleShowFeatures = useCallback(() => {
|
||||
const {
|
||||
showFeaturesPanel,
|
||||
isRestoring,
|
||||
setShowFeaturesPanel,
|
||||
} = workflowStore.getState()
|
||||
if (getNodesReadOnly() && !isRestoring)
|
||||
return
|
||||
setShowFeaturesPanel(!showFeaturesPanel)
|
||||
}, [workflowStore, getNodesReadOnly])
|
||||
|
||||
const updateAppDetail = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetchAppDetail({ url: '/apps', id: appID! })
|
||||
@ -76,18 +97,27 @@ const AppPublisherTrigger = () => {
|
||||
}
|
||||
}, [appID, setAppDetail])
|
||||
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
|
||||
const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
||||
// const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges<CommonEdgeType>()
|
||||
const needWarningNodes = useChecklist(nodes, edges)
|
||||
|
||||
const updatePublishedWorkflow = useInvalidateAppWorkflow()
|
||||
const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
|
||||
// First check if there are any items in the checklist
|
||||
if (!validateBeforeRun())
|
||||
// if (!validateBeforeRun())
|
||||
// throw new Error('Checklist has unresolved items')
|
||||
|
||||
if (needWarningNodes.length > 0) {
|
||||
notify({ type: 'error', message: t('workflow.panel.checklistTip') })
|
||||
throw new Error('Checklist has unresolved items')
|
||||
}
|
||||
|
||||
// Then perform the detailed validation
|
||||
if (await handleCheckBeforePublish()) {
|
||||
const res = await publishWorkflow({
|
||||
url: `/apps/${appID}/workflows/publish`,
|
||||
title: params?.title || '',
|
||||
releaseNotes: params?.releaseNotes || '',
|
||||
})
|
||||
@ -104,7 +134,7 @@ const AppPublisherTrigger = () => {
|
||||
else {
|
||||
throw new Error('Checklist failed')
|
||||
}
|
||||
}, [validateBeforeRun, handleCheckBeforePublish, publishWorkflow, updatePublishedWorkflow, appID, updateAppDetail, invalidateAppTriggers, workflowStore, resetWorkflowVersionHistory])
|
||||
}, [needWarningNodes, handleCheckBeforePublish, publishWorkflow, notify, appID, t, updatePublishedWorkflow, updateAppDetail, workflowStore, resetWorkflowVersionHistory, invalidateAppTriggers])
|
||||
|
||||
const onPublisherToggle = useCallback((state: boolean) => {
|
||||
if (state)
|
||||
@ -116,20 +146,32 @@ const AppPublisherTrigger = () => {
|
||||
}, [workflowStore])
|
||||
|
||||
return (
|
||||
<AppPublisher
|
||||
{...{
|
||||
publishedAt,
|
||||
draftUpdatedAt,
|
||||
disabled: nodesReadOnly,
|
||||
toolPublished,
|
||||
inputs: variables,
|
||||
onRefreshData: handleToolConfigureUpdate,
|
||||
onPublish,
|
||||
onToggle: onPublisherToggle,
|
||||
crossAxisOffset: 4,
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
className={cn(
|
||||
'text-components-button-secondary-text',
|
||||
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
|
||||
)}
|
||||
onClick={handleShowFeatures}
|
||||
>
|
||||
<RiApps2AddLine className='mr-1 h-4 w-4 text-components-button-secondary-text' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<AppPublisher
|
||||
{...{
|
||||
publishedAt,
|
||||
draftUpdatedAt,
|
||||
disabled: nodesReadOnly,
|
||||
toolPublished,
|
||||
inputs: variables,
|
||||
onRefreshData: handleToolConfigureUpdate,
|
||||
onPublish,
|
||||
onToggle: onPublisherToggle,
|
||||
crossAxisOffset: 4,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AppPublisherTrigger)
|
||||
export default memo(FeaturesTrigger)
|
||||
@ -1,31 +1,66 @@
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import type { HeaderProps } from '@/app/components/workflow/header'
|
||||
import Header from '@/app/components/workflow/header'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import {
|
||||
fetchWorkflowRunHistory,
|
||||
} from '@/service/workflow'
|
||||
import ChatVariableTrigger from './chat-variable-trigger'
|
||||
import AppPublisherTrigger from './app-publisher-trigger'
|
||||
import FeaturesTrigger from './features-trigger'
|
||||
import { useResetWorkflowVersionHistory } from '@/service/use-workflow'
|
||||
import { useIsChatMode } from '../../hooks'
|
||||
|
||||
const WorkflowHeader = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
|
||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const handleClearLogAndMessageModal = useCallback(() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}, [setCurrentLogItem, setShowMessageLogModal])
|
||||
|
||||
const viewHistoryProps = useMemo(() => {
|
||||
return {
|
||||
onClearLogAndMessageModal: handleClearLogAndMessageModal,
|
||||
historyUrl: isChatMode ? `/apps/${appDetail!.id}/advanced-chat/workflow-runs` : `/apps/${appDetail!.id}/workflow-runs`,
|
||||
historyFetcher: fetchWorkflowRunHistory,
|
||||
}
|
||||
}, [appDetail, isChatMode, handleClearLogAndMessageModal])
|
||||
|
||||
const headerProps: HeaderProps = useMemo(() => {
|
||||
return {
|
||||
normal: {
|
||||
components: {
|
||||
left: <ChatVariableTrigger />,
|
||||
middle: <AppPublisherTrigger />,
|
||||
middle: <FeaturesTrigger />,
|
||||
},
|
||||
runAndHistoryProps: {
|
||||
showRunButton: !isChatMode,
|
||||
showPreviewButton: isChatMode,
|
||||
viewHistoryProps,
|
||||
},
|
||||
},
|
||||
viewHistory: {
|
||||
viewHistoryProps,
|
||||
},
|
||||
restoring: {
|
||||
onRestoreSettled: resetWorkflowVersionHistory,
|
||||
},
|
||||
}
|
||||
}, [resetWorkflowVersionHistory])
|
||||
}, [resetWorkflowVersionHistory, isChatMode, viewHistoryProps])
|
||||
return (
|
||||
<Header {...headerProps} />
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowHeader
|
||||
export default memo(WorkflowHeader)
|
||||
|
||||
@ -7,7 +7,10 @@ import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||
import type { WorkflowProps } from '@/app/components/workflow'
|
||||
import WorkflowChildren from './workflow-children'
|
||||
import {
|
||||
useAvailableNodesMetaData,
|
||||
useConfigsMap,
|
||||
useDSL,
|
||||
useGetRunAndTraceUrl,
|
||||
useInspectVarsCrud,
|
||||
useNodesSyncDraft,
|
||||
useSetWorkflowVarsWithValue,
|
||||
@ -15,7 +18,7 @@ import {
|
||||
useWorkflowRun,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||
const WorkflowMain = ({
|
||||
@ -64,10 +67,16 @@ const WorkflowMain = ({
|
||||
handleWorkflowStartRunInChatflow,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
} = useWorkflowStartRun()
|
||||
const appId = useStore(s => s.appId)
|
||||
const availableNodesMetaData = useAvailableNodesMetaData()
|
||||
const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl()
|
||||
const {
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
} = useDSL()
|
||||
|
||||
const configsMap = useConfigsMap()
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||
flowId: appId,
|
||||
...useConfigsMap(),
|
||||
...configsMap,
|
||||
})
|
||||
const {
|
||||
hasNodeInspectVars,
|
||||
@ -85,7 +94,6 @@ const WorkflowMain = ({
|
||||
resetConversationVar,
|
||||
invalidateConversationVarValues,
|
||||
} = useInspectVarsCrud()
|
||||
const configsMap = useConfigsMap()
|
||||
|
||||
const hooksStore = useMemo(() => {
|
||||
return {
|
||||
@ -100,6 +108,10 @@ const WorkflowMain = ({
|
||||
handleStartWorkflowRun,
|
||||
handleWorkflowStartRunInChatflow,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
availableNodesMetaData,
|
||||
getWorkflowRunAndTraceUrl,
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
fetchInspectVars,
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
@ -129,6 +141,10 @@ const WorkflowMain = ({
|
||||
handleStartWorkflowRun,
|
||||
handleWorkflowStartRunInChatflow,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
availableNodesMetaData,
|
||||
getWorkflowRunAndTraceUrl,
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
fetchInspectVars,
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
@ -153,7 +169,7 @@ const WorkflowMain = ({
|
||||
edges={edges}
|
||||
viewport={viewport}
|
||||
onWorkflowDataUpdate={handleWorkflowDataUpdate}
|
||||
hooksStore={hooksStore}
|
||||
hooksStore={hooksStore as any}
|
||||
>
|
||||
<WorkflowChildren />
|
||||
</WorkflowWithInnerContext>
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
@ -30,9 +33,6 @@ const ChatVariablePanel = dynamic(() => import('@/app/components/workflow/panel/
|
||||
const GlobalVariablePanel = dynamic(() => import('@/app/components/workflow/panel/global-variable-panel'), {
|
||||
ssr: false,
|
||||
})
|
||||
const VersionHistoryPanel = dynamic(() => import('@/app/components/workflow/panel/version-history-panel'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
const WorkflowPanelOnLeft = () => {
|
||||
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
|
||||
@ -67,7 +67,6 @@ const WorkflowPanelOnRight = () => {
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
|
||||
const showGlobalVariablePanel = useStore(s => s.showGlobalVariablePanel)
|
||||
const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -101,27 +100,34 @@ const WorkflowPanelOnRight = () => {
|
||||
<GlobalVariablePanel />
|
||||
)
|
||||
}
|
||||
{
|
||||
showWorkflowVersionHistoryPanel && (
|
||||
<VersionHistoryPanel/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
const WorkflowPanel = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const versionHistoryPanelProps = useMemo(() => {
|
||||
const appId = appDetail?.id
|
||||
return {
|
||||
getVersionListUrl: `/apps/${appId}/workflows`,
|
||||
deleteVersionUrl: (versionId: string) => `/apps/${appId}/workflows/${versionId}`,
|
||||
updateVersionUrl: (versionId: string) => `/apps/${appId}/workflows/${versionId}`,
|
||||
latestVersionId: appDetail?.workflow?.id,
|
||||
}
|
||||
}, [appDetail?.id, appDetail?.workflow?.id])
|
||||
|
||||
const panelProps: PanelProps = useMemo(() => {
|
||||
return {
|
||||
components: {
|
||||
left: <WorkflowPanelOnLeft />,
|
||||
right: <WorkflowPanelOnRight />,
|
||||
},
|
||||
versionHistoryPanelProps,
|
||||
}
|
||||
}, [])
|
||||
}, [versionHistoryPanelProps])
|
||||
|
||||
return (
|
||||
<Panel {...panelProps} />
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowPanel
|
||||
export default memo(WorkflowPanel)
|
||||
|
||||
@ -4,7 +4,10 @@ export * from './use-nodes-sync-draft'
|
||||
export * from './use-workflow-run'
|
||||
export * from './use-workflow-start-run'
|
||||
export * from './use-is-chat-mode'
|
||||
export * from './use-available-nodes-meta-data'
|
||||
export * from './use-workflow-refresh-draft'
|
||||
export * from './use-get-run-and-trace-url'
|
||||
export * from './use-DSL'
|
||||
export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
export * from './use-inspect-vars-crud'
|
||||
export * from './use-configs-map'
|
||||
|
||||
82
web/app/components/workflow-app/hooks/use-DSL.ts
Normal file
82
web/app/components/workflow-app/hooks/use-DSL.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
DSL_EXPORT_CHECK,
|
||||
} from '@/app/components/workflow/constants'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import { exportAppConfig } from '@/service/apps'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
|
||||
export const useDSL = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [exporting, setExporting] = useState(false)
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
|
||||
const handleExportDSL = useCallback(async (include = false, workflowId?: string) => {
|
||||
if (!appDetail)
|
||||
return
|
||||
|
||||
if (exporting)
|
||||
return
|
||||
|
||||
try {
|
||||
setExporting(true)
|
||||
await doSyncWorkflowDraft()
|
||||
const { data } = await exportAppConfig({
|
||||
appID: appDetail.id,
|
||||
include,
|
||||
workflowID: workflowId,
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
a.download = `${appDetail.name}.yml`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('app.exportFailed') })
|
||||
}
|
||||
finally {
|
||||
setExporting(false)
|
||||
}
|
||||
}, [appDetail, notify, t, doSyncWorkflowDraft, exporting])
|
||||
|
||||
const exportCheck = useCallback(async () => {
|
||||
if (!appDetail)
|
||||
return
|
||||
try {
|
||||
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`)
|
||||
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
|
||||
if (list.length === 0) {
|
||||
handleExportDSL()
|
||||
return
|
||||
}
|
||||
eventEmitter?.emit({
|
||||
type: DSL_EXPORT_CHECK,
|
||||
payload: {
|
||||
data: list,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
catch {
|
||||
notify({ type: 'error', message: t('app.exportFailed') })
|
||||
}
|
||||
}, [appDetail, eventEmitter, handleExportDSL, notify, t])
|
||||
|
||||
return {
|
||||
exportCheck,
|
||||
handleExportDSL,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import StartDefault from '@/app/components/workflow/nodes/start/default'
|
||||
import TriggerWebhookDefault from '@/app/components/workflow/nodes/trigger-webhook/default'
|
||||
import TriggerScheduleDefault from '@/app/components/workflow/nodes/trigger-schedule/default'
|
||||
import TriggerPluginDefault from '@/app/components/workflow/nodes/trigger-plugin/default'
|
||||
import EndDefault from '@/app/components/workflow/nodes/end/default'
|
||||
import AnswerDefault from '@/app/components/workflow/nodes/answer/default'
|
||||
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
|
||||
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store'
|
||||
import { useIsChatMode } from './use-is-chat-mode'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
export const useAvailableNodesMetaData = () => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const language = useGetLanguage()
|
||||
|
||||
const mergedNodesMetaData = useMemo(() => [
|
||||
...WORKFLOW_COMMON_NODES,
|
||||
StartDefault,
|
||||
...(
|
||||
isChatMode
|
||||
? [AnswerDefault]
|
||||
: [
|
||||
EndDefault,
|
||||
TriggerWebhookDefault,
|
||||
TriggerScheduleDefault,
|
||||
TriggerPluginDefault,
|
||||
]
|
||||
),
|
||||
], [isChatMode])
|
||||
|
||||
const prefixLink = useMemo(() => {
|
||||
if (language === 'zh_Hans')
|
||||
return 'https://docs.dify.ai/zh-hans/guides/workflow/node/'
|
||||
|
||||
return 'https://docs.dify.ai/guides/workflow/node/'
|
||||
}, [language])
|
||||
|
||||
const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => {
|
||||
const { metaData } = node
|
||||
const title = t(`workflow.blocks.${metaData.type}`)
|
||||
const description = t(`workflow.blocksAbout.${metaData.type}`)
|
||||
return {
|
||||
...node,
|
||||
metaData: {
|
||||
...metaData,
|
||||
title,
|
||||
description,
|
||||
helpLinkUri: `${prefixLink}${metaData.helpLinkUri}`,
|
||||
},
|
||||
defaultValue: {
|
||||
...node.defaultValue,
|
||||
type: metaData.type,
|
||||
title,
|
||||
},
|
||||
}
|
||||
}), [mergedNodesMetaData, t, prefixLink])
|
||||
|
||||
const availableNodesMetaDataMap = useMemo(() => availableNodesMetaData.reduce((acc, node) => {
|
||||
acc![node.metaData.type] = node
|
||||
return acc
|
||||
}, {} as AvailableNodesMetaData['nodesMap']), [availableNodesMetaData])
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
nodes: availableNodesMetaData,
|
||||
nodesMap: {
|
||||
...availableNodesMetaDataMap,
|
||||
[BlockEnum.VariableAssigner]: availableNodesMetaDataMap?.[BlockEnum.VariableAggregator],
|
||||
},
|
||||
}
|
||||
}, [availableNodesMetaData, availableNodesMetaDataMap])
|
||||
}
|
||||
@ -1,13 +1,16 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { FlowType } from '@/types/common'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
|
||||
export const useConfigsMap = () => {
|
||||
const appId = useStore(s => s.appId)
|
||||
const fileSettings = useFeatures(s => s.features.file)
|
||||
return useMemo(() => {
|
||||
return {
|
||||
flowId: appId,
|
||||
conversationVarsUrl: `apps/${appId}/workflows/draft/conversation-variables`,
|
||||
systemVarsUrl: `apps/${appId}/workflows/draft/system-variables`,
|
||||
flowId: appId!,
|
||||
flowType: FlowType.appFlow,
|
||||
fileSettings,
|
||||
}
|
||||
}, [appId])
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
export const useGetRunAndTraceUrl = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const getWorkflowRunAndTraceUrl = useCallback((runId: string) => {
|
||||
const { appId } = workflowStore.getState()
|
||||
|
||||
return {
|
||||
runUrl: `/apps/${appId}/workflow-runs/${runId}`,
|
||||
traceUrl: `/apps/${appId}/workflow-runs/${runId}/node-executions`,
|
||||
}
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
getWorkflowRunAndTraceUrl,
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useInspectVarsCrudCommon } from '../../workflow/hooks/use-inspect-vars-crud-common'
|
||||
import { useConfigsMap } from './use-configs-map'
|
||||
|
||||
export const useInspectVarsCrud = () => {
|
||||
const appId = useStore(s => s.appId)
|
||||
const configsMap = useConfigsMap()
|
||||
const apis = useInspectVarsCrudCommon({
|
||||
flowId: appId,
|
||||
...configsMap,
|
||||
})
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { useParams } from 'next/navigation'
|
||||
import {
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
} from '@/app/components/workflow/hooks/use-workflow'
|
||||
@ -27,20 +28,20 @@ export const useNodesSyncDraft = () => {
|
||||
edges,
|
||||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const [x, y, zoom] = transform
|
||||
const {
|
||||
appId,
|
||||
conversationVariables,
|
||||
environmentVariables,
|
||||
syncWorkflowDraftHash,
|
||||
isWorkflowDataLoaded,
|
||||
// isWorkflowDataLoaded,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (appId) {
|
||||
const nodes = getNodes()
|
||||
if (appId && !!nodes.length) {
|
||||
const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
|
||||
// Prevent sync if workflow data hasn't been loaded yet
|
||||
if (!isWorkflowDataLoaded)
|
||||
if (!hasStartNode)
|
||||
return null
|
||||
|
||||
const features = featuresStore!.getState().features
|
||||
@ -52,7 +53,7 @@ export const useNodesSyncDraft = () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
const producedEdges = produce(edges, (draft) => {
|
||||
const producedEdges = produce(edges.filter(edge => !edge.data?._isTemp), (draft) => {
|
||||
draft.forEach((edge) => {
|
||||
Object.keys(edge.data).forEach((key) => {
|
||||
if (key.startsWith('_'))
|
||||
|
||||
@ -17,6 +17,8 @@ import {
|
||||
} from '@/service/workflow'
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||
import type { FileUploadConfigResponse } from '@/models/common'
|
||||
|
||||
export const useWorkflowInit = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const {
|
||||
@ -28,7 +30,7 @@ export const useWorkflowInit = () => {
|
||||
const [data, setData] = useState<FetchWorkflowDraftResponse>()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
useEffect(() => {
|
||||
workflowStore.setState({ appId: appDetail.id })
|
||||
workflowStore.setState({ appId: appDetail.id, appName: appDetail.name })
|
||||
}, [appDetail.id, workflowStore])
|
||||
|
||||
const handleUpdateWorkflowConfig = useCallback((config: Record<string, any>) => {
|
||||
@ -36,7 +38,16 @@ export const useWorkflowInit = () => {
|
||||
|
||||
setWorkflowConfig(config)
|
||||
}, [workflowStore])
|
||||
useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
|
||||
useWorkflowConfig(`/apps/${appDetail.id}/workflows/draft/config`, handleUpdateWorkflowConfig)
|
||||
|
||||
const handleUpdateWorkflowFileUploadConfig = useCallback((config: FileUploadConfigResponse) => {
|
||||
const { setFileUploadConfig } = workflowStore.getState()
|
||||
setFileUploadConfig(config)
|
||||
}, [workflowStore])
|
||||
const {
|
||||
data: fileUploadConfigResponse,
|
||||
isLoading: isFileUploadConfigLoading,
|
||||
} = useWorkflowConfig('/files/upload', handleUpdateWorkflowFileUploadConfig)
|
||||
|
||||
const handleGetInitialWorkflowData = useCallback(async () => {
|
||||
try {
|
||||
@ -125,6 +136,7 @@ export const useWorkflowInit = () => {
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading,
|
||||
isLoading: isLoading || isFileUploadConfigLoading,
|
||||
fileUploadConfigResponse,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,9 +31,10 @@ export const useWorkflowRun = () => {
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
|
||||
const pathname = usePathname()
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
|
||||
const configsMap = useConfigsMap()
|
||||
const { flowId, flowType } = configsMap
|
||||
const invalidAllLastRun = useInvalidAllLastRun(flowType, flowId)
|
||||
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||
...configsMap,
|
||||
})
|
||||
@ -201,7 +202,7 @@ export const useWorkflowRun = () => {
|
||||
if (onWorkflowFinished)
|
||||
onWorkflowFinished(params)
|
||||
if (isInWorkflowDebug) {
|
||||
fetchInspectVars()
|
||||
fetchInspectVars({})
|
||||
invalidAllLastRun()
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import {
|
||||
NODE_WIDTH_X_OFFSET,
|
||||
START_INITIAL_POSITION,
|
||||
} from '@/app/components/workflow/constants'
|
||||
import { useNodesInitialData } from '@/app/components/workflow/hooks'
|
||||
import { useIsChatMode } from './use-is-chat-mode'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import startDefault from '@/app/components/workflow/nodes/start/default'
|
||||
import llmDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
import answerDefault from '@/app/components/workflow/nodes/answer/default'
|
||||
|
||||
export const useWorkflowTemplate = () => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const nodesInitialData = useNodesInitialData()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { newNode: startNode } = generateNewNode({
|
||||
data: nodesInitialData.start,
|
||||
data: {
|
||||
...startDefault.defaultValue as StartNodeType,
|
||||
type: startDefault.metaData.type,
|
||||
title: t(`workflow.blocks.${startDefault.metaData.type}`),
|
||||
},
|
||||
position: START_INITIAL_POSITION,
|
||||
})
|
||||
|
||||
@ -19,12 +27,14 @@ export const useWorkflowTemplate = () => {
|
||||
const { newNode: llmNode } = generateNewNode({
|
||||
id: 'llm',
|
||||
data: {
|
||||
...nodesInitialData.llm,
|
||||
...llmDefault.defaultValue,
|
||||
memory: {
|
||||
window: { enabled: false, size: 10 },
|
||||
query_prompt_template: '{{#sys.query#}}\n\n{{#sys.files#}}',
|
||||
},
|
||||
selected: true,
|
||||
type: llmDefault.metaData.type,
|
||||
title: t(`workflow.blocks.${llmDefault.metaData.type}`),
|
||||
},
|
||||
position: {
|
||||
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET,
|
||||
@ -35,8 +45,10 @@ export const useWorkflowTemplate = () => {
|
||||
const { newNode: answerNode } = generateNewNode({
|
||||
id: 'answer',
|
||||
data: {
|
||||
...nodesInitialData.answer,
|
||||
...answerDefault.defaultValue,
|
||||
answer: `{{#${llmNode.id}.text#}}`,
|
||||
type: answerDefault.metaData.type,
|
||||
title: t(`workflow.blocks.${answerDefault.metaData.type}`),
|
||||
},
|
||||
position: {
|
||||
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET * 2,
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import useSWR from 'swr'
|
||||
import {
|
||||
SupportUploadFileTypes,
|
||||
} from '@/app/components/workflow/types'
|
||||
@ -23,12 +22,12 @@ import Loading from '@/app/components/base/loading'
|
||||
import { FeaturesProvider } from '@/app/components/base/features'
|
||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import { fetchFileUploadConfig } from '@/service/common'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import {
|
||||
WorkflowContextProvider,
|
||||
} from '@/app/components/workflow/context'
|
||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||
import { createWorkflowSlice } from './store/workflow/workflow-slice'
|
||||
import WorkflowAppMain from './components/workflow-main'
|
||||
|
||||
@ -36,10 +35,10 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
fileUploadConfigResponse,
|
||||
} = useWorkflowInit()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { isLoadingCurrentWorkspace, currentWorkspace } = useAppContext()
|
||||
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
|
||||
|
||||
// Initialize trigger status at application level
|
||||
const { setTriggerStatuses } = useTriggerStatusStore()
|
||||
@ -145,7 +144,7 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
const WorkflowAppWrapper = () => {
|
||||
return (
|
||||
<WorkflowContextProvider
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice}
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice as InjectWorkflowStoreSliceFn}
|
||||
>
|
||||
<WorkflowAppWithAdditionalContext />
|
||||
</WorkflowContextProvider>
|
||||
|
||||
@ -2,6 +2,7 @@ import type { StateCreator } from 'zustand'
|
||||
|
||||
export type WorkflowSliceShape = {
|
||||
appId: string
|
||||
appName: string
|
||||
notInitialWorkflow: boolean
|
||||
setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
|
||||
nodesDefaultConfigs: Record<string, any>
|
||||
@ -17,6 +18,7 @@ export type WorkflowSliceShape = {
|
||||
export type CreateWorkflowSlice = StateCreator<WorkflowSliceShape>
|
||||
export const createWorkflowSlice: StateCreator<WorkflowSliceShape> = set => ({
|
||||
appId: '',
|
||||
appName: '',
|
||||
notInitialWorkflow: false,
|
||||
setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })),
|
||||
nodesDefaultConfigs: {},
|
||||
|
||||
Reference in New Issue
Block a user