mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-22 09:01:09 +03:00
feat: Improve node search with fuzzy matching and ranking (#5370)
* Improve node search with fuzzy matching and ranking * PR changes
This commit is contained in:
@@ -116,17 +116,111 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Fuzzy search utility function that calculates similarity score
|
||||
const fuzzyScore = (searchTerm, text) => {
|
||||
const search = ((searchTerm ?? '') + '').trim().toLowerCase()
|
||||
if (!search) return 0
|
||||
const target = ((text ?? '') + '').toLowerCase()
|
||||
|
||||
let score = 0
|
||||
let searchIndex = 0
|
||||
let firstMatchIndex = -1
|
||||
let lastMatchIndex = -1
|
||||
let consecutiveMatches = 0
|
||||
|
||||
// Check for exact substring match
|
||||
const exactMatchIndex = target.indexOf(search)
|
||||
if (exactMatchIndex !== -1) {
|
||||
score = 1000
|
||||
// Bonus for match at start of string
|
||||
if (exactMatchIndex === 0) {
|
||||
score += 200
|
||||
}
|
||||
// Bonus for match at start of word
|
||||
else if (target[exactMatchIndex - 1] === ' ' || target[exactMatchIndex - 1] === '-' || target[exactMatchIndex - 1] === '_') {
|
||||
score += 100
|
||||
}
|
||||
// Penalty for how far into the string the match is
|
||||
score -= exactMatchIndex * 2
|
||||
// Penalty for length difference (shorter target = better match)
|
||||
score -= (target.length - search.length) * 3
|
||||
return score
|
||||
}
|
||||
|
||||
// Fuzzy matching with character-by-character scoring
|
||||
for (let i = 0; i < target.length && searchIndex < search.length; i++) {
|
||||
if (target[i] === search[searchIndex]) {
|
||||
// Base score for character match
|
||||
score += 10
|
||||
|
||||
// Bonus for consecutive matches
|
||||
if (lastMatchIndex === i - 1) {
|
||||
consecutiveMatches++
|
||||
score += 5 + consecutiveMatches * 2 // Increasing bonus for longer sequences
|
||||
} else {
|
||||
consecutiveMatches = 0
|
||||
}
|
||||
|
||||
// Bonus for match at start of string
|
||||
if (i === 0) {
|
||||
score += 20
|
||||
}
|
||||
|
||||
// Bonus for match after space or special character (word boundary)
|
||||
if (i > 0 && (target[i - 1] === ' ' || target[i - 1] === '-' || target[i - 1] === '_')) {
|
||||
score += 15
|
||||
}
|
||||
|
||||
if (firstMatchIndex === -1) firstMatchIndex = i
|
||||
lastMatchIndex = i
|
||||
searchIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// Return 0 if not all characters were matched
|
||||
if (searchIndex < search.length) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Penalty for length difference (favor shorter targets)
|
||||
score -= Math.max(0, target.length - search.length) * 2
|
||||
// Penalty for gaps between first/last matched span
|
||||
const span = lastMatchIndex - firstMatchIndex + 1
|
||||
const gaps = Math.max(0, span - search.length)
|
||||
score -= gaps * 3
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// Score and sort nodes by fuzzy search relevance
|
||||
const scoreAndSortNodes = (nodes, searchValue) => {
|
||||
// Return all nodes unsorted if search is empty
|
||||
if (!searchValue || searchValue.trim() === '') {
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Calculate fuzzy scores for each node
|
||||
const nodesWithScores = nodes.map((nd) => {
|
||||
const nameScore = fuzzyScore(searchValue, nd.name)
|
||||
const labelScore = fuzzyScore(searchValue, nd.label)
|
||||
const categoryScore = fuzzyScore(searchValue, nd.category) * 0.5 // Lower weight for category
|
||||
const maxScore = Math.max(nameScore, labelScore, categoryScore)
|
||||
|
||||
return { node: nd, score: maxScore }
|
||||
})
|
||||
|
||||
// Filter nodes with score > 0 and sort by score (highest first)
|
||||
return nodesWithScores
|
||||
.filter((item) => item.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map((item) => item.node)
|
||||
}
|
||||
|
||||
const getSearchedNodes = (value) => {
|
||||
if (isAgentCanvas) {
|
||||
const nodes = nodesData.filter((nd) => !blacklistCategoriesForAgentCanvas.includes(nd.category))
|
||||
nodes.push(...addException())
|
||||
const passed = nodes.filter((nd) => {
|
||||
const passesName = nd.name.toLowerCase().includes(value.toLowerCase())
|
||||
const passesLabel = nd.label.toLowerCase().includes(value.toLowerCase())
|
||||
const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase())
|
||||
return passesName || passesCategory || passesLabel
|
||||
})
|
||||
return passed
|
||||
return scoreAndSortNodes(nodes, value)
|
||||
}
|
||||
let nodes = nodesData.filter((nd) => nd.category !== 'Multi Agents' && nd.category !== 'Sequential Agents')
|
||||
|
||||
@@ -135,13 +229,7 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat
|
||||
nodes = nodes.filter((nd) => !nodeNames.includes(nd.name))
|
||||
}
|
||||
|
||||
const passed = nodes.filter((nd) => {
|
||||
const passesName = nd.name.toLowerCase().includes(value.toLowerCase())
|
||||
const passesLabel = nd.label.toLowerCase().includes(value.toLowerCase())
|
||||
const passesCategory = nd.category.toLowerCase().includes(value.toLowerCase())
|
||||
return passesName || passesCategory || passesLabel
|
||||
})
|
||||
return passed
|
||||
return scoreAndSortNodes(nodes, value)
|
||||
}
|
||||
|
||||
const filterSearch = (value, newTabValue) => {
|
||||
|
||||
Reference in New Issue
Block a user