| useWorkflowStore, | useWorkflowStore, | ||||
| } from '../store' | } from '../store' | ||||
| import { BlockEnum } from '../types' | import { BlockEnum } from '../types' | ||||
| import { useWorkflowUpdate } from '../hooks' | |||||
| import { useNodesReadOnly } from './use-workflow' | import { useNodesReadOnly } from './use-workflow' | ||||
| import { syncWorkflowDraft } from '@/service/workflow' | import { syncWorkflowDraft } from '@/service/workflow' | ||||
| import { useFeaturesStore } from '@/app/components/base/features/hooks' | import { useFeaturesStore } from '@/app/components/base/features/hooks' | ||||
| const workflowStore = useWorkflowStore() | const workflowStore = useWorkflowStore() | ||||
| const featuresStore = useFeaturesStore() | const featuresStore = useFeaturesStore() | ||||
| const { getNodesReadOnly } = useNodesReadOnly() | const { getNodesReadOnly } = useNodesReadOnly() | ||||
| const { handleRefreshWorkflowDraft } = useWorkflowUpdate() | |||||
| const debouncedSyncWorkflowDraft = useStore(s => s.debouncedSyncWorkflowDraft) | const debouncedSyncWorkflowDraft = useStore(s => s.debouncedSyncWorkflowDraft) | ||||
| const params = useParams() | const params = useParams() | ||||
| const getPostParams = useCallback((appIdParams?: string) => { | |||||
| const getPostParams = useCallback(() => { | |||||
| const { | const { | ||||
| getNodes, | getNodes, | ||||
| edges, | edges, | ||||
| transform, | transform, | ||||
| } = store.getState() | } = store.getState() | ||||
| const [x, y, zoom] = transform | const [x, y, zoom] = transform | ||||
| const appId = workflowStore.getState().appId | |||||
| const { | |||||
| appId, | |||||
| syncWorkflowDraftHash, | |||||
| } = workflowStore.getState() | |||||
| if (appId || appIdParams) { | |||||
| if (appId) { | |||||
| const nodes = getNodes() | const nodes = getNodes() | ||||
| const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start) | const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start) | ||||
| }) | }) | ||||
| }) | }) | ||||
| return { | return { | ||||
| url: `/apps/${appId || appIdParams}/workflows/draft`, | |||||
| url: `/apps/${appId}/workflows/draft`, | |||||
| params: { | params: { | ||||
| graph: { | graph: { | ||||
| nodes: producedNodes, | nodes: producedNodes, | ||||
| sensitive_word_avoidance: features.moderation, | sensitive_word_avoidance: features.moderation, | ||||
| file_upload: features.file, | file_upload: features.file, | ||||
| }, | }, | ||||
| hash: syncWorkflowDraftHash, | |||||
| }, | }, | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| }, [getPostParams, params.appId, getNodesReadOnly]) | }, [getPostParams, params.appId, getNodesReadOnly]) | ||||
| const doSyncWorkflowDraft = useCallback(async (appId?: string) => { | |||||
| const doSyncWorkflowDraft = useCallback(async (notRefreshWhenSyncError?: boolean) => { | |||||
| if (getNodesReadOnly()) | if (getNodesReadOnly()) | ||||
| return | return | ||||
| const postParams = getPostParams(appId) | |||||
| const postParams = getPostParams() | |||||
| if (postParams) { | if (postParams) { | ||||
| const res = await syncWorkflowDraft(postParams) | |||||
| workflowStore.getState().setDraftUpdatedAt(res.updated_at) | |||||
| const { | |||||
| setSyncWorkflowDraftHash, | |||||
| setDraftUpdatedAt, | |||||
| } = workflowStore.getState() | |||||
| try { | |||||
| const res = await syncWorkflowDraft(postParams) | |||||
| setSyncWorkflowDraftHash(res.hash) | |||||
| setDraftUpdatedAt(res.updated_at) | |||||
| } | |||||
| catch (error: any) { | |||||
| if (error && error.json && !error.bodyUsed) { | |||||
| error.json().then((err: any) => { | |||||
| if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError) | |||||
| handleRefreshWorkflowDraft() | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| }, [workflowStore, getPostParams, getNodesReadOnly]) | |||||
| }, [workflowStore, getPostParams, getNodesReadOnly, handleRefreshWorkflowDraft]) | |||||
| const handleSyncWorkflowDraft = useCallback((sync?: boolean, appId?: string) => { | |||||
| const handleSyncWorkflowDraft = useCallback((sync?: boolean, notRefreshWhenSyncError?: boolean) => { | |||||
| if (getNodesReadOnly()) | if (getNodesReadOnly()) | ||||
| return | return | ||||
| if (sync) | if (sync) | ||||
| doSyncWorkflowDraft(appId) | |||||
| doSyncWorkflowDraft(notRefreshWhenSyncError) | |||||
| else | else | ||||
| debouncedSyncWorkflowDraft(doSyncWorkflowDraft) | debouncedSyncWorkflowDraft(doSyncWorkflowDraft) | ||||
| }, [debouncedSyncWorkflowDraft, doSyncWorkflowDraft, getNodesReadOnly]) | }, [debouncedSyncWorkflowDraft, doSyncWorkflowDraft, getNodesReadOnly]) |
| import { useEdgesInteractions } from './use-edges-interactions' | import { useEdgesInteractions } from './use-edges-interactions' | ||||
| import { useNodesInteractions } from './use-nodes-interactions' | import { useNodesInteractions } from './use-nodes-interactions' | ||||
| import { useEventEmitterContextContext } from '@/context/event-emitter' | import { useEventEmitterContextContext } from '@/context/event-emitter' | ||||
| import { fetchWorkflowDraft } from '@/service/workflow' | |||||
| export const useWorkflowInteractions = () => { | export const useWorkflowInteractions = () => { | ||||
| const reactflow = useReactFlow() | |||||
| const workflowStore = useWorkflowStore() | const workflowStore = useWorkflowStore() | ||||
| const { handleNodeCancelRunningStatus } = useNodesInteractions() | const { handleNodeCancelRunningStatus } = useNodesInteractions() | ||||
| const { handleEdgeCancelRunningStatus } = useEdgesInteractions() | const { handleEdgeCancelRunningStatus } = useEdgesInteractions() | ||||
| const { eventEmitter } = useEventEmitterContextContext() | |||||
| const handleCancelDebugAndPreviewPanel = useCallback(() => { | const handleCancelDebugAndPreviewPanel = useCallback(() => { | ||||
| workflowStore.setState({ | workflowStore.setState({ | ||||
| handleEdgeCancelRunningStatus() | handleEdgeCancelRunningStatus() | ||||
| }, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus]) | }, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus]) | ||||
| return { | |||||
| handleCancelDebugAndPreviewPanel, | |||||
| } | |||||
| } | |||||
| export const useWorkflowUpdate = () => { | |||||
| const reactflow = useReactFlow() | |||||
| const workflowStore = useWorkflowStore() | |||||
| const { eventEmitter } = useEventEmitterContextContext() | |||||
| const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdator) => { | const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdator) => { | ||||
| const { | const { | ||||
| nodes, | nodes, | ||||
| setViewport(viewport) | setViewport(viewport) | ||||
| }, [eventEmitter, reactflow]) | }, [eventEmitter, reactflow]) | ||||
| const handleRefreshWorkflowDraft = useCallback(() => { | |||||
| const { | |||||
| appId, | |||||
| setSyncWorkflowDraftHash, | |||||
| } = workflowStore.getState() | |||||
| fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { | |||||
| handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdator) | |||||
| setSyncWorkflowDraftHash(response.hash) | |||||
| }) | |||||
| }, [handleUpdateWorkflowCanvas, workflowStore]) | |||||
| return { | return { | ||||
| handleCancelDebugAndPreviewPanel, | |||||
| handleUpdateWorkflowCanvas, | handleUpdateWorkflowCanvas, | ||||
| handleRefreshWorkflowDraft, | |||||
| } | } | ||||
| } | } |
| NodeRunningStatus, | NodeRunningStatus, | ||||
| WorkflowRunningStatus, | WorkflowRunningStatus, | ||||
| } from '../types' | } from '../types' | ||||
| import { useWorkflowInteractions } from './use-workflow-interactions' | |||||
| import { useWorkflowUpdate } from './use-workflow-interactions' | |||||
| import { useStore as useAppStore } from '@/app/components/app/store' | import { useStore as useAppStore } from '@/app/components/app/store' | ||||
| import type { IOtherOptions } from '@/service/base' | import type { IOtherOptions } from '@/service/base' | ||||
| import { ssePost } from '@/service/base' | import { ssePost } from '@/service/base' | ||||
| const reactflow = useReactFlow() | const reactflow = useReactFlow() | ||||
| const featuresStore = useFeaturesStore() | const featuresStore = useFeaturesStore() | ||||
| const { doSyncWorkflowDraft } = useNodesSyncDraft() | const { doSyncWorkflowDraft } = useNodesSyncDraft() | ||||
| const { handleUpdateWorkflowCanvas } = useWorkflowInteractions() | |||||
| const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() | |||||
| const handleBackupDraft = useCallback(() => { | const handleBackupDraft = useCallback(() => { | ||||
| const { | const { |
| } = useWorkflowTemplate() | } = useWorkflowTemplate() | ||||
| const { handleFetchAllTools } = useFetchToolsData() | const { handleFetchAllTools } = useFetchToolsData() | ||||
| const appDetail = useAppStore(state => state.appDetail)! | const appDetail = useAppStore(state => state.appDetail)! | ||||
| const setSyncWorkflowDraftHash = useStore(s => s.setSyncWorkflowDraftHash) | |||||
| const [data, setData] = useState<FetchWorkflowDraftResponse>() | const [data, setData] = useState<FetchWorkflowDraftResponse>() | ||||
| const [isLoading, setIsLoading] = useState(true) | const [isLoading, setIsLoading] = useState(true) | ||||
| workflowStore.setState({ appId: appDetail.id }) | workflowStore.setState({ appId: appDetail.id }) | ||||
| const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`) | const res = await fetchWorkflowDraft(`/apps/${appDetail.id}/workflows/draft`) | ||||
| setData(res) | setData(res) | ||||
| setSyncWorkflowDraftHash(res.hash) | |||||
| setIsLoading(false) | setIsLoading(false) | ||||
| } | } | ||||
| catch (error: any) { | catch (error: any) { | ||||
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| }, [appDetail, nodesTemplate, edgesTemplate, workflowStore]) | |||||
| }, [appDetail, nodesTemplate, edgesTemplate, workflowStore, setSyncWorkflowDraftHash]) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| handleGetInitialWorkflowData() | handleGetInitialWorkflowData() |
| useWorkflowInit, | useWorkflowInit, | ||||
| useWorkflowReadOnly, | useWorkflowReadOnly, | ||||
| useWorkflowStartRun, | useWorkflowStartRun, | ||||
| useWorkflowUpdate, | |||||
| } from './hooks' | } from './hooks' | ||||
| import Header from './header' | import Header from './header' | ||||
| import CustomNode from './nodes' | import CustomNode from './nodes' | ||||
| useEffect(() => { | useEffect(() => { | ||||
| return () => { | return () => { | ||||
| handleSyncWorkflowDraft(true) | |||||
| handleSyncWorkflowDraft(true, true) | |||||
| } | } | ||||
| }, []) | }, []) | ||||
| const { handleRefreshWorkflowDraft } = useWorkflowUpdate() | |||||
| const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { | const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { | ||||
| if (document.visibilityState === 'hidden') | if (document.visibilityState === 'hidden') | ||||
| syncWorkflowDraftWhenPageClose() | syncWorkflowDraftWhenPageClose() | ||||
| }, [syncWorkflowDraftWhenPageClose]) | |||||
| else if (document.visibilityState === 'visible') | |||||
| handleRefreshWorkflowDraft() | |||||
| }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft]) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose) | document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose) |
| setNodeMenu: (nodeMenu: Shape['nodeMenu']) => void | setNodeMenu: (nodeMenu: Shape['nodeMenu']) => void | ||||
| mousePosition: { pageX: number; pageY: number; elementX: number; elementY: number } | mousePosition: { pageX: number; pageY: number; elementX: number; elementY: number } | ||||
| setMousePosition: (mousePosition: Shape['mousePosition']) => void | setMousePosition: (mousePosition: Shape['mousePosition']) => void | ||||
| syncWorkflowDraftHash: string | |||||
| setSyncWorkflowDraftHash: (hash: string) => void | |||||
| } | } | ||||
| export const createWorkflowStore = () => { | export const createWorkflowStore = () => { | ||||
| setNodeMenu: nodeMenu => set(() => ({ nodeMenu })), | setNodeMenu: nodeMenu => set(() => ({ nodeMenu })), | ||||
| mousePosition: { pageX: 0, pageY: 0, elementX: 0, elementY: 0 }, | mousePosition: { pageX: 0, pageY: 0, elementX: 0, elementY: 0 }, | ||||
| setMousePosition: mousePosition => set(() => ({ mousePosition })), | setMousePosition: mousePosition => set(() => ({ mousePosition })), | ||||
| syncWorkflowDraftHash: '', | |||||
| setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })), | |||||
| })) | })) | ||||
| } | } | ||||
| } from '@/types/workflow' | } from '@/types/workflow' | ||||
| import type { BlockEnum } from '@/app/components/workflow/types' | import type { BlockEnum } from '@/app/components/workflow/types' | ||||
| export const fetchWorkflowDraft: Fetcher<FetchWorkflowDraftResponse, string> = (url) => { | |||||
| return get<FetchWorkflowDraftResponse>(url, {}, { silent: true }) | |||||
| export const fetchWorkflowDraft = (url: string) => { | |||||
| return get(url, {}, { silent: true }) as Promise<FetchWorkflowDraftResponse> | |||||
| } | } | ||||
| export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features'> }) => { | export const syncWorkflowDraft = ({ url, params }: { url: string; params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features'> }) => { | ||||
| return post<CommonResponse & { updated_at: number }>(url, { body: params }) | |||||
| return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true }) | |||||
| } | } | ||||
| export const fetchNodesDefaultConfigs: Fetcher<NodesDefaultConfigsResponse, string> = (url) => { | export const fetchNodesDefaultConfigs: Fetcher<NodesDefaultConfigsResponse, string> = (url) => { |
| name: string | name: string | ||||
| email: string | email: string | ||||
| } | } | ||||
| hash: string | |||||
| updated_at: number | updated_at: number | ||||
| } | } | ||||