mirror of
https://github.com/langgenius/dify.git
synced 2025-11-13 17:59:59 +00:00
feat(goto-anything): add RAG pipeline node search (#25948)
This commit is contained in:
parent
4047a6bb12
commit
ab910c736c
@ -167,10 +167,39 @@ import { appAction } from './app'
|
|||||||
import { knowledgeAction } from './knowledge'
|
import { knowledgeAction } from './knowledge'
|
||||||
import { pluginAction } from './plugin'
|
import { pluginAction } from './plugin'
|
||||||
import { workflowNodesAction } from './workflow-nodes'
|
import { workflowNodesAction } from './workflow-nodes'
|
||||||
|
import { ragPipelineNodesAction } from './rag-pipeline-nodes'
|
||||||
import type { ActionItem, SearchResult } from './types'
|
import type { ActionItem, SearchResult } from './types'
|
||||||
import { slashAction } from './commands'
|
import { slashAction } from './commands'
|
||||||
import { slashCommandRegistry } from './commands/registry'
|
import { slashCommandRegistry } from './commands/registry'
|
||||||
|
|
||||||
|
// Create dynamic Actions based on context
|
||||||
|
export const createActions = (isWorkflowPage: boolean, isRagPipelinePage: boolean) => {
|
||||||
|
const baseActions = {
|
||||||
|
slash: slashAction,
|
||||||
|
app: appAction,
|
||||||
|
knowledge: knowledgeAction,
|
||||||
|
plugin: pluginAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add appropriate node search based on context
|
||||||
|
if (isRagPipelinePage) {
|
||||||
|
return {
|
||||||
|
...baseActions,
|
||||||
|
node: ragPipelineNodesAction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isWorkflowPage) {
|
||||||
|
return {
|
||||||
|
...baseActions,
|
||||||
|
node: workflowNodesAction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default actions without node search
|
||||||
|
return baseActions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy export for backward compatibility
|
||||||
export const Actions = {
|
export const Actions = {
|
||||||
slash: slashAction,
|
slash: slashAction,
|
||||||
app: appAction,
|
app: appAction,
|
||||||
@ -183,6 +212,7 @@ export const searchAnything = async (
|
|||||||
locale: string,
|
locale: string,
|
||||||
query: string,
|
query: string,
|
||||||
actionItem?: ActionItem,
|
actionItem?: ActionItem,
|
||||||
|
dynamicActions?: Record<string, ActionItem>,
|
||||||
): Promise<SearchResult[]> => {
|
): Promise<SearchResult[]> => {
|
||||||
if (actionItem) {
|
if (actionItem) {
|
||||||
const searchTerm = query.replace(actionItem.key, '').replace(actionItem.shortcut, '').trim()
|
const searchTerm = query.replace(actionItem.key, '').replace(actionItem.shortcut, '').trim()
|
||||||
@ -198,7 +228,7 @@ export const searchAnything = async (
|
|||||||
if (query.startsWith('@') || query.startsWith('/'))
|
if (query.startsWith('@') || query.startsWith('/'))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
const globalSearchActions = Object.values(Actions)
|
const globalSearchActions = Object.values(dynamicActions || Actions)
|
||||||
|
|
||||||
// Use Promise.allSettled to handle partial failures gracefully
|
// Use Promise.allSettled to handle partial failures gracefully
|
||||||
const searchPromises = globalSearchActions.map(async (action) => {
|
const searchPromises = globalSearchActions.map(async (action) => {
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type { ActionItem } from './types'
|
||||||
|
|
||||||
|
// Create the RAG pipeline nodes action
|
||||||
|
export const ragPipelineNodesAction: ActionItem = {
|
||||||
|
key: '@node',
|
||||||
|
shortcut: '@node',
|
||||||
|
title: 'Search RAG Pipeline Nodes',
|
||||||
|
description: 'Find and jump to nodes in the current RAG pipeline by name or type',
|
||||||
|
searchFn: undefined, // Will be set by useRagPipelineSearch hook
|
||||||
|
search: async (_, searchTerm = '', _locale) => {
|
||||||
|
try {
|
||||||
|
// Use the searchFn if available (set by useRagPipelineSearch hook)
|
||||||
|
if (ragPipelineNodesAction.searchFn)
|
||||||
|
return ragPipelineNodesAction.searchFn(searchTerm)
|
||||||
|
|
||||||
|
// If not in RAG pipeline context, return empty array
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.warn('RAG pipeline nodes search failed:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ export const workflowNodesAction: ActionItem = {
|
|||||||
title: 'Search Workflow Nodes',
|
title: 'Search Workflow Nodes',
|
||||||
description: 'Find and jump to nodes in the current workflow by name or type',
|
description: 'Find and jump to nodes in the current workflow by name or type',
|
||||||
searchFn: undefined, // Will be set by useWorkflowSearch hook
|
searchFn: undefined, // Will be set by useWorkflowSearch hook
|
||||||
search: async (_, searchTerm = '', locale) => {
|
search: async (_, searchTerm = '', _locale) => {
|
||||||
try {
|
try {
|
||||||
// Use the searchFn if available (set by useWorkflowSearch hook)
|
// Use the searchFn if available (set by useWorkflowSearch hook)
|
||||||
if (workflowNodesAction.searchFn)
|
if (workflowNodesAction.searchFn)
|
||||||
|
|||||||
@ -12,11 +12,16 @@ type GotoAnythingContextType = {
|
|||||||
* Whether the current page is a workflow page
|
* Whether the current page is a workflow page
|
||||||
*/
|
*/
|
||||||
isWorkflowPage: boolean
|
isWorkflowPage: boolean
|
||||||
|
/**
|
||||||
|
* Whether the current page is a RAG pipeline page
|
||||||
|
*/
|
||||||
|
isRagPipelinePage: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create context with default values
|
// Create context with default values
|
||||||
const GotoAnythingContext = createContext<GotoAnythingContextType>({
|
const GotoAnythingContext = createContext<GotoAnythingContextType>({
|
||||||
isWorkflowPage: false,
|
isWorkflowPage: false,
|
||||||
|
isRagPipelinePage: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,17 +38,28 @@ type GotoAnythingProviderProps = {
|
|||||||
*/
|
*/
|
||||||
export const GotoAnythingProvider: React.FC<GotoAnythingProviderProps> = ({ children }) => {
|
export const GotoAnythingProvider: React.FC<GotoAnythingProviderProps> = ({ children }) => {
|
||||||
const [isWorkflowPage, setIsWorkflowPage] = useState(false)
|
const [isWorkflowPage, setIsWorkflowPage] = useState(false)
|
||||||
|
const [isRagPipelinePage, setIsRagPipelinePage] = useState(false)
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
// Update context based on current pathname
|
// Update context based on current pathname using more robust route matching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if current path contains workflow
|
if (!pathname) {
|
||||||
const isWorkflow = pathname?.includes('/workflow') || false
|
setIsWorkflowPage(false)
|
||||||
|
setIsRagPipelinePage(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workflow pages: /app/[appId]/workflow or /workflow/[token] (shared)
|
||||||
|
const isWorkflow = /^\/app\/[^/]+\/workflow$/.test(pathname) || /^\/workflow\/[^/]+$/.test(pathname)
|
||||||
|
// RAG Pipeline pages: /datasets/[datasetId]/pipeline
|
||||||
|
const isRagPipeline = /^\/datasets\/[^/]+\/pipeline$/.test(pathname)
|
||||||
|
|
||||||
setIsWorkflowPage(isWorkflow)
|
setIsWorkflowPage(isWorkflow)
|
||||||
|
setIsRagPipelinePage(isRagPipeline)
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GotoAnythingContext.Provider value={{ isWorkflowPage }}>
|
<GotoAnythingContext.Provider value={{ isWorkflowPage, isRagPipelinePage }}>
|
||||||
{children}
|
{children}
|
||||||
</GotoAnythingContext.Provider>
|
</GotoAnythingContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { useDebounce, useKeyPress } from 'ahooks'
|
|||||||
import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, isMac } from '@/app/components/workflow/utils/common'
|
import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, isMac } from '@/app/components/workflow/utils/common'
|
||||||
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
|
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
|
||||||
import { RiSearchLine } from '@remixicon/react'
|
import { RiSearchLine } from '@remixicon/react'
|
||||||
import { Actions as AllActions, type SearchResult, matchAction, searchAnything } from './actions'
|
import { type SearchResult, createActions, matchAction, searchAnything } from './actions'
|
||||||
import { GotoAnythingProvider, useGotoAnythingContext } from './context'
|
import { GotoAnythingProvider, useGotoAnythingContext } from './context'
|
||||||
import { slashCommandRegistry } from './actions/commands/registry'
|
import { slashCommandRegistry } from './actions/commands/registry'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
@ -29,7 +29,7 @@ const GotoAnything: FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const defaultLocale = useGetLanguage()
|
const defaultLocale = useGetLanguage()
|
||||||
const { isWorkflowPage } = useGotoAnythingContext()
|
const { isWorkflowPage, isRagPipelinePage } = useGotoAnythingContext()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [show, setShow] = useState<boolean>(false)
|
const [show, setShow] = useState<boolean>(false)
|
||||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||||
@ -38,16 +38,9 @@ const GotoAnything: FC<Props> = ({
|
|||||||
|
|
||||||
// Filter actions based on context
|
// Filter actions based on context
|
||||||
const Actions = useMemo(() => {
|
const Actions = useMemo(() => {
|
||||||
// Create a filtered copy of actions based on current page context
|
// Create actions based on current page context
|
||||||
if (isWorkflowPage) {
|
return createActions(isWorkflowPage, isRagPipelinePage)
|
||||||
// Include all actions on workflow pages
|
}, [isWorkflowPage, isRagPipelinePage])
|
||||||
return AllActions
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const { app, knowledge, plugin, slash } = AllActions
|
|
||||||
return { app, knowledge, plugin, slash }
|
|
||||||
}
|
|
||||||
}, [isWorkflowPage])
|
|
||||||
|
|
||||||
const [activePlugin, setActivePlugin] = useState<Plugin>()
|
const [activePlugin, setActivePlugin] = useState<Plugin>()
|
||||||
|
|
||||||
@ -99,9 +92,11 @@ const GotoAnything: FC<Props> = ({
|
|||||||
|
|
||||||
const query = searchQueryDebouncedValue.toLowerCase()
|
const query = searchQueryDebouncedValue.toLowerCase()
|
||||||
const action = matchAction(query, Actions)
|
const action = matchAction(query, Actions)
|
||||||
return action
|
|
||||||
? (action.key === '/' ? '@command' : action.key)
|
if (!action)
|
||||||
: 'general'
|
return 'general'
|
||||||
|
|
||||||
|
return action.key === '/' ? '@command' : action.key
|
||||||
}, [searchQueryDebouncedValue, Actions, isCommandsMode, searchQuery])
|
}, [searchQueryDebouncedValue, Actions, isCommandsMode, searchQuery])
|
||||||
|
|
||||||
const { data: searchResults = [], isLoading, isError, error } = useQuery(
|
const { data: searchResults = [], isLoading, isError, error } = useQuery(
|
||||||
@ -112,13 +107,14 @@ const GotoAnything: FC<Props> = ({
|
|||||||
searchQueryDebouncedValue,
|
searchQueryDebouncedValue,
|
||||||
searchMode,
|
searchMode,
|
||||||
isWorkflowPage,
|
isWorkflowPage,
|
||||||
|
isRagPipelinePage,
|
||||||
defaultLocale,
|
defaultLocale,
|
||||||
Object.keys(Actions).sort().join(','),
|
Object.keys(Actions).sort().join(','),
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const query = searchQueryDebouncedValue.toLowerCase()
|
const query = searchQueryDebouncedValue.toLowerCase()
|
||||||
const action = matchAction(query, Actions)
|
const action = matchAction(query, Actions)
|
||||||
return await searchAnything(defaultLocale, query, action)
|
return await searchAnything(defaultLocale, query, action, Actions)
|
||||||
},
|
},
|
||||||
enabled: !!searchQueryDebouncedValue && !isCommandsMode,
|
enabled: !!searchQueryDebouncedValue && !isCommandsMode,
|
||||||
staleTime: 30000,
|
staleTime: 30000,
|
||||||
@ -446,18 +442,20 @@ const GotoAnything: FC<Props> = ({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className='opacity-60'>
|
<span className='opacity-60'>
|
||||||
{isCommandsMode
|
{(() => {
|
||||||
? t('app.gotoAnything.selectToNavigate')
|
if (isCommandsMode)
|
||||||
: searchQuery.trim()
|
return t('app.gotoAnything.selectToNavigate')
|
||||||
? t('app.gotoAnything.searching')
|
|
||||||
: t('app.gotoAnything.startTyping')
|
if (searchQuery.trim())
|
||||||
}
|
return t('app.gotoAnything.searching')
|
||||||
|
|
||||||
|
return t('app.gotoAnything.startTyping')
|
||||||
|
})()}
|
||||||
</span>
|
</span>
|
||||||
<span className='opacity-60'>
|
<span className='opacity-60'>
|
||||||
{searchQuery.trim() || isCommandsMode
|
{searchQuery.trim() || isCommandsMode
|
||||||
? t('app.gotoAnything.tips')
|
? t('app.gotoAnything.tips')
|
||||||
: t('app.gotoAnything.pressEscToClose')
|
: t('app.gotoAnything.pressEscToClose')}
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import PublishToast from './publish-toast'
|
import PublishToast from './publish-toast'
|
||||||
|
import { useRagPipelineSearch } from '../hooks/use-rag-pipeline-search'
|
||||||
|
|
||||||
const RagPipelineChildren = () => {
|
const RagPipelineChildren = () => {
|
||||||
const { eventEmitter } = useEventEmitterContextContext()
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
@ -30,6 +31,9 @@ const RagPipelineChildren = () => {
|
|||||||
handleExportDSL,
|
handleExportDSL,
|
||||||
} = useDSL()
|
} = useDSL()
|
||||||
|
|
||||||
|
// Initialize RAG pipeline search functionality
|
||||||
|
useRagPipelineSearch()
|
||||||
|
|
||||||
eventEmitter?.useSubscription((v: any) => {
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
if (v.type === DSL_EXPORT_CHECK)
|
if (v.type === DSL_EXPORT_CHECK)
|
||||||
setSecretEnvList(v.payload.data as EnvironmentVariable[])
|
setSecretEnvList(v.payload.data as EnvironmentVariable[])
|
||||||
|
|||||||
@ -0,0 +1,168 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
|
import { useNodes } from 'reactflow'
|
||||||
|
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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user