diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
index f4d49425ae..d5df70f004 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
@@ -1,11 +1,11 @@
'use client'
-import Workflow from '@/app/components/workflow'
+import WorkflowApp from '@/app/components/workflow-app'
const Page = () => {
return (
-
+
)
}
diff --git a/web/app/components/workflow-app/components/workflow-children.tsx b/web/app/components/workflow-app/components/workflow-children.tsx
new file mode 100644
index 0000000000..6a6bbcd61a
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-children.tsx
@@ -0,0 +1,69 @@
+import {
+ memo,
+ useState,
+} from 'react'
+import type { EnvironmentVariable } from '@/app/components/workflow/types'
+import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
+import { useStore } from '@/app/components/workflow/store'
+import Features from '@/app/components/workflow/features'
+import PluginDependency from '@/app/components/workflow/plugin-dependency'
+import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
+import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
+import {
+ useDSL,
+ usePanelInteractions,
+} from '@/app/components/workflow/hooks'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+import WorkflowHeader from './workflow-header'
+import WorkflowPanel from './workflow-panel'
+
+const WorkflowChildren = () => {
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [secretEnvList, setSecretEnvList] = useState([])
+ const showFeaturesPanel = useStore(s => s.showFeaturesPanel)
+ const showImportDSLModal = useStore(s => s.showImportDSLModal)
+ const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
+ const {
+ handlePaneContextmenuCancel,
+ } = usePanelInteractions()
+ const {
+ exportCheck,
+ handleExportDSL,
+ } = useDSL()
+
+ eventEmitter?.useSubscription((v: any) => {
+ if (v.type === DSL_EXPORT_CHECK)
+ setSecretEnvList(v.payload.data as EnvironmentVariable[])
+ })
+
+ return (
+ <>
+
+ {
+ showFeaturesPanel &&
+ }
+ {
+ showImportDSLModal && (
+ setShowImportDSLModal(false)}
+ onBackup={exportCheck}
+ onImport={handlePaneContextmenuCancel}
+ />
+ )
+ }
+ {
+ secretEnvList.length > 0 && (
+ setSecretEnvList([])}
+ />
+ )
+ }
+
+
+ >
+ )
+}
+
+export default memo(WorkflowChildren)
diff --git a/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx
new file mode 100644
index 0000000000..df93914285
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.tsx
@@ -0,0 +1,11 @@
+import { memo } from 'react'
+import ChatVariableButton from '@/app/components/workflow/header/chat-variable-button'
+import {
+ useNodesReadOnly,
+} from '@/app/components/workflow/hooks'
+
+const ChatVariableTrigger = () => {
+ const { nodesReadOnly } = useNodesReadOnly()
+ return
+}
+export default memo(ChatVariableTrigger)
diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx
new file mode 100644
index 0000000000..da64409090
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx
@@ -0,0 +1,152 @@
+import {
+ memo,
+ useCallback,
+ useMemo,
+} from 'react'
+import { useNodes } from 'reactflow'
+import { RiApps2AddLine } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import {
+ useStore,
+ useWorkflowStore,
+} from '@/app/components/workflow/store'
+import {
+ useChecklistBeforePublish,
+ useNodesReadOnly,
+ useNodesSyncDraft,
+} 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 {
+ BlockEnum,
+ InputVarType,
+} from '@/app/components/workflow/types'
+import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
+import { useToastContext } from '@/app/components/base/toast'
+import { usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow'
+import type { PublishWorkflowParams } from '@/types/workflow'
+import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
+import { useStore as useAppStore } from '@/app/components/app/store'
+import { useSelector as useAppSelector } from '@/context/app-context'
+
+const FeaturesTrigger = () => {
+ const { t } = useTranslation()
+ const workflowStore = useWorkflowStore()
+ const appDetail = useAppStore(s => s.appDetail)
+ const appID = appDetail?.id
+ const setAppDetail = useAppStore(s => s.setAppDetail)
+ const systemFeatures = useAppSelector(state => state.systemFeatures)
+ const {
+ nodesReadOnly,
+ getNodesReadOnly,
+ } = useNodesReadOnly()
+ const publishedAt = useStore(s => s.publishedAt)
+ const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
+ const toolPublished = useStore(s => s.toolPublished)
+ const nodes = useNodes()
+ const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
+ const startVariables = startNode?.data.variables
+ const fileSettings = useFeatures(s => s.features.file)
+ const variables = useMemo(() => {
+ const data = startVariables || []
+ if (fileSettings?.image?.enabled) {
+ return [
+ ...data,
+ {
+ type: InputVarType.files,
+ variable: '__image',
+ required: false,
+ label: 'files',
+ },
+ ]
+ }
+
+ return data
+ }, [fileSettings?.image?.enabled, startVariables])
+
+ const { handleCheckBeforePublish } = useChecklistBeforePublish()
+ const { handleSyncWorkflowDraft } = useNodesSyncDraft()
+ const { notify } = useToastContext()
+
+ const handleShowFeatures = useCallback(() => {
+ const {
+ showFeaturesPanel,
+ isRestoring,
+ setShowFeaturesPanel,
+ } = workflowStore.getState()
+ if (getNodesReadOnly() && !isRestoring)
+ return
+ setShowFeaturesPanel(!showFeaturesPanel)
+ }, [workflowStore, getNodesReadOnly])
+
+ const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
+
+ const updateAppDetail = useCallback(async () => {
+ try {
+ const res = await fetchAppDetail({ url: '/apps', id: appID! })
+ if (systemFeatures.enable_web_sso_switch_component) {
+ const ssoRes = await fetchAppSSO({ appId: appID! })
+ setAppDetail({ ...res, enable_sso: ssoRes.enabled })
+ }
+ else {
+ setAppDetail({ ...res })
+ }
+ }
+ catch (error) {
+ console.error(error)
+ }
+ }, [appID, setAppDetail, systemFeatures.enable_web_sso_switch_component])
+ const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
+ const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
+ if (await handleCheckBeforePublish()) {
+ const res = await publishWorkflow({
+ title: params?.title || '',
+ releaseNotes: params?.releaseNotes || '',
+ })
+
+ if (res) {
+ notify({ type: 'success', message: t('common.api.actionSuccess') })
+ updateAppDetail()
+ workflowStore.getState().setPublishedAt(res.created_at)
+ resetWorkflowVersionHistory()
+ }
+ }
+ else {
+ throw new Error('Checklist failed')
+ }
+ }, [handleCheckBeforePublish, notify, t, workflowStore, publishWorkflow, resetWorkflowVersionHistory, updateAppDetail])
+
+ const onPublisherToggle = useCallback((state: boolean) => {
+ if (state)
+ handleSyncWorkflowDraft(true)
+ }, [handleSyncWorkflowDraft])
+
+ const handleToolConfigureUpdate = useCallback(() => {
+ workflowStore.setState({ toolPublished: true })
+ }, [workflowStore])
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default memo(FeaturesTrigger)
diff --git a/web/app/components/workflow-app/components/workflow-header/index.tsx b/web/app/components/workflow-app/components/workflow-header/index.tsx
new file mode 100644
index 0000000000..4eb8df7162
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-header/index.tsx
@@ -0,0 +1,31 @@
+import { useMemo } from 'react'
+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 ChatVariableTrigger from './chat-variable-trigger'
+import FeaturesTrigger from './features-trigger'
+import { useResetWorkflowVersionHistory } from '@/service/use-workflow'
+
+const WorkflowHeader = () => {
+ const appDetail = useAppStore(s => s.appDetail)
+ const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
+
+ const headerProps: HeaderProps = useMemo(() => {
+ return {
+ normal: {
+ components: {
+ left: ,
+ middle: ,
+ },
+ },
+ restoring: {
+ onRestoreSettled: resetWorkflowVersionHistory,
+ },
+ }
+ }, [resetWorkflowVersionHistory])
+ return (
+
+ )
+}
+
+export default WorkflowHeader
diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx
new file mode 100644
index 0000000000..4ff1f4c624
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-main.tsx
@@ -0,0 +1,87 @@
+import {
+ useCallback,
+ useMemo,
+} from 'react'
+import { useFeaturesStore } from '@/app/components/base/features/hooks'
+import { WorkflowWithInnerContext } from '@/app/components/workflow'
+import type { WorkflowProps } from '@/app/components/workflow'
+import WorkflowChildren from './workflow-children'
+import {
+ useNodesSyncDraft,
+ useWorkflowRun,
+ useWorkflowStartRun,
+} from '../hooks'
+
+type WorkflowMainProps = Pick
+const WorkflowMain = ({
+ nodes,
+ edges,
+ viewport,
+}: WorkflowMainProps) => {
+ const featuresStore = useFeaturesStore()
+
+ const handleWorkflowDataUpdate = useCallback((payload: any) => {
+ if (payload.features && featuresStore) {
+ const { setFeatures } = featuresStore.getState()
+
+ setFeatures(payload.features)
+ }
+ }, [featuresStore])
+
+ const {
+ doSyncWorkflowDraft,
+ syncWorkflowDraftWhenPageClose,
+ } = useNodesSyncDraft()
+ const {
+ handleBackupDraft,
+ handleLoadBackupDraft,
+ handleRestoreFromPublishedWorkflow,
+ handleRun,
+ handleStopRun,
+ } = useWorkflowRun()
+ const {
+ handleStartWorkflowRun,
+ handleWorkflowStartRunInChatflow,
+ handleWorkflowStartRunInWorkflow,
+ } = useWorkflowStartRun()
+
+ const hooksStore = useMemo(() => {
+ return {
+ syncWorkflowDraftWhenPageClose,
+ doSyncWorkflowDraft,
+ handleBackupDraft,
+ handleLoadBackupDraft,
+ handleRestoreFromPublishedWorkflow,
+ handleRun,
+ handleStopRun,
+ handleStartWorkflowRun,
+ handleWorkflowStartRunInChatflow,
+ handleWorkflowStartRunInWorkflow,
+ }
+ }, [
+ syncWorkflowDraftWhenPageClose,
+ doSyncWorkflowDraft,
+ handleBackupDraft,
+ handleLoadBackupDraft,
+ handleRestoreFromPublishedWorkflow,
+ handleRun,
+ handleStopRun,
+ handleStartWorkflowRun,
+ handleWorkflowStartRunInChatflow,
+ handleWorkflowStartRunInWorkflow,
+ ])
+
+ return (
+
+
+
+ )
+}
+
+export default WorkflowMain
diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx
new file mode 100644
index 0000000000..3c1b5c8aac
--- /dev/null
+++ b/web/app/components/workflow-app/components/workflow-panel.tsx
@@ -0,0 +1,109 @@
+import { useMemo } from 'react'
+import { useShallow } from 'zustand/react/shallow'
+import { useStore } from '@/app/components/workflow/store'
+import {
+ useIsChatMode,
+} from '../hooks'
+import DebugAndPreview from '@/app/components/workflow/panel/debug-and-preview'
+import Record from '@/app/components/workflow/panel/record'
+import WorkflowPreview from '@/app/components/workflow/panel/workflow-preview'
+import ChatRecord from '@/app/components/workflow/panel/chat-record'
+import ChatVariablePanel from '@/app/components/workflow/panel/chat-variable-panel'
+import GlobalVariablePanel from '@/app/components/workflow/panel/global-variable-panel'
+import VersionHistoryPanel from '@/app/components/workflow/panel/version-history-panel'
+import { useStore as useAppStore } from '@/app/components/app/store'
+import MessageLogModal from '@/app/components/base/message-log-modal'
+import type { PanelProps } from '@/app/components/workflow/panel'
+import Panel from '@/app/components/workflow/panel'
+
+const WorkflowPanelOnLeft = () => {
+ const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
+ currentLogItem: state.currentLogItem,
+ setCurrentLogItem: state.setCurrentLogItem,
+ showMessageLogModal: state.showMessageLogModal,
+ setShowMessageLogModal: state.setShowMessageLogModal,
+ currentLogModalActiveTab: state.currentLogModalActiveTab,
+ })))
+ return (
+ <>
+ {
+ showMessageLogModal && (
+ {
+ setCurrentLogItem()
+ setShowMessageLogModal(false)
+ }}
+ defaultTab={currentLogModalActiveTab}
+ />
+ )
+ }
+ >
+ )
+}
+const WorkflowPanelOnRight = () => {
+ const isChatMode = useIsChatMode()
+ const historyWorkflowData = useStore(s => s.historyWorkflowData)
+ 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 (
+ <>
+ {
+ historyWorkflowData && !isChatMode && (
+
+ )
+ }
+ {
+ historyWorkflowData && isChatMode && (
+
+ )
+ }
+ {
+ showDebugAndPreviewPanel && isChatMode && (
+
+ )
+ }
+ {
+ showDebugAndPreviewPanel && !isChatMode && (
+
+ )
+ }
+ {
+ showChatVariablePanel && (
+
+ )
+ }
+ {
+ showGlobalVariablePanel && (
+
+ )
+ }
+ {
+ showWorkflowVersionHistoryPanel && (
+
+ )
+ }
+ >
+ )
+}
+const WorkflowPanel = () => {
+ const panelProps: PanelProps = useMemo(() => {
+ return {
+ components: {
+ left: ,
+ right: ,
+ },
+ }
+ }, [])
+
+ return (
+
+ )
+}
+
+export default WorkflowPanel
diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts
new file mode 100644
index 0000000000..1517eb9a16
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/index.ts
@@ -0,0 +1,6 @@
+export * from './use-workflow-init'
+export * from './use-workflow-template'
+export * from './use-nodes-sync-draft'
+export * from './use-workflow-run'
+export * from './use-workflow-start-run'
+export * from './use-is-chat-mode'
diff --git a/web/app/components/workflow-app/hooks/use-is-chat-mode.ts b/web/app/components/workflow-app/hooks/use-is-chat-mode.ts
new file mode 100644
index 0000000000..3cdfc77b2a
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-is-chat-mode.ts
@@ -0,0 +1,7 @@
+import { useStore as useAppStore } from '@/app/components/app/store'
+
+export const useIsChatMode = () => {
+ const appDetail = useAppStore(s => s.appDetail)
+
+ return appDetail?.mode === 'advanced-chat'
+}
diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts
new file mode 100644
index 0000000000..7c6eb6a5be
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts
@@ -0,0 +1,148 @@
+import { useCallback } from 'react'
+import produce from 'immer'
+import { useStoreApi } from 'reactflow'
+import { useParams } from 'next/navigation'
+import {
+ useWorkflowStore,
+} from '@/app/components/workflow/store'
+import { BlockEnum } from '@/app/components/workflow/types'
+import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
+import {
+ useNodesReadOnly,
+} from '@/app/components/workflow/hooks/use-workflow'
+import { syncWorkflowDraft } from '@/service/workflow'
+import { useFeaturesStore } from '@/app/components/base/features/hooks'
+import { API_PREFIX } from '@/config'
+
+export const useNodesSyncDraft = () => {
+ const store = useStoreApi()
+ const workflowStore = useWorkflowStore()
+ const featuresStore = useFeaturesStore()
+ const { getNodesReadOnly } = useNodesReadOnly()
+ const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
+ const params = useParams()
+
+ const getPostParams = useCallback(() => {
+ const {
+ getNodes,
+ edges,
+ transform,
+ } = store.getState()
+ const [x, y, zoom] = transform
+ const {
+ appId,
+ conversationVariables,
+ environmentVariables,
+ syncWorkflowDraftHash,
+ } = workflowStore.getState()
+
+ if (appId) {
+ const nodes = getNodes()
+ const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start)
+
+ if (!hasStartNode)
+ return
+
+ const features = featuresStore!.getState().features
+ const producedNodes = produce(nodes, (draft) => {
+ draft.forEach((node) => {
+ Object.keys(node.data).forEach((key) => {
+ if (key.startsWith('_'))
+ delete node.data[key]
+ })
+ })
+ })
+ const producedEdges = produce(edges, (draft) => {
+ draft.forEach((edge) => {
+ Object.keys(edge.data).forEach((key) => {
+ if (key.startsWith('_'))
+ delete edge.data[key]
+ })
+ })
+ })
+ return {
+ url: `/apps/${appId}/workflows/draft`,
+ params: {
+ graph: {
+ nodes: producedNodes,
+ edges: producedEdges,
+ viewport: {
+ x,
+ y,
+ zoom,
+ },
+ },
+ features: {
+ opening_statement: features.opening?.enabled ? (features.opening?.opening_statement || '') : '',
+ suggested_questions: features.opening?.enabled ? (features.opening?.suggested_questions || []) : [],
+ suggested_questions_after_answer: features.suggested,
+ text_to_speech: features.text2speech,
+ speech_to_text: features.speech2text,
+ retriever_resource: features.citation,
+ sensitive_word_avoidance: features.moderation,
+ file_upload: features.file,
+ },
+ environment_variables: environmentVariables,
+ conversation_variables: conversationVariables,
+ hash: syncWorkflowDraftHash,
+ },
+ }
+ }
+ }, [store, featuresStore, workflowStore])
+
+ const syncWorkflowDraftWhenPageClose = useCallback(() => {
+ if (getNodesReadOnly())
+ return
+ const postParams = getPostParams()
+
+ if (postParams) {
+ navigator.sendBeacon(
+ `${API_PREFIX}/apps/${params.appId}/workflows/draft?_token=${localStorage.getItem('console_token')}`,
+ JSON.stringify(postParams.params),
+ )
+ }
+ }, [getPostParams, params.appId, getNodesReadOnly])
+
+ const doSyncWorkflowDraft = useCallback(async (
+ notRefreshWhenSyncError?: boolean,
+ callback?: {
+ onSuccess?: () => void
+ onError?: () => void
+ onSettled?: () => void
+ },
+ ) => {
+ if (getNodesReadOnly())
+ return
+ const postParams = getPostParams()
+
+ if (postParams) {
+ const {
+ setSyncWorkflowDraftHash,
+ setDraftUpdatedAt,
+ } = workflowStore.getState()
+ try {
+ const res = await syncWorkflowDraft(postParams)
+ setSyncWorkflowDraftHash(res.hash)
+ setDraftUpdatedAt(res.updated_at)
+ callback?.onSuccess && callback.onSuccess()
+ }
+ catch (error: any) {
+ if (error && error.json && !error.bodyUsed) {
+ error.json().then((err: any) => {
+ if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
+ handleRefreshWorkflowDraft()
+ })
+ }
+ callback?.onError && callback.onError()
+ }
+ finally {
+ callback?.onSettled && callback.onSettled()
+ }
+ }
+ }, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft])
+
+ return {
+ doSyncWorkflowDraft,
+ syncWorkflowDraftWhenPageClose,
+ }
+}
diff --git a/web/app/components/workflow-app/hooks/use-workflow-init.ts b/web/app/components/workflow-app/hooks/use-workflow-init.ts
new file mode 100644
index 0000000000..e1c4c25a4e
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-workflow-init.ts
@@ -0,0 +1,123 @@
+import {
+ useCallback,
+ useEffect,
+ useState,
+} from 'react'
+import {
+ useStore,
+ useWorkflowStore,
+} from '@/app/components/workflow/store'
+import { useWorkflowTemplate } from './use-workflow-template'
+import { useStore as useAppStore } from '@/app/components/app/store'
+import {
+ fetchNodesDefaultConfigs,
+ fetchPublishedWorkflow,
+ fetchWorkflowDraft,
+ syncWorkflowDraft,
+} from '@/service/workflow'
+import type { FetchWorkflowDraftResponse } from '@/types/workflow'
+import { useWorkflowConfig } from '@/service/use-workflow'
+
+export const useWorkflowInit = () => {
+ const workflowStore = useWorkflowStore()
+ const {
+ nodes: nodesTemplate,
+ edges: edgesTemplate,
+ } = useWorkflowTemplate()
+ const appDetail = useAppStore(state => state.appDetail)!
+ const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash)
+ const [data, setData] = useState()
+ const [isLoading, setIsLoading] = useState(true)
+ useEffect(() => {
+ workflowStore.setState({ appId: appDetail.id })
+ }, [appDetail.id, workflowStore])
+
+ const handleUpdateWorkflowConfig = useCallback((config: Record) => {
+ const { setWorkflowConfig } = workflowStore.getState()
+
+ setWorkflowConfig(config)
+ }, [workflowStore])
+ useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
+
+ const handleGetInitialWorkflowData = useCallback(async () => {
+ try {
+ const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
+ setData(res)
+ workflowStore.setState({
+ envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
+ acc[env.id] = env.value
+ return acc
+ }, {} as Record),
+ environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
+ conversationVariables: res.conversation_variables || [],
+ })
+ setSyncWorkflowDraftHash(res.hash)
+ setIsLoading(false)
+ }
+ catch (error: any) {
+ if (error && error.json && !error.bodyUsed && appDetail) {
+ error.json().then((err: any) => {
+ if (err.code === 'draft_workflow_not_exist') {
+ workflowStore.setState({ notInitialWorkflow: true })
+ syncWorkflowDraft({
+ url: `/apps/${appDetail.id}/workflows/draft`,
+ params: {
+ graph: {
+ nodes: nodesTemplate,
+ edges: edgesTemplate,
+ },
+ features: {
+ retriever_resource: { enabled: true },
+ },
+ environment_variables: [],
+ conversation_variables: [],
+ },
+ }).then((res) => {
+ workflowStore.getState().setDraftUpdatedAt(res.updated_at)
+ handleGetInitialWorkflowData()
+ })
+ }
+ })
+ }
+ }
+ }, [appDetail, nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash])
+
+ useEffect(() => {
+ handleGetInitialWorkflowData()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ const handleFetchPreloadData = useCallback(async () => {
+ try {
+ const nodesDefaultConfigsData = await fetchNodesDefaultConfigs(`/apps/${appDetail?.id}/workflows/default-workflow-block-configs`)
+ const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`)
+ workflowStore.setState({
+ nodesDefaultConfigs: nodesDefaultConfigsData.reduce((acc, block) => {
+ if (!acc[block.type])
+ acc[block.type] = { ...block.config }
+ return acc
+ }, {} as Record),
+ })
+ workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at)
+ }
+ catch (e) {
+ console.error(e)
+ }
+ }, [workflowStore, appDetail])
+
+ useEffect(() => {
+ handleFetchPreloadData()
+ }, [handleFetchPreloadData])
+
+ useEffect(() => {
+ if (data) {
+ workflowStore.getState().setDraftUpdatedAt(data.updated_at)
+ workflowStore.getState().setToolPublished(data.tool_published)
+ }
+ }, [data, workflowStore])
+
+ return {
+ data,
+ isLoading,
+ }
+}
diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts
new file mode 100644
index 0000000000..1e484d0760
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts
@@ -0,0 +1,357 @@
+import { useCallback } from 'react'
+import {
+ useReactFlow,
+ useStoreApi,
+} from 'reactflow'
+import produce from 'immer'
+import { v4 as uuidV4 } from 'uuid'
+import { usePathname } from 'next/navigation'
+import { useWorkflowStore } from '@/app/components/workflow/store'
+import { WorkflowRunningStatus } from '@/app/components/workflow/types'
+import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-interactions'
+import { useWorkflowRunEvent } from '@/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event'
+import { useStore as useAppStore } from '@/app/components/app/store'
+import type { IOtherOptions } from '@/service/base'
+import { ssePost } from '@/service/base'
+import { stopWorkflowRun } from '@/service/workflow'
+import { useFeaturesStore } from '@/app/components/base/features/hooks'
+import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
+import type { VersionHistory } from '@/types/workflow'
+import { noop } from 'lodash-es'
+import { useNodesSyncDraft } from './use-nodes-sync-draft'
+
+export const useWorkflowRun = () => {
+ const store = useStoreApi()
+ const workflowStore = useWorkflowStore()
+ const reactflow = useReactFlow()
+ const featuresStore = useFeaturesStore()
+ const { doSyncWorkflowDraft } = useNodesSyncDraft()
+ const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
+ const pathname = usePathname()
+
+ const {
+ handleWorkflowStarted,
+ handleWorkflowFinished,
+ handleWorkflowFailed,
+ handleWorkflowNodeStarted,
+ handleWorkflowNodeFinished,
+ handleWorkflowNodeIterationStarted,
+ handleWorkflowNodeIterationNext,
+ handleWorkflowNodeIterationFinished,
+ handleWorkflowNodeLoopStarted,
+ handleWorkflowNodeLoopNext,
+ handleWorkflowNodeLoopFinished,
+ handleWorkflowNodeRetry,
+ handleWorkflowAgentLog,
+ handleWorkflowTextChunk,
+ handleWorkflowTextReplace,
+ } = useWorkflowRunEvent()
+
+ const handleBackupDraft = useCallback(() => {
+ const {
+ getNodes,
+ edges,
+ } = store.getState()
+ const { getViewport } = reactflow
+ const {
+ backupDraft,
+ setBackupDraft,
+ environmentVariables,
+ } = workflowStore.getState()
+ const { features } = featuresStore!.getState()
+
+ if (!backupDraft) {
+ setBackupDraft({
+ nodes: getNodes(),
+ edges,
+ viewport: getViewport(),
+ features,
+ environmentVariables,
+ })
+ doSyncWorkflowDraft()
+ }
+ }, [reactflow, workflowStore, store, featuresStore, doSyncWorkflowDraft])
+
+ const handleLoadBackupDraft = useCallback(() => {
+ const {
+ backupDraft,
+ setBackupDraft,
+ setEnvironmentVariables,
+ } = workflowStore.getState()
+
+ if (backupDraft) {
+ const {
+ nodes,
+ edges,
+ viewport,
+ features,
+ environmentVariables,
+ } = backupDraft
+ handleUpdateWorkflowCanvas({
+ nodes,
+ edges,
+ viewport,
+ })
+ setEnvironmentVariables(environmentVariables)
+ featuresStore!.setState({ features })
+ setBackupDraft(undefined)
+ }
+ }, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
+
+ const handleRun = useCallback(async (
+ params: any,
+ callback?: IOtherOptions,
+ ) => {
+ const {
+ getNodes,
+ setNodes,
+ } = store.getState()
+ const newNodes = produce(getNodes(), (draft) => {
+ draft.forEach((node) => {
+ node.data.selected = false
+ node.data._runningStatus = undefined
+ })
+ })
+ setNodes(newNodes)
+ await doSyncWorkflowDraft()
+
+ const {
+ onWorkflowStarted,
+ onWorkflowFinished,
+ onNodeStarted,
+ onNodeFinished,
+ onIterationStart,
+ onIterationNext,
+ onIterationFinish,
+ onLoopStart,
+ onLoopNext,
+ onLoopFinish,
+ onNodeRetry,
+ onAgentLog,
+ onError,
+ ...restCallback
+ } = callback || {}
+ workflowStore.setState({ historyWorkflowData: undefined })
+ const appDetail = useAppStore.getState().appDetail
+ const workflowContainer = document.getElementById('workflow-container')
+
+ const {
+ clientWidth,
+ clientHeight,
+ } = workflowContainer!
+
+ let url = ''
+ if (appDetail?.mode === 'advanced-chat')
+ url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
+
+ if (appDetail?.mode === 'workflow')
+ url = `/apps/${appDetail.id}/workflows/draft/run`
+
+ const {
+ setWorkflowRunningData,
+ } = workflowStore.getState()
+ setWorkflowRunningData({
+ result: {
+ status: WorkflowRunningStatus.Running,
+ },
+ tracing: [],
+ resultText: '',
+ })
+
+ let ttsUrl = ''
+ let ttsIsPublic = false
+ if (params.token) {
+ ttsUrl = '/text-to-audio'
+ ttsIsPublic = true
+ }
+ else if (params.appId) {
+ if (pathname.search('explore/installed') > -1)
+ ttsUrl = `/installed-apps/${params.appId}/text-to-audio`
+ else
+ ttsUrl = `/apps/${params.appId}/text-to-audio`
+ }
+ const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', noop)
+
+ ssePost(
+ url,
+ {
+ body: params,
+ },
+ {
+ onWorkflowStarted: (params) => {
+ handleWorkflowStarted(params)
+
+ if (onWorkflowStarted)
+ onWorkflowStarted(params)
+ },
+ onWorkflowFinished: (params) => {
+ handleWorkflowFinished(params)
+
+ if (onWorkflowFinished)
+ onWorkflowFinished(params)
+ },
+ onError: (params) => {
+ handleWorkflowFailed()
+
+ if (onError)
+ onError(params)
+ },
+ onNodeStarted: (params) => {
+ handleWorkflowNodeStarted(
+ params,
+ {
+ clientWidth,
+ clientHeight,
+ },
+ )
+
+ if (onNodeStarted)
+ onNodeStarted(params)
+ },
+ onNodeFinished: (params) => {
+ handleWorkflowNodeFinished(params)
+
+ if (onNodeFinished)
+ onNodeFinished(params)
+ },
+ onIterationStart: (params) => {
+ handleWorkflowNodeIterationStarted(
+ params,
+ {
+ clientWidth,
+ clientHeight,
+ },
+ )
+
+ if (onIterationStart)
+ onIterationStart(params)
+ },
+ onIterationNext: (params) => {
+ handleWorkflowNodeIterationNext(params)
+
+ if (onIterationNext)
+ onIterationNext(params)
+ },
+ onIterationFinish: (params) => {
+ handleWorkflowNodeIterationFinished(params)
+
+ if (onIterationFinish)
+ onIterationFinish(params)
+ },
+ onLoopStart: (params) => {
+ handleWorkflowNodeLoopStarted(
+ params,
+ {
+ clientWidth,
+ clientHeight,
+ },
+ )
+
+ if (onLoopStart)
+ onLoopStart(params)
+ },
+ onLoopNext: (params) => {
+ handleWorkflowNodeLoopNext(params)
+
+ if (onLoopNext)
+ onLoopNext(params)
+ },
+ onLoopFinish: (params) => {
+ handleWorkflowNodeLoopFinished(params)
+
+ if (onLoopFinish)
+ onLoopFinish(params)
+ },
+ onNodeRetry: (params) => {
+ handleWorkflowNodeRetry(params)
+
+ if (onNodeRetry)
+ onNodeRetry(params)
+ },
+ onAgentLog: (params) => {
+ handleWorkflowAgentLog(params)
+
+ if (onAgentLog)
+ onAgentLog(params)
+ },
+ onTextChunk: (params) => {
+ handleWorkflowTextChunk(params)
+ },
+ onTextReplace: (params) => {
+ handleWorkflowTextReplace(params)
+ },
+ onTTSChunk: (messageId: string, audio: string) => {
+ if (!audio || audio === '')
+ return
+ player.playAudioWithAudio(audio, true)
+ AudioPlayerManager.getInstance().resetMsgId(messageId)
+ },
+ onTTSEnd: (messageId: string, audio: string) => {
+ player.playAudioWithAudio(audio, false)
+ },
+ ...restCallback,
+ },
+ )
+ }, [
+ store,
+ workflowStore,
+ doSyncWorkflowDraft,
+ handleWorkflowStarted,
+ handleWorkflowFinished,
+ handleWorkflowFailed,
+ handleWorkflowNodeStarted,
+ handleWorkflowNodeFinished,
+ handleWorkflowNodeIterationStarted,
+ handleWorkflowNodeIterationNext,
+ handleWorkflowNodeIterationFinished,
+ handleWorkflowNodeLoopStarted,
+ handleWorkflowNodeLoopNext,
+ handleWorkflowNodeLoopFinished,
+ handleWorkflowNodeRetry,
+ handleWorkflowTextChunk,
+ handleWorkflowTextReplace,
+ handleWorkflowAgentLog,
+ pathname],
+ )
+
+ const handleStopRun = useCallback((taskId: string) => {
+ const appId = useAppStore.getState().appDetail?.id
+
+ stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
+ }, [])
+
+ const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
+ const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } }))
+ const edges = publishedWorkflow.graph.edges
+ const viewport = publishedWorkflow.graph.viewport!
+ handleUpdateWorkflowCanvas({
+ nodes,
+ edges,
+ viewport,
+ })
+ const mappedFeatures = {
+ opening: {
+ enabled: !!publishedWorkflow.features.opening_statement || !!publishedWorkflow.features.suggested_questions.length,
+ opening_statement: publishedWorkflow.features.opening_statement,
+ suggested_questions: publishedWorkflow.features.suggested_questions,
+ },
+ suggested: publishedWorkflow.features.suggested_questions_after_answer,
+ text2speech: publishedWorkflow.features.text_to_speech,
+ speech2text: publishedWorkflow.features.speech_to_text,
+ citation: publishedWorkflow.features.retriever_resource,
+ moderation: publishedWorkflow.features.sensitive_word_avoidance,
+ file: publishedWorkflow.features.file_upload,
+ }
+
+ featuresStore?.setState({ features: mappedFeatures })
+ workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
+ }, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
+
+ return {
+ handleBackupDraft,
+ handleLoadBackupDraft,
+ handleRun,
+ handleStopRun,
+ handleRestoreFromPublishedWorkflow,
+ }
+}
diff --git a/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx
new file mode 100644
index 0000000000..3f5ea1c1df
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx
@@ -0,0 +1,96 @@
+import { useCallback } from 'react'
+import { useStoreApi } from 'reactflow'
+import { useWorkflowStore } from '@/app/components/workflow/store'
+import {
+ BlockEnum,
+ WorkflowRunningStatus,
+} from '@/app/components/workflow/types'
+import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
+import { useFeaturesStore } from '@/app/components/base/features/hooks'
+import {
+ useIsChatMode,
+ useNodesSyncDraft,
+ useWorkflowRun,
+} from '.'
+
+export const useWorkflowStartRun = () => {
+ const store = useStoreApi()
+ const workflowStore = useWorkflowStore()
+ const featuresStore = useFeaturesStore()
+ const isChatMode = useIsChatMode()
+ const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
+ const { handleRun } = useWorkflowRun()
+ const { doSyncWorkflowDraft } = useNodesSyncDraft()
+
+ const handleWorkflowStartRunInWorkflow = useCallback(async () => {
+ const {
+ workflowRunningData,
+ } = workflowStore.getState()
+
+ if (workflowRunningData?.result.status === WorkflowRunningStatus.Running)
+ return
+
+ const { getNodes } = store.getState()
+ const nodes = getNodes()
+ const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
+ const startVariables = startNode?.data.variables || []
+ const fileSettings = featuresStore!.getState().features.file
+ const {
+ showDebugAndPreviewPanel,
+ setShowDebugAndPreviewPanel,
+ setShowInputsPanel,
+ setShowEnvPanel,
+ } = workflowStore.getState()
+
+ setShowEnvPanel(false)
+
+ if (showDebugAndPreviewPanel) {
+ handleCancelDebugAndPreviewPanel()
+ return
+ }
+
+ if (!startVariables.length && !fileSettings?.image?.enabled) {
+ await doSyncWorkflowDraft()
+ handleRun({ inputs: {}, files: [] })
+ setShowDebugAndPreviewPanel(true)
+ setShowInputsPanel(false)
+ }
+ else {
+ setShowDebugAndPreviewPanel(true)
+ setShowInputsPanel(true)
+ }
+ }, [store, workflowStore, featuresStore, handleCancelDebugAndPreviewPanel, handleRun, doSyncWorkflowDraft])
+
+ const handleWorkflowStartRunInChatflow = useCallback(async () => {
+ const {
+ showDebugAndPreviewPanel,
+ setShowDebugAndPreviewPanel,
+ setHistoryWorkflowData,
+ setShowEnvPanel,
+ setShowChatVariablePanel,
+ } = workflowStore.getState()
+
+ setShowEnvPanel(false)
+ setShowChatVariablePanel(false)
+
+ if (showDebugAndPreviewPanel)
+ handleCancelDebugAndPreviewPanel()
+ else
+ setShowDebugAndPreviewPanel(true)
+
+ setHistoryWorkflowData(undefined)
+ }, [workflowStore, handleCancelDebugAndPreviewPanel])
+
+ const handleStartWorkflowRun = useCallback(() => {
+ if (!isChatMode)
+ handleWorkflowStartRunInWorkflow()
+ else
+ handleWorkflowStartRunInChatflow()
+ }, [isChatMode, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow])
+
+ return {
+ handleStartWorkflowRun,
+ handleWorkflowStartRunInWorkflow,
+ handleWorkflowStartRunInChatflow,
+ }
+}
diff --git a/web/app/components/workflow/hooks/use-workflow-template.ts b/web/app/components/workflow-app/hooks/use-workflow-template.ts
similarity index 87%
rename from web/app/components/workflow/hooks/use-workflow-template.ts
rename to web/app/components/workflow-app/hooks/use-workflow-template.ts
index c2dc956b63..9f47b981dc 100644
--- a/web/app/components/workflow/hooks/use-workflow-template.ts
+++ b/web/app/components/workflow-app/hooks/use-workflow-template.ts
@@ -1,10 +1,10 @@
-import { generateNewNode } from '../utils'
+import { generateNewNode } from '@/app/components/workflow/utils'
import {
NODE_WIDTH_X_OFFSET,
START_INITIAL_POSITION,
-} from '../constants'
-import { useIsChatMode } from './use-workflow'
-import { useNodesInitialData } from './use-nodes-data'
+} from '@/app/components/workflow/constants'
+import { useNodesInitialData } from '@/app/components/workflow/hooks'
+import { useIsChatMode } from './use-is-chat-mode'
export const useWorkflowTemplate = () => {
const isChatMode = useIsChatMode()
diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx
new file mode 100644
index 0000000000..761a7f29c4
--- /dev/null
+++ b/web/app/components/workflow-app/index.tsx
@@ -0,0 +1,108 @@
+import {
+ useMemo,
+} from 'react'
+import useSWR from 'swr'
+import {
+ SupportUploadFileTypes,
+} from '@/app/components/workflow/types'
+import {
+ useWorkflowInit,
+} from './hooks'
+import {
+ initialEdges,
+ initialNodes,
+} from '@/app/components/workflow/utils'
+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 WorkflowWithDefaultContext from '@/app/components/workflow'
+import {
+ WorkflowContextProvider,
+} from '@/app/components/workflow/context'
+import { createWorkflowSlice } from './store/workflow/workflow-slice'
+import WorkflowAppMain from './components/workflow-main'
+
+const WorkflowAppWithAdditionalContext = () => {
+ const {
+ data,
+ isLoading,
+ } = useWorkflowInit()
+ const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
+
+ const nodesData = useMemo(() => {
+ if (data)
+ return initialNodes(data.graph.nodes, data.graph.edges)
+
+ return []
+ }, [data])
+ const edgesData = useMemo(() => {
+ if (data)
+ return initialEdges(data.graph.edges, data.graph.nodes)
+
+ return []
+ }, [data])
+
+ if (!data || isLoading) {
+ return (
+
+
+
+ )
+ }
+
+ const features = data.features || {}
+ const initialFeatures: FeaturesData = {
+ file: {
+ image: {
+ enabled: !!features.file_upload?.image?.enabled,
+ number_limits: features.file_upload?.image?.number_limits || 3,
+ transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
+ },
+ enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
+ allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
+ allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
+ allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
+ number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
+ fileUploadConfig: fileUploadConfigResponse,
+ },
+ opening: {
+ enabled: !!features.opening_statement,
+ opening_statement: features.opening_statement,
+ suggested_questions: features.suggested_questions,
+ },
+ suggested: features.suggested_questions_after_answer || { enabled: false },
+ speech2text: features.speech_to_text || { enabled: false },
+ text2speech: features.text_to_speech || { enabled: false },
+ citation: features.retriever_resource || { enabled: false },
+ moderation: features.sensitive_word_avoidance || { enabled: false },
+ }
+
+ return (
+
+
+
+
+
+ )
+}
+
+const WorkflowAppWrapper = () => {
+ return (
+
+
+
+ )
+}
+
+export default WorkflowAppWrapper
diff --git a/web/app/components/workflow-app/store/workflow/workflow-slice.ts b/web/app/components/workflow-app/store/workflow/workflow-slice.ts
new file mode 100644
index 0000000000..77626e52b1
--- /dev/null
+++ b/web/app/components/workflow-app/store/workflow/workflow-slice.ts
@@ -0,0 +1,18 @@
+import type { StateCreator } from 'zustand'
+
+export type WorkflowSliceShape = {
+ appId: string
+ notInitialWorkflow: boolean
+ setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
+ nodesDefaultConfigs: Record
+ setNodesDefaultConfigs: (nodesDefaultConfigs: Record) => void
+}
+
+export type CreateWorkflowSlice = StateCreator
+export const createWorkflowSlice: StateCreator = set => ({
+ appId: '',
+ notInitialWorkflow: false,
+ setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })),
+ nodesDefaultConfigs: {},
+ setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
+})
diff --git a/web/app/components/workflow/context.tsx b/web/app/components/workflow/context.tsx
index bb34ce6319..cae14fc2b2 100644
--- a/web/app/components/workflow/context.tsx
+++ b/web/app/components/workflow/context.tsx
@@ -2,19 +2,24 @@ import {
createContext,
useRef,
} from 'react'
-import { createWorkflowStore } from './store'
+import {
+ createWorkflowStore,
+} from './store'
+import type { StateCreator } from 'zustand'
+import type { WorkflowSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice'
type WorkflowStore = ReturnType
export const WorkflowContext = createContext(null)
-type WorkflowProviderProps = {
+export type WorkflowProviderProps = {
children: React.ReactNode
+ injectWorkflowStoreSliceFn?: StateCreator
}
-export const WorkflowContextProvider = ({ children }: WorkflowProviderProps) => {
+export const WorkflowContextProvider = ({ children, injectWorkflowStoreSliceFn }: WorkflowProviderProps) => {
const storeRef = useRef(undefined)
if (!storeRef.current)
- storeRef.current = createWorkflowStore()
+ storeRef.current = createWorkflowStore({ injectWorkflowStoreSliceFn })
return (
diff --git a/web/app/components/workflow/header/editing-title.tsx b/web/app/components/workflow/header/editing-title.tsx
index b99564a5f9..2444cf8c29 100644
--- a/web/app/components/workflow/header/editing-title.tsx
+++ b/web/app/components/workflow/header/editing-title.tsx
@@ -1,13 +1,13 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
-import { useWorkflow } from '../hooks'
+import { useFormatTimeFromNow } from '../hooks'
import { useStore } from '@/app/components/workflow/store'
import useTimestamp from '@/hooks/use-timestamp'
const EditingTitle = () => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
- const { formatTimeFromNow } = useWorkflow()
+ const { formatTimeFromNow } = useFormatTimeFromNow()
const draftUpdatedAt = useStore(state => state.draftUpdatedAt)
const publishedAt = useStore(state => state.publishedAt)
const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft)
diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx
new file mode 100644
index 0000000000..ec016b1b65
--- /dev/null
+++ b/web/app/components/workflow/header/header-in-normal.tsx
@@ -0,0 +1,69 @@
+import {
+ useCallback,
+} from 'react'
+import { useNodes } from 'reactflow'
+import {
+ useStore,
+ useWorkflowStore,
+} from '../store'
+import type { StartNodeType } from '../nodes/start/types'
+import {
+ useNodesInteractions,
+ useNodesReadOnly,
+ useWorkflowRun,
+} from '../hooks'
+import Divider from '../../base/divider'
+import RunAndHistory from './run-and-history'
+import EditingTitle from './editing-title'
+import EnvButton from './env-button'
+import VersionHistoryButton from './version-history-button'
+
+export type HeaderInNormalProps = {
+ components?: {
+ left?: React.ReactNode
+ middle?: React.ReactNode
+ }
+}
+const HeaderInNormal = ({
+ components,
+}: HeaderInNormalProps) => {
+ const workflowStore = useWorkflowStore()
+ const { nodesReadOnly } = useNodesReadOnly()
+ const { handleNodeSelect } = useNodesInteractions()
+ const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
+ const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
+ const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
+ const nodes = useNodes()
+ const selectedNode = nodes.find(node => node.data.selected)
+ const { handleBackupDraft } = useWorkflowRun()
+
+ const onStartRestoring = useCallback(() => {
+ workflowStore.setState({ isRestoring: true })
+ handleBackupDraft()
+ // clear right panel
+ if (selectedNode)
+ handleNodeSelect(selectedNode.id, true)
+ setShowWorkflowVersionHistoryPanel(true)
+ setShowEnvPanel(false)
+ setShowDebugAndPreviewPanel(false)
+ }, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode,
+ setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel])
+
+ return (
+ <>
+
+
+
+
+ {components?.left}
+
+
+
+ {components?.middle}
+
+
+ >
+ )
+}
+
+export default HeaderInNormal
diff --git a/web/app/components/workflow/header/header-in-restoring.tsx b/web/app/components/workflow/header/header-in-restoring.tsx
new file mode 100644
index 0000000000..4d1954587d
--- /dev/null
+++ b/web/app/components/workflow/header/header-in-restoring.tsx
@@ -0,0 +1,93 @@
+import {
+ useCallback,
+} from 'react'
+import { RiHistoryLine } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import {
+ useStore,
+ useWorkflowStore,
+} from '../store'
+import {
+ WorkflowVersion,
+} from '../types'
+import {
+ useNodesSyncDraft,
+ useWorkflowRun,
+} from '../hooks'
+import Toast from '../../base/toast'
+import RestoringTitle from './restoring-title'
+import Button from '@/app/components/base/button'
+
+export type HeaderInRestoringProps = {
+ onRestoreSettled?: () => void
+}
+const HeaderInRestoring = ({
+ onRestoreSettled,
+}: HeaderInRestoringProps) => {
+ const { t } = useTranslation()
+ const workflowStore = useWorkflowStore()
+ const currentVersion = useStore(s => s.currentVersion)
+ const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
+
+ const {
+ handleLoadBackupDraft,
+ } = useWorkflowRun()
+ const { handleSyncWorkflowDraft } = useNodesSyncDraft()
+
+ const handleCancelRestore = useCallback(() => {
+ handleLoadBackupDraft()
+ workflowStore.setState({ isRestoring: false })
+ setShowWorkflowVersionHistoryPanel(false)
+ }, [workflowStore, handleLoadBackupDraft, setShowWorkflowVersionHistoryPanel])
+
+ const handleRestore = useCallback(() => {
+ setShowWorkflowVersionHistoryPanel(false)
+ workflowStore.setState({ isRestoring: false })
+ workflowStore.setState({ backupDraft: undefined })
+ handleSyncWorkflowDraft(true, false, {
+ onSuccess: () => {
+ Toast.notify({
+ type: 'success',
+ message: t('workflow.versionHistory.action.restoreSuccess'),
+ })
+ },
+ onError: () => {
+ Toast.notify({
+ type: 'error',
+ message: t('workflow.versionHistory.action.restoreFailure'),
+ })
+ },
+ onSettled: () => {
+ onRestoreSettled?.()
+ },
+ })
+ }, [handleSyncWorkflowDraft, workflowStore, setShowWorkflowVersionHistoryPanel, onRestoreSettled, t])
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default HeaderInRestoring
diff --git a/web/app/components/workflow/header/header-in-view-history.tsx b/web/app/components/workflow/header/header-in-view-history.tsx
new file mode 100644
index 0000000000..81858ccc89
--- /dev/null
+++ b/web/app/components/workflow/header/header-in-view-history.tsx
@@ -0,0 +1,50 @@
+import {
+ useCallback,
+} from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+ useWorkflowStore,
+} from '../store'
+import {
+ useWorkflowRun,
+} from '../hooks'
+import Divider from '../../base/divider'
+import RunningTitle from './running-title'
+import ViewHistory from './view-history'
+import Button from '@/app/components/base/button'
+import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
+
+const HeaderInHistory = () => {
+ const { t } = useTranslation()
+ const workflowStore = useWorkflowStore()
+
+ const {
+ handleLoadBackupDraft,
+ } = useWorkflowRun()
+
+ const handleGoBackToEdit = useCallback(() => {
+ handleLoadBackupDraft()
+ workflowStore.setState({ historyWorkflowData: undefined })
+ }, [workflowStore, handleLoadBackupDraft])
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default HeaderInHistory
diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx
index 7e99f5dd6b..e5391afb09 100644
--- a/web/app/components/workflow/header/index.tsx
+++ b/web/app/components/workflow/header/index.tsx
@@ -1,292 +1,51 @@
-import type { FC } from 'react'
import {
- memo,
- useCallback,
- useMemo,
-} from 'react'
-import { RiApps2AddLine, RiHistoryLine } from '@remixicon/react'
-import { useNodes } from 'reactflow'
-import { useTranslation } from 'react-i18next'
-import { useContext, useContextSelector } from 'use-context-selector'
-import {
- useStore,
- useWorkflowStore,
-} from '../store'
-import {
- BlockEnum,
- InputVarType,
- WorkflowVersion,
-} from '../types'
-import type { StartNodeType } from '../nodes/start/types'
-import {
- useChecklistBeforePublish,
- useIsChatMode,
- useNodesInteractions,
- useNodesReadOnly,
- useNodesSyncDraft,
useWorkflowMode,
- useWorkflowRun,
} from '../hooks'
-import AppPublisher from '../../app/app-publisher'
-import Toast, { ToastContext } from '../../base/toast'
-import Divider from '../../base/divider'
-import RunAndHistory from './run-and-history'
-import EditingTitle from './editing-title'
-import RunningTitle from './running-title'
-import RestoringTitle from './restoring-title'
-import ViewHistory from './view-history'
-import ChatVariableButton from './chat-variable-button'
-import EnvButton from './env-button'
-import VersionHistoryButton from './version-history-button'
-import Button from '@/app/components/base/button'
-import { useStore as useAppStore } from '@/app/components/app/store'
-import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
-import { useFeatures } from '@/app/components/base/features/hooks'
-import { usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow'
-import type { PublishWorkflowParams } from '@/types/workflow'
-import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
-import AppContext from '@/context/app-context'
+import type { HeaderInNormalProps } from './header-in-normal'
+import HeaderInNormal from './header-in-normal'
+import HeaderInHistory from './header-in-view-history'
+import type { HeaderInRestoringProps } from './header-in-restoring'
+import HeaderInRestoring from './header-in-restoring'
-const Header: FC = () => {
- const { t } = useTranslation()
- const workflowStore = useWorkflowStore()
- const appDetail = useAppStore(s => s.appDetail)
- const setAppDetail = useAppStore(s => s.setAppDetail)
- const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures)
- const appID = appDetail?.id
- const isChatMode = useIsChatMode()
- const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
- const { handleNodeSelect } = useNodesInteractions()
- const publishedAt = useStore(s => s.publishedAt)
- const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
- const toolPublished = useStore(s => s.toolPublished)
- const currentVersion = useStore(s => s.currentVersion)
- const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
- const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
- const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
- const nodes = useNodes()
- const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
- const selectedNode = nodes.find(node => node.data.selected)
- const startVariables = startNode?.data.variables
- const fileSettings = useFeatures(s => s.features.file)
- const variables = useMemo(() => {
- const data = startVariables || []
- if (fileSettings?.image?.enabled) {
- return [
- ...data,
- {
- type: InputVarType.files,
- variable: '__image',
- required: false,
- label: 'files',
- },
- ]
- }
-
- return data
- }, [fileSettings?.image?.enabled, startVariables])
-
- const {
- handleLoadBackupDraft,
- handleBackupDraft,
- } = useWorkflowRun()
- const { handleCheckBeforePublish } = useChecklistBeforePublish()
- const { handleSyncWorkflowDraft } = useNodesSyncDraft()
- const { notify } = useContext(ToastContext)
+export type HeaderProps = {
+ normal?: HeaderInNormalProps
+ restoring?: HeaderInRestoringProps
+}
+const Header = ({
+ normal: normalProps,
+ restoring: restoringProps,
+}: HeaderProps) => {
const {
normal,
restoring,
viewHistory,
} = useWorkflowMode()
- const handleShowFeatures = useCallback(() => {
- const {
- showFeaturesPanel,
- isRestoring,
- setShowFeaturesPanel,
- } = workflowStore.getState()
- if (getNodesReadOnly() && !isRestoring)
- return
- setShowFeaturesPanel(!showFeaturesPanel)
- }, [workflowStore, getNodesReadOnly])
-
- const handleCancelRestore = useCallback(() => {
- handleLoadBackupDraft()
- workflowStore.setState({ isRestoring: false })
- setShowWorkflowVersionHistoryPanel(false)
- }, [workflowStore, handleLoadBackupDraft, setShowWorkflowVersionHistoryPanel])
-
- const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
-
- const handleRestore = useCallback(() => {
- setShowWorkflowVersionHistoryPanel(false)
- workflowStore.setState({ isRestoring: false })
- workflowStore.setState({ backupDraft: undefined })
- handleSyncWorkflowDraft(true, false, {
- onSuccess: () => {
- Toast.notify({
- type: 'success',
- message: t('workflow.versionHistory.action.restoreSuccess'),
- })
- },
- onError: () => {
- Toast.notify({
- type: 'error',
- message: t('workflow.versionHistory.action.restoreFailure'),
- })
- },
- onSettled: () => {
- resetWorkflowVersionHistory()
- },
- })
- }, [handleSyncWorkflowDraft, workflowStore, setShowWorkflowVersionHistoryPanel, resetWorkflowVersionHistory, t])
-
- const updateAppDetail = useCallback(async () => {
- try {
- const res = await fetchAppDetail({ url: '/apps', id: appID! })
- if (systemFeatures.enable_web_sso_switch_component) {
- const ssoRes = await fetchAppSSO({ appId: appID! })
- setAppDetail({ ...res, enable_sso: ssoRes.enabled })
- }
- else {
- setAppDetail({ ...res })
- }
- }
- catch (error) {
- console.error(error)
- }
- }, [appID, setAppDetail, systemFeatures.enable_web_sso_switch_component])
-
- const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
-
- const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
- if (await handleCheckBeforePublish()) {
- const res = await publishWorkflow({
- title: params?.title || '',
- releaseNotes: params?.releaseNotes || '',
- })
-
- if (res) {
- notify({ type: 'success', message: t('common.api.actionSuccess') })
- updateAppDetail()
- workflowStore.getState().setPublishedAt(res.created_at)
- resetWorkflowVersionHistory()
- }
- }
- else {
- throw new Error('Checklist failed')
- }
- }, [handleCheckBeforePublish, notify, t, workflowStore, publishWorkflow, resetWorkflowVersionHistory, updateAppDetail])
-
- const onStartRestoring = useCallback(() => {
- workflowStore.setState({ isRestoring: true })
- handleBackupDraft()
- // clear right panel
- if (selectedNode)
- handleNodeSelect(selectedNode.id, true)
- setShowWorkflowVersionHistoryPanel(true)
- setShowEnvPanel(false)
- setShowDebugAndPreviewPanel(false)
- }, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode,
- setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel])
-
- const onPublisherToggle = useCallback((state: boolean) => {
- if (state)
- handleSyncWorkflowDraft(true)
- }, [handleSyncWorkflowDraft])
-
- const handleGoBackToEdit = useCallback(() => {
- handleLoadBackupDraft()
- workflowStore.setState({ historyWorkflowData: undefined })
- }, [workflowStore, handleLoadBackupDraft])
-
- const handleToolConfigureUpdate = useCallback(() => {
- workflowStore.setState({ toolPublished: true })
- }, [workflowStore])
-
return (
-
- {
- normal &&
- }
- {
- viewHistory &&
- }
- {
- restoring &&
- }
-
{
normal && (
-
- {/*
*/}
- {isChatMode &&
}
-
-
-
-
-
-
-
+
)
}
{
viewHistory && (
-
-
-
-
-
+
)
}
{
restoring && (
-
-
-
-
+
)
}
)
}
-export default memo(Header)
+export default Header
diff --git a/web/app/components/workflow/header/restoring-title.tsx b/web/app/components/workflow/header/restoring-title.tsx
index 310ab5c35a..26cdd79d13 100644
--- a/web/app/components/workflow/header/restoring-title.tsx
+++ b/web/app/components/workflow/header/restoring-title.tsx
@@ -1,13 +1,13 @@
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
-import { useWorkflow } from '../hooks'
+import { useFormatTimeFromNow } from '../hooks'
import { useStore } from '../store'
import { WorkflowVersion } from '../types'
import useTimestamp from '@/hooks/use-timestamp'
const RestoringTitle = () => {
const { t } = useTranslation()
- const { formatTimeFromNow } = useWorkflow()
+ const { formatTimeFromNow } = useFormatTimeFromNow()
const { formatTime } = useTimestamp()
const currentVersion = useStore(state => state.currentVersion)
const isDraft = currentVersion?.version === WorkflowVersion.Draft
diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx
index 1298c0e42d..21b4462867 100644
--- a/web/app/components/workflow/header/view-history.tsx
+++ b/web/app/components/workflow/header/view-history.tsx
@@ -11,9 +11,9 @@ import {
RiErrorWarningLine,
} from '@remixicon/react'
import {
+ useFormatTimeFromNow,
useIsChatMode,
useNodesInteractions,
- useWorkflow,
useWorkflowInteractions,
useWorkflowRun,
} from '../hooks'
@@ -50,7 +50,7 @@ const ViewHistory = ({
const { t } = useTranslation()
const isChatMode = useIsChatMode()
const [open, setOpen] = useState(false)
- const { formatTimeFromNow } = useWorkflow()
+ const { formatTimeFromNow } = useFormatTimeFromNow()
const {
handleNodesCancelSelected,
} = useNodesInteractions()
diff --git a/web/app/components/workflow/hooks-store/index.ts b/web/app/components/workflow/hooks-store/index.ts
new file mode 100644
index 0000000000..40b4132dfd
--- /dev/null
+++ b/web/app/components/workflow/hooks-store/index.ts
@@ -0,0 +1,2 @@
+export * from './provider'
+export * from './store'
diff --git a/web/app/components/workflow/hooks-store/provider.tsx b/web/app/components/workflow/hooks-store/provider.tsx
new file mode 100644
index 0000000000..c1090ae3f8
--- /dev/null
+++ b/web/app/components/workflow/hooks-store/provider.tsx
@@ -0,0 +1,36 @@
+import {
+ createContext,
+ useEffect,
+ useRef,
+} from 'react'
+import { useStore } from 'reactflow'
+import {
+ createHooksStore,
+} from './store'
+import type { Shape } from './store'
+
+type HooksStore = ReturnType
+export const HooksStoreContext = createContext(null)
+type HooksStoreContextProviderProps = Partial & {
+ children: React.ReactNode
+}
+export const HooksStoreContextProvider = ({ children, ...restProps }: HooksStoreContextProviderProps) => {
+ const storeRef = useRef(undefined)
+ const d3Selection = useStore(s => s.d3Selection)
+ const d3Zoom = useStore(s => s.d3Zoom)
+
+ useEffect(() => {
+ if (storeRef.current && d3Selection && d3Zoom)
+ storeRef.current.getState().refreshAll(restProps)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [d3Selection, d3Zoom])
+
+ if (!storeRef.current)
+ storeRef.current = createHooksStore(restProps)
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts
new file mode 100644
index 0000000000..2e40cbfbc9
--- /dev/null
+++ b/web/app/components/workflow/hooks-store/store.ts
@@ -0,0 +1,72 @@
+import { useContext } from 'react'
+import {
+ noop,
+} from 'lodash-es'
+import {
+ useStore as useZustandStore,
+} from 'zustand'
+import { createStore } from 'zustand/vanilla'
+import { HooksStoreContext } from './provider'
+
+type CommonHooksFnMap = {
+ doSyncWorkflowDraft: (
+ notRefreshWhenSyncError?: boolean,
+ callback?: {
+ onSuccess?: () => void
+ onError?: () => void
+ onSettled?: () => void
+ }
+ ) => Promise
+ syncWorkflowDraftWhenPageClose: () => void
+ handleBackupDraft: () => void
+ handleLoadBackupDraft: () => void
+ handleRestoreFromPublishedWorkflow: (...args: any[]) => void
+ handleRun: (...args: any[]) => void
+ handleStopRun: (...args: any[]) => void
+ handleStartWorkflowRun: () => void
+ handleWorkflowStartRunInWorkflow: () => void
+ handleWorkflowStartRunInChatflow: () => void
+}
+
+export type Shape = {
+ refreshAll: (props: Partial) => void
+} & CommonHooksFnMap
+
+export const createHooksStore = ({
+ doSyncWorkflowDraft = async () => noop(),
+ syncWorkflowDraftWhenPageClose = noop,
+ handleBackupDraft = noop,
+ handleLoadBackupDraft = noop,
+ handleRestoreFromPublishedWorkflow = noop,
+ handleRun = noop,
+ handleStopRun = noop,
+ handleStartWorkflowRun = noop,
+ handleWorkflowStartRunInWorkflow = noop,
+ handleWorkflowStartRunInChatflow = noop,
+}: Partial) => {
+ return createStore(set => ({
+ refreshAll: props => set(state => ({ ...state, ...props })),
+ doSyncWorkflowDraft,
+ syncWorkflowDraftWhenPageClose,
+ handleBackupDraft,
+ handleLoadBackupDraft,
+ handleRestoreFromPublishedWorkflow,
+ handleRun,
+ handleStopRun,
+ handleStartWorkflowRun,
+ handleWorkflowStartRunInWorkflow,
+ handleWorkflowStartRunInChatflow,
+ }))
+}
+
+export function useHooksStore(selector: (state: Shape) => T): T {
+ const store = useContext(HooksStoreContext)
+ if (!store)
+ throw new Error('Missing HooksStoreContext.Provider in the tree')
+
+ return useZustandStore(store, selector)
+}
+
+export const useHooksStoreApi = () => {
+ return useContext(HooksStoreContext)!
+}
diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts
index 463e9b3271..20a34c69e3 100644
--- a/web/app/components/workflow/hooks/index.ts
+++ b/web/app/components/workflow/hooks/index.ts
@@ -5,7 +5,6 @@ export * from './use-nodes-data'
export * from './use-nodes-sync-draft'
export * from './use-workflow'
export * from './use-workflow-run'
-export * from './use-workflow-template'
export * from './use-checklist'
export * from './use-selection-interactions'
export * from './use-panel-interactions'
@@ -16,3 +15,4 @@ export * from './use-workflow-variables'
export * from './use-shortcuts'
export * from './use-workflow-interactions'
export * from './use-workflow-mode'
+export * from './use-format-time-from-now'
diff --git a/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts
new file mode 100644
index 0000000000..c4c709cd25
--- /dev/null
+++ b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts
@@ -0,0 +1,27 @@
+import { useCallback } from 'react'
+import produce from 'immer'
+import { useStoreApi } from 'reactflow'
+
+export const useEdgesInteractionsWithoutSync = () => {
+ const store = useStoreApi()
+
+ const handleEdgeCancelRunningStatus = useCallback(() => {
+ const {
+ edges,
+ setEdges,
+ } = store.getState()
+
+ const newEdges = produce(edges, (draft) => {
+ draft.forEach((edge) => {
+ edge.data._sourceRunningStatus = undefined
+ edge.data._targetRunningStatus = undefined
+ edge.data._waitingRun = false
+ })
+ })
+ setEdges(newEdges)
+ }, [store])
+
+ return {
+ handleEdgeCancelRunningStatus,
+ }
+}
diff --git a/web/app/components/workflow/hooks/use-edges-interactions.ts b/web/app/components/workflow/hooks/use-edges-interactions.ts
index 688f0b26ce..306af1e96c 100644
--- a/web/app/components/workflow/hooks/use-edges-interactions.ts
+++ b/web/app/components/workflow/hooks/use-edges-interactions.ts
@@ -151,28 +151,11 @@ export const useEdgesInteractions = () => {
setEdges(newEdges)
}, [store, getNodesReadOnly])
- const handleEdgeCancelRunningStatus = useCallback(() => {
- const {
- edges,
- setEdges,
- } = store.getState()
-
- const newEdges = produce(edges, (draft) => {
- draft.forEach((edge) => {
- edge.data._sourceRunningStatus = undefined
- edge.data._targetRunningStatus = undefined
- edge.data._waitingRun = false
- })
- })
- setEdges(newEdges)
- }, [store])
-
return {
handleEdgeEnter,
handleEdgeLeave,
handleEdgeDeleteByDeleteBranch,
handleEdgeDelete,
handleEdgesChange,
- handleEdgeCancelRunningStatus,
}
}
diff --git a/web/app/components/workflow/hooks/use-format-time-from-now.ts b/web/app/components/workflow/hooks/use-format-time-from-now.ts
new file mode 100644
index 0000000000..b2b521557f
--- /dev/null
+++ b/web/app/components/workflow/hooks/use-format-time-from-now.ts
@@ -0,0 +1,12 @@
+import dayjs from 'dayjs'
+import { useCallback } from 'react'
+import { useI18N } from '@/context/i18n'
+
+export const useFormatTimeFromNow = () => {
+ const { locale } = useI18N()
+ const formatTimeFromNow = useCallback((time: number) => {
+ return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
+ }, [locale])
+
+ return { formatTimeFromNow }
+}
diff --git a/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts b/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts
new file mode 100644
index 0000000000..7fbf0ce868
--- /dev/null
+++ b/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts
@@ -0,0 +1,27 @@
+import { useCallback } from 'react'
+import produce from 'immer'
+import { useStoreApi } from 'reactflow'
+
+export const useNodesInteractionsWithoutSync = () => {
+ const store = useStoreApi()
+
+ const handleNodeCancelRunningStatus = useCallback(() => {
+ const {
+ getNodes,
+ setNodes,
+ } = store.getState()
+
+ const nodes = getNodes()
+ const newNodes = produce(nodes, (draft) => {
+ draft.forEach((node) => {
+ node.data._runningStatus = undefined
+ node.data._waitingRun = false
+ })
+ })
+ setNodes(newNodes)
+ }, [store])
+
+ return {
+ handleNodeCancelRunningStatus,
+ }
+}
diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts
index 90231cfcc8..94b10c9929 100644
--- a/web/app/components/workflow/hooks/use-nodes-interactions.ts
+++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts
@@ -1177,22 +1177,6 @@ export const useNodesInteractions = () => {
saveStateToHistory(WorkflowHistoryEvent.NodeChange)
}, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory])
- const handleNodeCancelRunningStatus = useCallback(() => {
- const {
- getNodes,
- setNodes,
- } = store.getState()
-
- const nodes = getNodes()
- const newNodes = produce(nodes, (draft) => {
- draft.forEach((node) => {
- node.data._runningStatus = undefined
- node.data._waitingRun = false
- })
- })
- setNodes(newNodes)
- }, [store])
-
const handleNodesCancelSelected = useCallback(() => {
const {
getNodes,
@@ -1554,7 +1538,6 @@ export const useNodesInteractions = () => {
handleNodeDelete,
handleNodeChange,
handleNodeAdd,
- handleNodeCancelRunningStatus,
handleNodesCancelSelected,
handleNodeContextMenu,
handleNodesCopy,
diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts
index 5cd8f36bff..e6cc3a97e3 100644
--- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts
+++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts
@@ -1,147 +1,17 @@
import { useCallback } from 'react'
-import produce from 'immer'
-import { useStoreApi } from 'reactflow'
-import { useParams } from 'next/navigation'
import {
useStore,
- useWorkflowStore,
} from '../store'
-import { BlockEnum } from '../types'
-import { useWorkflowUpdate } from '../hooks'
import {
useNodesReadOnly,
} from './use-workflow'
-import { syncWorkflowDraft } from '@/service/workflow'
-import { useFeaturesStore } from '@/app/components/base/features/hooks'
-import { API_PREFIX } from '@/config'
+import { useHooksStore } from '@/app/components/workflow/hooks-store'
export const useNodesSyncDraft = () => {
- const store = useStoreApi()
- const workflowStore = useWorkflowStore()
- const featuresStore = useFeaturesStore()
const { getNodesReadOnly } = useNodesReadOnly()
- const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
const debouncedSyncWorkflowDraft = useStore(s => s.debouncedSyncWorkflowDraft)
- const params = useParams()
-
- const getPostParams = useCallback(() => {
- const {
- getNodes,
- edges,
- transform,
- } = store.getState()
- const [x, y, zoom] = transform
- const {
- appId,
- conversationVariables,
- environmentVariables,
- syncWorkflowDraftHash,
- } = workflowStore.getState()
-
- if (appId) {
- const nodes = getNodes()
- const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start)
-
- if (!hasStartNode)
- return
-
- const features = featuresStore!.getState().features
- const producedNodes = produce(nodes, (draft) => {
- draft.forEach((node) => {
- Object.keys(node.data).forEach((key) => {
- if (key.startsWith('_'))
- delete node.data[key]
- })
- })
- })
- const producedEdges = produce(edges, (draft) => {
- draft.forEach((edge) => {
- Object.keys(edge.data).forEach((key) => {
- if (key.startsWith('_'))
- delete edge.data[key]
- })
- })
- })
- return {
- url: `/apps/${appId}/workflows/draft`,
- params: {
- graph: {
- nodes: producedNodes,
- edges: producedEdges,
- viewport: {
- x,
- y,
- zoom,
- },
- },
- features: {
- opening_statement: features.opening?.enabled ? (features.opening?.opening_statement || '') : '',
- suggested_questions: features.opening?.enabled ? (features.opening?.suggested_questions || []) : [],
- suggested_questions_after_answer: features.suggested,
- text_to_speech: features.text2speech,
- speech_to_text: features.speech2text,
- retriever_resource: features.citation,
- sensitive_word_avoidance: features.moderation,
- file_upload: features.file,
- },
- environment_variables: environmentVariables,
- conversation_variables: conversationVariables,
- hash: syncWorkflowDraftHash,
- },
- }
- }
- }, [store, featuresStore, workflowStore])
-
- const syncWorkflowDraftWhenPageClose = useCallback(() => {
- if (getNodesReadOnly())
- return
- const postParams = getPostParams()
-
- if (postParams) {
- navigator.sendBeacon(
- `${API_PREFIX}/apps/${params.appId}/workflows/draft?_token=${localStorage.getItem('console_token')}`,
- JSON.stringify(postParams.params),
- )
- }
- }, [getPostParams, params.appId, getNodesReadOnly])
-
- const doSyncWorkflowDraft = useCallback(async (
- notRefreshWhenSyncError?: boolean,
- callback?: {
- onSuccess?: () => void
- onError?: () => void
- onSettled?: () => void
- },
- ) => {
- if (getNodesReadOnly())
- return
- const postParams = getPostParams()
-
- if (postParams) {
- const {
- setSyncWorkflowDraftHash,
- setDraftUpdatedAt,
- } = workflowStore.getState()
- try {
- const res = await syncWorkflowDraft(postParams)
- setSyncWorkflowDraftHash(res.hash)
- setDraftUpdatedAt(res.updated_at)
- callback?.onSuccess && callback.onSuccess()
- }
- catch (error: any) {
- if (error && error.json && !error.bodyUsed) {
- error.json().then((err: any) => {
- if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
- handleRefreshWorkflowDraft()
- })
- }
- callback?.onError && callback.onError()
- }
- finally {
- callback?.onSettled && callback.onSettled()
- }
- }
- }, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft])
+ const doSyncWorkflowDraft = useHooksStore(s => s.doSyncWorkflowDraft)
+ const syncWorkflowDraftWhenPageClose = useHooksStore(s => s.syncWorkflowDraftWhenPageClose)
const handleSyncWorkflowDraft = useCallback((
sync?: boolean,
diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts
index 202867e22f..740868c594 100644
--- a/web/app/components/workflow/hooks/use-workflow-interactions.ts
+++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts
@@ -25,8 +25,8 @@ import {
useSelectionInteractions,
useWorkflowReadOnly,
} from '../hooks'
-import { useEdgesInteractions } from './use-edges-interactions'
-import { useNodesInteractions } from './use-nodes-interactions'
+import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'
+import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import { useEventEmitterContextContext } from '@/context/event-emitter'
@@ -37,8 +37,8 @@ import { useStore as useAppStore } from '@/app/components/app/store'
export const useWorkflowInteractions = () => {
const workflowStore = useWorkflowStore()
- const { handleNodeCancelRunningStatus } = useNodesInteractions()
- const { handleEdgeCancelRunningStatus } = useEdgesInteractions()
+ const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync()
+ const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
const handleCancelDebugAndPreviewPanel = useCallback(() => {
workflowStore.setState({
diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts
index 99d9a45702..05a60ebb4b 100644
--- a/web/app/components/workflow/hooks/use-workflow-run.ts
+++ b/web/app/components/workflow/hooks/use-workflow-run.ts
@@ -1,350 +1,11 @@
-import { useCallback } from 'react'
-import {
- useReactFlow,
- useStoreApi,
-} from 'reactflow'
-import produce from 'immer'
-import { v4 as uuidV4 } from 'uuid'
-import { usePathname } from 'next/navigation'
-import { useWorkflowStore } from '../store'
-import { useNodesSyncDraft } from '../hooks'
-import { WorkflowRunningStatus } from '../types'
-import { useWorkflowUpdate } from './use-workflow-interactions'
-import { useWorkflowRunEvent } from './use-workflow-run-event/use-workflow-run-event'
-import { useStore as useAppStore } from '@/app/components/app/store'
-import type { IOtherOptions } from '@/service/base'
-import { ssePost } from '@/service/base'
-import { stopWorkflowRun } from '@/service/workflow'
-import { useFeaturesStore } from '@/app/components/base/features/hooks'
-import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
-import type { VersionHistory } from '@/types/workflow'
-import { noop } from 'lodash-es'
+import { useHooksStore } from '@/app/components/workflow/hooks-store'
export const useWorkflowRun = () => {
- const store = useStoreApi()
- const workflowStore = useWorkflowStore()
- const reactflow = useReactFlow()
- const featuresStore = useFeaturesStore()
- const { doSyncWorkflowDraft } = useNodesSyncDraft()
- const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
- const pathname = usePathname()
- const {
- handleWorkflowStarted,
- handleWorkflowFinished,
- handleWorkflowFailed,
- handleWorkflowNodeStarted,
- handleWorkflowNodeFinished,
- handleWorkflowNodeIterationStarted,
- handleWorkflowNodeIterationNext,
- handleWorkflowNodeIterationFinished,
- handleWorkflowNodeLoopStarted,
- handleWorkflowNodeLoopNext,
- handleWorkflowNodeLoopFinished,
- handleWorkflowNodeRetry,
- handleWorkflowAgentLog,
- handleWorkflowTextChunk,
- handleWorkflowTextReplace,
- } = useWorkflowRunEvent()
-
- const handleBackupDraft = useCallback(() => {
- const {
- getNodes,
- edges,
- } = store.getState()
- const { getViewport } = reactflow
- const {
- backupDraft,
- setBackupDraft,
- environmentVariables,
- } = workflowStore.getState()
- const { features } = featuresStore!.getState()
-
- if (!backupDraft) {
- setBackupDraft({
- nodes: getNodes(),
- edges,
- viewport: getViewport(),
- features,
- environmentVariables,
- })
- doSyncWorkflowDraft()
- }
- }, [reactflow, workflowStore, store, featuresStore, doSyncWorkflowDraft])
-
- const handleLoadBackupDraft = useCallback(() => {
- const {
- backupDraft,
- setBackupDraft,
- setEnvironmentVariables,
- } = workflowStore.getState()
-
- if (backupDraft) {
- const {
- nodes,
- edges,
- viewport,
- features,
- environmentVariables,
- } = backupDraft
- handleUpdateWorkflowCanvas({
- nodes,
- edges,
- viewport,
- })
- setEnvironmentVariables(environmentVariables)
- featuresStore!.setState({ features })
- setBackupDraft(undefined)
- }
- }, [handleUpdateWorkflowCanvas, workflowStore, featuresStore])
-
- const handleRun = useCallback(async (
- params: any,
- callback?: IOtherOptions,
- ) => {
- const {
- getNodes,
- setNodes,
- } = store.getState()
- const newNodes = produce(getNodes(), (draft) => {
- draft.forEach((node) => {
- node.data.selected = false
- node.data._runningStatus = undefined
- })
- })
- setNodes(newNodes)
- await doSyncWorkflowDraft()
-
- const {
- onWorkflowStarted,
- onWorkflowFinished,
- onNodeStarted,
- onNodeFinished,
- onIterationStart,
- onIterationNext,
- onIterationFinish,
- onLoopStart,
- onLoopNext,
- onLoopFinish,
- onNodeRetry,
- onAgentLog,
- onError,
- ...restCallback
- } = callback || {}
- workflowStore.setState({ historyWorkflowData: undefined })
- const appDetail = useAppStore.getState().appDetail
- const workflowContainer = document.getElementById('workflow-container')
-
- const {
- clientWidth,
- clientHeight,
- } = workflowContainer!
-
- let url = ''
- if (appDetail?.mode === 'advanced-chat')
- url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
-
- if (appDetail?.mode === 'workflow')
- url = `/apps/${appDetail.id}/workflows/draft/run`
-
- const {
- setWorkflowRunningData,
- } = workflowStore.getState()
- setWorkflowRunningData({
- result: {
- status: WorkflowRunningStatus.Running,
- },
- tracing: [],
- resultText: '',
- })
-
- let ttsUrl = ''
- let ttsIsPublic = false
- if (params.token) {
- ttsUrl = '/text-to-audio'
- ttsIsPublic = true
- }
- else if (params.appId) {
- if (pathname.search('explore/installed') > -1)
- ttsUrl = `/installed-apps/${params.appId}/text-to-audio`
- else
- ttsUrl = `/apps/${params.appId}/text-to-audio`
- }
- const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', noop)
-
- ssePost(
- url,
- {
- body: params,
- },
- {
- onWorkflowStarted: (params) => {
- handleWorkflowStarted(params)
-
- if (onWorkflowStarted)
- onWorkflowStarted(params)
- },
- onWorkflowFinished: (params) => {
- handleWorkflowFinished(params)
-
- if (onWorkflowFinished)
- onWorkflowFinished(params)
- },
- onError: (params) => {
- handleWorkflowFailed()
-
- if (onError)
- onError(params)
- },
- onNodeStarted: (params) => {
- handleWorkflowNodeStarted(
- params,
- {
- clientWidth,
- clientHeight,
- },
- )
-
- if (onNodeStarted)
- onNodeStarted(params)
- },
- onNodeFinished: (params) => {
- handleWorkflowNodeFinished(params)
-
- if (onNodeFinished)
- onNodeFinished(params)
- },
- onIterationStart: (params) => {
- handleWorkflowNodeIterationStarted(
- params,
- {
- clientWidth,
- clientHeight,
- },
- )
-
- if (onIterationStart)
- onIterationStart(params)
- },
- onIterationNext: (params) => {
- handleWorkflowNodeIterationNext(params)
-
- if (onIterationNext)
- onIterationNext(params)
- },
- onIterationFinish: (params) => {
- handleWorkflowNodeIterationFinished(params)
-
- if (onIterationFinish)
- onIterationFinish(params)
- },
- onLoopStart: (params) => {
- handleWorkflowNodeLoopStarted(
- params,
- {
- clientWidth,
- clientHeight,
- },
- )
-
- if (onLoopStart)
- onLoopStart(params)
- },
- onLoopNext: (params) => {
- handleWorkflowNodeLoopNext(params)
-
- if (onLoopNext)
- onLoopNext(params)
- },
- onLoopFinish: (params) => {
- handleWorkflowNodeLoopFinished(params)
-
- if (onLoopFinish)
- onLoopFinish(params)
- },
- onNodeRetry: (params) => {
- handleWorkflowNodeRetry(params)
-
- if (onNodeRetry)
- onNodeRetry(params)
- },
- onAgentLog: (params) => {
- handleWorkflowAgentLog(params)
-
- if (onAgentLog)
- onAgentLog(params)
- },
- onTextChunk: (params) => {
- handleWorkflowTextChunk(params)
- },
- onTextReplace: (params) => {
- handleWorkflowTextReplace(params)
- },
- onTTSChunk: (messageId: string, audio: string) => {
- if (!audio || audio === '')
- return
- player.playAudioWithAudio(audio, true)
- AudioPlayerManager.getInstance().resetMsgId(messageId)
- },
- onTTSEnd: (messageId: string, audio: string) => {
- player.playAudioWithAudio(audio, false)
- },
- ...restCallback,
- },
- )
- }, [
- store,
- workflowStore,
- doSyncWorkflowDraft,
- handleWorkflowStarted,
- handleWorkflowFinished,
- handleWorkflowFailed,
- handleWorkflowNodeStarted,
- handleWorkflowNodeFinished,
- handleWorkflowNodeIterationStarted,
- handleWorkflowNodeIterationNext,
- handleWorkflowNodeIterationFinished,
- handleWorkflowNodeLoopStarted,
- handleWorkflowNodeLoopNext,
- handleWorkflowNodeLoopFinished,
- handleWorkflowNodeRetry,
- handleWorkflowTextChunk,
- handleWorkflowTextReplace,
- handleWorkflowAgentLog,
- pathname],
- )
-
- const handleStopRun = useCallback((taskId: string) => {
- const appId = useAppStore.getState().appDetail?.id
-
- stopWorkflowRun(`/apps/${appId}/workflow-runs/tasks/${taskId}/stop`)
- }, [])
-
- const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
- const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } }))
- const edges = publishedWorkflow.graph.edges
- const viewport = publishedWorkflow.graph.viewport!
- handleUpdateWorkflowCanvas({
- nodes,
- edges,
- viewport,
- })
- const mappedFeatures = {
- opening: {
- enabled: !!publishedWorkflow.features.opening_statement || !!publishedWorkflow.features.suggested_questions.length,
- opening_statement: publishedWorkflow.features.opening_statement,
- suggested_questions: publishedWorkflow.features.suggested_questions,
- },
- suggested: publishedWorkflow.features.suggested_questions_after_answer,
- text2speech: publishedWorkflow.features.text_to_speech,
- speech2text: publishedWorkflow.features.speech_to_text,
- citation: publishedWorkflow.features.retriever_resource,
- moderation: publishedWorkflow.features.sensitive_word_avoidance,
- file: publishedWorkflow.features.file_upload,
- }
-
- featuresStore?.setState({ features: mappedFeatures })
- workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
- }, [featuresStore, handleUpdateWorkflowCanvas, workflowStore])
+ const handleBackupDraft = useHooksStore(s => s.handleBackupDraft)
+ const handleLoadBackupDraft = useHooksStore(s => s.handleLoadBackupDraft)
+ const handleRestoreFromPublishedWorkflow = useHooksStore(s => s.handleRestoreFromPublishedWorkflow)
+ const handleRun = useHooksStore(s => s.handleRun)
+ const handleStopRun = useHooksStore(s => s.handleStopRun)
return {
handleBackupDraft,
diff --git a/web/app/components/workflow/hooks/use-workflow-start-run.tsx b/web/app/components/workflow/hooks/use-workflow-start-run.tsx
index b2b1c69975..0f4e68fe95 100644
--- a/web/app/components/workflow/hooks/use-workflow-start-run.tsx
+++ b/web/app/components/workflow/hooks/use-workflow-start-run.tsx
@@ -1,92 +1,9 @@
-import { useCallback } from 'react'
-import { useStoreApi } from 'reactflow'
-import { useWorkflowStore } from '../store'
-import {
- BlockEnum,
- WorkflowRunningStatus,
-} from '../types'
-import {
- useIsChatMode,
- useNodesSyncDraft,
- useWorkflowInteractions,
- useWorkflowRun,
-} from './index'
-import { useFeaturesStore } from '@/app/components/base/features/hooks'
+import { useHooksStore } from '@/app/components/workflow/hooks-store'
export const useWorkflowStartRun = () => {
- const store = useStoreApi()
- const workflowStore = useWorkflowStore()
- const featuresStore = useFeaturesStore()
- const isChatMode = useIsChatMode()
- const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
- const { handleRun } = useWorkflowRun()
- const { doSyncWorkflowDraft } = useNodesSyncDraft()
-
- const handleWorkflowStartRunInWorkflow = useCallback(async () => {
- const {
- workflowRunningData,
- } = workflowStore.getState()
-
- if (workflowRunningData?.result.status === WorkflowRunningStatus.Running)
- return
-
- const { getNodes } = store.getState()
- const nodes = getNodes()
- const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
- const startVariables = startNode?.data.variables || []
- const fileSettings = featuresStore!.getState().features.file
- const {
- showDebugAndPreviewPanel,
- setShowDebugAndPreviewPanel,
- setShowInputsPanel,
- setShowEnvPanel,
- } = workflowStore.getState()
-
- setShowEnvPanel(false)
-
- if (showDebugAndPreviewPanel) {
- handleCancelDebugAndPreviewPanel()
- return
- }
-
- if (!startVariables.length && !fileSettings?.image?.enabled) {
- await doSyncWorkflowDraft()
- handleRun({ inputs: {}, files: [] })
- setShowDebugAndPreviewPanel(true)
- setShowInputsPanel(false)
- }
- else {
- setShowDebugAndPreviewPanel(true)
- setShowInputsPanel(true)
- }
- }, [store, workflowStore, featuresStore, handleCancelDebugAndPreviewPanel, handleRun, doSyncWorkflowDraft])
-
- const handleWorkflowStartRunInChatflow = useCallback(async () => {
- const {
- showDebugAndPreviewPanel,
- setShowDebugAndPreviewPanel,
- setHistoryWorkflowData,
- setShowEnvPanel,
- setShowChatVariablePanel,
- } = workflowStore.getState()
-
- setShowEnvPanel(false)
- setShowChatVariablePanel(false)
-
- if (showDebugAndPreviewPanel)
- handleCancelDebugAndPreviewPanel()
- else
- setShowDebugAndPreviewPanel(true)
-
- setHistoryWorkflowData(undefined)
- }, [workflowStore, handleCancelDebugAndPreviewPanel])
-
- const handleStartWorkflowRun = useCallback(() => {
- if (!isChatMode)
- handleWorkflowStartRunInWorkflow()
- else
- handleWorkflowStartRunInChatflow()
- }, [isChatMode, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow])
+ const handleStartWorkflowRun = useHooksStore(s => s.handleStartWorkflowRun)
+ const handleWorkflowStartRunInWorkflow = useHooksStore(s => s.handleWorkflowStartRunInWorkflow)
+ const handleWorkflowStartRunInChatflow = useHooksStore(s => s.handleWorkflowStartRunInChatflow)
return {
handleStartWorkflowRun,
diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts
index 7a15afa2e4..d59744cba8 100644
--- a/web/app/components/workflow/hooks/use-workflow.ts
+++ b/web/app/components/workflow/hooks/use-workflow.ts
@@ -1,13 +1,9 @@
import {
useCallback,
- useEffect,
useMemo,
- useState,
} from 'react'
-import dayjs from 'dayjs'
import { uniqBy } from 'lodash-es'
import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
import {
getIncomers,
getOutgoers,
@@ -40,25 +36,15 @@ import {
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
import { useNodesExtraData } from './use-nodes-data'
-import { useWorkflowTemplate } from './use-workflow-template'
import { useStore as useAppStore } from '@/app/components/app/store'
-import {
- fetchNodesDefaultConfigs,
- fetchPublishedWorkflow,
- fetchWorkflowDraft,
- syncWorkflowDraft,
-} from '@/service/workflow'
-import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import {
fetchAllBuiltInTools,
fetchAllCustomTools,
fetchAllWorkflowTools,
} from '@/service/tools'
-import I18n from '@/context/i18n'
import { CollectionType } from '@/app/components/tools/types'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
-import { useWorkflowConfig } from '@/service/use-workflow'
import { basePath } from '@/utils/var'
import { canFindTool } from '@/utils'
@@ -70,12 +56,9 @@ export const useIsChatMode = () => {
export const useWorkflow = () => {
const { t } = useTranslation()
- const { locale } = useContext(I18n)
const store = useStoreApi()
const workflowStore = useWorkflowStore()
- const appId = useStore(s => s.appId)
const nodesExtraData = useNodesExtraData()
- const { data: workflowConfig } = useWorkflowConfig(appId)
const setPanelWidth = useCallback((width: number) => {
localStorage.setItem('workflow-node-panel-width', `${width}`)
workflowStore.setState({ panelWidth: width })
@@ -344,6 +327,7 @@ export const useWorkflow = () => {
parallelList,
hasAbnormalEdges,
} = getParallelInfo(nodes, edges, parentNodeId)
+ const { workflowConfig } = workflowStore.getState()
if (hasAbnormalEdges)
return false
@@ -359,7 +343,7 @@ export const useWorkflow = () => {
}
return true
- }, [t, workflowStore, workflowConfig?.parallel_depth_limit])
+ }, [t, workflowStore])
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
const {
@@ -407,10 +391,6 @@ export const useWorkflow = () => {
return !hasCycle(targetNode)
}, [store, nodesExtraData, checkParallelLimit])
- const formatTimeFromNow = useCallback((time: number) => {
- return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
- }, [locale])
-
const getNode = useCallback((nodeId?: string) => {
const { getNodes } = store.getState()
const nodes = getNodes()
@@ -432,7 +412,6 @@ export const useWorkflow = () => {
checkNestedParallelLimit,
isValidConnection,
isFromStartNode,
- formatTimeFromNow,
getNode,
getBeforeNodeById,
getIterationNodeChildren,
@@ -478,107 +457,6 @@ export const useFetchToolsData = () => {
}
}
-export const useWorkflowInit = () => {
- const workflowStore = useWorkflowStore()
- const {
- nodes: nodesTemplate,
- edges: edgesTemplate,
- } = useWorkflowTemplate()
- const { handleFetchAllTools } = useFetchToolsData()
- const appDetail = useAppStore(state => state.appDetail)!
- const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash)
- const [data, setData] = useState()
- const [isLoading, setIsLoading] = useState(true)
- useEffect(() => {
- workflowStore.setState({ appId: appDetail.id })
- }, [appDetail.id, workflowStore])
-
- const handleGetInitialWorkflowData = useCallback(async () => {
- try {
- const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`)
- setData(res)
- workflowStore.setState({
- envSecrets: (res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
- acc[env.id] = env.value
- return acc
- }, {} as Record),
- environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
- conversationVariables: res.conversation_variables || [],
- })
- setSyncWorkflowDraftHash(res.hash)
- setIsLoading(false)
- }
- catch (error: any) {
- if (error && error.json && !error.bodyUsed && appDetail) {
- error.json().then((err: any) => {
- if (err.code === 'draft_workflow_not_exist') {
- workflowStore.setState({ notInitialWorkflow: true })
- syncWorkflowDraft({
- url: `/apps/${appDetail.id}/workflows/draft`,
- params: {
- graph: {
- nodes: nodesTemplate,
- edges: edgesTemplate,
- },
- features: {
- retriever_resource: { enabled: true },
- },
- environment_variables: [],
- conversation_variables: [],
- },
- }).then((res) => {
- workflowStore.getState().setDraftUpdatedAt(res.updated_at)
- handleGetInitialWorkflowData()
- })
- }
- })
- }
- }
- }, [appDetail, nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash])
-
- useEffect(() => {
- handleGetInitialWorkflowData()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
-
- const handleFetchPreloadData = useCallback(async () => {
- try {
- const nodesDefaultConfigsData = await fetchNodesDefaultConfigs(`/apps/${appDetail?.id}/workflows/default-workflow-block-configs`)
- const publishedWorkflow = await fetchPublishedWorkflow(`/apps/${appDetail?.id}/workflows/publish`)
- workflowStore.setState({
- nodesDefaultConfigs: nodesDefaultConfigsData.reduce((acc, block) => {
- if (!acc[block.type])
- acc[block.type] = { ...block.config }
- return acc
- }, {} as Record),
- })
- workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at)
- }
- catch (e) {
- console.error(e)
- }
- }, [workflowStore, appDetail])
-
- useEffect(() => {
- handleFetchPreloadData()
- handleFetchAllTools('builtin')
- handleFetchAllTools('custom')
- handleFetchAllTools('workflow')
- }, [handleFetchPreloadData, handleFetchAllTools])
-
- useEffect(() => {
- if (data) {
- workflowStore.getState().setDraftUpdatedAt(data.updated_at)
- workflowStore.getState().setToolPublished(data.tool_published)
- }
- }, [data, workflowStore])
-
- return {
- data,
- isLoading,
- }
-}
-
export const useWorkflowReadOnly = () => {
const workflowStore = useWorkflowStore()
const workflowRunningData = useStore(s => s.workflowRunningData)
diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx
index 4c48afb56c..7575270cb1 100644
--- a/web/app/components/workflow/index.tsx
+++ b/web/app/components/workflow/index.tsx
@@ -5,11 +5,8 @@ import {
memo,
useCallback,
useEffect,
- useMemo,
useRef,
- useState,
} from 'react'
-import useSWR from 'swr'
import { setAutoFreeze } from 'immer'
import {
useEventListener,
@@ -31,17 +28,14 @@ import 'reactflow/dist/style.css'
import './style.css'
import type {
Edge,
- EnvironmentVariable,
Node,
} from './types'
import {
ControlMode,
- SupportUploadFileTypes,
} from './types'
-import { WorkflowContextProvider } from './context'
import {
- useDSL,
useEdgesInteractions,
+ useFetchToolsData,
useNodesInteractions,
useNodesReadOnly,
useNodesSyncDraft,
@@ -49,11 +43,9 @@ import {
useSelectionInteractions,
useShortcuts,
useWorkflow,
- useWorkflowInit,
useWorkflowReadOnly,
useWorkflowUpdate,
} from './hooks'
-import Header from './header'
import CustomNode from './nodes'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
@@ -66,42 +58,28 @@ import { CUSTOM_SIMPLE_NODE } from './simple-node/constants'
import Operator from './operator'
import CustomEdge from './custom-edge'
import CustomConnectionLine from './custom-connection-line'
-import Panel from './panel'
-import Features from './features'
import HelpLine from './help-line'
import CandidateNode from './candidate-node'
import PanelContextmenu from './panel-contextmenu'
import NodeContextmenu from './node-contextmenu'
import SyncingDataModal from './syncing-data-modal'
-import UpdateDSLModal from './update-dsl-modal'
-import DSLExportConfirmModal from './dsl-export-confirm-modal'
import LimitTips from './limit-tips'
-import PluginDependency from './plugin-dependency'
import {
useStore,
useWorkflowStore,
} from './store'
-import {
- initialEdges,
- initialNodes,
-} from './utils'
import {
CUSTOM_EDGE,
CUSTOM_NODE,
- DSL_EXPORT_CHECK,
ITERATION_CHILDREN_Z_INDEX,
WORKFLOW_DATA_UPDATE,
} from './constants'
import { WorkflowHistoryProvider } from './workflow-history-store'
-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 { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import Confirm from '@/app/components/base/confirm'
-import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
-import { fetchFileUploadConfig } from '@/service/common'
import DatasetsDetailProvider from './datasets-detail-store/provider'
+import { HooksStoreContextProvider } from './hooks-store'
+import type { Shape as HooksStoreShape } from './hooks-store'
const nodeTypes = {
[CUSTOM_NODE]: CustomNode,
@@ -114,32 +92,32 @@ const edgeTypes = {
[CUSTOM_EDGE]: CustomEdge,
}
-type WorkflowProps = {
+export type WorkflowProps = {
nodes: Node[]
edges: Edge[]
viewport?: Viewport
+ children?: React.ReactNode
+ onWorkflowDataUpdate?: (v: any) => void
}
-const Workflow: FC = memo(({
+export const Workflow: FC = memo(({
nodes: originalNodes,
edges: originalEdges,
viewport,
+ children,
+ onWorkflowDataUpdate,
}) => {
const workflowContainerRef = useRef(null)
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()
- const featuresStore = useFeaturesStore()
const [nodes, setNodes] = useNodesState(originalNodes)
const [edges, setEdges] = useEdgesState(originalEdges)
- const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
const controlMode = useStore(s => s.controlMode)
const nodeAnimation = useStore(s => s.nodeAnimation)
const showConfirm = useStore(s => s.showConfirm)
- const showImportDSLModal = useStore(s => s.showImportDSLModal)
const {
setShowConfirm,
setControlPromptEditorRerenderKey,
- setShowImportDSLModal,
setSyncWorkflowDraftHash,
} = workflowStore.getState()
const {
@@ -148,12 +126,10 @@ const Workflow: FC = memo(({
} = useNodesSyncDraft()
const { workflowReadOnly } = useWorkflowReadOnly()
const { nodesReadOnly } = useNodesReadOnly()
-
- const [secretEnvList, setSecretEnvList] = useState([])
-
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
+ console.log(v, 'eventEmitter')
if (v.type === WORKFLOW_DATA_UPDATE) {
setNodes(v.payload.nodes)
setEdges(v.payload.edges)
@@ -161,19 +137,13 @@ const Workflow: FC = memo(({
if (v.payload.viewport)
reactflow.setViewport(v.payload.viewport)
- if (v.payload.features && featuresStore) {
- const { setFeatures } = featuresStore.getState()
-
- setFeatures(v.payload.features)
- }
-
if (v.payload.hash)
setSyncWorkflowDraftHash(v.payload.hash)
+ onWorkflowDataUpdate?.(v.payload)
+
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
}
- if (v.type === DSL_EXPORT_CHECK)
- setSecretEnvList(v.payload.data as EnvironmentVariable[])
})
useEffect(() => {
@@ -231,6 +201,12 @@ const Workflow: FC = memo(({
})
}
})
+ const { handleFetchAllTools } = useFetchToolsData()
+ useEffect(() => {
+ handleFetchAllTools('builtin')
+ handleFetchAllTools('custom')
+ handleFetchAllTools('workflow')
+ }, [handleFetchAllTools])
const {
handleNodeDragStart,
@@ -258,15 +234,10 @@ const Workflow: FC = memo(({
} = useSelectionInteractions()
const {
handlePaneContextMenu,
- handlePaneContextmenuCancel,
} = usePanelInteractions()
const {
isValidConnection,
} = useWorkflow()
- const {
- exportCheck,
- handleExportDSL,
- } = useDSL()
useOnViewportChange({
onEnd: () => {
@@ -297,12 +268,7 @@ const Workflow: FC = memo(({
>
-
-
- {
- showFeaturesPanel &&
- }
@@ -317,26 +283,8 @@ const Workflow: FC = memo(({
/>
)
}
- {
- showImportDSLModal && (
- setShowImportDSLModal(false)}
- onBackup={exportCheck}
- onImport={handlePaneContextmenuCancel}
- />
- )
- }
- {
- secretEnvList.length > 0 && (
- setSecretEnvList([])}
- />
- )
- }
-
+ {children}
= memo(({
)
})
-Workflow.displayName = 'Workflow'
-const WorkflowWrap = memo(() => {
- const {
- data,
- isLoading,
- } = useWorkflowInit()
- const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
+type WorkflowWithInnerContextProps = WorkflowProps & {
+ hooksStore?: Partial
+}
+export const WorkflowWithInnerContext = memo(({
+ hooksStore,
+ ...restProps
+}: WorkflowWithInnerContextProps) => {
+ return (
+
+
+
+ )
+})
- const nodesData = useMemo(() => {
- if (data)
- return initialNodes(data.graph.nodes, data.graph.edges)
-
- return []
- }, [data])
- const edgesData = useMemo(() => {
- if (data)
- return initialEdges(data.graph.edges, data.graph.nodes)
-
- return []
- }, [data])
-
- if (!data || isLoading) {
- return (
-
-
-
- )
- }
-
- const features = data.features || {}
- const initialFeatures: FeaturesData = {
- file: {
- image: {
- enabled: !!features.file_upload?.image?.enabled,
- number_limits: features.file_upload?.image?.number_limits || 3,
- transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
- },
- enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
- allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
- allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
- allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
- number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
- fileUploadConfig: fileUploadConfigResponse,
- },
- opening: {
- enabled: !!features.opening_statement,
- opening_statement: features.opening_statement,
- suggested_questions: features.suggested_questions,
- },
- suggested: features.suggested_questions_after_answer || { enabled: false },
- speech2text: features.speech_to_text || { enabled: false },
- text2speech: features.text_to_speech || { enabled: false },
- citation: features.retriever_resource || { enabled: false },
- moderation: features.sensitive_word_avoidance || { enabled: false },
+type WorkflowWithDefaultContextProps =
+ Pick
+ & {
+ children: React.ReactNode
}
+const WorkflowWithDefaultContext = ({
+ nodes,
+ edges,
+ children,
+}: WorkflowWithDefaultContextProps) => {
return (
-
-
-
-
-
+ nodes={nodes}
+ edges={edges} >
+
+ {children}
+
)
-})
-WorkflowWrap.displayName = 'WorkflowWrap'
-
-const WorkflowContainer = () => {
- return (
-
-
-
- )
}
-export default memo(WorkflowContainer)
+export default memo(WorkflowWithDefaultContext)
diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx
index 40920ab256..9d48c6f1d5 100644
--- a/web/app/components/workflow/panel/index.tsx
+++ b/web/app/components/workflow/panel/index.tsx
@@ -1,43 +1,25 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useNodes } from 'reactflow'
-import { useShallow } from 'zustand/react/shallow'
import type { CommonNodeType } from '../types'
import { Panel as NodePanel } from '../nodes'
import { useStore } from '../store'
-import {
- useIsChatMode,
-} from '../hooks'
-import DebugAndPreview from './debug-and-preview'
-import Record from './record'
-import WorkflowPreview from './workflow-preview'
-import ChatRecord from './chat-record'
-import ChatVariablePanel from './chat-variable-panel'
import EnvPanel from './env-panel'
-import GlobalVariablePanel from './global-variable-panel'
-import VersionHistoryPanel from './version-history-panel'
import cn from '@/utils/classnames'
-import { useStore as useAppStore } from '@/app/components/app/store'
-import MessageLogModal from '@/app/components/base/message-log-modal'
-const Panel: FC = () => {
+export type PanelProps = {
+ components?: {
+ left?: React.ReactNode
+ right?: React.ReactNode
+ }
+}
+const Panel: FC = ({
+ components,
+}) => {
const nodes = useNodes()
- const isChatMode = useIsChatMode()
const selectedNode = nodes.find(node => node.data.selected)
- const historyWorkflowData = useStore(s => s.historyWorkflowData)
- const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
const showEnvPanel = useStore(s => s.showEnvPanel)
- const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
- const showGlobalVariablePanel = useStore(s => s.showGlobalVariablePanel)
- const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)
const isRestoring = useStore(s => s.isRestoring)
- const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
- currentLogItem: state.currentLogItem,
- setCurrentLogItem: state.setCurrentLogItem,
- showMessageLogModal: state.showMessageLogModal,
- setShowMessageLogModal: state.setShowMessageLogModal,
- currentLogModalActiveTab: state.currentLogModalActiveTab,
- })))
return (
{
key={`${isRestoring}`}
>
{
- showMessageLogModal && (
-
{
- setCurrentLogItem()
- setShowMessageLogModal(false)
- }}
- defaultTab={currentLogModalActiveTab}
- />
- )
+ components?.left
}
{
!!selectedNode && (
@@ -65,23 +36,11 @@ const Panel: FC = () => {
)
}
{
- historyWorkflowData && !isChatMode && (
-
- )
+ components?.right
}
{
- historyWorkflowData && isChatMode && (
-
- )
- }
- {
- showDebugAndPreviewPanel && isChatMode && (
-
- )
- }
- {
- showDebugAndPreviewPanel && !isChatMode && (
-
+ isRestoring && (
+
)
}
{
@@ -89,21 +48,6 @@ const Panel: FC = () => {
)
}
- {
- showChatVariablePanel && (
-
- )
- }
- {
- showGlobalVariablePanel && (
-
- )
- }
- {
- showWorkflowVersionHistoryPanel && (
-
- )
- }
)
}
diff --git a/web/app/components/workflow/store/workflow/index.ts b/web/app/components/workflow/store/workflow/index.ts
index 769b986606..0e2f5eb0f7 100644
--- a/web/app/components/workflow/store/workflow/index.ts
+++ b/web/app/components/workflow/store/workflow/index.ts
@@ -1,4 +1,7 @@
import { useContext } from 'react'
+import type {
+ StateCreator,
+} from 'zustand'
import {
useStore as useZustandStore,
} from 'zustand'
@@ -26,6 +29,7 @@ import { createWorkflowDraftSlice } from './workflow-draft-slice'
import type { WorkflowSliceShape } from './workflow-slice'
import { createWorkflowSlice } from './workflow-slice'
import { WorkflowContext } from '@/app/components/workflow/context'
+import type { WorkflowSliceShape as WorkflowAppSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice'
export type Shape =
ChatVariableSliceShape &
@@ -38,9 +42,16 @@ export type Shape =
ToolSliceShape &
VersionSliceShape &
WorkflowDraftSliceShape &
- WorkflowSliceShape
+ WorkflowSliceShape &
+ WorkflowAppSliceShape
+
+type CreateWorkflowStoreParams = {
+ injectWorkflowStoreSliceFn?: StateCreator
+}
+
+export const createWorkflowStore = (params: CreateWorkflowStoreParams) => {
+ const { injectWorkflowStoreSliceFn } = params || {}
-export const createWorkflowStore = () => {
return createStore((...args) => ({
...createChatVariableSlice(...args),
...createEnvVariableSlice(...args),
@@ -53,6 +64,7 @@ export const createWorkflowStore = () => {
...createVersionSlice(...args),
...createWorkflowDraftSlice(...args),
...createWorkflowSlice(...args),
+ ...(injectWorkflowStoreSliceFn?.(...args) || {} as WorkflowAppSliceShape),
}))
}
diff --git a/web/app/components/workflow/store/workflow/node-slice.ts b/web/app/components/workflow/store/workflow/node-slice.ts
index d937dc2099..2068ee0ba1 100644
--- a/web/app/components/workflow/store/workflow/node-slice.ts
+++ b/web/app/components/workflow/store/workflow/node-slice.ts
@@ -12,8 +12,6 @@ import type {
export type NodeSliceShape = {
showSingleRunPanel: boolean
setShowSingleRunPanel: (showSingleRunPanel: boolean) => void
- nodesDefaultConfigs: Record
- setNodesDefaultConfigs: (nodesDefaultConfigs: Record) => void
nodeAnimation: boolean
setNodeAnimation: (nodeAnimation: boolean) => void
candidateNode?: Node
@@ -55,8 +53,6 @@ export type NodeSliceShape = {
export const createNodeSlice: StateCreator = set => ({
showSingleRunPanel: false,
setShowSingleRunPanel: showSingleRunPanel => set(() => ({ showSingleRunPanel })),
- nodesDefaultConfigs: {},
- setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
nodeAnimation: false,
setNodeAnimation: nodeAnimation => set(() => ({ nodeAnimation })),
candidateNode: undefined,
diff --git a/web/app/components/workflow/store/workflow/workflow-slice.ts b/web/app/components/workflow/store/workflow/workflow-slice.ts
index 19248161d2..6bb69cdfcd 100644
--- a/web/app/components/workflow/store/workflow/workflow-slice.ts
+++ b/web/app/components/workflow/store/workflow/workflow-slice.ts
@@ -10,11 +10,8 @@ type PreviewRunningData = WorkflowRunningData & {
}
export type WorkflowSliceShape = {
- appId: string
workflowRunningData?: PreviewRunningData
setWorkflowRunningData: (workflowData: PreviewRunningData) => void
- notInitialWorkflow: boolean
- setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
clipboardElements: Node[]
setClipboardElements: (clipboardElements: Node[]) => void
selection: null | { x1: number; y1: number; x2: number; y2: number }
@@ -33,14 +30,13 @@ export type WorkflowSliceShape = {
setShowImportDSLModal: (showImportDSLModal: boolean) => void
showTips: string
setShowTips: (showTips: string) => void
+ workflowConfig?: Record
+ setWorkflowConfig: (workflowConfig: Record) => void
}
export const createWorkflowSlice: StateCreator = set => ({
- appId: '',
workflowRunningData: undefined,
setWorkflowRunningData: workflowRunningData => set(() => ({ workflowRunningData })),
- notInitialWorkflow: false,
- setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })),
clipboardElements: [],
setClipboardElements: clipboardElements => set(() => ({ clipboardElements })),
selection: null,
@@ -62,4 +58,6 @@ export const createWorkflowSlice: StateCreator = set => ({
setShowImportDSLModal: showImportDSLModal => set(() => ({ showImportDSLModal })),
showTips: '',
setShowTips: showTips => set(() => ({ showTips })),
+ workflowConfig: undefined,
+ setWorkflowConfig: workflowConfig => set(() => ({ workflowConfig })),
})
diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts
index ee4132d22f..4321552cc7 100644
--- a/web/service/use-workflow.ts
+++ b/web/service/use-workflow.ts
@@ -21,10 +21,14 @@ export const useAppWorkflow = (appID: string) => {
})
}
-export const useWorkflowConfig = (appId: string) => {
+export const useWorkflowConfig = (appId: string, onSuccess: (v: WorkflowConfigResponse) => void) => {
return useQuery({
queryKey: [NAME_SPACE, 'config', appId],
- queryFn: () => get(`/apps/${appId}/workflows/draft/config`),
+ queryFn: async () => {
+ const data = await get(`/apps/${appId}/workflows/draft/config`)
+ onSuccess(data)
+ return data
+ },
})
}