feat(goto-anything): add RAG pipeline node search (#25948)

This commit is contained in:
GuanMu
2025-09-19 18:28:13 +08:00
committed by GitHub
parent 4047a6bb12
commit ab910c736c
7 changed files with 270 additions and 30 deletions

View File

@@ -167,10 +167,39 @@ import { appAction } from './app'
import { knowledgeAction } from './knowledge'
import { pluginAction } from './plugin'
import { workflowNodesAction } from './workflow-nodes'
import { ragPipelineNodesAction } from './rag-pipeline-nodes'
import type { ActionItem, SearchResult } from './types'
import { slashAction } from './commands'
import { slashCommandRegistry } from './commands/registry'
// Create dynamic Actions based on context
export const createActions = (isWorkflowPage: boolean, isRagPipelinePage: boolean) => {
const baseActions = {
slash: slashAction,
app: appAction,
knowledge: knowledgeAction,
plugin: pluginAction,
}
// Add appropriate node search based on context
if (isRagPipelinePage) {
return {
...baseActions,
node: ragPipelineNodesAction,
}
}
else if (isWorkflowPage) {
return {
...baseActions,
node: workflowNodesAction,
}
}
// Default actions without node search
return baseActions
}
// Legacy export for backward compatibility
export const Actions = {
slash: slashAction,
app: appAction,
@@ -183,6 +212,7 @@ export const searchAnything = async (
locale: string,
query: string,
actionItem?: ActionItem,
dynamicActions?: Record<string, ActionItem>,
): Promise<SearchResult[]> => {
if (actionItem) {
const searchTerm = query.replace(actionItem.key, '').replace(actionItem.shortcut, '').trim()
@@ -198,7 +228,7 @@ export const searchAnything = async (
if (query.startsWith('@') || query.startsWith('/'))
return []
const globalSearchActions = Object.values(Actions)
const globalSearchActions = Object.values(dynamicActions || Actions)
// Use Promise.allSettled to handle partial failures gracefully
const searchPromises = globalSearchActions.map(async (action) => {

View File

@@ -0,0 +1,24 @@
import type { ActionItem } from './types'
// Create the RAG pipeline nodes action
export const ragPipelineNodesAction: ActionItem = {
key: '@node',
shortcut: '@node',
title: 'Search RAG Pipeline Nodes',
description: 'Find and jump to nodes in the current RAG pipeline by name or type',
searchFn: undefined, // Will be set by useRagPipelineSearch hook
search: async (_, searchTerm = '', _locale) => {
try {
// Use the searchFn if available (set by useRagPipelineSearch hook)
if (ragPipelineNodesAction.searchFn)
return ragPipelineNodesAction.searchFn(searchTerm)
// If not in RAG pipeline context, return empty array
return []
}
catch (error) {
console.warn('RAG pipeline nodes search failed:', error)
return []
}
},
}

View File

@@ -7,7 +7,7 @@ export const workflowNodesAction: ActionItem = {
title: 'Search Workflow Nodes',
description: 'Find and jump to nodes in the current workflow by name or type',
searchFn: undefined, // Will be set by useWorkflowSearch hook
search: async (_, searchTerm = '', locale) => {
search: async (_, searchTerm = '', _locale) => {
try {
// Use the searchFn if available (set by useWorkflowSearch hook)
if (workflowNodesAction.searchFn)

View File

@@ -12,11 +12,16 @@ type GotoAnythingContextType = {
* Whether the current page is a workflow page
*/
isWorkflowPage: boolean
/**
* Whether the current page is a RAG pipeline page
*/
isRagPipelinePage: boolean
}
// Create context with default values
const GotoAnythingContext = createContext<GotoAnythingContextType>({
isWorkflowPage: false,
isRagPipelinePage: false,
})
/**
@@ -33,17 +38,28 @@ type GotoAnythingProviderProps = {
*/
export const GotoAnythingProvider: React.FC<GotoAnythingProviderProps> = ({ children }) => {
const [isWorkflowPage, setIsWorkflowPage] = useState(false)
const [isRagPipelinePage, setIsRagPipelinePage] = useState(false)
const pathname = usePathname()
// Update context based on current pathname
// Update context based on current pathname using more robust route matching
useEffect(() => {
// Check if current path contains workflow
const isWorkflow = pathname?.includes('/workflow') || false
if (!pathname) {
setIsWorkflowPage(false)
setIsRagPipelinePage(false)
return
}
// Workflow pages: /app/[appId]/workflow or /workflow/[token] (shared)
const isWorkflow = /^\/app\/[^/]+\/workflow$/.test(pathname) || /^\/workflow\/[^/]+$/.test(pathname)
// RAG Pipeline pages: /datasets/[datasetId]/pipeline
const isRagPipeline = /^\/datasets\/[^/]+\/pipeline$/.test(pathname)
setIsWorkflowPage(isWorkflow)
setIsRagPipelinePage(isRagPipeline)
}, [pathname])
return (
<GotoAnythingContext.Provider value={{ isWorkflowPage }}>
<GotoAnythingContext.Provider value={{ isWorkflowPage, isRagPipelinePage }}>
{children}
</GotoAnythingContext.Provider>
)

View File

@@ -9,7 +9,7 @@ import { useDebounce, useKeyPress } from 'ahooks'
import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, isMac } from '@/app/components/workflow/utils/common'
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
import { RiSearchLine } from '@remixicon/react'
import { Actions as AllActions, type SearchResult, matchAction, searchAnything } from './actions'
import { type SearchResult, createActions, matchAction, searchAnything } from './actions'
import { GotoAnythingProvider, useGotoAnythingContext } from './context'
import { slashCommandRegistry } from './actions/commands/registry'
import { useQuery } from '@tanstack/react-query'
@@ -29,7 +29,7 @@ const GotoAnything: FC<Props> = ({
}) => {
const router = useRouter()
const defaultLocale = useGetLanguage()
const { isWorkflowPage } = useGotoAnythingContext()
const { isWorkflowPage, isRagPipelinePage } = useGotoAnythingContext()
const { t } = useTranslation()
const [show, setShow] = useState<boolean>(false)
const [searchQuery, setSearchQuery] = useState<string>('')
@@ -38,16 +38,9 @@ const GotoAnything: FC<Props> = ({
// Filter actions based on context
const Actions = useMemo(() => {
// Create a filtered copy of actions based on current page context
if (isWorkflowPage) {
// Include all actions on workflow pages
return AllActions
}
else {
const { app, knowledge, plugin, slash } = AllActions
return { app, knowledge, plugin, slash }
}
}, [isWorkflowPage])
// Create actions based on current page context
return createActions(isWorkflowPage, isRagPipelinePage)
}, [isWorkflowPage, isRagPipelinePage])
const [activePlugin, setActivePlugin] = useState<Plugin>()
@@ -99,9 +92,11 @@ const GotoAnything: FC<Props> = ({
const query = searchQueryDebouncedValue.toLowerCase()
const action = matchAction(query, Actions)
return action
? (action.key === '/' ? '@command' : action.key)
: 'general'
if (!action)
return 'general'
return action.key === '/' ? '@command' : action.key
}, [searchQueryDebouncedValue, Actions, isCommandsMode, searchQuery])
const { data: searchResults = [], isLoading, isError, error } = useQuery(
@@ -112,13 +107,14 @@ const GotoAnything: FC<Props> = ({
searchQueryDebouncedValue,
searchMode,
isWorkflowPage,
isRagPipelinePage,
defaultLocale,
Object.keys(Actions).sort().join(','),
],
queryFn: async () => {
const query = searchQueryDebouncedValue.toLowerCase()
const action = matchAction(query, Actions)
return await searchAnything(defaultLocale, query, action)
return await searchAnything(defaultLocale, query, action, Actions)
},
enabled: !!searchQueryDebouncedValue && !isCommandsMode,
staleTime: 30000,
@@ -446,18 +442,20 @@ const GotoAnything: FC<Props> = ({
) : (
<>
<span className='opacity-60'>
{isCommandsMode
? t('app.gotoAnything.selectToNavigate')
: searchQuery.trim()
? t('app.gotoAnything.searching')
: t('app.gotoAnything.startTyping')
}
{(() => {
if (isCommandsMode)
return t('app.gotoAnything.selectToNavigate')
if (searchQuery.trim())
return t('app.gotoAnything.searching')
return t('app.gotoAnything.startTyping')
})()}
</span>
<span className='opacity-60'>
{searchQuery.trim() || isCommandsMode
? t('app.gotoAnything.tips')
: t('app.gotoAnything.pressEscToClose')
}
: t('app.gotoAnything.pressEscToClose')}
</span>
</>
)}