You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

use-workflow-search.tsx 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. 'use client'
  2. import { useCallback, useEffect, useMemo } from 'react'
  3. import { useNodes } from 'reactflow'
  4. import { useNodesInteractions } from './use-nodes-interactions'
  5. import type { CommonNodeType } from '../types'
  6. import { workflowNodesAction } from '@/app/components/goto-anything/actions/workflow-nodes'
  7. import BlockIcon from '@/app/components/workflow/block-icon'
  8. import { setupNodeSelectionListener } from '../utils/node-navigation'
  9. /**
  10. * Hook to register workflow nodes search functionality
  11. */
  12. export const useWorkflowSearch = () => {
  13. const nodes = useNodes()
  14. const { handleNodeSelect } = useNodesInteractions()
  15. // Filter and process nodes for search
  16. const searchableNodes = useMemo(() => {
  17. const filteredNodes = nodes.filter((node) => {
  18. if (!node.id || !node.data || node.type === 'sticky') return false
  19. const nodeData = node.data as CommonNodeType
  20. const nodeType = nodeData?.type
  21. const internalStartNodes = ['iteration-start', 'loop-start']
  22. return !internalStartNodes.includes(nodeType)
  23. })
  24. const result = filteredNodes
  25. .map((node) => {
  26. const nodeData = node.data as CommonNodeType
  27. return {
  28. id: node.id,
  29. title: nodeData?.title || nodeData?.type || 'Untitled',
  30. type: nodeData?.type || '',
  31. desc: nodeData?.desc || '',
  32. blockType: nodeData?.type,
  33. nodeData,
  34. }
  35. })
  36. return result
  37. }, [nodes])
  38. // Create search function for workflow nodes
  39. const searchWorkflowNodes = useCallback((query: string) => {
  40. if (!searchableNodes.length || !query.trim()) return []
  41. const searchTerm = query.toLowerCase()
  42. const results = searchableNodes
  43. .map((node) => {
  44. const titleMatch = node.title.toLowerCase()
  45. const typeMatch = node.type.toLowerCase()
  46. const descMatch = node.desc?.toLowerCase() || ''
  47. let score = 0
  48. if (titleMatch.startsWith(searchTerm)) score += 100
  49. else if (titleMatch.includes(searchTerm)) score += 50
  50. else if (typeMatch === searchTerm) score += 80
  51. else if (typeMatch.includes(searchTerm)) score += 30
  52. else if (descMatch.includes(searchTerm)) score += 20
  53. return score > 0
  54. ? {
  55. id: node.id,
  56. title: node.title,
  57. description: node.desc || node.type,
  58. type: 'workflow-node' as const,
  59. path: `#${node.id}`,
  60. icon: (
  61. <BlockIcon
  62. type={node.blockType}
  63. className="shrink-0"
  64. size="sm"
  65. />
  66. ),
  67. metadata: {
  68. nodeId: node.id,
  69. nodeData: node.nodeData,
  70. },
  71. // Add required data property for SearchResult type
  72. data: node.nodeData,
  73. }
  74. : null
  75. })
  76. .filter((node): node is NonNullable<typeof node> => node !== null)
  77. .sort((a, b) => {
  78. const aTitle = a.title.toLowerCase()
  79. const bTitle = b.title.toLowerCase()
  80. if (aTitle.startsWith(searchTerm) && !bTitle.startsWith(searchTerm)) return -1
  81. if (!aTitle.startsWith(searchTerm) && bTitle.startsWith(searchTerm)) return 1
  82. return 0
  83. })
  84. return results
  85. }, [searchableNodes])
  86. // Directly set the search function on the action object
  87. useEffect(() => {
  88. if (searchableNodes.length > 0) {
  89. // Set the search function directly on the action
  90. workflowNodesAction.searchFn = searchWorkflowNodes
  91. }
  92. return () => {
  93. // Clean up when component unmounts
  94. workflowNodesAction.searchFn = undefined
  95. }
  96. }, [searchableNodes, searchWorkflowNodes])
  97. // Set up node selection event listener using the utility function
  98. useEffect(() => {
  99. return setupNodeSelectionListener(handleNodeSelect)
  100. }, [handleNodeSelect])
  101. return null
  102. }