This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export * from './use-available-nodes-meta-data'
export * from './use-pipeline-refresh-draft'
export * from './use-nodes-sync-draft'
export * from './use-pipeline-run'
export * from './use-pipeline-start-run'
export * from './use-pipeline-init'
export * from './use-get-run-and-trace-url'
export * from './use-DSL'
export * from './use-input-field-panel'

View File

@@ -0,0 +1,83 @@
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 { useToastContext } from '@/app/components/base/toast'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useExportPipelineDSL } from '@/service/use-pipeline'
export const useDSL = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const { eventEmitter } = useEventEmitterContextContext()
const [exporting, setExporting] = useState(false)
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const workflowStore = useWorkflowStore()
const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
const handleExportDSL = useCallback(async (include = false) => {
const { pipelineId, knowledgeName } = workflowStore.getState()
if (!pipelineId)
return
if (exporting)
return
try {
setExporting(true)
await doSyncWorkflowDraft()
const { data } = await exportPipelineConfig({
pipelineId,
include,
})
const a = document.createElement('a')
const file = new Blob([data], { type: 'application/yaml' })
const url = URL.createObjectURL(file)
a.href = url
a.download = `${knowledgeName}.pipeline`
a.click()
URL.revokeObjectURL(url)
}
catch {
notify({ type: 'error', message: t('app.exportFailed') })
}
finally {
setExporting(false)
}
}, [notify, t, doSyncWorkflowDraft, exporting, exportPipelineConfig, workflowStore])
const exportCheck = useCallback(async () => {
const { pipelineId } = workflowStore.getState()
if (!pipelineId)
return
try {
const workflowDraft = await fetchWorkflowDraft(`/rag/pipelines/${pipelineId}/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') })
}
}, [eventEmitter, handleExportDSL, notify, t, workflowStore])
return {
exportCheck,
handleExportDSL,
}
}

View File

@@ -0,0 +1,71 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useGetLanguage } from '@/context/i18n'
import knowledgeBaseDefault from '@/app/components/workflow/nodes/knowledge-base/default'
import dataSourceDefault from '@/app/components/workflow/nodes/data-source/default'
import dataSourceEmptyDefault from '@/app/components/workflow/nodes/data-source-empty/default'
import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node'
import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store'
import { BlockEnum } from '@/app/components/workflow/types'
export const useAvailableNodesMetaData = () => {
const { t } = useTranslation()
const language = useGetLanguage()
const mergedNodesMetaData = useMemo(() => [
...WORKFLOW_COMMON_NODES,
{
...dataSourceDefault,
defaultValue: {
...dataSourceDefault.defaultValue,
_dataSourceStartToAdd: true,
},
},
knowledgeBaseDefault,
dataSourceEmptyDefault,
], [])
const helpLinkUri = useMemo(() => {
if (language === 'zh_Hans')
return 'https://docs.dify.ai/zh-hans/guides/knowledge-base/knowledge-pipeline/knowledge-pipeline-orchestration#%E6%AD%A5%E9%AA%A4%E4%B8%80%EF%BC%9A%E6%95%B0%E6%8D%AE%E6%BA%90%E9%85%8D%E7%BD%AE'
if (language === 'ja_JP')
return 'https://docs.dify.ai/ja-jp/guides/knowledge-base/knowledge-pipeline/knowledge-pipeline-orchestration#%E3%82%B9%E3%83%86%E3%83%83%E3%83%971%EF%BC%9A%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AE%E8%A8%AD%E5%AE%9A'
return 'https://docs.dify.ai/en/guides/knowledge-base/knowledge-pipeline/knowledge-pipeline-orchestration#step-1%3A-data-source'
}, [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,
},
defaultValue: {
...node.defaultValue,
type: metaData.type,
title,
},
}
}), [mergedNodesMetaData, t])
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

@@ -0,0 +1,24 @@
import { useMemo } from 'react'
import { useStore } from '@/app/components/workflow/store'
import { FlowType } from '@/types/common'
import { Resolution, TransferMethod } from '@/types/app'
export const useConfigsMap = () => {
const pipelineId = useStore(s => s.pipelineId)
const fileUploadConfig = useStore(s => s.fileUploadConfig)
return useMemo(() => {
return {
flowId: pipelineId!,
flowType: FlowType.ragPipeline,
fileSettings: {
image: {
enabled: false,
detail: Resolution.high,
number_limits: 3,
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
},
fileUploadConfig,
},
}
}, [pipelineId])
}

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 { pipelineId } = workflowStore.getState()
return {
runUrl: `/rag/pipelines/${pipelineId}/workflow-runs/${runId}`,
traceUrl: `/rag/pipelines/${pipelineId}/workflow-runs/${runId}/node-executions`,
}
}, [workflowStore])
return {
getWorkflowRunAndTraceUrl,
}
}

View File

@@ -0,0 +1,54 @@
import { useCallback, useMemo } from 'react'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import type { InputFieldEditorProps } from '../components/panel/input-field/editor'
export const useInputFieldPanel = () => {
const workflowStore = useWorkflowStore()
const showInputFieldPreviewPanel = useStore(state => state.showInputFieldPreviewPanel)
const inputFieldEditPanelProps = useStore(state => state.inputFieldEditPanelProps)
const isPreviewing = useMemo(() => {
return showInputFieldPreviewPanel
}, [showInputFieldPreviewPanel])
const isEditing = useMemo(() => {
return !!inputFieldEditPanelProps
}, [inputFieldEditPanelProps])
const closeAllInputFieldPanels = useCallback(() => {
const {
setShowInputFieldPanel,
setShowInputFieldPreviewPanel,
setInputFieldEditPanelProps,
} = workflowStore.getState()
setShowInputFieldPanel?.(false)
setShowInputFieldPreviewPanel?.(false)
setInputFieldEditPanelProps?.(null)
}, [workflowStore])
const toggleInputFieldPreviewPanel = useCallback(() => {
const {
showInputFieldPreviewPanel,
setShowInputFieldPreviewPanel,
} = workflowStore.getState()
setShowInputFieldPreviewPanel?.(!showInputFieldPreviewPanel)
}, [workflowStore])
const toggleInputFieldEditPanel = useCallback((editContent: InputFieldEditorProps | null) => {
const {
setInputFieldEditPanelProps,
} = workflowStore.getState()
setInputFieldEditPanelProps?.(editContent)
}, [workflowStore])
return {
closeAllInputFieldPanels,
toggleInputFieldPreviewPanel,
toggleInputFieldEditPanel,
isPreviewing,
isEditing,
}
}

View File

@@ -0,0 +1,54 @@
import { useMemo } from 'react'
import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types'
import { type RAGPipelineVariables, VAR_TYPE_MAP } from '@/models/pipeline'
import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types'
export const useInitialData = (variables: RAGPipelineVariables, lastRunInputData?: Record<string, any>) => {
const initialData = useMemo(() => {
return variables.reduce((acc, item) => {
const type = VAR_TYPE_MAP[item.type]
const variableName = item.variable
const defaultValue = lastRunInputData?.[variableName] || item.default_value
if ([BaseFieldType.textInput, BaseFieldType.paragraph, BaseFieldType.select].includes(type))
acc[variableName] = defaultValue ?? ''
if (type === BaseFieldType.numberInput)
acc[variableName] = defaultValue ?? 0
if (type === BaseFieldType.checkbox)
acc[variableName] = defaultValue ?? false
if ([BaseFieldType.file, BaseFieldType.fileList].includes(type))
acc[variableName] = defaultValue ?? []
return acc
}, {} as Record<string, any>)
}, [lastRunInputData, variables])
return initialData
}
export const useConfigurations = (variables: RAGPipelineVariables) => {
const configurations = useMemo(() => {
const configurations: BaseConfiguration[] = []
variables.forEach((item) => {
configurations.push({
type: VAR_TYPE_MAP[item.type],
variable: item.variable,
label: item.label,
required: item.required,
maxLength: item.max_length,
options: item.options?.map(option => ({
label: option,
value: option,
})),
showConditions: [],
placeholder: item.placeholder,
tooltip: item.tooltips,
unit: item.unit,
allowedFileTypes: item.allowed_file_types,
allowedFileExtensions: item.allowed_file_extensions,
allowedFileUploadMethods: item.allowed_file_upload_methods,
})
})
return configurations
}, [variables])
return configurations
}

View File

@@ -0,0 +1,13 @@
import { useInspectVarsCrudCommon } from '../../workflow/hooks/use-inspect-vars-crud-common'
import { useConfigsMap } from './use-configs-map'
export const useInspectVarsCrud = () => {
const configsMap = useConfigsMap()
const apis = useInspectVarsCrudCommon({
...configsMap,
})
return {
...apis,
}
}

View File

@@ -0,0 +1,131 @@
import { useCallback } from 'react'
import { produce } from 'immer'
import { useStoreApi } from 'reactflow'
import {
useWorkflowStore,
} from '@/app/components/workflow/store'
import {
useNodesReadOnly,
} from '@/app/components/workflow/hooks/use-workflow'
import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-serial-async-callback'
import { API_PREFIX } from '@/config'
import { syncWorkflowDraft } from '@/service/workflow'
import { usePipelineRefreshDraft } from '.'
export const useNodesSyncDraft = () => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const { getNodesReadOnly } = useNodesReadOnly()
const { handleRefreshWorkflowDraft } = usePipelineRefreshDraft()
const getPostParams = useCallback(() => {
const {
getNodes,
edges,
transform,
} = store.getState()
const nodesOriginal = getNodes()
const nodes = nodesOriginal.filter(node => !node.data._isTempNode)
const [x, y, zoom] = transform
const {
pipelineId,
environmentVariables,
syncWorkflowDraftHash,
ragPipelineVariables,
} = workflowStore.getState()
if (pipelineId && !!nodes.length) {
const producedNodes = produce(nodes, (draft) => {
draft.forEach((node) => {
Object.keys(node.data).forEach((key) => {
if (key.startsWith('_'))
delete node.data[key]
})
})
})
const producedEdges = produce(edges, (draft) => {
draft.forEach((edge) => {
Object.keys(edge.data).forEach((key) => {
if (key.startsWith('_'))
delete edge.data[key]
})
})
})
return {
url: `/rag/pipelines/${pipelineId}/workflows/draft`,
params: {
graph: {
nodes: producedNodes,
edges: producedEdges,
viewport: {
x,
y,
zoom,
},
},
environment_variables: environmentVariables,
rag_pipeline_variables: ragPipelineVariables,
hash: syncWorkflowDraftHash,
},
}
}
}, [store, workflowStore])
const syncWorkflowDraftWhenPageClose = useCallback(() => {
if (getNodesReadOnly())
return
const postParams = getPostParams()
if (postParams) {
navigator.sendBeacon(
`${API_PREFIX}${postParams.url}`,
JSON.stringify(postParams.params),
)
}
}, [getPostParams, getNodesReadOnly])
const performSync = useCallback(async (
notRefreshWhenSyncError?: boolean,
callback?: {
onSuccess?: () => void
onError?: () => void
onSettled?: () => void
},
) => {
if (getNodesReadOnly())
return
const postParams = getPostParams()
if (postParams) {
const {
setSyncWorkflowDraftHash,
setDraftUpdatedAt,
} = workflowStore.getState()
try {
const res = await syncWorkflowDraft(postParams)
setSyncWorkflowDraftHash(res.hash)
setDraftUpdatedAt(res.updated_at)
callback?.onSuccess?.()
}
catch (error: any) {
if (error && error.json && !error.bodyUsed) {
error.json().then((err: any) => {
if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
handleRefreshWorkflowDraft()
})
}
callback?.onError?.()
}
finally {
callback?.onSettled?.()
}
}
}, [getPostParams, getNodesReadOnly, workflowStore, handleRefreshWorkflowDraft])
const doSyncWorkflowDraft = useSerialAsyncCallback(performSync, getNodesReadOnly)
return {
doSyncWorkflowDraft,
syncWorkflowDraftWhenPageClose,
}
}

View File

@@ -0,0 +1,63 @@
import { useCallback } from 'react'
import {
useStore,
useWorkflowStore,
} from '@/app/components/workflow/store'
import { useWorkflowConfig } from '@/service/use-workflow'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useDataSourceList } from '@/service/use-pipeline'
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
import { basePath } from '@/utils/var'
import type { FileUploadConfigResponse } from '@/models/common'
export const usePipelineConfig = () => {
const pipelineId = useStore(s => s.pipelineId)
const workflowStore = useWorkflowStore()
const handleUpdateNodesDefaultConfigs = useCallback((nodesDefaultConfigs: Record<string, any> | Record<string, any>[]) => {
const { setNodesDefaultConfigs } = workflowStore.getState()
let res: Record<string, any> = {}
if (Array.isArray(nodesDefaultConfigs)) {
nodesDefaultConfigs.forEach((item) => {
res[item.type] = item.config
})
}
else {
res = nodesDefaultConfigs as Record<string, any>
}
setNodesDefaultConfigs!(res)
}, [workflowStore])
useWorkflowConfig(
pipelineId ? `/rag/pipelines/${pipelineId}/workflows/default-workflow-block-configs` : '',
handleUpdateNodesDefaultConfigs,
)
const handleUpdatePublishedAt = useCallback((publishedWorkflow: FetchWorkflowDraftResponse) => {
const { setPublishedAt } = workflowStore.getState()
setPublishedAt(publishedWorkflow?.created_at)
}, [workflowStore])
useWorkflowConfig(
pipelineId ? `/rag/pipelines/${pipelineId}/workflows/publish` : '',
handleUpdatePublishedAt,
)
const handleUpdateDataSourceList = useCallback((dataSourceList: DataSourceItem[]) => {
dataSourceList.forEach((item) => {
const icon = item.declaration.identity.icon
if (typeof icon == 'string' && !icon.includes(basePath))
item.declaration.identity.icon = `${basePath}${icon}`
})
const { setDataSourceList } = workflowStore.getState()
setDataSourceList!(dataSourceList)
}, [workflowStore])
const handleUpdateWorkflowFileUploadConfig = useCallback((config: FileUploadConfigResponse) => {
const { setFileUploadConfig } = workflowStore.getState()
setFileUploadConfig(config)
}, [workflowStore])
useWorkflowConfig('/files/upload', handleUpdateWorkflowFileUploadConfig)
useDataSourceList(!!pipelineId, handleUpdateDataSourceList)
}

View File

@@ -0,0 +1,95 @@
import {
useCallback,
useEffect,
useState,
} from 'react'
import {
useWorkflowStore,
} from '@/app/components/workflow/store'
import { usePipelineTemplate } from './use-pipeline-template'
import {
fetchWorkflowDraft,
syncWorkflowDraft,
} from '@/service/workflow'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { usePipelineConfig } from './use-pipeline-config'
export const usePipelineInit = () => {
const workflowStore = useWorkflowStore()
const {
nodes: nodesTemplate,
edges: edgesTemplate,
} = usePipelineTemplate()
const [data, setData] = useState<FetchWorkflowDraftResponse>()
const [isLoading, setIsLoading] = useState(true)
const datasetId = useDatasetDetailContextWithSelector(s => s.dataset)?.pipeline_id
const knowledgeName = useDatasetDetailContextWithSelector(s => s.dataset)?.name
const knowledgeIcon = useDatasetDetailContextWithSelector(s => s.dataset)?.icon_info
useEffect(() => {
workflowStore.setState({ pipelineId: datasetId, knowledgeName, knowledgeIcon })
}, [datasetId, workflowStore, knowledgeName, knowledgeIcon])
usePipelineConfig()
const handleGetInitialWorkflowData = useCallback(async () => {
const {
setEnvSecrets,
setEnvironmentVariables,
setSyncWorkflowDraftHash,
setDraftUpdatedAt,
setToolPublished,
setRagPipelineVariables,
} = workflowStore.getState()
try {
const res = await fetchWorkflowDraft(`/rag/pipelines/${datasetId}/workflows/draft`)
setData(res)
setDraftUpdatedAt(res.updated_at)
setToolPublished(res.tool_published)
setEnvSecrets((res.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
acc[env.id] = env.value
return acc
}, {} as Record<string, string>))
setEnvironmentVariables(res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
setSyncWorkflowDraftHash(res.hash)
setRagPipelineVariables?.(res.rag_pipeline_variables || [])
setIsLoading(false)
}
catch (error: any) {
if (error && error.json && !error.bodyUsed && datasetId) {
error.json().then((err: any) => {
if (err.code === 'draft_workflow_not_exist') {
workflowStore.setState({
notInitialWorkflow: true,
shouldAutoOpenStartNodeSelector: true,
})
syncWorkflowDraft({
url: `/rag/pipelines/${datasetId}/workflows/draft`,
params: {
graph: {
nodes: nodesTemplate,
edges: edgesTemplate,
},
environment_variables: [],
},
}).then((res) => {
const { setDraftUpdatedAt } = workflowStore.getState()
setDraftUpdatedAt(res.updated_at)
handleGetInitialWorkflowData()
})
}
})
}
}
}, [nodesTemplate, edgesTemplate, workflowStore, datasetId])
useEffect(() => {
handleGetInitialWorkflowData()
}, [])
return {
data,
isLoading,
}
}

View File

@@ -0,0 +1,43 @@
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { fetchWorkflowDraft } from '@/service/workflow'
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
import { processNodesWithoutDataSource } from '../utils'
export const usePipelineRefreshDraft = () => {
const workflowStore = useWorkflowStore()
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
const handleRefreshWorkflowDraft = useCallback(() => {
const {
pipelineId,
setSyncWorkflowDraftHash,
setIsSyncingWorkflowDraft,
setEnvironmentVariables,
setEnvSecrets,
} = workflowStore.getState()
setIsSyncingWorkflowDraft(true)
fetchWorkflowDraft(`/rag/pipelines/${pipelineId}/workflows/draft`).then((response) => {
const {
nodes: processedNodes,
viewport,
} = processNodesWithoutDataSource(response.graph.nodes, response.graph.viewport)
handleUpdateWorkflowCanvas({
...response.graph,
nodes: processedNodes,
viewport,
} as WorkflowDataUpdater)
setSyncWorkflowDraftHash(response.hash)
setEnvSecrets((response.environment_variables || []).filter(env => env.value_type === 'secret').reduce((acc, env) => {
acc[env.id] = env.value
return acc
}, {} as Record<string, string>))
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
}).finally(() => setIsSyncingWorkflowDraft(false))
}, [handleUpdateWorkflowCanvas, workflowStore])
return {
handleRefreshWorkflowDraft,
}
}

View File

@@ -0,0 +1,319 @@
import { useCallback } from 'react'
import {
useReactFlow,
useStoreApi,
} from 'reactflow'
import { produce } from 'immer'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-interactions'
import { useWorkflowRunEvent } from '@/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event'
import type { IOtherOptions } from '@/service/base'
import { ssePost } from '@/service/base'
import { stopWorkflowRun } from '@/service/workflow'
import type { VersionHistory } from '@/types/workflow'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars'
import { useInvalidAllLastRun } from '@/service/use-workflow'
import { FlowType } from '@/types/common'
export const usePipelineRun = () => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
const {
handleWorkflowStarted,
handleWorkflowFinished,
handleWorkflowFailed,
handleWorkflowNodeStarted,
handleWorkflowNodeFinished,
handleWorkflowNodeIterationStarted,
handleWorkflowNodeIterationNext,
handleWorkflowNodeIterationFinished,
handleWorkflowNodeLoopStarted,
handleWorkflowNodeLoopNext,
handleWorkflowNodeLoopFinished,
handleWorkflowNodeRetry,
handleWorkflowAgentLog,
handleWorkflowTextChunk,
handleWorkflowTextReplace,
} = useWorkflowRunEvent()
const handleBackupDraft = useCallback(() => {
const {
getNodes,
edges,
} = store.getState()
const { getViewport } = reactflow
const {
backupDraft,
setBackupDraft,
environmentVariables,
} = workflowStore.getState()
if (!backupDraft) {
setBackupDraft({
nodes: getNodes(),
edges,
viewport: getViewport(),
environmentVariables,
})
doSyncWorkflowDraft()
}
}, [reactflow, workflowStore, store, doSyncWorkflowDraft])
const handleLoadBackupDraft = useCallback(() => {
const {
backupDraft,
setBackupDraft,
setEnvironmentVariables,
} = workflowStore.getState()
if (backupDraft) {
const {
nodes,
edges,
viewport,
environmentVariables,
} = backupDraft
handleUpdateWorkflowCanvas({
nodes,
edges,
viewport,
})
setEnvironmentVariables(environmentVariables)
setBackupDraft(undefined)
}
}, [handleUpdateWorkflowCanvas, workflowStore])
const pipelineId = useStore(s => s.pipelineId)
const invalidAllLastRun = useInvalidAllLastRun(FlowType.ragPipeline, pipelineId)
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
flowType: FlowType.ragPipeline,
flowId: pipelineId!,
})
const handleRun = useCallback(async (
params: any,
callback?: IOtherOptions,
) => {
const {
getNodes,
setNodes,
} = store.getState()
const newNodes = produce(getNodes(), (draft) => {
draft.forEach((node) => {
node.data.selected = false
node.data._runningStatus = undefined
})
})
setNodes(newNodes)
await doSyncWorkflowDraft()
const {
onWorkflowStarted,
onWorkflowFinished,
onNodeStarted,
onNodeFinished,
onIterationStart,
onIterationNext,
onIterationFinish,
onLoopStart,
onLoopNext,
onLoopFinish,
onNodeRetry,
onAgentLog,
onError,
...restCallback
} = callback || {}
const { pipelineId } = workflowStore.getState()
workflowStore.setState({ historyWorkflowData: undefined })
const workflowContainer = document.getElementById('workflow-container')
const {
clientWidth,
clientHeight,
} = workflowContainer!
const url = `/rag/pipelines/${pipelineId}/workflows/draft/run`
const {
setWorkflowRunningData,
} = workflowStore.getState()
setWorkflowRunningData({
result: {
inputs_truncated: false,
process_data_truncated: false,
outputs_truncated: false,
status: WorkflowRunningStatus.Running,
},
tracing: [],
resultText: '',
})
ssePost(
url,
{
body: params,
},
{
onWorkflowStarted: (params) => {
handleWorkflowStarted(params)
if (onWorkflowStarted)
onWorkflowStarted(params)
},
onWorkflowFinished: (params) => {
handleWorkflowFinished(params)
fetchInspectVars({})
invalidAllLastRun()
if (onWorkflowFinished)
onWorkflowFinished(params)
},
onError: (params) => {
handleWorkflowFailed()
if (onError)
onError(params)
},
onNodeStarted: (params) => {
handleWorkflowNodeStarted(
params,
{
clientWidth,
clientHeight,
},
)
if (onNodeStarted)
onNodeStarted(params)
},
onNodeFinished: (params) => {
handleWorkflowNodeFinished(params)
if (onNodeFinished)
onNodeFinished(params)
},
onIterationStart: (params) => {
handleWorkflowNodeIterationStarted(
params,
{
clientWidth,
clientHeight,
},
)
if (onIterationStart)
onIterationStart(params)
},
onIterationNext: (params) => {
handleWorkflowNodeIterationNext(params)
if (onIterationNext)
onIterationNext(params)
},
onIterationFinish: (params) => {
handleWorkflowNodeIterationFinished(params)
if (onIterationFinish)
onIterationFinish(params)
},
onLoopStart: (params) => {
handleWorkflowNodeLoopStarted(
params,
{
clientWidth,
clientHeight,
},
)
if (onLoopStart)
onLoopStart(params)
},
onLoopNext: (params) => {
handleWorkflowNodeLoopNext(params)
if (onLoopNext)
onLoopNext(params)
},
onLoopFinish: (params) => {
handleWorkflowNodeLoopFinished(params)
if (onLoopFinish)
onLoopFinish(params)
},
onNodeRetry: (params) => {
handleWorkflowNodeRetry(params)
if (onNodeRetry)
onNodeRetry(params)
},
onAgentLog: (params) => {
handleWorkflowAgentLog(params)
if (onAgentLog)
onAgentLog(params)
},
onTextChunk: (params) => {
handleWorkflowTextChunk(params)
},
onTextReplace: (params) => {
handleWorkflowTextReplace(params)
},
...restCallback,
},
)
}, [
store,
workflowStore,
doSyncWorkflowDraft,
handleWorkflowStarted,
handleWorkflowFinished,
handleWorkflowFailed,
handleWorkflowNodeStarted,
handleWorkflowNodeFinished,
handleWorkflowNodeIterationStarted,
handleWorkflowNodeIterationNext,
handleWorkflowNodeIterationFinished,
handleWorkflowNodeLoopStarted,
handleWorkflowNodeLoopNext,
handleWorkflowNodeLoopFinished,
handleWorkflowNodeRetry,
handleWorkflowTextChunk,
handleWorkflowTextReplace,
handleWorkflowAgentLog,
],
)
const handleStopRun = useCallback((taskId: string) => {
const { pipelineId } = workflowStore.getState()
stopWorkflowRun(`/rag/pipelines/${pipelineId}/workflow-runs/tasks/${taskId}/stop`)
}, [workflowStore])
const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
const nodes = publishedWorkflow.graph.nodes.map(node => ({ ...node, selected: false, data: { ...node.data, selected: false } }))
const edges = publishedWorkflow.graph.edges
const viewport = publishedWorkflow.graph.viewport!
handleUpdateWorkflowCanvas({
nodes,
edges,
viewport,
})
workflowStore.getState().setEnvironmentVariables(publishedWorkflow.environment_variables || [])
workflowStore.getState().setRagPipelineVariables?.(publishedWorkflow.rag_pipeline_variables || [])
}, [handleUpdateWorkflowCanvas, workflowStore])
return {
handleBackupDraft,
handleLoadBackupDraft,
handleRun,
handleStopRun,
handleRestoreFromPublishedWorkflow,
}
}

View File

@@ -0,0 +1,64 @@
import { useCallback } from 'react'
import { useWorkflowStore } from '@/app/components/workflow/store'
import {
WorkflowRunningStatus,
} from '@/app/components/workflow/types'
import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
import {
useInputFieldPanel,
useNodesSyncDraft,
} from '.'
export const usePipelineStartRun = () => {
const workflowStore = useWorkflowStore()
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const { closeAllInputFieldPanels } = useInputFieldPanel()
const handleWorkflowStartRunInWorkflow = useCallback(async () => {
const {
workflowRunningData,
} = workflowStore.getState()
if (workflowRunningData?.result.status === WorkflowRunningStatus.Running)
return
const {
isPreparingDataSource,
setIsPreparingDataSource,
showDebugAndPreviewPanel,
setShowEnvPanel,
setShowDebugAndPreviewPanel,
} = workflowStore.getState()
if (!isPreparingDataSource && workflowRunningData) {
workflowStore.setState({
isPreparingDataSource: true,
workflowRunningData: undefined,
})
return
}
setShowEnvPanel(false)
closeAllInputFieldPanels()
if (showDebugAndPreviewPanel) {
setIsPreparingDataSource?.(false)
handleCancelDebugAndPreviewPanel()
return
}
await doSyncWorkflowDraft()
setIsPreparingDataSource?.(true)
setShowDebugAndPreviewPanel(true)
}, [workflowStore, handleCancelDebugAndPreviewPanel, doSyncWorkflowDraft])
const handleStartWorkflowRun = useCallback(() => {
handleWorkflowStartRunInWorkflow()
}, [handleWorkflowStartRunInWorkflow])
return {
handleStartWorkflowRun,
handleWorkflowStartRunInWorkflow,
}
}

View File

@@ -0,0 +1,30 @@
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({
id: 'knowledgeBase',
data: {
...knowledgeBaseDefault.defaultValue as KnowledgeBaseNodeType,
type: knowledgeBaseDefault.metaData.type,
title: t(`workflow.blocks.${knowledgeBaseDefault.metaData.type}`),
selected: true,
},
position: {
x: START_INITIAL_POSITION.x + 500,
y: START_INITIAL_POSITION.y,
},
})
return {
nodes: [knowledgeBaseNode],
edges: [],
}
}

View File

@@ -0,0 +1,115 @@
import { useCallback } from 'react'
import { getOutgoers, useStoreApi } from 'reactflow'
import { BlockEnum, type Node, type ValueSelector } from '../../workflow/types'
import { uniqBy } from 'lodash-es'
import { findUsedVarNodes, updateNodeVars } from '../../workflow/nodes/_base/components/variable/utils'
import type { DataSourceNodeType } from '../../workflow/nodes/data-source/types'
export const usePipeline = () => {
const store = useStoreApi()
const getAllDatasourceNodes = useCallback(() => {
const {
getNodes,
} = store.getState()
const nodes = getNodes() as Node<DataSourceNodeType>[]
const datasourceNodes = nodes.filter(node => node.data.type === BlockEnum.DataSource)
return datasourceNodes
}, [store])
const getAllNodesInSameBranch = useCallback((nodeId: string) => {
const {
getNodes,
edges,
} = store.getState()
const nodes = getNodes()
const list: Node[] = []
const traverse = (root: Node, callback: (node: Node) => void) => {
if (root) {
const outgoers = getOutgoers(root, nodes, edges)
if (outgoers.length) {
outgoers.forEach((node) => {
callback(node)
traverse(node, callback)
})
}
}
}
if (nodeId === 'shared') {
const allDatasourceNodes = getAllDatasourceNodes()
if (allDatasourceNodes.length === 0)
return []
list.push(...allDatasourceNodes)
allDatasourceNodes.forEach((node) => {
traverse(node, (childNode) => {
list.push(childNode)
})
})
}
else {
const currentNode = nodes.find(node => node.id === nodeId)!
if (!currentNode)
return []
list.push(currentNode)
traverse(currentNode, (node) => {
list.push(node)
})
}
return uniqBy(list, 'id')
}, [getAllDatasourceNodes, store])
const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => {
const nodeId = varSelector[1] // Assuming the first element is always 'VARIABLE_PREFIX'(rag)
const afterNodes = getAllNodesInSameBranch(nodeId)
const effectNodes = findUsedVarNodes(varSelector, afterNodes)
return effectNodes.length > 0
}, [getAllNodesInSameBranch])
const handleInputVarRename = useCallback((nodeId: string, oldValeSelector: ValueSelector, newVarSelector: ValueSelector) => {
const { getNodes, setNodes } = store.getState()
const afterNodes = getAllNodesInSameBranch(nodeId)
const effectNodes = findUsedVarNodes(oldValeSelector, afterNodes)
if (effectNodes.length > 0) {
const newNodes = getNodes().map((node) => {
if (effectNodes.find(n => n.id === node.id))
return updateNodeVars(node, oldValeSelector, newVarSelector)
return node
})
setNodes(newNodes)
}
}, [getAllNodesInSameBranch, store])
const removeUsedVarInNodes = useCallback((varSelector: ValueSelector) => {
const nodeId = varSelector[1] // Assuming the first element is always 'VARIABLE_PREFIX'(rag)
const { getNodes, setNodes } = store.getState()
const afterNodes = getAllNodesInSameBranch(nodeId)
const effectNodes = findUsedVarNodes(varSelector, afterNodes)
if (effectNodes.length > 0) {
const newNodes = getNodes().map((node) => {
if (effectNodes.find(n => n.id === node.id))
return updateNodeVars(node, varSelector, [])
return node
})
setNodes(newNodes)
}
}, [getAllNodesInSameBranch, store])
return {
handleInputVarRename,
isVarUsedInNodes,
removeUsedVarInNodes,
}
}

View File

@@ -0,0 +1,168 @@
'use client'
import { useCallback, useEffect, useMemo } from 'react'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { useNodesInteractions } from '@/app/components/workflow/hooks/use-nodes-interactions'
import type { CommonNodeType } from '@/app/components/workflow/types'
import { ragPipelineNodesAction } from '@/app/components/goto-anything/actions/rag-pipeline-nodes'
import BlockIcon from '@/app/components/workflow/block-icon'
import { setupNodeSelectionListener } from '@/app/components/workflow/utils/node-navigation'
import { BlockEnum } from '@/app/components/workflow/types'
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
import type { ToolNodeType } from '@/app/components/workflow/nodes/tool/types'
import type { KnowledgeRetrievalNodeType } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import { useGetToolIcon } from '@/app/components/workflow/hooks/use-tool-icon'
/**
* Hook to register RAG pipeline nodes search functionality
*/
export const useRagPipelineSearch = () => {
const nodes = useNodes()
const { handleNodeSelect } = useNodesInteractions()
const getToolIcon = useGetToolIcon()
// Process nodes to create searchable data structure
const searchableNodes = useMemo(() => {
return nodes.map((node) => {
const nodeData = node.data as CommonNodeType
const title = nodeData.title || nodeData.type || 'Untitled Node'
let desc = nodeData.desc || ''
// Keep the original node title for consistency with workflow display
// Only enhance description for better search context
if (nodeData.type === BlockEnum.Tool) {
const toolData = nodeData as ToolNodeType
desc = toolData.tool_description || toolData.tool_label || desc
}
if (nodeData.type === BlockEnum.LLM) {
const llmData = nodeData as LLMNodeType
if (llmData.model?.provider && llmData.model?.name)
desc = `${llmData.model.name} (${llmData.model.provider}) - ${llmData.model.mode || desc}`
}
if (nodeData.type === BlockEnum.KnowledgeRetrieval) {
const knowledgeData = nodeData as KnowledgeRetrievalNodeType
if (knowledgeData.dataset_ids?.length)
desc = `Knowledge Retrieval with ${knowledgeData.dataset_ids.length} datasets - ${desc}`
}
return {
id: node.id,
title,
desc,
type: nodeData.type,
blockType: nodeData.type,
nodeData,
toolIcon: getToolIcon(nodeData),
modelInfo: nodeData.type === BlockEnum.LLM ? {
provider: (nodeData as LLMNodeType).model?.provider,
name: (nodeData as LLMNodeType).model?.name,
mode: (nodeData as LLMNodeType).model?.mode,
} : {
provider: undefined,
name: undefined,
mode: undefined,
},
}
})
}, [nodes, getToolIcon])
// Calculate relevance score for search results
const calculateScore = useCallback((node: {
title: string;
type: string;
desc: string;
modelInfo: { provider?: string; name?: string; mode?: string }
}, searchTerm: string): number => {
if (!searchTerm) return 1
let score = 0
const term = searchTerm.toLowerCase()
// Title match (highest priority)
if (node.title.toLowerCase().includes(term))
score += 10
// Type match
if (node.type.toLowerCase().includes(term))
score += 8
// Description match
if (node.desc.toLowerCase().includes(term))
score += 5
// Model info matches (for LLM nodes)
if (node.modelInfo.provider?.toLowerCase().includes(term))
score += 6
if (node.modelInfo.name?.toLowerCase().includes(term))
score += 6
if (node.modelInfo.mode?.toLowerCase().includes(term))
score += 4
return score
}, [])
// Create search function for RAG pipeline nodes
const searchRagPipelineNodes = useCallback((query: string) => {
if (!searchableNodes.length) return []
const searchTerm = query.toLowerCase().trim()
const results = searchableNodes
.map((node) => {
const score = calculateScore(node, searchTerm)
return score > 0 ? {
id: node.id,
title: node.title,
description: node.desc || node.type,
type: 'workflow-node' as const,
path: `#${node.id}`,
icon: (
<BlockIcon
type={node.blockType}
className="shrink-0"
size="sm"
toolIcon={node.toolIcon}
/>
),
metadata: {
nodeId: node.id,
nodeData: node.nodeData,
},
data: node.nodeData,
score,
} : null
})
.filter((node): node is NonNullable<typeof node> => node !== null)
.sort((a, b) => {
// If no search term, sort alphabetically
if (!searchTerm) return a.title.localeCompare(b.title)
// Sort by relevance score (higher score first)
return (b.score || 0) - (a.score || 0)
})
return results
}, [searchableNodes, calculateScore])
// Directly set the search function on the action object
useEffect(() => {
if (searchableNodes.length > 0) {
// Set the search function directly on the action
ragPipelineNodesAction.searchFn = searchRagPipelineNodes
}
return () => {
// Clean up when component unmounts
ragPipelineNodesAction.searchFn = undefined
}
}, [searchableNodes, searchRagPipelineNodes])
// Set up node selection event listener using the utility function
useEffect(() => {
return setupNodeSelectionListener(handleNodeSelect)
}, [handleNodeSelect])
return null
}