mirror of
https://github.com/langgenius/dify.git
synced 2025-12-27 10:02:25 +00:00
checklist
This commit is contained in:
parent
cf73faf174
commit
eff123a11c
@ -1,4 +1,8 @@
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
@ -7,20 +11,31 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
||||
import Popup from './popup'
|
||||
|
||||
const Publisher = () => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const handleOpenChange = useCallback((newOpen: boolean) => {
|
||||
if (newOpen)
|
||||
handleSyncWorkflowDraft()
|
||||
setOpen(newOpen)
|
||||
}, [handleSyncWorkflowDraft])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 40,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemTrigger onClick={() => handleOpenChange(!open)}>
|
||||
<Button variant='primary'>
|
||||
{t('workflow.common.publish')}
|
||||
<RiArrowDownSLine className='h-4 w-4' />
|
||||
|
||||
@ -9,11 +9,22 @@ import {
|
||||
RiPlayCircleLine,
|
||||
RiTerminalBoxLine,
|
||||
} from '@remixicon/react'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useFormatTimeFromNow } from '@/app/components/workflow/hooks'
|
||||
import {
|
||||
useChecklistBeforePublish,
|
||||
useFormatTimeFromNow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { getKeyboardKeyCodeBySystem } from '@/app/components/workflow/utils'
|
||||
import { usePublishWorkflow } from '@/service/use-workflow'
|
||||
import type { PublishWorkflowParams } from '@/types/workflow'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
|
||||
const PUBLISH_SHORTCUT = ['⌘', '⇧', 'P']
|
||||
|
||||
@ -22,16 +33,40 @@ const Popup = () => {
|
||||
const [published, setPublished] = useState(false)
|
||||
const publishedAt = useStore(s => s.publishedAt)
|
||||
const draftUpdatedAt = useStore(s => s.draftUpdatedAt)
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
||||
const { notify } = useToastContext()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
try {
|
||||
const handlePublish = useCallback(async (params?: PublishWorkflowParams) => {
|
||||
if (await handleCheckBeforePublish()) {
|
||||
const res = await publishWorkflow({
|
||||
url: `/rag/pipelines/${pipelineId}/workflows/publish`,
|
||||
title: params?.title || '',
|
||||
releaseNotes: params?.releaseNotes || '',
|
||||
})
|
||||
setPublished(true)
|
||||
|
||||
if (res) {
|
||||
notify({ type: 'success', message: t('common.api.actionSuccess') })
|
||||
workflowStore.getState().setPublishedAt(res.created_at)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
setPublished(false)
|
||||
else {
|
||||
throw new Error('Checklist failed')
|
||||
}
|
||||
}, [])
|
||||
}, [workflowStore, notify, t, publishWorkflow, pipelineId, handleCheckBeforePublish])
|
||||
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.p`, (e) => {
|
||||
e.preventDefault()
|
||||
if (published)
|
||||
return
|
||||
handlePublish()
|
||||
},
|
||||
{ exactMatch: true, useCapture: true },
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='w-[320px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'>
|
||||
|
||||
@ -12,8 +12,21 @@ export const useAvailableNodesMetaData = () => {
|
||||
|
||||
const mergedNodesMetaData = useMemo(() => [
|
||||
...WORKFLOW_COMMON_NODES,
|
||||
knowledgeBaseDefault,
|
||||
dataSourceDefault,
|
||||
{
|
||||
...dataSourceDefault,
|
||||
metaData: {
|
||||
...dataSourceDefault.metaData,
|
||||
isStart: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
...knowledgeBaseDefault,
|
||||
metaData: {
|
||||
...knowledgeBaseDefault.metaData,
|
||||
isRequired: true,
|
||||
isUndeletable: true,
|
||||
},
|
||||
},
|
||||
], [])
|
||||
|
||||
const prefixLink = useMemo(() => {
|
||||
|
||||
@ -1,6 +1,25 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||
import {
|
||||
START_INITIAL_POSITION,
|
||||
} from '@/app/components/workflow/constants'
|
||||
import type { KnowledgeBaseNodeType } from '@/app/components/workflow/nodes/knowledge-base/types'
|
||||
import knowledgeBaseDefault from '@/app/components/workflow/nodes/knowledge-base/default'
|
||||
|
||||
export const usePipelineTemplate = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { newNode: knowledgeBaseNode } = generateNewNode({
|
||||
data: {
|
||||
...knowledgeBaseDefault.defaultValue as KnowledgeBaseNodeType,
|
||||
type: knowledgeBaseDefault.metaData.type,
|
||||
title: t(`workflow.blocks.${knowledgeBaseDefault.metaData.type}`),
|
||||
},
|
||||
position: START_INITIAL_POSITION,
|
||||
})
|
||||
|
||||
return {
|
||||
nodes: [],
|
||||
nodes: [knowledgeBaseNode],
|
||||
edges: [],
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ const FeaturesTrigger = () => {
|
||||
setShowFeaturesPanel(!showFeaturesPanel)
|
||||
}, [workflowStore, getNodesReadOnly])
|
||||
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory(appDetail!.id)
|
||||
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory()
|
||||
|
||||
const updateAppDetail = useCallback(async () => {
|
||||
try {
|
||||
@ -89,10 +89,11 @@ const FeaturesTrigger = () => {
|
||||
console.error(error)
|
||||
}
|
||||
}, [appID, setAppDetail])
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
||||
const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
|
||||
if (await handleCheckBeforePublish()) {
|
||||
const res = await publishWorkflow({
|
||||
url: `/apps/${appID}/workflows/publish`,
|
||||
title: params?.title || '',
|
||||
releaseNotes: params?.releaseNotes || '',
|
||||
})
|
||||
@ -107,7 +108,7 @@ const FeaturesTrigger = () => {
|
||||
else {
|
||||
throw new Error('Checklist failed')
|
||||
}
|
||||
}, [handleCheckBeforePublish, notify, t, workflowStore, publishWorkflow, resetWorkflowVersionHistory, updateAppDetail])
|
||||
}, [handleCheckBeforePublish, notify, appID, t, workflowStore, publishWorkflow, resetWorkflowVersionHistory, updateAppDetail])
|
||||
|
||||
const onPublisherToggle = useCallback((state: boolean) => {
|
||||
if (state)
|
||||
|
||||
@ -15,11 +15,35 @@ export const useAvailableNodesMetaData = () => {
|
||||
|
||||
const mergedNodesMetaData = useMemo(() => [
|
||||
...WORKFLOW_COMMON_NODES,
|
||||
StartDefault,
|
||||
{
|
||||
...StartDefault,
|
||||
metaData: {
|
||||
...StartDefault.metaData,
|
||||
isStart: true,
|
||||
isRequired: true,
|
||||
isUndeletable: true,
|
||||
},
|
||||
},
|
||||
...(
|
||||
isChatMode
|
||||
? [AnswerDefault]
|
||||
: [EndDefault]
|
||||
? [
|
||||
{
|
||||
...AnswerDefault,
|
||||
metaData: {
|
||||
...AnswerDefault.metaData,
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
...EndDefault,
|
||||
metaData: {
|
||||
...EndDefault.metaData,
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
),
|
||||
], [isChatMode])
|
||||
|
||||
|
||||
@ -21,6 +21,9 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => {
|
||||
if (block.metaData.type === BlockEnum.DataSource)
|
||||
return false
|
||||
|
||||
if (block.metaData.type === BlockEnum.Tool)
|
||||
return false
|
||||
|
||||
if (block.metaData.type === BlockEnum.IterationStart)
|
||||
return false
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ export const SUPPORT_OUTPUT_VARS_NODE = [
|
||||
BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier,
|
||||
BlockEnum.ParameterExtractor, BlockEnum.Iteration, BlockEnum.Loop,
|
||||
BlockEnum.DocExtractor, BlockEnum.ListFilter,
|
||||
BlockEnum.Agent,
|
||||
BlockEnum.Agent, BlockEnum.DataSource,
|
||||
]
|
||||
|
||||
export const LLM_OUTPUT_STRUCT: Var[] = [
|
||||
|
||||
@ -19,6 +19,7 @@ import assignerDefault from '@/app/components/workflow/nodes/assigner/default'
|
||||
import httpRequestDefault from '@/app/components/workflow/nodes/http/default'
|
||||
import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
|
||||
import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default'
|
||||
import toolDefault from '@/app/components/workflow/nodes/tool/default'
|
||||
|
||||
export const WORKFLOW_COMMON_NODES = [
|
||||
llmDefault,
|
||||
@ -39,4 +40,5 @@ export const WORKFLOW_COMMON_NODES = [
|
||||
parameterExtractorDefault,
|
||||
httpRequestDefault,
|
||||
listOperatorDefault,
|
||||
toolDefault,
|
||||
]
|
||||
|
||||
@ -21,13 +21,13 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean)
|
||||
} = useNodesMetaData()
|
||||
const availableNodesType = useMemo(() => availableNodes.map(node => node.metaData.type), [availableNodes])
|
||||
const availablePrevBlocks = useMemo(() => {
|
||||
if (!nodeType || nodeType === BlockEnum.Start)
|
||||
if (!nodeType || nodeType === BlockEnum.Start || nodeType === BlockEnum.DataSource)
|
||||
return []
|
||||
|
||||
return availableNodesType
|
||||
}, [availableNodesType, nodeType])
|
||||
const availableNextBlocks = useMemo(() => {
|
||||
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd)
|
||||
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase)
|
||||
return []
|
||||
|
||||
return availableNodesType
|
||||
@ -35,11 +35,11 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean)
|
||||
|
||||
const getAvailableBlocks = useCallback((nodeType?: BlockEnum, inContainer?: boolean) => {
|
||||
let availablePrevBlocks = availableNodesType
|
||||
if (!nodeType || nodeType === BlockEnum.Start)
|
||||
if (!nodeType || nodeType === BlockEnum.Start || nodeType === BlockEnum.DataSource)
|
||||
availablePrevBlocks = []
|
||||
|
||||
let availableNextBlocks = availableNodesType
|
||||
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd)
|
||||
if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd || nodeType === BlockEnum.KnowledgeBase)
|
||||
availableNextBlocks = []
|
||||
|
||||
return {
|
||||
|
||||
@ -21,7 +21,6 @@ import {
|
||||
MAX_TREE_DEPTH,
|
||||
} from '../constants'
|
||||
import type { ToolNodeType } from '../nodes/tool/types'
|
||||
import { useIsChatMode } from './use-workflow'
|
||||
import { useNodesMetaData } from './use-nodes-meta-data'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
@ -38,7 +37,6 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const { nodesMap: nodesExtraData } = useNodesMetaData()
|
||||
const isChatMode = useIsChatMode()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
@ -101,7 +99,6 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
if (node.type === CUSTOM_NODE) {
|
||||
const checkData = getCheckData(node.data)
|
||||
const { errorMessage } = nodesExtraData![node.data.type].checkValid(checkData, t, moreDataForCheckValid)
|
||||
|
||||
if (errorMessage || !validNodes.find(n => n.id === node.id)) {
|
||||
list.push({
|
||||
id: node.id,
|
||||
@ -115,26 +112,21 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) {
|
||||
list.push({
|
||||
id: 'answer-need-added',
|
||||
type: BlockEnum.Answer,
|
||||
title: t('workflow.blocks.answer'),
|
||||
errorMessage: t('workflow.common.needAnswerNode'),
|
||||
})
|
||||
}
|
||||
const isRequiredNodesType = Object.keys(nodesExtraData!).filter((key: any) => (nodesExtraData as any)[key].metaData.isRequired)
|
||||
|
||||
if (!isChatMode && !nodes.find(node => node.data.type === BlockEnum.End)) {
|
||||
list.push({
|
||||
id: 'end-need-added',
|
||||
type: BlockEnum.End,
|
||||
title: t('workflow.blocks.end'),
|
||||
errorMessage: t('workflow.common.needEndNode'),
|
||||
})
|
||||
}
|
||||
isRequiredNodesType.forEach((type: string) => {
|
||||
if (!nodes.find(node => node.data.type === type)) {
|
||||
list.push({
|
||||
id: `${type}-need-added`,
|
||||
type,
|
||||
title: t(`workflow.blocks.${type}`),
|
||||
errorMessage: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return list
|
||||
}, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData])
|
||||
}, [nodes, edges, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders, getCheckData])
|
||||
|
||||
return needWarningNodes
|
||||
}
|
||||
@ -146,7 +138,6 @@ export const useChecklistBeforePublish = () => {
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const { notify } = useToastContext()
|
||||
const isChatMode = useIsChatMode()
|
||||
const store = useStoreApi()
|
||||
const { nodesMap: nodesExtraData } = useNodesMetaData()
|
||||
const { data: strategyProviders } = useStrategyProviders()
|
||||
@ -241,18 +232,18 @@ export const useChecklistBeforePublish = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (isChatMode && !nodes.find(node => node.data.type === BlockEnum.Answer)) {
|
||||
notify({ type: 'error', message: t('workflow.common.needAnswerNode') })
|
||||
return false
|
||||
}
|
||||
const isRequiredNodesType = Object.keys(nodesExtraData!).filter((key: any) => (nodesExtraData as any)[key].metaData.isRequired)
|
||||
|
||||
if (!isChatMode && !nodes.find(node => node.data.type === BlockEnum.End)) {
|
||||
notify({ type: 'error', message: t('workflow.common.needEndNode') })
|
||||
return false
|
||||
for(let i = 0; i < isRequiredNodesType.length; i++) {
|
||||
const type = isRequiredNodesType[i]
|
||||
if (!nodes.find(node => node.data.type === type)) {
|
||||
notify({ type: 'error', message: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }) })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, [store, isChatMode, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData])
|
||||
}, [store, notify, t, buildInTools, customTools, workflowTools, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData])
|
||||
|
||||
return {
|
||||
handleCheckBeforePublish,
|
||||
|
||||
@ -549,7 +549,7 @@ export const useNodesInteractions = () => {
|
||||
if (!currentNode)
|
||||
return
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Start)
|
||||
if (nodesMetaDataMap?.[currentNode.data.type as BlockEnum].metaData.isUndeletable)
|
||||
return
|
||||
|
||||
if (currentNode.data.type === BlockEnum.Iteration) {
|
||||
@ -656,7 +656,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
else
|
||||
saveStateToHistory(WorkflowHistoryEvent.NodeDelete)
|
||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t])
|
||||
}, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, t, nodesMetaDataMap])
|
||||
|
||||
const handleNodeAdd = useCallback<OnNodeAdd>((
|
||||
{
|
||||
|
||||
@ -302,6 +302,9 @@ export type NodeDefault<T = {}> = {
|
||||
author: string
|
||||
description?: string
|
||||
helpLinkUri?: string
|
||||
isRequired?: boolean
|
||||
isUndeletable?: boolean
|
||||
isStart?: boolean
|
||||
}
|
||||
defaultValue: Partial<T>
|
||||
checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string }
|
||||
|
||||
@ -46,6 +46,7 @@ const translation = {
|
||||
setVarValuePlaceholder: 'Set variable',
|
||||
needConnectTip: 'This step is not connected to anything',
|
||||
maxTreeDepth: 'Maximum limit of {{depth}} nodes per branch',
|
||||
needAdd: '{{node}} block must be added',
|
||||
needEndNode: 'The End block must be added',
|
||||
needAnswerNode: 'The Answer block must be added',
|
||||
workflowProcess: 'Workflow Process',
|
||||
|
||||
@ -45,6 +45,7 @@ const translation = {
|
||||
setVarValuePlaceholder: '设置变量值',
|
||||
needConnectTip: '此节点尚未连接到其他节点',
|
||||
maxTreeDepth: '每个分支最大限制 {{depth}} 个节点',
|
||||
needAdd: '必须添加{{node}}节点',
|
||||
needEndNode: '必须添加结束节点',
|
||||
needAnswerNode: '必须添加直接回复节点',
|
||||
workflowProcess: '工作流',
|
||||
|
||||
@ -76,10 +76,10 @@ export const useDeleteWorkflow = () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const usePublishWorkflow = (appId: string) => {
|
||||
export const usePublishWorkflow = () => {
|
||||
return useMutation({
|
||||
mutationKey: [NAME_SPACE, 'publish'],
|
||||
mutationFn: (params: PublishWorkflowParams) => post<CommonResponse & { created_at: number }>(`/apps/${appId}/workflows/publish`, {
|
||||
mutationFn: (params: PublishWorkflowParams) => post<CommonResponse & { created_at: number }>(params.url, {
|
||||
body: {
|
||||
marked_name: params.title,
|
||||
marked_comment: params.releaseNotes,
|
||||
|
||||
@ -345,6 +345,7 @@ export type WorkflowConfigResponse = {
|
||||
}
|
||||
|
||||
export type PublishWorkflowParams = {
|
||||
url: string
|
||||
title: string
|
||||
releaseNotes: string
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user