diff --git a/lightrag_webui/src/components/graph/GraphSearch.tsx b/lightrag_webui/src/components/graph/GraphSearch.tsx
index 2ba36bda..0570acf3 100644
--- a/lightrag_webui/src/components/graph/GraphSearch.tsx
+++ b/lightrag_webui/src/components/graph/GraphSearch.tsx
@@ -10,29 +10,27 @@ import { searchResultLimit } from '@/lib/constants'
import { useGraphStore } from '@/stores/graph'
import MiniSearch from 'minisearch'
import { useTranslation } from 'react-i18next'
+import { OptionItem } from './graphSearchTypes'
+import { messageId, searchCache } from './graphSearchUtils'
-interface OptionItem {
- id: string
- type: 'nodes' | 'edges' | 'message'
- message?: string
+const NodeOption = ({ id }: { id: string }) => {
+ const graph = useGraphStore.use.sigmaGraph()
+ if (!graph?.hasNode(id)) {
+ return null
+ }
+ return
}
function OptionComponent(item: OptionItem) {
return (
- {item.type === 'nodes' &&
}
+ {item.type === 'nodes' &&
}
{item.type === 'edges' &&
}
{item.type === 'message' &&
{item.message}
}
)
}
-const messageId = '__message_item'
-// Reset this cache when graph changes to ensure fresh search results
-const lastGraph: any = {
- graph: null,
- searchEngine: null
-}
/**
* Component thats display the search input.
@@ -53,18 +51,18 @@ export const GraphSearchInput = ({
useEffect(() => {
if (graph) {
// Reset cache to ensure fresh search results with new graph data
- lastGraph.graph = null;
- lastGraph.searchEngine = null;
+ searchCache.graph = null;
+ searchCache.searchEngine = null;
}
}, [graph]);
const searchEngine = useMemo(() => {
- if (lastGraph.graph == graph) {
- return lastGraph.searchEngine
+ if (searchCache.graph == graph) {
+ return searchCache.searchEngine
}
if (!graph || graph.nodes().length == 0) return
- lastGraph.graph = graph
+ searchCache.graph = graph
const searchEngine = new MiniSearch({
idField: 'id',
@@ -85,7 +83,7 @@ export const GraphSearchInput = ({
}))
searchEngine.addAll(documents)
- lastGraph.searchEngine = searchEngine
+ searchCache.searchEngine = searchEngine
return searchEngine
}, [graph])
@@ -95,22 +93,38 @@ export const GraphSearchInput = ({
const loadOptions = useCallback(
async (query?: string): Promise => {
if (onFocus) onFocus(null)
- if (!graph || !searchEngine) return []
+
+ // 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 []
+ }
- // If no query, return first searchResultLimit nodes
+ // Verify graph has nodes before proceeding
+ if (graph.nodes().length === 0) {
+ return []
+ }
+
+ // If no query, return first searchResultLimit nodes that exist
if (!query) {
- const nodeIds = graph.nodes().slice(0, searchResultLimit)
+ const nodeIds = graph.nodes()
+ .filter(id => graph.hasNode(id))
+ .slice(0, searchResultLimit)
return nodeIds.map(id => ({
id,
type: 'nodes'
}))
}
- // If has query, search nodes
- const result: OptionItem[] = searchEngine.search(query).map((r: { id: string }) => ({
- id: r.id,
- type: 'nodes'
- }))
+ // If has query, search nodes and verify they still exist
+ const result: OptionItem[] = searchEngine.search(query)
+ .filter((r: { id: string }) => graph.hasNode(r.id))
+ .map((r: { id: string }) => ({
+ id: r.id,
+ type: 'nodes'
+ }))
// prettier-ignore
return result.length <= searchResultLimit
diff --git a/lightrag_webui/src/components/graph/graphSearchTypes.ts b/lightrag_webui/src/components/graph/graphSearchTypes.ts
new file mode 100644
index 00000000..80e392da
--- /dev/null
+++ b/lightrag_webui/src/components/graph/graphSearchTypes.ts
@@ -0,0 +1,5 @@
+export interface OptionItem {
+ id: string
+ type: 'nodes' | 'edges' | 'message'
+ message?: string
+}
diff --git a/lightrag_webui/src/components/graph/graphSearchUtils.ts b/lightrag_webui/src/components/graph/graphSearchUtils.ts
new file mode 100644
index 00000000..6f4cd4c9
--- /dev/null
+++ b/lightrag_webui/src/components/graph/graphSearchUtils.ts
@@ -0,0 +1,28 @@
+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
+}
+
+export const updateSearchEngine = (nodeId: string, graph: DirectedGraph) => {
+ if (!searchCache.searchEngine || !graph) return
+
+ const newDocument = {
+ id: nodeId,
+ label: graph.getNodeAttribute(nodeId, 'label')
+ }
+ searchCache.searchEngine.add(newDocument)
+}
+
+export const removeFromSearchEngine = (nodeId: string) => {
+ if (!searchCache.searchEngine) return
+ searchCache.searchEngine.remove({ id: nodeId })
+}
diff --git a/lightrag_webui/src/hooks/useLightragGraph.tsx b/lightrag_webui/src/hooks/useLightragGraph.tsx
index 98c7fc0a..bf727138 100644
--- a/lightrag_webui/src/hooks/useLightragGraph.tsx
+++ b/lightrag_webui/src/hooks/useLightragGraph.tsx
@@ -11,6 +11,7 @@ import { useSettingsStore } from '@/stores/settings'
import { useTabVisibility } from '@/contexts/useTabVisibility'
import seedrandom from 'seedrandom'
+import { searchCache, updateSearchEngine, removeFromSearchEngine } from '@/components/graph/graphSearchUtils'
const validateGraph = (graph: RawGraph) => {
if (!graph) {
@@ -544,6 +545,8 @@ const useLightrangeGraph = () => {
rawGraph.nodes.push(newNode);
// Update nodeIdMap
rawGraph.nodeIdMap[nodeId] = rawGraph.nodes.length - 1;
+ // Update search engine with new node
+ updateSearchEngine(nodeId, sigmaGraph);
}
}
@@ -572,8 +575,12 @@ const useLightrangeGraph = () => {
}
}
- // Update the dynamic edge map
+ // Update the dynamic edge map and invalidate search cache
rawGraph.buildDynamicMap();
+
+ // Force search engine rebuild by invalidating cache
+ searchCache.graph = null;
+ searchCache.searchEngine = null;
// STEP 4: Update the expanded node's size
if (sigmaGraph.hasNode(nodeId)) {
@@ -710,11 +717,17 @@ const useLightrangeGraph = () => {
// Remove from nodeIdMap
delete rawGraph.nodeIdMap[nodeToDelete];
+ // Remove from search engine
+ removeFromSearchEngine(nodeToDelete);
}
}
- // Rebuild the dynamic edge map
+ // Rebuild the dynamic edge map and invalidate search cache
rawGraph.buildDynamicMap();
+
+ // Force search engine rebuild by invalidating cache
+ searchCache.graph = null;
+ searchCache.searchEngine = null;
// Show notification if we deleted more than just the selected node
if (nodesToDelete.size > 1) {