Merge remote-tracking branch 'origin/main' into feat/trigger

This commit is contained in:
yessenia
2025-09-25 17:14:24 +08:00
3013 changed files with 148826 additions and 44294 deletions

View File

@ -4,7 +4,10 @@ export * from './use-nodes-sync-draft'
export * from './use-workflow-run'
export * from './use-workflow-start-run'
export * from './use-is-chat-mode'
export * from './use-available-nodes-meta-data'
export * from './use-workflow-refresh-draft'
export * from './use-get-run-and-trace-url'
export * from './use-DSL'
export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars'
export * from './use-inspect-vars-crud'
export * from './use-configs-map'

View File

@ -0,0 +1,82 @@
import {
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
DSL_EXPORT_CHECK,
} from '@/app/components/workflow/constants'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
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 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, workflowId?: string) => {
if (!appDetail)
return
if (exporting)
return
try {
setExporting(true)
await doSyncWorkflowDraft()
const { data } = await exportAppConfig({
appID: appDetail.id,
include,
workflowID: workflowId,
})
const a = document.createElement('a')
const file = new Blob([data], { type: 'application/yaml' })
const url = URL.createObjectURL(file)
a.href = url
a.download = `${appDetail.name}.yml`
a.click()
URL.revokeObjectURL(url)
}
catch {
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 {
notify({ type: 'error', message: t('app.exportFailed') })
}
}, [appDetail, eventEmitter, handleExportDSL, notify, t])
return {
exportCheck,
handleExportDSL,
}
}

View File

@ -0,0 +1,76 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetLanguage } from '@/context/i18n'
import StartDefault from '@/app/components/workflow/nodes/start/default'
import TriggerWebhookDefault from '@/app/components/workflow/nodes/trigger-webhook/default'
import TriggerScheduleDefault from '@/app/components/workflow/nodes/trigger-schedule/default'
import TriggerPluginDefault from '@/app/components/workflow/nodes/trigger-plugin/default'
import EndDefault from '@/app/components/workflow/nodes/end/default'
import AnswerDefault from '@/app/components/workflow/nodes/answer/default'
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store'
import { useIsChatMode } from './use-is-chat-mode'
import { BlockEnum } from '@/app/components/workflow/types'
export const useAvailableNodesMetaData = () => {
const { t } = useTranslation()
const isChatMode = useIsChatMode()
const language = useGetLanguage()
const mergedNodesMetaData = useMemo(() => [
...WORKFLOW_COMMON_NODES,
StartDefault,
...(
isChatMode
? [AnswerDefault]
: [
EndDefault,
TriggerWebhookDefault,
TriggerScheduleDefault,
TriggerPluginDefault,
]
),
], [isChatMode])
const prefixLink = useMemo(() => {
if (language === 'zh_Hans')
return 'https://docs.dify.ai/zh-hans/guides/workflow/node/'
return 'https://docs.dify.ai/guides/workflow/node/'
}, [language])
const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => {
const { metaData } = node
const title = t(`workflow.blocks.${metaData.type}`)
const description = t(`workflow.blocksAbout.${metaData.type}`)
return {
...node,
metaData: {
...metaData,
title,
description,
helpLinkUri: `${prefixLink}${metaData.helpLinkUri}`,
},
defaultValue: {
...node.defaultValue,
type: metaData.type,
title,
},
}
}), [mergedNodesMetaData, t, prefixLink])
const availableNodesMetaDataMap = useMemo(() => availableNodesMetaData.reduce((acc, node) => {
acc![node.metaData.type] = node
return acc
}, {} as AvailableNodesMetaData['nodesMap']), [availableNodesMetaData])
return useMemo(() => {
return {
nodes: availableNodesMetaData,
nodesMap: {
...availableNodesMetaDataMap,
[BlockEnum.VariableAssigner]: availableNodesMetaDataMap?.[BlockEnum.VariableAggregator],
},
}
}, [availableNodesMetaData, availableNodesMetaDataMap])
}

View File

@ -1,13 +1,16 @@
import { useMemo } from 'react'
import { useStore } from '@/app/components/workflow/store'
import { FlowType } from '@/types/common'
import { useFeatures } from '@/app/components/base/features/hooks'
export const useConfigsMap = () => {
const appId = useStore(s => s.appId)
const fileSettings = useFeatures(s => s.features.file)
return useMemo(() => {
return {
flowId: appId,
conversationVarsUrl: `apps/${appId}/workflows/draft/conversation-variables`,
systemVarsUrl: `apps/${appId}/workflows/draft/system-variables`,
flowId: appId!,
flowType: FlowType.appFlow,
fileSettings,
}
}, [appId])
}

View File

@ -0,0 +1,18 @@
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
export const useGetRunAndTraceUrl = () => {
const workflowStore = useWorkflowStore()
const getWorkflowRunAndTraceUrl = useCallback((runId: string) => {
const { appId } = workflowStore.getState()
return {
runUrl: `/apps/${appId}/workflow-runs/${runId}`,
traceUrl: `/apps/${appId}/workflow-runs/${runId}/node-executions`,
}
}, [workflowStore])
return {
getWorkflowRunAndTraceUrl,
}
}

View File

