Spaces:
Build error
Build error
import { | |
useCallback, | |
useState, | |
} from 'react' | |
import { useTranslation } from 'react-i18next' | |
import { useReactFlow, useStoreApi } from 'reactflow' | |
import produce from 'immer' | |
import { useStore, useWorkflowStore } from '../store' | |
import { | |
CUSTOM_NODE, DSL_EXPORT_CHECK, | |
WORKFLOW_DATA_UPDATE, | |
} from '../constants' | |
import type { Node, WorkflowDataUpdater } from '../types' | |
import { ControlMode } from '../types' | |
import { | |
getLayoutByDagre, | |
initialEdges, | |
initialNodes, | |
} from '../utils' | |
import { | |
useNodesReadOnly, | |
useSelectionInteractions, | |
useWorkflowReadOnly, | |
} from '../hooks' | |
import { useEdgesInteractions } from './use-edges-interactions' | |
import { useNodesInteractions } from './use-nodes-interactions' | |
import { useNodesSyncDraft } from './use-nodes-sync-draft' | |
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' | |
import { useEventEmitterContextContext } from '@/context/event-emitter' | |
import { fetchWorkflowDraft } from '@/service/workflow' | |
import { exportAppConfig } from '@/service/apps' | |
import { useToastContext } from '@/app/components/base/toast' | |
import { useStore as useAppStore } from '@/app/components/app/store' | |
export const useWorkflowInteractions = () => { | |
const workflowStore = useWorkflowStore() | |
const { handleNodeCancelRunningStatus } = useNodesInteractions() | |
const { handleEdgeCancelRunningStatus } = useEdgesInteractions() | |
const handleCancelDebugAndPreviewPanel = useCallback(() => { | |
workflowStore.setState({ | |
showDebugAndPreviewPanel: false, | |
workflowRunningData: undefined, | |
}) | |
handleNodeCancelRunningStatus() | |
handleEdgeCancelRunningStatus() | |
}, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus]) | |
return { | |
handleCancelDebugAndPreviewPanel, | |
} | |
} | |
export const useWorkflowMoveMode = () => { | |
const setControlMode = useStore(s => s.setControlMode) | |
const { | |
getNodesReadOnly, | |
} = useNodesReadOnly() | |
const { handleSelectionCancel } = useSelectionInteractions() | |
const handleModePointer = useCallback(() => { | |
if (getNodesReadOnly()) | |
return | |
setControlMode(ControlMode.Pointer) | |
}, [getNodesReadOnly, setControlMode]) | |
const handleModeHand = useCallback(() => { | |
if (getNodesReadOnly()) | |
return | |
setControlMode(ControlMode.Hand) | |
handleSelectionCancel() | |
}, [getNodesReadOnly, setControlMode, handleSelectionCancel]) | |
return { | |
handleModePointer, | |
handleModeHand, | |
} | |
} | |
export const useWorkflowOrganize = () => { | |
const workflowStore = useWorkflowStore() | |
const store = useStoreApi() | |
const reactflow = useReactFlow() | |
const { getNodesReadOnly } = useNodesReadOnly() | |
const { saveStateToHistory } = useWorkflowHistory() | |
const { handleSyncWorkflowDraft } = useNodesSyncDraft() | |
const handleLayout = useCallback(async () => { | |
if (getNodesReadOnly()) | |
return | |
workflowStore.setState({ nodeAnimation: true }) | |
const { | |
getNodes, | |
edges, | |
setNodes, | |
} = store.getState() | |
const { setViewport } = reactflow | |
const nodes = getNodes() | |
const layout = getLayoutByDagre(nodes, edges) | |
const rankMap = {} as Record<string, Node> | |
nodes.forEach((node) => { | |
if (!node.parentId && node.type === CUSTOM_NODE) { | |
const rank = layout.node(node.id).rank! | |
if (!rankMap[rank]) { | |
rankMap[rank] = node | |
} | |
else { | |
if (rankMap[rank].position.y > node.position.y) | |
rankMap[rank] = node | |
} | |
} | |
}) | |
const newNodes = produce(nodes, (draft) => { | |
draft.forEach((node) => { | |
if (!node.parentId && node.type === CUSTOM_NODE) { | |
const nodeWithPosition = layout.node(node.id) | |
node.position = { | |
x: nodeWithPosition.x - node.width! / 2, | |
y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2, | |
} | |
} | |
}) | |
}) | |
setNodes(newNodes) | |
const zoom = 0.7 | |
setViewport({ | |
x: 0, | |
y: 0, | |
zoom, | |
}) | |
saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize) | |
setTimeout(() => { | |
handleSyncWorkflowDraft() | |
}) | |
}, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]) | |
return { | |
handleLayout, | |
} | |
} | |
export const useWorkflowZoom = () => { | |
const { handleSyncWorkflowDraft } = useNodesSyncDraft() | |
const { getWorkflowReadOnly } = useWorkflowReadOnly() | |
const { | |
zoomIn, | |
zoomOut, | |
zoomTo, | |
fitView, | |
} = useReactFlow() | |
const handleFitView = useCallback(() => { | |
if (getWorkflowReadOnly()) | |
return | |
fitView() | |
handleSyncWorkflowDraft() | |
}, [getWorkflowReadOnly, fitView, handleSyncWorkflowDraft]) | |
const handleBackToOriginalSize = useCallback(() => { | |
if (getWorkflowReadOnly()) | |
return | |
zoomTo(1) | |
handleSyncWorkflowDraft() | |
}, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]) | |
const handleSizeToHalf = useCallback(() => { | |
if (getWorkflowReadOnly()) | |
return | |
zoomTo(0.5) | |
handleSyncWorkflowDraft() | |
}, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]) | |
const handleZoomOut = useCallback(() => { | |
if (getWorkflowReadOnly()) | |
return | |
zoomOut() | |
handleSyncWorkflowDraft() | |
}, [getWorkflowReadOnly, zoomOut, handleSyncWorkflowDraft]) | |
const handleZoomIn = useCallback(() => { | |
if (getWorkflowReadOnly()) | |
return | |
zoomIn() | |
handleSyncWorkflowDraft() | |
}, [getWorkflowReadOnly, zoomIn, handleSyncWorkflowDraft]) | |
return { | |
handleFitView, | |
handleBackToOriginalSize, | |
handleSizeToHalf, | |
handleZoomOut, | |
handleZoomIn, | |
} | |
} | |
export const useWorkflowUpdate = () => { | |
const reactflow = useReactFlow() | |
const workflowStore = useWorkflowStore() | |
const { eventEmitter } = useEventEmitterContextContext() | |
const handleUpdateWorkflowCanvas = useCallback((payload: WorkflowDataUpdater) => { | |
const { | |
nodes, | |
edges, | |
viewport, | |
} = payload | |
const { setViewport } = reactflow | |
eventEmitter?.emit({ | |
type: WORKFLOW_DATA_UPDATE, | |
payload: { | |
nodes: initialNodes(nodes, edges), | |
edges: initialEdges(edges, nodes), | |
}, | |
} as any) | |
setViewport(viewport) | |
}, [eventEmitter, reactflow]) | |
const handleRefreshWorkflowDraft = useCallback(() => { | |
const { | |
appId, | |
setSyncWorkflowDraftHash, | |
setIsSyncingWorkflowDraft, | |
setEnvironmentVariables, | |
setEnvSecrets, | |
setConversationVariables, | |
} = workflowStore.getState() | |
setIsSyncingWorkflowDraft(true) | |
fetchWorkflowDraft(`/apps/${appId}/workflows/draft`).then((response) => { | |
handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater) | |
setSyncWorkflowDraftHash(response.hash) | |
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => { | |
acc[env.id] = env.value | |
return acc | |
}, {} as Record<string, string>)) | |
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || []) | |
// #TODO chatVar sync# | |
setConversationVariables(response.conversation_variables || []) | |
}).finally(() => setIsSyncingWorkflowDraft(false)) | |
}, [handleUpdateWorkflowCanvas, workflowStore]) | |
return { | |
handleUpdateWorkflowCanvas, | |
handleRefreshWorkflowDraft, | |
} | |
} | |
export const useDSL = () => { | |
const { t } = useTranslation() | |
const { notify } = useToastContext() | |
const { eventEmitter } = useEventEmitterContextContext() | |
const [exporting, setExporting] = useState(false) | |
const { doSyncWorkflowDraft } = useNodesSyncDraft() | |
const appDetail = useAppStore(s => s.appDetail) | |
const handleExportDSL = useCallback(async (include = false) => { | |
if (!appDetail) | |
return | |
if (exporting) | |
return | |
try { | |
setExporting(true) | |
await doSyncWorkflowDraft() | |
const { data } = await exportAppConfig({ | |
appID: appDetail.id, | |
include, | |
}) | |
const a = document.createElement('a') | |
const file = new Blob([data], { type: 'application/yaml' }) | |
a.href = URL.createObjectURL(file) | |
a.download = `${appDetail.name}.yml` | |
a.click() | |
} | |
catch (e) { | |
notify({ type: 'error', message: t('app.exportFailed') }) | |
} | |
finally { | |
setExporting(false) | |
} | |
}, [appDetail, notify, t, doSyncWorkflowDraft, exporting]) | |
const exportCheck = useCallback(async () => { | |
if (!appDetail) | |
return | |
try { | |
const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`) | |
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret') | |
if (list.length === 0) { | |
handleExportDSL() | |
return | |
} | |
eventEmitter?.emit({ | |
type: DSL_EXPORT_CHECK, | |
payload: { | |
data: list, | |
}, | |
} as any) | |
} | |
catch (e) { | |
notify({ type: 'error', message: t('app.exportFailed') }) | |
} | |
}, [appDetail, eventEmitter, handleExportDSL, notify, t]) | |
return { | |
exportCheck, | |
handleExportDSL, | |
} | |
} | |