| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- '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
- }
|