mirror of https://github.com/langgenius/dify.git
fix: checklist
This commit is contained in:
parent
fb2fa625b4
commit
0202469254
|
|
@ -1,23 +1,21 @@
|
|||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
useEdges,
|
||||
useNodes,
|
||||
} from 'reactflow'
|
||||
import BlockIcon from '../block-icon'
|
||||
import {
|
||||
useNodesExtraData,
|
||||
useChecklist,
|
||||
useNodesInteractions,
|
||||
} from '../hooks'
|
||||
import type { CommonNodeType } from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useStore } from '../store'
|
||||
import type {
|
||||
CommonEdgeType,
|
||||
CommonNodeType,
|
||||
} from '../types'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
|
|
@ -34,44 +32,9 @@ const WorkflowChecklist = () => {
|
|||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const edges = useEdges<CommonEdgeType>()
|
||||
const needWarningNodes = useChecklist(nodes, edges)
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
const incomers = getIncomers(node, nodes, edges)
|
||||
const outgoers = getOutgoers(node, nodes, edges)
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t)
|
||||
let toolIcon
|
||||
|
||||
if (node.data.type === BlockEnum.Tool) {
|
||||
if (node.data.provider_type === 'builtin')
|
||||
toolIcon = buildInTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (node.data.provider_type === 'custom')
|
||||
toolIcon = customTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
}
|
||||
|
||||
if (errorMessage || ((!incomers.length && !outgoers.length))) {
|
||||
list.push({
|
||||
id: node.id,
|
||||
type: node.data.type,
|
||||
title: node.data.title,
|
||||
toolIcon,
|
||||
unConnected: !incomers.length && !outgoers.length,
|
||||
errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
useWorkflowStore,
|
||||
} from '../store'
|
||||
import {
|
||||
useChecklistBeforePublish,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowRun,
|
||||
|
|
@ -43,10 +44,10 @@ const Header: FC = () => {
|
|||
const {
|
||||
handleLoadBackupDraft,
|
||||
handleRunSetting,
|
||||
handleCheckBeforePublish,
|
||||
handleBackupDraft,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
} = useWorkflowRun()
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { notify } = useContext(ToastContext)
|
||||
|
||||
|
|
@ -84,6 +85,9 @@ const Header: FC = () => {
|
|||
workflowStore.getState().setPublishedAt(res.created_at)
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Checklist failed')
|
||||
}
|
||||
}, [appID, handleCheckBeforePublish, notify, t, workflowStore])
|
||||
|
||||
const onStartRestoring = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -5,3 +5,5 @@ export * from './use-nodes-data'
|
|||
export * from './use-nodes-sync-draft'
|
||||
export * from './use-workflow'
|
||||
export * from './use-workflow-run'
|
||||
export * from './use-workflow-template'
|
||||
export * from './use-checklist'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
} from '../types'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useStore } from '../store'
|
||||
import { getValidTreeNodes } from '../utils'
|
||||
import { MAX_TREE_DEEPTH } from '../constants'
|
||||
import { useIsChatMode } from './use-workflow'
|
||||
import { useNodesExtraData } from './use-nodes-data'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
||||
export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const { t } = useTranslation()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
|
||||
const needWarningNodes = useMemo(() => {
|
||||
const list = []
|
||||
const { validNodes } = getValidTreeNodes(nodes, edges)
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t)
|
||||
let toolIcon
|
||||
|
||||
if (node.data.type === BlockEnum.Tool) {
|
||||
if (node.data.provider_type === 'builtin')
|
||||
toolIcon = buildInTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
|
||||
if (node.data.provider_type === 'custom')
|
||||
toolIcon = customTools.find(tool => tool.id === node.data.provider_id)?.icon
|
||||
}
|
||||
|
||||
if (errorMessage || !validNodes.find(n => n.id === node.id)) {
|
||||
list.push({
|
||||
id: node.id,
|
||||
type: node.data.type,
|
||||
title: node.data.title,
|
||||
toolIcon,
|
||||
unConnected: !validNodes.find(n => n.id === node.id),
|
||||
errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}, [t, nodes, edges, nodesExtraData, buildInTools, customTools])
|
||||
|
||||
return needWarningNodes
|
||||
}
|
||||
|
||||
export const useChecklistBeforePublish = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const isChatMode = useIsChatMode()
|
||||
const store = useStoreApi()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
|
||||
const handleCheckBeforePublish = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const {
|
||||
validNodes,
|
||||
maxDepth,
|
||||
} = getValidTreeNodes(nodes, edges)
|
||||
|
||||
if (maxDepth > MAX_TREE_DEEPTH) {
|
||||
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEEPTH }) })
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
const { errorMessage } = nodesExtraData[node.data.type as BlockEnum].checkValid(node.data, t)
|
||||
|
||||
if (errorMessage) {
|
||||
notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` })
|
||||
return false
|
||||
}
|
||||
|
||||
if (!validNodes.find(n => n.id === node.id)) {
|
||||
notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnecttip')}` })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) {
|
||||
notify({ type: 'error', message: t('workflow.common.needAnswerNode') })
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isChatMode && !nodes.find(node => node.data.type === BlockEnum.End)) {
|
||||
notify({ type: 'error', message: t('workflow.common.needEndNode') })
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, [nodesExtraData, notify, t, store, isChatMode])
|
||||
|
||||
return {
|
||||
handleCheckBeforePublish,
|
||||
}
|
||||
}
|
||||
|
|
@ -3,15 +3,12 @@ import {
|
|||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import {
|
||||
NodeRunningStatus,
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import { MAX_TREE_DEEPTH } from '../constants'
|
||||
import { useNodesExtraData } from './use-nodes-data'
|
||||
import { useWorkflow } from './use-workflow'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import type { IOtherOptions } from '@/service/base'
|
||||
|
|
@ -21,20 +18,13 @@ import {
|
|||
stopWorkflowRun,
|
||||
} from '@/service/workflow'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
||||
export const useWorkflowRun = () => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const reactflow = useReactFlow()
|
||||
const featuresStore = useFeaturesStore()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const {
|
||||
getValidTreeNodes,
|
||||
renderTreeFromRecord,
|
||||
} = useWorkflow()
|
||||
const { renderTreeFromRecord } = useWorkflow()
|
||||
|
||||
const handleBackupDraft = useCallback(() => {
|
||||
const {
|
||||
|
|
@ -331,33 +321,6 @@ export const useWorkflowRun = () => {
|
|||
}
|
||||
}, [featuresStore, workflowStore, renderTreeFromRecord])
|
||||
|
||||
const handleCheckBeforePublish = useCallback(() => {
|
||||
const {
|
||||
validNodes,
|
||||
maxDepth,
|
||||
} = getValidTreeNodes()
|
||||
|
||||
if (!validNodes.length)
|
||||
return false
|
||||
|
||||
if (maxDepth > MAX_TREE_DEEPTH) {
|
||||
notify({ type: 'error', message: t('workflow.common.maxTreeDepth', { depth: MAX_TREE_DEEPTH }) })
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < validNodes.length; i++) {
|
||||
const node = validNodes[i]
|
||||
const { errorMessage } = nodesExtraData[node.data.type].checkValid(node.data, t)
|
||||
|
||||
if (errorMessage) {
|
||||
notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, [getValidTreeNodes, nodesExtraData, notify, t])
|
||||
|
||||
return {
|
||||
handleBackupDraft,
|
||||
handleLoadBackupDraft,
|
||||
|
|
@ -365,6 +328,5 @@ export const useWorkflowRun = () => {
|
|||
handleRun,
|
||||
handleStopRun,
|
||||
handleRestoreFromPublishedWorkflow,
|
||||
handleCheckBeforePublish,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,50 +283,6 @@ export const useWorkflow = () => {
|
|||
return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow()
|
||||
}, [locale])
|
||||
|
||||
const getValidTreeNodes = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
edges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
|
||||
if (!startNode) {
|
||||
return {
|
||||
validNodes: [],
|
||||
maxDepth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const list: Node[] = [startNode]
|
||||
let maxDepth = 1
|
||||
|
||||
const traverse = (root: Node, depth: number) => {
|
||||
if (depth > maxDepth)
|
||||
maxDepth = depth
|
||||
|
||||
const outgoers = getOutgoers(root, nodes, edges)
|
||||
|
||||
if (outgoers.length) {
|
||||
outgoers.forEach((outgoer) => {
|
||||
list.push(outgoer)
|
||||
traverse(outgoer, depth + 1)
|
||||
})
|
||||
}
|
||||
else {
|
||||
list.push(root)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(startNode, maxDepth)
|
||||
|
||||
return {
|
||||
validNodes: uniqBy(list, 'id'),
|
||||
maxDepth,
|
||||
}
|
||||
}, [store])
|
||||
|
||||
const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => {
|
||||
const { setNodes } = store.getState()
|
||||
const { setViewport, setEdges } = reactflow
|
||||
|
|
@ -356,7 +312,6 @@ export const useWorkflow = () => {
|
|||
isNodeVarsUsedInNodes,
|
||||
isValidConnection,
|
||||
formatTimeFromNow,
|
||||
getValidTreeNodes,
|
||||
renderTreeFromRecord,
|
||||
getNode,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import {
|
||||
Position,
|
||||
getConnectedEdges,
|
||||
getOutgoers,
|
||||
} from 'reactflow'
|
||||
import dagre from 'dagre'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import {
|
||||
cloneDeep,
|
||||
uniqBy,
|
||||
} from 'lodash-es'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
|
|
@ -186,3 +190,41 @@ export const generateNewNode = ({ data, position, id }: Pick<Node, 'data' | 'pos
|
|||
sourcePosition: Position.Right,
|
||||
} as Node
|
||||
}
|
||||
|
||||
export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
|
||||
if (!startNode) {
|
||||
return {
|
||||
validNodes: [],
|
||||
maxDepth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const list: Node[] = [startNode]
|
||||
let maxDepth = 1
|
||||
|
||||
const traverse = (root: Node, depth: number) => {
|
||||
if (depth > maxDepth)
|
||||
maxDepth = depth
|
||||
|
||||
const outgoers = getOutgoers(root, nodes, edges)
|
||||
|
||||
if (outgoers.length) {
|
||||
outgoers.forEach((outgoer) => {
|
||||
list.push(outgoer)
|
||||
traverse(outgoer, depth + 1)
|
||||
})
|
||||
}
|
||||
else {
|
||||
list.push(root)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(startNode, maxDepth)
|
||||
|
||||
return {
|
||||
validNodes: uniqBy(list, 'id'),
|
||||
maxDepth,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const translation = {
|
|||
setVarValuePlaceholder: 'Set variable',
|
||||
needConnecttip: 'This step is not connected to anything',
|
||||
maxTreeDepth: 'Maximum limit of {{depth}} nodes per branch',
|
||||
needEndNode: 'Please add an end node',
|
||||
needAnswerNode: 'Please add a answer node',
|
||||
workflowProcess: 'Workflow Process',
|
||||
notRunning: 'Not running yet',
|
||||
previewPlaceholder: 'Enter content in the box below to start debugging the Chatbot',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const translation = {
|
|||
setVarValuePlaceholder: '设置变量值',
|
||||
needConnecttip: '此节点尚未连接到其他节点',
|
||||
maxTreeDepth: '每个分支最大限制 {{depth}} 个节点',
|
||||
needEndNode: '请添加结束节点',
|
||||
needAnswerNode: '请添加直接回复节点',
|
||||
workflowProcess: '工作流',
|
||||
notRunning: '尚未运行',
|
||||
previewPlaceholder: '在下面的框中输入内容开始调试聊天机器人',
|
||||
|
|
|
|||
Loading…
Reference in New Issue