diff --git a/lightrag_webui/src/components/graph/GraphSearch.tsx b/lightrag_webui/src/components/graph/GraphSearch.tsx index c03a2467..2ad81326 100644 --- a/lightrag_webui/src/components/graph/GraphSearch.tsx +++ b/lightrag_webui/src/components/graph/GraphSearch.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useEffect, useMemo } from 'react' +import { FC, useCallback, useEffect } from 'react' import { EdgeById, NodeById, @@ -11,7 +11,9 @@ import { useGraphStore } from '@/stores/graph' import MiniSearch from 'minisearch' import { useTranslation } from 'react-i18next' import { OptionItem } from './graphSearchTypes' -import { messageId, searchCache } from './graphSearchUtils' + +// Message item identifier for search results +export const messageId = '__message_item' const NodeOption = ({ id }: { id: string }) => { const graph = useGraphStore.use.sigmaGraph() @@ -46,25 +48,24 @@ export const GraphSearchInput = ({ }) => { const { t } = useTranslation() const graph = useGraphStore.use.sigmaGraph() + const searchEngine = useGraphStore.use.searchEngine() - // Force reset the cache when graph changes + // Reset search engine when graph changes useEffect(() => { if (graph) { - // Reset cache to ensure fresh search results with new graph data - searchCache.graph = null; - searchCache.searchEngine = null; + useGraphStore.getState().resetSearchEngine() } }, [graph]); - const searchEngine = useMemo(() => { - if (searchCache.graph == graph) { - return searchCache.searchEngine + // Create search engine when needed + useEffect(() => { + // Skip if no graph, empty graph, or search engine already exists + if (!graph || graph.nodes().length === 0 || searchEngine) { + return } - if (!graph || graph.nodes().length == 0) return - searchCache.graph = graph - - const searchEngine = new MiniSearch({ + // Create new search engine + const newSearchEngine = new MiniSearch({ idField: 'id', fields: ['label'], searchOptions: { @@ -76,16 +77,16 @@ export const GraphSearchInput = ({ } }) - // Add documents + // Add nodes to search engine const documents = graph.nodes().map((id: string) => ({ id: id, label: graph.getNodeAttribute(id, 'label') })) - searchEngine.addAll(documents) + newSearchEngine.addAll(documents) - searchCache.searchEngine = searchEngine - return searchEngine - }, [graph]) + // Update search engine in store + useGraphStore.getState().setSearchEngine(newSearchEngine) + }, [graph, searchEngine]) /** * Loading the options while the user is typing. @@ -96,9 +97,6 @@ export const GraphSearchInput = ({ // Safety checks to prevent crashes if (!graph || !searchEngine) { - // Reset cache to ensure fresh search engine initialization on next render - searchCache.graph = null - searchCache.searchEngine = null return [] } @@ -107,7 +105,7 @@ export const GraphSearchInput = ({ return [] } - // If no query, return first searchResultLimit nodes that exist + // If no query, return some nodes for user to select if (!query) { const nodeIds = graph.nodes() .filter(id => graph.hasNode(id)) diff --git a/lightrag_webui/src/components/graph/graphSearchUtils.ts b/lightrag_webui/src/components/graph/graphSearchUtils.ts deleted file mode 100644 index d715a02d..00000000 --- a/lightrag_webui/src/components/graph/graphSearchUtils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DirectedGraph } from 'graphology' -import MiniSearch from 'minisearch' - -export const messageId = '__message_item' - -// Reset this cache when graph changes to ensure fresh search results -export const searchCache: { - graph: DirectedGraph | null; - searchEngine: MiniSearch | null; -} = { - graph: null, - searchEngine: null -} diff --git a/lightrag_webui/src/hooks/useLightragGraph.tsx b/lightrag_webui/src/hooks/useLightragGraph.tsx index 3268b04b..d7322413 100644 --- a/lightrag_webui/src/hooks/useLightragGraph.tsx +++ b/lightrag_webui/src/hooks/useLightragGraph.tsx @@ -11,7 +11,6 @@ import { useSettingsStore } from '@/stores/settings' import { useTabVisibility } from '@/contexts/useTabVisibility' import seedrandom from 'seedrandom' -import { searchCache } from '@/components/graph/graphSearchUtils' const validateGraph = (graph: RawGraph) => { if (!graph) { @@ -599,9 +598,8 @@ const useLightrangeGraph = () => { // Update the dynamic edge map and invalidate search cache rawGraph.buildDynamicMap(); - // Force search engine rebuild by invalidating cache - searchCache.graph = null; - searchCache.searchEngine = null; + // Reset search engine to force rebuild + useGraphStore.getState().resetSearchEngine(); // Update sizes for all nodes with discarded edges updateNodeSizes(sigmaGraph, nodesWithDiscardedEdges, minDegree, range, scale); @@ -718,9 +716,8 @@ const useLightrangeGraph = () => { // Rebuild the dynamic edge map and invalidate search cache rawGraph.buildDynamicMap(); - // Force search engine rebuild by invalidating cache - searchCache.graph = null; - searchCache.searchEngine = null; + // Reset search engine to force rebuild + useGraphStore.getState().resetSearchEngine(); // Show notification if we deleted more than just the selected node if (nodesToDelete.size > 1) { diff --git a/lightrag_webui/src/stores/graph.ts b/lightrag_webui/src/stores/graph.ts index e1c01137..605c93d9 100644 --- a/lightrag_webui/src/stores/graph.ts +++ b/lightrag_webui/src/stores/graph.ts @@ -2,6 +2,7 @@ import { create } from 'zustand' import { createSelectors } from '@/lib/utils' import { DirectedGraph } from 'graphology' import { getGraphLabels } from '@/api/lightrag' +import MiniSearch from 'minisearch' export type RawNodeType = { id: string @@ -69,6 +70,9 @@ interface GraphState { sigmaInstance: any | null allDatabaseLabels: string[] + // 搜索引擎状态 + searchEngine: MiniSearch | null + moveToSelectedNode: boolean isFetching: boolean shouldRender: boolean @@ -94,6 +98,10 @@ interface GraphState { setIsFetching: (isFetching: boolean) => void setShouldRender: (shouldRender: boolean) => void + // 搜索引擎方法 + setSearchEngine: (engine: MiniSearch | null) => void + resetSearchEngine: () => void + // Methods to set global flags setGraphDataFetchAttempted: (attempted: boolean) => void setLabelsFetchAttempted: (attempted: boolean) => void @@ -126,6 +134,8 @@ const useGraphStoreBase = create()((set) => ({ sigmaInstance: null, allDatabaseLabels: ['*'], + searchEngine: null, + setIsFetching: (isFetching: boolean) => set({ isFetching }), setShouldRender: (shouldRender: boolean) => set({ shouldRender }), @@ -149,6 +159,7 @@ const useGraphStoreBase = create()((set) => ({ focusedEdge: null, rawGraph: null, sigmaGraph: null, // to avoid other components from acccessing graph objects + searchEngine: null, // 重置搜索引擎 moveToSelectedNode: false, shouldRender: false }); @@ -183,6 +194,9 @@ const useGraphStoreBase = create()((set) => ({ setSigmaInstance: (instance: any) => set({ sigmaInstance: instance }), + setSearchEngine: (engine: MiniSearch | null) => set({ searchEngine: engine }), + resetSearchEngine: () => set({ searchEngine: null }), + // Methods to set global flags setGraphDataFetchAttempted: (attempted: boolean) => set({ graphDataFetchAttempted: attempted }), setLabelsFetchAttempted: (attempted: boolean) => set({ labelsFetchAttempted: attempted }),