yangdx
commited on
Commit
·
a5f80ff
1
Parent(s):
88fc1cb
fix: Replace global searchCache with Zustand state management
Browse files
lightrag_webui/src/components/graph/GraphSearch.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import { FC, useCallback, useEffect
|
2 |
import {
|
3 |
EdgeById,
|
4 |
NodeById,
|
@@ -11,7 +11,9 @@ import { useGraphStore } from '@/stores/graph'
|
|
11 |
import MiniSearch from 'minisearch'
|
12 |
import { useTranslation } from 'react-i18next'
|
13 |
import { OptionItem } from './graphSearchTypes'
|
14 |
-
|
|
|
|
|
15 |
|
16 |
const NodeOption = ({ id }: { id: string }) => {
|
17 |
const graph = useGraphStore.use.sigmaGraph()
|
@@ -46,25 +48,24 @@ export const GraphSearchInput = ({
|
|
46 |
}) => {
|
47 |
const { t } = useTranslation()
|
48 |
const graph = useGraphStore.use.sigmaGraph()
|
|
|
49 |
|
50 |
-
//
|
51 |
useEffect(() => {
|
52 |
if (graph) {
|
53 |
-
|
54 |
-
searchCache.graph = null;
|
55 |
-
searchCache.searchEngine = null;
|
56 |
}
|
57 |
}, [graph]);
|
58 |
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
62 |
}
|
63 |
-
if (!graph || graph.nodes().length == 0) return
|
64 |
-
|
65 |
-
searchCache.graph = graph
|
66 |
|
67 |
-
|
|
|
68 |
idField: 'id',
|
69 |
fields: ['label'],
|
70 |
searchOptions: {
|
@@ -76,16 +77,16 @@ export const GraphSearchInput = ({
|
|
76 |
}
|
77 |
})
|
78 |
|
79 |
-
// Add
|
80 |
const documents = graph.nodes().map((id: string) => ({
|
81 |
id: id,
|
82 |
label: graph.getNodeAttribute(id, 'label')
|
83 |
}))
|
84 |
-
|
85 |
|
86 |
-
|
87 |
-
|
88 |
-
}, [graph])
|
89 |
|
90 |
/**
|
91 |
* Loading the options while the user is typing.
|
@@ -96,9 +97,6 @@ export const GraphSearchInput = ({
|
|
96 |
|
97 |
// Safety checks to prevent crashes
|
98 |
if (!graph || !searchEngine) {
|
99 |
-
// Reset cache to ensure fresh search engine initialization on next render
|
100 |
-
searchCache.graph = null
|
101 |
-
searchCache.searchEngine = null
|
102 |
return []
|
103 |
}
|
104 |
|
@@ -107,7 +105,7 @@ export const GraphSearchInput = ({
|
|
107 |
return []
|
108 |
}
|
109 |
|
110 |
-
// If no query, return
|
111 |
if (!query) {
|
112 |
const nodeIds = graph.nodes()
|
113 |
.filter(id => graph.hasNode(id))
|
|
|
1 |
+
import { FC, useCallback, useEffect } from 'react'
|
2 |
import {
|
3 |
EdgeById,
|
4 |
NodeById,
|
|
|
11 |
import MiniSearch from 'minisearch'
|
12 |
import { useTranslation } from 'react-i18next'
|
13 |
import { OptionItem } from './graphSearchTypes'
|
14 |
+
|
15 |
+
// Message item identifier for search results
|
16 |
+
export const messageId = '__message_item'
|
17 |
|
18 |
const NodeOption = ({ id }: { id: string }) => {
|
19 |
const graph = useGraphStore.use.sigmaGraph()
|
|
|
48 |
}) => {
|
49 |
const { t } = useTranslation()
|
50 |
const graph = useGraphStore.use.sigmaGraph()
|
51 |
+
const searchEngine = useGraphStore.use.searchEngine()
|
52 |
|
53 |
+
// Reset search engine when graph changes
|
54 |
useEffect(() => {
|
55 |
if (graph) {
|
56 |
+
useGraphStore.getState().resetSearchEngine()
|
|
|
|
|
57 |
}
|
58 |
}, [graph]);
|
59 |
|
60 |
+
// Create search engine when needed
|
61 |
+
useEffect(() => {
|
62 |
+
// Skip if no graph, empty graph, or search engine already exists
|
63 |
+
if (!graph || graph.nodes().length === 0 || searchEngine) {
|
64 |
+
return
|
65 |
}
|
|
|
|
|
|
|
66 |
|
67 |
+
// Create new search engine
|
68 |
+
const newSearchEngine = new MiniSearch({
|
69 |
idField: 'id',
|
70 |
fields: ['label'],
|
71 |
searchOptions: {
|
|
|
77 |
}
|
78 |
})
|
79 |
|
80 |
+
// Add nodes to search engine
|
81 |
const documents = graph.nodes().map((id: string) => ({
|
82 |
id: id,
|
83 |
label: graph.getNodeAttribute(id, 'label')
|
84 |
}))
|
85 |
+
newSearchEngine.addAll(documents)
|
86 |
|
87 |
+
// Update search engine in store
|
88 |
+
useGraphStore.getState().setSearchEngine(newSearchEngine)
|
89 |
+
}, [graph, searchEngine])
|
90 |
|
91 |
/**
|
92 |
* Loading the options while the user is typing.
|
|
|
97 |
|
98 |
// Safety checks to prevent crashes
|
99 |
if (!graph || !searchEngine) {
|
|
|
|
|
|
|
100 |
return []
|
101 |
}
|
102 |
|
|
|
105 |
return []
|
106 |
}
|
107 |
|
108 |
+
// If no query, return some nodes for user to select
|
109 |
if (!query) {
|
110 |
const nodeIds = graph.nodes()
|
111 |
.filter(id => graph.hasNode(id))
|
lightrag_webui/src/components/graph/graphSearchUtils.ts
DELETED
@@ -1,13 +0,0 @@
|
|
1 |
-
import { DirectedGraph } from 'graphology'
|
2 |
-
import MiniSearch from 'minisearch'
|
3 |
-
|
4 |
-
export const messageId = '__message_item'
|
5 |
-
|
6 |
-
// Reset this cache when graph changes to ensure fresh search results
|
7 |
-
export const searchCache: {
|
8 |
-
graph: DirectedGraph | null;
|
9 |
-
searchEngine: MiniSearch | null;
|
10 |
-
} = {
|
11 |
-
graph: null,
|
12 |
-
searchEngine: null
|
13 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lightrag_webui/src/hooks/useLightragGraph.tsx
CHANGED
@@ -11,7 +11,6 @@ import { useSettingsStore } from '@/stores/settings'
|
|
11 |
import { useTabVisibility } from '@/contexts/useTabVisibility'
|
12 |
|
13 |
import seedrandom from 'seedrandom'
|
14 |
-
import { searchCache } from '@/components/graph/graphSearchUtils'
|
15 |
|
16 |
const validateGraph = (graph: RawGraph) => {
|
17 |
if (!graph) {
|
@@ -599,9 +598,8 @@ const useLightrangeGraph = () => {
|
|
599 |
// Update the dynamic edge map and invalidate search cache
|
600 |
rawGraph.buildDynamicMap();
|
601 |
|
602 |
-
//
|
603 |
-
|
604 |
-
searchCache.searchEngine = null;
|
605 |
|
606 |
// Update sizes for all nodes with discarded edges
|
607 |
updateNodeSizes(sigmaGraph, nodesWithDiscardedEdges, minDegree, range, scale);
|
@@ -718,9 +716,8 @@ const useLightrangeGraph = () => {
|
|
718 |
// Rebuild the dynamic edge map and invalidate search cache
|
719 |
rawGraph.buildDynamicMap();
|
720 |
|
721 |
-
//
|
722 |
-
|
723 |
-
searchCache.searchEngine = null;
|
724 |
|
725 |
// Show notification if we deleted more than just the selected node
|
726 |
if (nodesToDelete.size > 1) {
|
|
|
11 |
import { useTabVisibility } from '@/contexts/useTabVisibility'
|
12 |
|
13 |
import seedrandom from 'seedrandom'
|
|
|
14 |
|
15 |
const validateGraph = (graph: RawGraph) => {
|
16 |
if (!graph) {
|
|
|
598 |
// Update the dynamic edge map and invalidate search cache
|
599 |
rawGraph.buildDynamicMap();
|
600 |
|
601 |
+
// Reset search engine to force rebuild
|
602 |
+
useGraphStore.getState().resetSearchEngine();
|
|
|
603 |
|
604 |
// Update sizes for all nodes with discarded edges
|
605 |
updateNodeSizes(sigmaGraph, nodesWithDiscardedEdges, minDegree, range, scale);
|
|
|
716 |
// Rebuild the dynamic edge map and invalidate search cache
|
717 |
rawGraph.buildDynamicMap();
|
718 |
|
719 |
+
// Reset search engine to force rebuild
|
720 |
+
useGraphStore.getState().resetSearchEngine();
|
|
|
721 |
|
722 |
// Show notification if we deleted more than just the selected node
|
723 |
if (nodesToDelete.size > 1) {
|
lightrag_webui/src/stores/graph.ts
CHANGED
@@ -2,6 +2,7 @@ import { create } from 'zustand'
|
|
2 |
import { createSelectors } from '@/lib/utils'
|
3 |
import { DirectedGraph } from 'graphology'
|
4 |
import { getGraphLabels } from '@/api/lightrag'
|
|
|
5 |
|
6 |
export type RawNodeType = {
|
7 |
id: string
|
@@ -69,6 +70,9 @@ interface GraphState {
|
|
69 |
sigmaInstance: any | null
|
70 |
allDatabaseLabels: string[]
|
71 |
|
|
|
|
|
|
|
72 |
moveToSelectedNode: boolean
|
73 |
isFetching: boolean
|
74 |
shouldRender: boolean
|
@@ -94,6 +98,10 @@ interface GraphState {
|
|
94 |
setIsFetching: (isFetching: boolean) => void
|
95 |
setShouldRender: (shouldRender: boolean) => void
|
96 |
|
|
|
|
|
|
|
|
|
97 |
// Methods to set global flags
|
98 |
setGraphDataFetchAttempted: (attempted: boolean) => void
|
99 |
setLabelsFetchAttempted: (attempted: boolean) => void
|
@@ -126,6 +134,8 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
|
|
126 |
sigmaInstance: null,
|
127 |
allDatabaseLabels: ['*'],
|
128 |
|
|
|
|
|
129 |
|
130 |
setIsFetching: (isFetching: boolean) => set({ isFetching }),
|
131 |
setShouldRender: (shouldRender: boolean) => set({ shouldRender }),
|
@@ -149,6 +159,7 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
|
|
149 |
focusedEdge: null,
|
150 |
rawGraph: null,
|
151 |
sigmaGraph: null, // to avoid other components from acccessing graph objects
|
|
|
152 |
moveToSelectedNode: false,
|
153 |
shouldRender: false
|
154 |
});
|
@@ -183,6 +194,9 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
|
|
183 |
|
184 |
setSigmaInstance: (instance: any) => set({ sigmaInstance: instance }),
|
185 |
|
|
|
|
|
|
|
186 |
// Methods to set global flags
|
187 |
setGraphDataFetchAttempted: (attempted: boolean) => set({ graphDataFetchAttempted: attempted }),
|
188 |
setLabelsFetchAttempted: (attempted: boolean) => set({ labelsFetchAttempted: attempted }),
|
|
|
2 |
import { createSelectors } from '@/lib/utils'
|
3 |
import { DirectedGraph } from 'graphology'
|
4 |
import { getGraphLabels } from '@/api/lightrag'
|
5 |
+
import MiniSearch from 'minisearch'
|
6 |
|
7 |
export type RawNodeType = {
|
8 |
id: string
|
|
|
70 |
sigmaInstance: any | null
|
71 |
allDatabaseLabels: string[]
|
72 |
|
73 |
+
// 搜索引擎状态
|
74 |
+
searchEngine: MiniSearch | null
|
75 |
+
|
76 |
moveToSelectedNode: boolean
|
77 |
isFetching: boolean
|
78 |
shouldRender: boolean
|
|
|
98 |
setIsFetching: (isFetching: boolean) => void
|
99 |
setShouldRender: (shouldRender: boolean) => void
|
100 |
|
101 |
+
// 搜索引擎方法
|
102 |
+
setSearchEngine: (engine: MiniSearch | null) => void
|
103 |
+
resetSearchEngine: () => void
|
104 |
+
|
105 |
// Methods to set global flags
|
106 |
setGraphDataFetchAttempted: (attempted: boolean) => void
|
107 |
setLabelsFetchAttempted: (attempted: boolean) => void
|
|
|
134 |
sigmaInstance: null,
|
135 |
allDatabaseLabels: ['*'],
|
136 |
|
137 |
+
searchEngine: null,
|
138 |
+
|
139 |
|
140 |
setIsFetching: (isFetching: boolean) => set({ isFetching }),
|
141 |
setShouldRender: (shouldRender: boolean) => set({ shouldRender }),
|
|
|
159 |
focusedEdge: null,
|
160 |
rawGraph: null,
|
161 |
sigmaGraph: null, // to avoid other components from acccessing graph objects
|
162 |
+
searchEngine: null, // 重置搜索引擎
|
163 |
moveToSelectedNode: false,
|
164 |
shouldRender: false
|
165 |
});
|
|
|
194 |
|
195 |
setSigmaInstance: (instance: any) => set({ sigmaInstance: instance }),
|
196 |
|
197 |
+
setSearchEngine: (engine: MiniSearch | null) => set({ searchEngine: engine }),
|
198 |
+
resetSearchEngine: () => set({ searchEngine: null }),
|
199 |
+
|
200 |
// Methods to set global flags
|
201 |
setGraphDataFetchAttempted: (attempted: boolean) => set({ graphDataFetchAttempted: attempted }),
|
202 |
setLabelsFetchAttempted: (attempted: boolean) => set({ labelsFetchAttempted: attempted }),
|