mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 04:26:30 +08:00
Node search supports model and name search (#24331)
This commit is contained in:
parent
ad2c541163
commit
295b47cbff
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
npm add -g pnpm@10.13.1
|
npm add -g pnpm@10.15.0
|
||||||
cd web && pnpm install
|
cd web && pnpm install
|
||||||
pipx install uv
|
pipx install uv
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { useStore } from '../store'
|
|||||||
import type { Emoji } from '@/app/components/tools/types'
|
import type { Emoji } from '@/app/components/tools/types'
|
||||||
import { CollectionType } from '@/app/components/tools/types'
|
import { CollectionType } from '@/app/components/tools/types'
|
||||||
import { canFindTool } from '@/utils'
|
import { canFindTool } from '@/utils'
|
||||||
|
import type { LLMNodeType } from '../nodes/llm/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to register workflow nodes search functionality
|
* Hook to register workflow nodes search functionality
|
||||||
@ -26,6 +27,32 @@ export const useWorkflowSearch = () => {
|
|||||||
const workflowTools = useStore(s => s.workflowTools)
|
const workflowTools = useStore(s => s.workflowTools)
|
||||||
const mcpTools = useStore(s => s.mcpTools)
|
const mcpTools = useStore(s => s.mcpTools)
|
||||||
|
|
||||||
|
// Extract tool icon logic - clean separation of concerns
|
||||||
|
const getToolIcon = useCallback((nodeData: CommonNodeType): string | Emoji | undefined => {
|
||||||
|
if (nodeData?.type !== BlockEnum.Tool) return undefined
|
||||||
|
|
||||||
|
const toolCollections: Record<string, any[]> = {
|
||||||
|
[CollectionType.builtIn]: buildInTools,
|
||||||
|
[CollectionType.custom]: customTools,
|
||||||
|
[CollectionType.mcp]: mcpTools,
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetTools = (nodeData.provider_type && toolCollections[nodeData.provider_type]) || workflowTools
|
||||||
|
return targetTools.find((tool: any) => canFindTool(tool.id, nodeData.provider_id))?.icon
|
||||||
|
}, [buildInTools, customTools, workflowTools, mcpTools])
|
||||||
|
|
||||||
|
// Extract model info logic - clean extraction
|
||||||
|
const getModelInfo = useCallback((nodeData: CommonNodeType) => {
|
||||||
|
if (nodeData?.type !== BlockEnum.LLM) return {}
|
||||||
|
|
||||||
|
const llmNodeData = nodeData as LLMNodeType
|
||||||
|
return llmNodeData.model ? {
|
||||||
|
provider: llmNodeData.model.provider,
|
||||||
|
name: llmNodeData.model.name,
|
||||||
|
mode: llmNodeData.model.mode,
|
||||||
|
} : {}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const searchableNodes = useMemo(() => {
|
const searchableNodes = useMemo(() => {
|
||||||
const filteredNodes = nodes.filter((node) => {
|
const filteredNodes = nodes.filter((node) => {
|
||||||
if (!node.id || !node.data || node.type === 'sticky') return false
|
if (!node.id || !node.data || node.type === 'sticky') return false
|
||||||
@ -37,37 +64,58 @@ export const useWorkflowSearch = () => {
|
|||||||
return !internalStartNodes.includes(nodeType)
|
return !internalStartNodes.includes(nodeType)
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = filteredNodes
|
return filteredNodes.map((node) => {
|
||||||
.map((node) => {
|
const nodeData = node.data as CommonNodeType
|
||||||
const nodeData = node.data as CommonNodeType
|
|
||||||
|
|
||||||
// compute tool icon if node is a Tool
|
return {
|
||||||
let toolIcon: string | Emoji | undefined
|
id: node.id,
|
||||||
if (nodeData?.type === BlockEnum.Tool) {
|
title: nodeData?.title || nodeData?.type || 'Untitled',
|
||||||
let targetTools = workflowTools
|
type: nodeData?.type || '',
|
||||||
if (nodeData.provider_type === CollectionType.builtIn)
|
desc: nodeData?.desc || '',
|
||||||
targetTools = buildInTools
|
blockType: nodeData?.type,
|
||||||
else if (nodeData.provider_type === CollectionType.custom)
|
nodeData,
|
||||||
targetTools = customTools
|
toolIcon: getToolIcon(nodeData),
|
||||||
else if (nodeData.provider_type === CollectionType.mcp)
|
modelInfo: getModelInfo(nodeData),
|
||||||
targetTools = mcpTools
|
}
|
||||||
|
})
|
||||||
|
}, [nodes, getToolIcon, getModelInfo])
|
||||||
|
|
||||||
toolIcon = targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, nodeData.provider_id))?.icon
|
// Calculate search score - clean scoring logic
|
||||||
}
|
const calculateScore = useCallback((node: {
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
desc: string;
|
||||||
|
modelInfo: { provider?: string; name?: string; mode?: string }
|
||||||
|
}, searchTerm: string): number => {
|
||||||
|
if (!searchTerm) return 1
|
||||||
|
|
||||||
return {
|
const titleMatch = node.title.toLowerCase()
|
||||||
id: node.id,
|
const typeMatch = node.type.toLowerCase()
|
||||||
title: nodeData?.title || nodeData?.type || 'Untitled',
|
const descMatch = node.desc?.toLowerCase() || ''
|
||||||
type: nodeData?.type || '',
|
const modelProviderMatch = node.modelInfo?.provider?.toLowerCase() || ''
|
||||||
desc: nodeData?.desc || '',
|
const modelNameMatch = node.modelInfo?.name?.toLowerCase() || ''
|
||||||
blockType: nodeData?.type,
|
const modelModeMatch = node.modelInfo?.mode?.toLowerCase() || ''
|
||||||
nodeData,
|
|
||||||
toolIcon,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
let score = 0
|
||||||
}, [nodes, buildInTools, customTools, workflowTools, mcpTools])
|
|
||||||
|
// Title matching (exact prefix > partial match)
|
||||||
|
if (titleMatch.startsWith(searchTerm)) score += 100
|
||||||
|
else if (titleMatch.includes(searchTerm)) score += 50
|
||||||
|
|
||||||
|
// Type matching (exact > partial)
|
||||||
|
if (typeMatch === searchTerm) score += 80
|
||||||
|
else if (typeMatch.includes(searchTerm)) score += 30
|
||||||
|
|
||||||
|
// Description matching (additive)
|
||||||
|
if (descMatch.includes(searchTerm)) score += 20
|
||||||
|
|
||||||
|
// LLM model matching (additive - can combine multiple matches)
|
||||||
|
if (modelNameMatch && modelNameMatch.includes(searchTerm)) score += 60
|
||||||
|
if (modelProviderMatch && modelProviderMatch.includes(searchTerm)) score += 40
|
||||||
|
if (modelModeMatch && modelModeMatch.includes(searchTerm)) score += 30
|
||||||
|
|
||||||
|
return score
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Create search function for workflow nodes
|
// Create search function for workflow nodes
|
||||||
const searchWorkflowNodes = useCallback((query: string) => {
|
const searchWorkflowNodes = useCallback((query: string) => {
|
||||||
@ -77,67 +125,40 @@ export const useWorkflowSearch = () => {
|
|||||||
|
|
||||||
const results = searchableNodes
|
const results = searchableNodes
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
const titleMatch = node.title.toLowerCase()
|
const score = calculateScore(node, searchTerm)
|
||||||
const typeMatch = node.type.toLowerCase()
|
|
||||||
const descMatch = node.desc?.toLowerCase() || ''
|
|
||||||
|
|
||||||
let score = 0
|
return score > 0 ? {
|
||||||
|
id: node.id,
|
||||||
// If no search term, show all nodes with base score
|
title: node.title,
|
||||||
if (!searchTerm) {
|
description: node.desc || node.type,
|
||||||
score = 1
|
type: 'workflow-node' as const,
|
||||||
}
|
path: `#${node.id}`,
|
||||||
else {
|
icon: (
|
||||||
// Score based on search relevance
|
<BlockIcon
|
||||||
if (titleMatch.startsWith(searchTerm)) score += 100
|
type={node.blockType}
|
||||||
else if (titleMatch.includes(searchTerm)) score += 50
|
className="shrink-0"
|
||||||
else if (typeMatch === searchTerm) score += 80
|
size="sm"
|
||||||
else if (typeMatch.includes(searchTerm)) score += 30
|
toolIcon={node.toolIcon}
|
||||||
else if (descMatch.includes(searchTerm)) score += 20
|
/>
|
||||||
}
|
),
|
||||||
|
metadata: {
|
||||||
return score > 0
|
nodeId: node.id,
|
||||||
? {
|
nodeData: node.nodeData,
|
||||||
id: node.id,
|
},
|
||||||
title: node.title,
|
data: node.nodeData,
|
||||||
description: node.desc || node.type,
|
score,
|
||||||
type: 'workflow-node' as const,
|
} : null
|
||||||
path: `#${node.id}`,
|
|
||||||
icon: (
|
|
||||||
<BlockIcon
|
|
||||||
type={node.blockType}
|
|
||||||
className="shrink-0"
|
|
||||||
size="sm"
|
|
||||||
toolIcon={node.toolIcon}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
metadata: {
|
|
||||||
nodeId: node.id,
|
|
||||||
nodeData: node.nodeData,
|
|
||||||
},
|
|
||||||
// Add required data property for SearchResult type
|
|
||||||
data: node.nodeData,
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
})
|
})
|
||||||
.filter((node): node is NonNullable<typeof node> => node !== null)
|
.filter((node): node is NonNullable<typeof node> => node !== null)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// If no search term, sort alphabetically
|
// If no search term, sort alphabetically
|
||||||
if (!searchTerm)
|
if (!searchTerm) return a.title.localeCompare(b.title)
|
||||||
return a.title.localeCompare(b.title)
|
// Sort by relevance score (higher score first)
|
||||||
|
return (b.score || 0) - (a.score || 0)
|
||||||
// Sort by relevance when searching
|
|
||||||
const aTitle = a.title.toLowerCase()
|
|
||||||
const bTitle = b.title.toLowerCase()
|
|
||||||
|
|
||||||
if (aTitle.startsWith(searchTerm) && !bTitle.startsWith(searchTerm)) return -1
|
|
||||||
if (!aTitle.startsWith(searchTerm) && bTitle.startsWith(searchTerm)) return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}, [searchableNodes])
|
}, [searchableNodes, calculateScore])
|
||||||
|
|
||||||
// Directly set the search function on the action object
|
// Directly set the search function on the action object
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "dify-web",
|
"name": "dify-web",
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.14.0",
|
"packageManager": "pnpm@10.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v22.11.0"
|
"node": ">=v22.11.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user