mirror of
https://github.com/langgenius/dify.git
synced 2026-01-25 21:22:16 +08:00
Complete workflow liberalization following PR #24627: 1. Remove Start node deletion restriction by removing isUndeletable property 2. Fix draft sync blocking when no Start node exists 3. Restore isWorkflowDataLoaded protection to prevent race conditions 4. Ensure all entry nodes (Start + 3 trigger types) have equal deletion rights This allows workflows with only trigger nodes and fixes the issue where added nodes would disappear after page refresh due to sync API blocking. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
4.4 KiB
TypeScript
147 lines
4.4 KiB
TypeScript
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 {
|
|
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'
|
|
import { useWorkflowRefreshDraft } from '.'
|
|
|
|
export const useNodesSyncDraft = () => {
|
|
const store = useStoreApi()
|
|
const workflowStore = useWorkflowStore()
|
|
const featuresStore = useFeaturesStore()
|
|
const { getNodesReadOnly } = useNodesReadOnly()
|
|
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
|
|
const params = useParams()
|
|
|
|
const getPostParams = useCallback(() => {
|
|
const {
|
|
getNodes,
|
|
edges,
|
|
transform,
|
|
} = store.getState()
|
|
const nodes = getNodes()
|
|
const [x, y, zoom] = transform
|
|
const {
|
|
appId,
|
|
conversationVariables,
|
|
environmentVariables,
|
|
syncWorkflowDraftHash,
|
|
isWorkflowDataLoaded,
|
|
} = workflowStore.getState()
|
|
|
|
if (appId) {
|
|
if (!isWorkflowDataLoaded)
|
|
return null
|
|
|
|
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.filter(edge => !edge.data?._isTemp), (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,
|
|
}
|
|
}
|