| | |
| | | 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, |
| | |
| | | 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 { basePath } from '@/utils/var' |
| | | import { canFindTool } from '@/utils' |
| | | import { useWorkflowConfig } from '@/service/use-workflow' |
| | | |
| | | export const useIsChatMode = () => { |
| | | const appDetail = useAppStore(s => s.appDetail) |
| | |
| | | |
| | | 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 }) |
| | |
| | | const currentNode = nodes.find(node => node.id === nodeId) |
| | | |
| | | if (currentNode?.parentId) |
| | | startNode = nodes.find(node => node.parentId === currentNode.parentId && (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_LOOP_START_NODE)) |
| | | startNode = nodes.find(node => node.parentId === currentNode.parentId && node.type === CUSTOM_ITERATION_START_NODE) |
| | | |
| | | if (!startNode) |
| | | return [] |
| | |
| | | |
| | | list.push(...incomers) |
| | | |
| | | return uniqBy(list, 'id').filter((item: Node) => { |
| | | return uniqBy(list, 'id').filter((item) => { |
| | | return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type) |
| | | }) |
| | | }, [store]) |
| | |
| | | |
| | | const length = list.length |
| | | if (length) { |
| | | return uniqBy(list, 'id').reverse().filter((item: Node) => { |
| | | return uniqBy(list, 'id').reverse().filter((item) => { |
| | | return SUPPORT_OUTPUT_VARS_NODE.includes(item.data.type) |
| | | }) |
| | | } |
| | |
| | | return nodes.filter(node => node.parentId === nodeId) |
| | | }, [store]) |
| | | |
| | | const getLoopNodeChildren = useCallback((nodeId: string) => { |
| | | const { |
| | | getNodes, |
| | | } = store.getState() |
| | | const nodes = getNodes() |
| | | |
| | | return nodes.filter(node => node.parentId === nodeId) |
| | | }, [store]) |
| | | |
| | | const isFromStartNode = useCallback((nodeId: string) => { |
| | | const { getNodes } = store.getState() |
| | | const nodes = getNodes() |
| | |
| | | setNodes(newNodes) |
| | | } |
| | | |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [store]) |
| | | |
| | | const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => { |
| | |
| | | parallelList, |
| | | hasAbnormalEdges, |
| | | } = getParallelInfo(nodes, edges, parentNodeId) |
| | | const { workflowConfig } = workflowStore.getState() |
| | | |
| | | if (hasAbnormalEdges) |
| | | return false |
| | |
| | | } |
| | | |
| | | return true |
| | | }, [t, workflowStore]) |
| | | }, [t, workflowStore, workflowConfig?.parallel_depth_limit]) |
| | | |
| | | const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => { |
| | | const { |
| | |
| | | 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() |
| | |
| | | checkNestedParallelLimit, |
| | | isValidConnection, |
| | | isFromStartNode, |
| | | formatTimeFromNow, |
| | | getNode, |
| | | getBeforeNodeById, |
| | | getIterationNodeChildren, |
| | | getLoopNodeChildren, |
| | | } |
| | | } |
| | | |
| | |
| | | if (type === 'builtin') { |
| | | const buildInTools = await fetchAllBuiltInTools() |
| | | |
| | | if (basePath) { |
| | | buildInTools.forEach((item) => { |
| | | if (typeof item.icon == 'string' && !item.icon.includes(basePath)) |
| | | item.icon = `${basePath}${item.icon}` |
| | | }) |
| | | } |
| | | workflowStore.setState({ |
| | | buildInTools: buildInTools || [], |
| | | }) |
| | |
| | | |
| | | return { |
| | | handleFetchAllTools, |
| | | } |
| | | } |
| | | |
| | | 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<FetchWorkflowDraftResponse>() |
| | | 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<string, string>), |
| | | environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [], |
| | | // #TODO chatVar sync# |
| | | 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<string, any>), |
| | | }) |
| | | workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at) |
| | | } |
| | | catch (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, |
| | | } |
| | | } |
| | | |
| | |
| | | targetTools = customTools |
| | | else |
| | | targetTools = workflowTools |
| | | return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon |
| | | return targetTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.icon |
| | | } |
| | | }, [data, buildInTools, customTools, workflowTools]) |
| | | |
| | |
| | | }, [iterationId, store]) |
| | | return { |
| | | isNodeInIteration, |
| | | } |
| | | } |
| | | |
| | | export const useIsNodeInLoop = (loopId: string) => { |
| | | const store = useStoreApi() |
| | | |
| | | const isNodeInLoop = useCallback((nodeId: string) => { |
| | | const { |
| | | getNodes, |
| | | } = store.getState() |
| | | const nodes = getNodes() |
| | | const node = nodes.find(node => node.id === nodeId) |
| | | |
| | | if (!node) |
| | | return false |
| | | |
| | | if (node.parentId === loopId) |
| | | return true |
| | | |
| | | return false |
| | | }, [loopId, store]) |
| | | return { |
| | | isNodeInLoop, |
| | | } |
| | | } |