wwf
2 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/workflow/hooks/use-workflow.ts
@@ -1,9 +1,13 @@
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,
@@ -36,17 +40,24 @@
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)
@@ -56,9 +67,12 @@
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 })
@@ -74,7 +88,7 @@
    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 []
@@ -103,7 +117,7 @@
    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])
@@ -150,7 +164,7 @@
    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)
      })
    }
@@ -224,15 +238,6 @@
    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()
@@ -274,7 +279,7 @@
      setNodes(newNodes)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [store])
  const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => {
@@ -327,7 +332,6 @@
      parallelList,
      hasAbnormalEdges,
    } = getParallelInfo(nodes, edges, parentNodeId)
    const { workflowConfig } = workflowStore.getState()
    if (hasAbnormalEdges)
      return false
@@ -343,7 +347,7 @@
    }
    return true
  }, [t, workflowStore])
  }, [t, workflowStore, workflowConfig?.parallel_depth_limit])
  const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
    const {
@@ -391,6 +395,10 @@
    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()
@@ -412,10 +420,10 @@
    checkNestedParallelLimit,
    isValidConnection,
    isFromStartNode,
    formatTimeFromNow,
    getNode,
    getBeforeNodeById,
    getIterationNodeChildren,
    getLoopNodeChildren,
  }
}
@@ -426,12 +434,6 @@
    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 || [],
      })
@@ -454,6 +456,108 @@
  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,
  }
}
@@ -505,7 +609,7 @@
        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])
@@ -532,28 +636,5 @@
  }, [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,
  }
}