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 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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) return []
  41. const searchTerm = query.toLowerCase().trim()
  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 no search term, show all nodes with base score
  49. if (!searchTerm) {
  50. score = 1
  51. }
  52. else {
  53. // Score based on search relevance
  54. if (titleMatch.startsWith(searchTerm)) score += 100
  55. else if (titleMatch.includes(searchTerm)) score += 50
  56. else if (typeMatch === searchTerm) score += 80
  57. else if (typeMatch.includes(searchTerm)) score += 30
  58. else if (descMatch.includes(searchTerm)) score += 20
  59. }
  60. return score > 0
  61. ? {
  62. id: node.id,
  63. title: node.title,
  64. description: node.desc || node.type,
  65. type: 'workflow-node' as const,
  66. path: `#${node.id}`,
  67. icon: (
  68. <BlockIcon
  69. type={node.blockType}
  70. className="shrink-0"
  71. size="sm"
  72. />
  73. ),
  74. metadata: {
  75. nodeId: node.id,
  76. nodeData: node.nodeData,
  77. },
  78. // Add required data property for SearchResult type
  79. data: node.nodeData,
  80. }
  81. : null
  82. })
  83. .filter((node): node is NonNullable<typeof node> => node !== null)
  84. .sort((a, b) => {
  85. // If no search term, sort alphabetically
  86. if (!searchTerm)
  87. return a.title.localeCompare(b.title)
  88. // Sort by relevance when searching
  89. const aTitle = a.title.toLowerCase()
  90. const bTitle = b.title.toLowerCase()
  91. if (aTitle.startsWith(searchTerm) && !bTitle.startsWith(searchTerm)) return -1
  92. if (!aTitle.startsWith(searchTerm) && bTitle.startsWith(searchTerm)) return 1
  93. return 0
  94. })
  95. return results
  96. }, [searchableNodes])
  97. // Directly set the search function on the action object
  98. useEffect(() => {
  99. if (searchableNodes.length > 0) {
  100. // Set the search function directly on the action
  101. workflowNodesAction.searchFn = searchWorkflowNodes
  102. }
  103. return () => {
  104. // Clean up when component unmounts
  105. workflowNodesAction.searchFn = undefined
  106. }
  107. }, [searchableNodes, searchWorkflowNodes])
  108. // Set up node selection event listener using the utility function
  109. useEffect(() => {
  110. return setupNodeSelectionListener(handleNodeSelect)
  111. }, [handleNodeSelect])
  112. return null
  113. }