@ -1,12 +1,9 @@
import { useStore } from '@/app/components/workflow/store'
import { useInspectVarsCrudCommon } from '../../workflow/hooks/use-inspect-vars-crud-common'
import { useConfigsMap } from './use-configs-map'
export const useInspectVarsCrud = () => {
const appId = useStore(s => s.appId)
const configsMap = useConfigsMap()
const apis = useInspectVarsCrudCommon({
flowId: appId,
...configsMap,
})

View File

@ -5,6 +5,7 @@ import { useParams } from 'next/navigation'
import {
useWorkflowStore,
} from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import {
useNodesReadOnly,
} from '@/app/components/workflow/hooks/use-workflow'
@ -27,20 +28,20 @@ export const useNodesSyncDraft = () => {
edges,
transform,
} = store.getState()
const nodes = getNodes()
const [x, y, zoom] = transform
const {
appId,
conversationVariables,
environmentVariables,
syncWorkflowDraftHash,
isWorkflowDataLoaded,
// isWorkflowDataLoaded,
} = workflowStore.getState()
if (appId) {
const nodes = getNodes()
if (appId && !!nodes.length) {
const hasStartNode = nodes.find(node => node.data.type === BlockEnum.Start)
// Prevent sync if workflow data hasn't been loaded yet
if (!isWorkflowDataLoaded)
if (!hasStartNode)
return null
const features = featuresStore!.getState().features
@ -52,7 +53,7 @@ export const useNodesSyncDraft = () => {
})
})
})
const producedEdges = produce(edges, (draft) => {
const producedEdges = produce(edges.filter(edge => !edge.data?._isTemp), (draft) => {
draft.forEach((edge) => {
Object.keys(edge.data).forEach((key) => {
if (key.startsWith('_'))

View File

@ -17,6 +17,8 @@ import {
} from '@/service/workflow'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useWorkflowConfig } from '@/service/use-workflow'
import type { FileUploadConfigResponse } from '@/models/common'
export const useWorkflowInit = () => {
const workflowStore = useWorkflowStore()
const {
@ -28,7 +30,7 @@ export const useWorkflowInit = () => {
const [data, setData] = useState<FetchWorkflowDraftResponse>()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
workflowStore.setState({ appId: appDetail.id })
workflowStore.setState({ appId: appDetail.id, appName: appDetail.name })
}, [appDetail.id, workflowStore])
const handleUpdateWorkflowConfig = useCallback((config: Record<string, any>) => {
@ -36,7 +38,16 @@ export const useWorkflowInit = () => {
setWorkflowConfig(config)
}, [workflowStore])
useWorkflowConfig(appDetail.id, handleUpdateWorkflowConfig)
useWorkflowConfig(`/apps/${appDetail.id}/workflows/draft/config`, handleUpdateWorkflowConfig)
const handleUpdateWorkflowFileUploadConfig = useCallback((config: FileUploadConfigResponse) => {
const { setFileUploadConfig } = workflowStore.getState()
setFileUploadConfig(config)
}, [workflowStore])
const {
data: fileUploadConfigResponse,
isLoading: isFileUploadConfigLoading,
} = useWorkflowConfig('/files/upload', handleUpdateWorkflowFileUploadConfig)
const handleGetInitialWorkflowData = useCallback(async () => {
try {
@ -125,6 +136,7 @@ export const useWorkflowInit = () => {
return {
data,
isLoading,
isLoading: isLoading || isFileUploadConfigLoading,
fileUploadConfigResponse,
}
}

View File

@ -31,9 +31,10 @@ export const useWorkflowRun = () => {
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
const pathname = usePathname()
const appId = useAppStore.getState().appDetail?.id
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
const configsMap = useConfigsMap()
const { flowId, flowType } = configsMap
const invalidAllLastRun = useInvalidAllLastRun(flowType, flowId)
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
...configsMap,
})
@ -201,7 +202,7 @@ export const useWorkflowRun = () => {
if (onWorkflowFinished)
onWorkflowFinished(params)
if (isInWorkflowDebug) {
fetchInspectVars()
fetchInspectVars({})
invalidAllLastRun()
}
},

View File

@ -1,17 +1,25 @@
import { useTranslation } from 'react-i18next'
import { generateNewNode } from '@/app/components/workflow/utils'
import {
NODE_WIDTH_X_OFFSET,
START_INITIAL_POSITION,
} from '@/app/components/workflow/constants'
import { useNodesInitialData } from '@/app/components/workflow/hooks'
import { useIsChatMode } from './use-is-chat-mode'
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
import startDefault from '@/app/components/workflow/nodes/start/default'
import llmDefault from '@/app/components/workflow/nodes/llm/default'
import answerDefault from '@/app/components/workflow/nodes/answer/default'
export const useWorkflowTemplate = () => {
const isChatMode = useIsChatMode()
const nodesInitialData = useNodesInitialData()
const { t } = useTranslation()
const { newNode: startNode } = generateNewNode({
data: nodesInitialData.start,
data: {
...startDefault.defaultValue as StartNodeType,
type: startDefault.metaData.type,
title: t(`workflow.blocks.${startDefault.metaData.type}`),
},
position: START_INITIAL_POSITION,
})
@ -19,12 +27,14 @@ export const useWorkflowTemplate = () => {
const { newNode: llmNode } = generateNewNode({
id: 'llm',
data: {
...nodesInitialData.llm,
...llmDefault.defaultValue,
memory: {
window: { enabled: false, size: 10 },
query_prompt_template: '{{#sys.query#}}\n\n{{#sys.files#}}',
},
selected: true,
type: llmDefault.metaData.type,
title: t(`workflow.blocks.${llmDefault.metaData.type}`),
},
position: {
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET,
@ -35,8 +45,10 @@ export const useWorkflowTemplate = () => {
const { newNode: answerNode } = generateNewNode({
id: 'answer',
data: {
...nodesInitialData.answer,
...answerDefault.defaultValue,
answer: `{{#${llmNode.id}.text#}}`,
type: answerDefault.metaData.type,
title: t(`workflow.blocks.${answerDefault.metaData.type}`),
},
position: {
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET * 2,