yangdx
commited on
Commit
·
dcae38a
1
Parent(s):
0a76061
Limit the search scope to labels in the current subgraph
Browse files- Decouple datasource label selection from the search input field
- Improve label selection handling logic
lightrag_webui/src/components/graph/GraphLabels.tsx
CHANGED
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'
|
|
9 |
const GraphLabels = () => {
|
10 |
const { t } = useTranslation()
|
11 |
const label = useSettingsStore.use.queryLabel()
|
12 |
-
const
|
13 |
|
14 |
const getSearchEngine = useCallback(() => {
|
15 |
// Create search engine
|
@@ -26,14 +26,14 @@ const GraphLabels = () => {
|
|
26 |
})
|
27 |
|
28 |
// Add documents
|
29 |
-
const documents =
|
30 |
searchEngine.addAll(documents)
|
31 |
|
32 |
return {
|
33 |
-
labels:
|
34 |
searchEngine
|
35 |
}
|
36 |
-
}, [
|
37 |
|
38 |
const fetchData = useCallback(
|
39 |
async (query?: string): Promise<string[]> => {
|
@@ -47,27 +47,11 @@ const GraphLabels = () => {
|
|
47 |
|
48 |
return result.length <= labelListLimit
|
49 |
? result
|
50 |
-
: [...result.slice(0, labelListLimit),
|
51 |
},
|
52 |
[getSearchEngine, t]
|
53 |
)
|
54 |
|
55 |
-
const setQueryLabel = useCallback((newLabel: string) => {
|
56 |
-
if (newLabel.startsWith('And ') && newLabel.endsWith(' others')) return
|
57 |
-
|
58 |
-
const currentLabel = useSettingsStore.getState().queryLabel
|
59 |
-
|
60 |
-
if (newLabel === '*' && currentLabel === '*') {
|
61 |
-
// When reselecting '*', just set it again to trigger a new fetch
|
62 |
-
useSettingsStore.getState().setQueryLabel('*')
|
63 |
-
} else if (newLabel === currentLabel && newLabel !== '*') {
|
64 |
-
// When selecting the same label (except '*'), switch to '*'
|
65 |
-
useSettingsStore.getState().setQueryLabel('*')
|
66 |
-
} else {
|
67 |
-
useSettingsStore.getState().setQueryLabel(newLabel)
|
68 |
-
}
|
69 |
-
}, [])
|
70 |
-
|
71 |
return (
|
72 |
<AsyncSelect<string>
|
73 |
className="ml-2"
|
@@ -81,8 +65,20 @@ const GraphLabels = () => {
|
|
81 |
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
|
82 |
label={t('graphPanel.graphLabels.label')}
|
83 |
placeholder={t('graphPanel.graphLabels.placeholder')}
|
84 |
-
value={label !== null ? label : ''}
|
85 |
-
onChange={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
clearable={false} // Prevent clearing value on reselect
|
87 |
/>
|
88 |
)
|
|
|
9 |
const GraphLabels = () => {
|
10 |
const { t } = useTranslation()
|
11 |
const label = useSettingsStore.use.queryLabel()
|
12 |
+
const allDatabaseLabels = useGraphStore.use.allDatabaseLabels()
|
13 |
|
14 |
const getSearchEngine = useCallback(() => {
|
15 |
// Create search engine
|
|
|
26 |
})
|
27 |
|
28 |
// Add documents
|
29 |
+
const documents = allDatabaseLabels.map((str, index) => ({ id: index, value: str }))
|
30 |
searchEngine.addAll(documents)
|
31 |
|
32 |
return {
|
33 |
+
labels: allDatabaseLabels,
|
34 |
searchEngine
|
35 |
}
|
36 |
+
}, [allDatabaseLabels])
|
37 |
|
38 |
const fetchData = useCallback(
|
39 |
async (query?: string): Promise<string[]> => {
|
|
|
47 |
|
48 |
return result.length <= labelListLimit
|
49 |
? result
|
50 |
+
: [...result.slice(0, labelListLimit), '...']
|
51 |
},
|
52 |
[getSearchEngine, t]
|
53 |
)
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
return (
|
56 |
<AsyncSelect<string>
|
57 |
className="ml-2"
|
|
|
65 |
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
|
66 |
label={t('graphPanel.graphLabels.label')}
|
67 |
placeholder={t('graphPanel.graphLabels.placeholder')}
|
68 |
+
value={label !== null ? label : '*'}
|
69 |
+
onChange={(newLabel) => {
|
70 |
+
const currentLabel = useSettingsStore.getState().queryLabel
|
71 |
+
|
72 |
+
if (newLabel === '...') {
|
73 |
+
newLabel = '*'
|
74 |
+
}
|
75 |
+
if (newLabel === currentLabel && newLabel !== '*') {
|
76 |
+
// 选择相同标签时切换到'*'
|
77 |
+
useSettingsStore.getState().setQueryLabel('*')
|
78 |
+
} else {
|
79 |
+
useSettingsStore.getState().setQueryLabel(newLabel)
|
80 |
+
}
|
81 |
+
}}
|
82 |
clearable={false} // Prevent clearing value on reselect
|
83 |
/>
|
84 |
)
|
lightrag_webui/src/hooks/useLightragGraph.tsx
CHANGED
@@ -169,6 +169,11 @@ const useLightrangeGraph = () => {
|
|
169 |
const minDegree = useSettingsStore.use.graphMinDegree()
|
170 |
const isFetching = useGraphStore.use.isFetching()
|
171 |
|
|
|
|
|
|
|
|
|
|
|
172 |
// Use ref to track fetch status
|
173 |
const fetchStatusRef = useRef<Record<string, boolean>>({});
|
174 |
|
@@ -222,7 +227,7 @@ const useLightrangeGraph = () => {
|
|
222 |
state.setSigmaGraph(newSigmaGraph)
|
223 |
state.setRawGraph(data)
|
224 |
|
225 |
-
// Extract labels from graph data
|
226 |
if (data) {
|
227 |
const labelSet = new Set<string>();
|
228 |
for (const node of data.nodes) {
|
@@ -241,6 +246,9 @@ const useLightrangeGraph = () => {
|
|
241 |
// Ensure * is there eventhough there is no graph data
|
242 |
state.setGraphLabels(['*']);
|
243 |
}
|
|
|
|
|
|
|
244 |
if (!data) {
|
245 |
// If data is invalid, remove the fetch flag to allow retry
|
246 |
delete fetchStatusRef.current[fetchKey];
|
|
|
169 |
const minDegree = useSettingsStore.use.graphMinDegree()
|
170 |
const isFetching = useGraphStore.use.isFetching()
|
171 |
|
172 |
+
// Fetch all database labels on mount
|
173 |
+
useEffect(() => {
|
174 |
+
useGraphStore.getState().fetchAllDatabaseLabels()
|
175 |
+
}, [])
|
176 |
+
|
177 |
// Use ref to track fetch status
|
178 |
const fetchStatusRef = useRef<Record<string, boolean>>({});
|
179 |
|
|
|
227 |
state.setSigmaGraph(newSigmaGraph)
|
228 |
state.setRawGraph(data)
|
229 |
|
230 |
+
// Extract labels from current graph data
|
231 |
if (data) {
|
232 |
const labelSet = new Set<string>();
|
233 |
for (const node of data.nodes) {
|
|
|
246 |
// Ensure * is there eventhough there is no graph data
|
247 |
state.setGraphLabels(['*']);
|
248 |
}
|
249 |
+
|
250 |
+
// Fetch all database labels after graph update
|
251 |
+
state.fetchAllDatabaseLabels();
|
252 |
if (!data) {
|
253 |
// If data is invalid, remove the fetch flag to allow retry
|
254 |
delete fetchStatusRef.current[fetchKey];
|
lightrag_webui/src/stores/graph.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { create } from 'zustand'
|
2 |
import { createSelectors } from '@/lib/utils'
|
3 |
import { DirectedGraph } from 'graphology'
|
|
|
4 |
|
5 |
export type RawNodeType = {
|
6 |
id: string
|
@@ -66,6 +67,7 @@ interface GraphState {
|
|
66 |
rawGraph: RawGraph | null
|
67 |
sigmaGraph: DirectedGraph | null
|
68 |
graphLabels: string[]
|
|
|
69 |
|
70 |
moveToSelectedNode: boolean
|
71 |
isFetching: boolean
|
@@ -82,6 +84,8 @@ interface GraphState {
|
|
82 |
setRawGraph: (rawGraph: RawGraph | null) => void
|
83 |
setSigmaGraph: (sigmaGraph: DirectedGraph | null) => void
|
84 |
setGraphLabels: (labels: string[]) => void
|
|
|
|
|
85 |
setIsFetching: (isFetching: boolean) => void
|
86 |
}
|
87 |
|
@@ -97,6 +101,7 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
|
|
97 |
rawGraph: null,
|
98 |
sigmaGraph: null,
|
99 |
graphLabels: ['*'],
|
|
|
100 |
|
101 |
setIsFetching: (isFetching: boolean) => set({ isFetching }),
|
102 |
setSelectedNode: (nodeId: string | null, moveToSelectedNode?: boolean) =>
|
@@ -132,6 +137,18 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
|
|
132 |
|
133 |
setGraphLabels: (labels: string[]) => set({ graphLabels: labels }),
|
134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
setMoveToSelectedNode: (moveToSelectedNode?: boolean) => set({ moveToSelectedNode })
|
136 |
}))
|
137 |
|
|
|
1 |
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
|
|
|
67 |
rawGraph: RawGraph | null
|
68 |
sigmaGraph: DirectedGraph | null
|
69 |
graphLabels: string[]
|
70 |
+
allDatabaseLabels: string[]
|
71 |
|
72 |
moveToSelectedNode: boolean
|
73 |
isFetching: boolean
|
|
|
84 |
setRawGraph: (rawGraph: RawGraph | null) => void
|
85 |
setSigmaGraph: (sigmaGraph: DirectedGraph | null) => void
|
86 |
setGraphLabels: (labels: string[]) => void
|
87 |
+
setAllDatabaseLabels: (labels: string[]) => void
|
88 |
+
fetchAllDatabaseLabels: () => Promise<void>
|
89 |
setIsFetching: (isFetching: boolean) => void
|
90 |
}
|
91 |
|
|
|
101 |
rawGraph: null,
|
102 |
sigmaGraph: null,
|
103 |
graphLabels: ['*'],
|
104 |
+
allDatabaseLabels: ['*'],
|
105 |
|
106 |
setIsFetching: (isFetching: boolean) => set({ isFetching }),
|
107 |
setSelectedNode: (nodeId: string | null, moveToSelectedNode?: boolean) =>
|
|
|
137 |
|
138 |
setGraphLabels: (labels: string[]) => set({ graphLabels: labels }),
|
139 |
|
140 |
+
setAllDatabaseLabels: (labels: string[]) => set({ allDatabaseLabels: labels }),
|
141 |
+
|
142 |
+
fetchAllDatabaseLabels: async () => {
|
143 |
+
try {
|
144 |
+
const labels = await getGraphLabels();
|
145 |
+
set({ allDatabaseLabels: ['*', ...labels] });
|
146 |
+
} catch (error) {
|
147 |
+
console.error('Failed to fetch all database labels:', error);
|
148 |
+
set({ allDatabaseLabels: ['*'] });
|
149 |
+
}
|
150 |
+
},
|
151 |
+
|
152 |
setMoveToSelectedNode: (moveToSelectedNode?: boolean) => set({ moveToSelectedNode })
|
153 |
}))
|
154 |
|