refactor goto-anything scopes and registry

This commit is contained in:
yyh 2025-12-27 22:31:38 +08:00 committed by crazywoola
parent e5a98b10a9
commit 1367497deb
14 changed files with 146 additions and 206 deletions

View File

@ -9,7 +9,7 @@ import type { MockedFunction } from 'vitest'
* 4. Ensure errors don't propagate to UI layer causing "search failed"
*/
import { Actions, searchAnything } from '@/app/components/goto-anything/actions'
import { appScope, knowledgeScope, pluginScope, searchAnything } from '@/app/components/goto-anything/actions'
import { fetchAppList } from '@/service/apps'
import { postMarketplace } from '@/service/base'
import { fetchDatasets } from '@/service/datasets'
@ -30,6 +30,7 @@ vi.mock('@/service/datasets', () => ({
const mockPostMarketplace = postMarketplace as MockedFunction<typeof postMarketplace>
const mockFetchAppList = fetchAppList as MockedFunction<typeof fetchAppList>
const mockFetchDatasets = fetchDatasets as MockedFunction<typeof fetchDatasets>
const searchScopes = [appScope, knowledgeScope, pluginScope]
describe('GotoAnything Search Error Handling', () => {
beforeEach(() => {
@ -49,10 +50,7 @@ describe('GotoAnything Search Error Handling', () => {
// Mock marketplace API failure (403 permission denied)
mockPostMarketplace.mockRejectedValue(new Error('HTTP 403: Forbidden'))
const pluginAction = Actions.plugin
// Directly call plugin action's search method
const result = await pluginAction.search('@plugin', 'test', 'en')
const result = await pluginScope.search('@plugin', 'test', 'en')
// Should return empty array instead of throwing error
expect(result).toEqual([])
@ -72,8 +70,7 @@ describe('GotoAnything Search Error Handling', () => {
data: { plugins: [] },
})
const pluginAction = Actions.plugin
const result = await pluginAction.search('@plugin', '', 'en')
const result = await pluginScope.search('@plugin', '', 'en')
expect(result).toEqual([])
})
@ -84,8 +81,7 @@ describe('GotoAnything Search Error Handling', () => {
data: null,
})
const pluginAction = Actions.plugin
const result = await pluginAction.search('@plugin', 'test', 'en')
const result = await pluginScope.search('@plugin', 'test', 'en')
expect(result).toEqual([])
})
@ -96,8 +92,7 @@ describe('GotoAnything Search Error Handling', () => {
// Mock app API failure
mockFetchAppList.mockRejectedValue(new Error('API Error'))
const appAction = Actions.app
const result = await appAction.search('@app', 'test', 'en')
const result = await appScope.search('@app', 'test', 'en')
expect(result).toEqual([])
})
@ -106,8 +101,7 @@ describe('GotoAnything Search Error Handling', () => {
// Mock knowledge API failure
mockFetchDatasets.mockRejectedValue(new Error('API Error'))
const knowledgeAction = Actions.knowledge
const result = await knowledgeAction.search('@knowledge', 'test', 'en')
const result = await knowledgeScope.search('@knowledge', 'test', 'en')
expect(result).toEqual([])
})
@ -120,7 +114,7 @@ describe('GotoAnything Search Error Handling', () => {
mockFetchDatasets.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 })
mockPostMarketplace.mockRejectedValue(new Error('Plugin API failed'))
const result = await searchAnything('en', 'test')
const result = await searchAnything('en', 'test', undefined, searchScopes)
// Should return successful results even if plugin search fails
expect(result).toEqual([])
@ -131,8 +125,7 @@ describe('GotoAnything Search Error Handling', () => {
// Mock plugin API failure
mockPostMarketplace.mockRejectedValue(new Error('Plugin service unavailable'))
const pluginAction = Actions.plugin
const result = await searchAnything('en', '@plugin test', pluginAction)
const result = await searchAnything('en', '@plugin test', pluginScope, searchScopes)
// Should return empty array instead of throwing error
expect(result).toEqual([])
@ -142,8 +135,7 @@ describe('GotoAnything Search Error Handling', () => {
// Mock app API failure
mockFetchAppList.mockRejectedValue(new Error('App service unavailable'))
const appAction = Actions.app
const result = await searchAnything('en', '@app test', appAction)
const result = await searchAnything('en', '@app test', appScope, searchScopes)
expect(result).toEqual([])
})
@ -157,9 +149,9 @@ describe('GotoAnything Search Error Handling', () => {
mockFetchDatasets.mockRejectedValue(new Error('Dataset API failed'))
const actions = [
{ name: '@plugin', action: Actions.plugin },
{ name: '@app', action: Actions.app },
{ name: '@knowledge', action: Actions.knowledge },
{ name: '@plugin', action: pluginScope },
{ name: '@app', action: appScope },
{ name: '@knowledge', action: knowledgeScope },
]
for (const { name, action } of actions) {
@ -173,7 +165,7 @@ describe('GotoAnything Search Error Handling', () => {
it('empty search term should be handled properly', async () => {
mockPostMarketplace.mockResolvedValue({ data: { plugins: [] } })
const result = await searchAnything('en', '@plugin ', Actions.plugin)
const result = await searchAnything('en', '@plugin ', pluginScope, searchScopes)
expect(result).toEqual([])
})
@ -183,7 +175,7 @@ describe('GotoAnything Search Error Handling', () => {
mockPostMarketplace.mockRejectedValue(timeoutError)
const result = await searchAnything('en', '@plugin test', Actions.plugin)
const result = await searchAnything('en', '@plugin test', pluginScope, searchScopes)
expect(result).toEqual([])
})
@ -191,7 +183,7 @@ describe('GotoAnything Search Error Handling', () => {
const parseError = new SyntaxError('Unexpected token in JSON')
mockPostMarketplace.mockRejectedValue(parseError)
const result = await searchAnything('en', '@plugin test', Actions.plugin)
const result = await searchAnything('en', '@plugin test', pluginScope, searchScopes)
expect(result).toEqual([])
})
})

View File

@ -1,4 +1,4 @@
import type { ActionItem, AppSearchResult } from './types'
import type { AppSearchResult, ScopeDescriptor } from './types'
import type { App } from '@/types/app'
import { fetchAppList } from '@/service/apps'
import { getRedirectionPath } from '@/utils/app-redirection'
@ -36,8 +36,8 @@ const parser = (apps: App[]): AppSearchResult[] => {
}))
}
export const appAction: ActionItem = {
key: ACTION_KEYS.APP,
export const appScope: ScopeDescriptor = {
id: 'app',
shortcut: ACTION_KEYS.APP,
title: 'Search Applications',
description: 'Search and navigate to your applications',

View File

@ -9,7 +9,7 @@ export {
export { slashCommandRegistry, SlashCommandRegistry } from './registry'
// Command system exports
export { slashAction } from './slash'
export { slashScope } from './slash'
export { registerSlashCommands, SlashCommandProvider, unregisterSlashCommands } from './slash'
export type { SlashCommandHandler } from './types'

View File

@ -1,5 +1,5 @@
'use client'
import type { ActionItem } from '../types'
import type { ScopeDescriptor } from '../types'
import type { SlashCommandDependencies } from './types'
import { useTheme } from 'next-themes'
import { useEffect } from 'react'
@ -8,7 +8,6 @@ import i18n from '@/i18n-config/i18next-config'
import { ACTION_KEYS } from '../../constants'
import { accountCommand } from './account'
import { bananaCommand } from './banana'
import { executeCommand } from './command-bus'
import { communityCommand } from './community'
import { docsCommand } from './docs'
import { forumCommand } from './forum'
@ -17,17 +16,11 @@ import { slashCommandRegistry } from './registry'
import { themeCommand } from './theme'
import { zenCommand } from './zen'
export const slashAction: ActionItem = {
key: ACTION_KEYS.SLASH,
export const slashScope: ScopeDescriptor = {
id: 'slash',
shortcut: ACTION_KEYS.SLASH,
title: i18n.t('app.gotoAnything.actions.slashTitle'),
description: i18n.t('app.gotoAnything.actions.slashDesc'),
action: (result) => {
if (result.type !== 'command')
return
const { command, args } = result.data
executeCommand(command, args)
},
search: async (query, _searchTerm = '') => {
// Delegate all search logic to the command registry system
return slashCommandRegistry.search(query, i18n.language)

View File

@ -5,96 +5,64 @@
* Actions handle different types of searches: apps, knowledge bases, plugins, workflow nodes, and commands.
*/
import type { ActionItem, ScopeDescriptor, SearchResult } from './types'
import type { ScopeContext, ScopeDescriptor, SearchResult } from './types'
import { ACTION_KEYS } from '../constants'
import { appAction } from './app'
import { slashAction } from './commands'
import { appScope } from './app'
import { slashScope } from './commands'
import { slashCommandRegistry } from './commands/registry'
import { knowledgeAction } from './knowledge'
import { pluginAction } from './plugin'
import { scopeRegistry } from './scope-registry'
import { knowledgeScope } from './knowledge'
import { pluginScope } from './plugin'
import { registerRagPipelineNodeScope } from './rag-pipeline-nodes'
import { scopeRegistry, useScopeRegistry } from './scope-registry'
import { registerWorkflowNodeScope } from './workflow-nodes'
let defaultScopesRegistered = false
let scopesInitialized = false
export const registerDefaultScopes = () => {
if (defaultScopesRegistered)
export const initGotoAnythingScopes = () => {
if (scopesInitialized)
return
defaultScopesRegistered = true
scopesInitialized = true
scopeRegistry.register({
id: 'slash',
shortcut: ACTION_KEYS.SLASH,
title: 'Commands',
description: 'Execute commands',
search: slashAction.search,
isAvailable: () => true,
})
scopeRegistry.register({
id: 'app',
shortcut: ACTION_KEYS.APP,
title: 'Search Applications',
description: 'Search and navigate to your applications',
search: appAction.search,
isAvailable: () => true,
})
scopeRegistry.register({
id: 'knowledge',
shortcut: ACTION_KEYS.KNOWLEDGE,
title: 'Search Knowledge Bases',
description: 'Search and navigate to your knowledge bases',
search: knowledgeAction.search,
isAvailable: () => true,
})
scopeRegistry.register({
id: 'plugin',
shortcut: ACTION_KEYS.PLUGIN,
title: 'Search Plugins',
description: 'Search and navigate to your plugins',
search: pluginAction.search,
isAvailable: () => true,
})
scopeRegistry.register(slashScope)
scopeRegistry.register(appScope)
scopeRegistry.register(knowledgeScope)
scopeRegistry.register(pluginScope)
registerWorkflowNodeScope()
registerRagPipelineNodeScope()
}
// Legacy export for backward compatibility
export const Actions = {
slash: slashAction,
app: appAction,
knowledge: knowledgeAction,
plugin: pluginAction,
export const useGotoAnythingScopes = (context: ScopeContext) => {
initGotoAnythingScopes()
return useScopeRegistry(context)
}
const getScopeId = (scope: ScopeDescriptor | ActionItem) => ('id' in scope ? scope.id : scope.key)
const isSlashScope = (scope: ScopeDescriptor) => {
if (scope.shortcut === ACTION_KEYS.SLASH)
return true
return scope.aliases?.includes(ACTION_KEYS.SLASH) ?? false
}
const isSlashScope = (scope: ScopeDescriptor | ActionItem) => scope.shortcut === ACTION_KEYS.SLASH
const getScopeShortcuts = (scope: ScopeDescriptor) => [scope.shortcut, ...(scope.aliases ?? [])]
export const searchAnything = async (
locale: string,
query: string,
scope?: ScopeDescriptor | ActionItem,
scopes?: (ScopeDescriptor | ActionItem)[],
scope: ScopeDescriptor | undefined,
scopes: ScopeDescriptor[],
): Promise<SearchResult[]> => {
registerDefaultScopes()
const trimmedQuery = query.trim()
// Backwards compatibility: if scopes is not provided or empty, use non-page-specific scopes
const effectiveScopes = (scopes && scopes.length > 0)
? scopes
: scopeRegistry.getScopes({ isWorkflowPage: false, isRagPipelinePage: false })
if (scope) {
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const scopeId = getScopeId(scope)
const prefixPattern = new RegExp(`^(${escapeRegExp(scope.shortcut)})\\s*`)
const shortcuts = getScopeShortcuts(scope).map(escapeRegExp)
const prefixPattern = new RegExp(`^(${shortcuts.join('|')})\\s*`)
const searchTerm = trimmedQuery.replace(prefixPattern, '').trim()
try {
return await scope.search(query, searchTerm, locale)
}
catch (error) {
console.warn(`Search failed for ${scopeId}:`, error)
console.warn(`Search failed for ${scope.id}:`, error)
return []
}
}
@ -103,11 +71,11 @@ export const searchAnything = async (
return []
// Filter out slash commands from general search
const searchScopes = effectiveScopes.filter(scope => !isSlashScope(scope))
const searchScopes = scopes.filter(scope => !isSlashScope(scope))
// Use Promise.allSettled to handle partial failures gracefully
const searchPromises = searchScopes.map(async (action) => {
const actionId = getScopeId(action)
const actionId = action.id
try {
const results = await action.search(query, query, locale)
return { success: true, data: results, actionType: actionId }
@ -128,7 +96,7 @@ export const searchAnything = async (
allResults.push(...result.value.data)
}
else {
const actionKey = getScopeId(searchScopes[index]) || 'unknown'
const actionKey = searchScopes[index]?.id || 'unknown'
failedActions.push(actionKey)
}
})
@ -142,11 +110,10 @@ export const searchAnything = async (
// ...
export const matchAction = (query: string, scopes: ScopeDescriptor[]) => {
registerDefaultScopes()
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return scopes.find((scope) => {
// Special handling for slash commands
if (scope.shortcut === ACTION_KEYS.SLASH) {
if (isSlashScope(scope)) {
const allCommands = slashCommandRegistry.getAllCommands()
return allCommands.some((cmd) => {
const cmdPattern = `/${cmd.name}`
@ -158,7 +125,8 @@ export const matchAction = (query: string, scopes: ScopeDescriptor[]) => {
// Check if query matches shortcut (exact or prefix)
// Only match if it's the full shortcut followed by space
const reg = new RegExp(`^(${escapeRegExp(scope.shortcut)})(?:\\s|$)`)
const shortcuts = getScopeShortcuts(scope).map(escapeRegExp)
const reg = new RegExp(`^(${shortcuts.join('|')})(?:\\s|$)`)
return reg.test(query)
})
}
@ -166,4 +134,4 @@ export const matchAction = (query: string, scopes: ScopeDescriptor[]) => {
export * from './commands'
export * from './scope-registry'
export * from './types'
export { appAction, knowledgeAction, pluginAction }
export { appScope, knowledgeScope, pluginScope }

View File

@ -1,4 +1,4 @@
import type { ActionItem, KnowledgeSearchResult } from './types'
import type { KnowledgeSearchResult, ScopeDescriptor } from './types'
import type { DataSet } from '@/models/datasets'
import { fetchDatasets } from '@/service/datasets'
import { cn } from '@/utils/classnames'
@ -31,9 +31,10 @@ const parser = (datasets: DataSet[]): KnowledgeSearchResult[] => {
})
}
export const knowledgeAction: ActionItem = {
key: ACTION_KEYS.KNOWLEDGE,
shortcut: '@kb',
export const knowledgeScope: ScopeDescriptor = {
id: 'knowledge',
shortcut: ACTION_KEYS.KNOWLEDGE,
aliases: ['@kb'],
title: 'Search Knowledge Bases',
description: 'Search and navigate to your knowledge bases',
// action,

View File

@ -1,5 +1,5 @@
import type { Plugin, PluginsFromMarketplaceResponse } from '../../plugins/types'
import type { ActionItem, PluginSearchResult } from './types'
import type { PluginSearchResult, ScopeDescriptor } from './types'
import { renderI18nObject } from '@/i18n-config'
import { postMarketplace } from '@/service/base'
import Icon from '../../plugins/card/base/card-icon'
@ -19,8 +19,8 @@ const parser = (plugins: Plugin[], locale: string): PluginSearchResult[] => {
})
}
export const pluginAction: ActionItem = {
key: ACTION_KEYS.PLUGIN,
export const pluginScope: ScopeDescriptor = {
id: 'plugin',
shortcut: ACTION_KEYS.PLUGIN,
title: 'Search Plugins',
description: 'Search and navigate to your plugins',

View File

@ -4,6 +4,7 @@ import { ACTION_KEYS } from '../constants'
import { scopeRegistry } from './scope-registry'
const scopeId = 'rag-pipeline-node'
let scopeRegistered = false
const buildSearchHandler = (searchFn?: (searchTerm: string) => SearchResult[]): ScopeSearchHandler => {
return async (_, searchTerm = '', _locale) => {
@ -19,11 +20,11 @@ const buildSearchHandler = (searchFn?: (searchTerm: string) => SearchResult[]):
}
}
export const setRagPipelineNodesSearchFn = (fn: (searchTerm: string) => SearchResult[]) => {
scopeRegistry.updateSearchHandler(scopeId, buildSearchHandler(fn))
}
export const registerRagPipelineNodeScope = () => {
if (scopeRegistered)
return
// Register the RAG pipeline nodes action
scopeRegistered = true
scopeRegistry.register({
id: scopeId,
shortcut: ACTION_KEYS.NODE,
@ -32,9 +33,9 @@ scopeRegistry.register({
isAvailable: context => context.isRagPipelinePage,
search: buildSearchHandler(),
})
// Legacy export
export const ragPipelineNodesAction = {
key: ACTION_KEYS.NODE,
search: async () => [],
}
export const setRagPipelineNodesSearchFn = (fn: (searchTerm: string) => SearchResult[]) => {
registerRagPipelineNodeScope()
scopeRegistry.updateSearchHandler(scopeId, buildSearchHandler(fn))
}

View File

@ -23,6 +23,10 @@ export type ScopeDescriptor = {
* Shortcut to trigger this scope (e.g. '@app')
*/
shortcut: string
/**
* Additional shortcuts that map to this scope (e.g. ['@kb'])
*/
aliases?: string[]
/**
* I18n key or string for the scope title
*/
@ -95,9 +99,7 @@ class ScopeRegistry {
export const scopeRegistry = new ScopeRegistry()
export const useScopeRegistry = (context: ScopeContext, initialize?: () => void) => {
initialize?.()
export const useScopeRegistry = (context: ScopeContext) => {
const subscribe = useCallback(
(listener: Listener) => scopeRegistry.subscribe(listener),
[],

View File

@ -1,8 +1,6 @@
import type { ReactNode } from 'react'
import type { TypeWithI18N } from '../../base/form/types'
import type { Plugin } from '../../plugins/types'
import type { CommonNodeType } from '../../workflow/types'
import type { ActionKey } from '../constants'
import type { DataSet } from '@/models/datasets'
import type { App } from '@/types/app'
@ -44,25 +42,4 @@ export type CommandSearchResult = {
export type SearchResult = AppSearchResult | PluginSearchResult | KnowledgeSearchResult | WorkflowNodeSearchResult | CommandSearchResult
// Legacy ActionItem for backward compatibility if needed, but we should move to ScopeDescriptor
export type ActionItem = {
key: ActionKey
shortcut: string
title: string | TypeWithI18N
description: string
/**
* @deprecated use search() instead
*/
action?: (data: SearchResult) => void
/**
* @deprecated use search() instead
*/
searchFn?: (searchTerm: string) => SearchResult[]
search: (
query: string,
searchTerm: string,
locale?: string,
) => (Promise<SearchResult[]> | SearchResult[])
}
export type { ScopeContext, ScopeDescriptor } from './scope-registry'

View File

@ -4,6 +4,7 @@ import { ACTION_KEYS } from '../constants'
import { scopeRegistry } from './scope-registry'
const scopeId = 'workflow-node'
let scopeRegistered = false
const buildSearchHandler = (searchFn?: (searchTerm: string) => SearchResult[]): ScopeSearchHandler => {
return async (_, searchTerm = '', _locale) => {
@ -19,11 +20,11 @@ const buildSearchHandler = (searchFn?: (searchTerm: string) => SearchResult[]):
}
}
export const setWorkflowNodesSearchFn = (fn: (searchTerm: string) => SearchResult[]) => {
scopeRegistry.updateSearchHandler(scopeId, buildSearchHandler(fn))
}
export const registerWorkflowNodeScope = () => {
if (scopeRegistered)
return
// Register the workflow nodes action
scopeRegistered = true
scopeRegistry.register({
id: scopeId,
shortcut: ACTION_KEYS.NODE,
@ -32,9 +33,9 @@ scopeRegistry.register({
isAvailable: context => context.isWorkflowPage,
search: buildSearchHandler(),
})
// Legacy export if needed (though we should migrate away from it)
export const workflowNodesAction = {
key: ACTION_KEYS.NODE,
search: async () => [], // Dummy implementation
}
export const setWorkflowNodesSearchFn = (fn: (searchTerm: string) => SearchResult[]) => {
registerWorkflowNodeScope()
scopeRegistry.updateSearchHandler(scopeId, buildSearchHandler(fn))
}

View File

@ -55,9 +55,11 @@ const CommandSelector: FC<Props> = ({ scopes, onCommandSelect, searchFilter, com
if (!searchFilter)
return true
// Match against shortcut or title
return scope.shortcut.toLowerCase().includes(searchFilter.toLowerCase())
|| scope.title.toLowerCase().includes(searchFilter.toLowerCase())
// Match against shortcut/aliases or title
const filterLower = searchFilter.toLowerCase()
const shortcuts = [scope.shortcut, ...(scope.aliases || [])]
return shortcuts.some(shortcut => shortcut.toLowerCase().includes(filterLower))
|| scope.title.toLowerCase().includes(filterLower)
}).map(scope => ({
key: scope.shortcut, // Map to shortcut for UI display consistency
shortcut: scope.shortcut,

View File

@ -48,33 +48,25 @@ vi.mock('./context', () => ({
GotoAnythingProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}))
const createScope = (id: ScopeDescriptor['id'], shortcut: string): ScopeDescriptor => ({
id,
shortcut,
title: `${id} title`,
description: `${id} desc`,
search: vi.fn(),
})
const scopesMock = [
createScope('slash', '/'),
createScope('app', '@app'),
createScope('plugin', '@plugin'),
]
type MatchAction = typeof import('./actions').matchAction
type SearchAnything = typeof import('./actions').searchAnything
const useScopeRegistryMock = vi.fn(() => scopesMock)
const matchActionMock = vi.fn<MatchAction>(() => undefined)
const searchAnythingMock = vi.fn<SearchAnything>(async () => mockQueryResult.data)
const registerDefaultScopesMock = vi.fn()
const mockState = vi.hoisted(() => {
const state = {
scopes: [] as ScopeDescriptor[],
useGotoAnythingScopesMock: vi.fn(() => state.scopes),
matchActionMock: vi.fn<MatchAction>(() => undefined),
searchAnythingMock: vi.fn<SearchAnything>(async () => []),
}
return state
})
vi.mock('./actions', () => ({
__esModule: true,
matchAction: (...args: Parameters<MatchAction>) => matchActionMock(...args),
searchAnything: (...args: Parameters<SearchAnything>) => searchAnythingMock(...args),
registerDefaultScopes: () => registerDefaultScopesMock(),
matchAction: (...args: Parameters<MatchAction>) => mockState.matchActionMock(...args),
searchAnything: (...args: Parameters<SearchAnything>) => mockState.searchAnythingMock(...args),
useGotoAnythingScopes: () => mockState.useGotoAnythingScopesMock(),
}))
vi.mock('./actions/commands', () => ({
@ -90,9 +82,19 @@ vi.mock('./actions/commands/registry', () => ({
},
}))
vi.mock('./actions/scope-registry', () => ({
useScopeRegistry: () => useScopeRegistryMock(),
}))
const createScope = (id: ScopeDescriptor['id'], shortcut: string): ScopeDescriptor => ({
id,
shortcut,
title: `${id} title`,
description: `${id} desc`,
search: vi.fn(),
})
const scopesMock = [
createScope('slash', '/'),
createScope('app', '@app'),
createScope('plugin', '@plugin'),
]
vi.mock('@/app/components/workflow/utils/common', () => ({
getKeyboardKeyCodeBySystem: () => 'ctrl',
@ -118,8 +120,10 @@ describe('GotoAnything', () => {
routerPush.mockClear()
Object.keys(keyPressHandlers).forEach(key => delete keyPressHandlers[key])
mockQueryResult = { data: [], isLoading: false, isError: false, error: null }
matchActionMock.mockReset()
searchAnythingMock.mockClear()
mockState.scopes = scopesMock
mockState.matchActionMock.mockReset()
mockState.searchAnythingMock.mockClear()
mockState.searchAnythingMock.mockImplementation(async () => mockQueryResult.data)
})
it('should open modal via shortcut and navigate to selected result', async () => {

View File

@ -17,10 +17,9 @@ import { getKeyboardKeyCodeBySystem, isEventTargetInputArea, isMac } from '@/app
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
import { useGetLanguage } from '@/context/i18n'
import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace'
import { matchAction, registerDefaultScopes, searchAnything } from './actions'
import { matchAction, searchAnything, useGotoAnythingScopes } from './actions'
import { executeCommand, SlashCommandProvider } from './actions/commands'
import { slashCommandRegistry } from './actions/commands/registry'
import { useScopeRegistry } from './actions/scope-registry'
import CommandSelector from './command-selector'
import { ACTION_KEYS, EMPTY_STATE_I18N_MAP, GROUP_HEADING_I18N_MAP } from './constants'
import { GotoAnythingProvider, useGotoAnythingContext } from './context'
@ -41,7 +40,7 @@ const GotoAnything: FC<Props> = ({
const inputRef = useRef<HTMLInputElement>(null)
// Fetch scopes from registry based on context
const scopes = useScopeRegistry({ isWorkflowPage, isRagPipelinePage }, registerDefaultScopes)
const scopes = useGotoAnythingScopes({ isWorkflowPage, isRagPipelinePage })
const [activePlugin, setActivePlugin] = useState<Plugin>()