zrguo commited on
Commit
74d902f
·
unverified ·
2 Parent(s): d00af94 15aa24f

Merge pull request #1003 from danielaskdd/add-graph-search-mode

Browse files
lightrag/api/routers/graph_routes.py CHANGED
@@ -3,7 +3,6 @@ This module contains all graph-related routes for the LightRAG API.
3
  """
4
 
5
  from typing import Optional
6
-
7
  from fastapi import APIRouter, Depends
8
 
9
  from ..utils_api import get_api_key_dependency, get_auth_dependency
@@ -25,23 +24,33 @@ def create_graph_routes(rag, api_key: Optional[str] = None):
25
  return await rag.get_graph_labels()
26
 
27
  @router.get("/graphs", dependencies=[Depends(optional_api_key)])
28
- async def get_knowledge_graph(label: str, max_depth: int = 3):
 
 
29
  """
30
  Retrieve a connected subgraph of nodes where the label includes the specified label.
31
  Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
32
  When reducing the number of nodes, the prioritization criteria are as follows:
33
- 1. Label matching nodes take precedence
34
- 2. Followed by nodes directly connected to the matching nodes
35
- 3. Finally, the degree of the nodes
 
36
  Maximum number of nodes is limited to env MAX_GRAPH_NODES(default: 1000)
37
 
38
  Args:
39
  label (str): Label to get knowledge graph for
40
  max_depth (int, optional): Maximum depth of graph. Defaults to 3.
 
 
41
 
42
  Returns:
43
  Dict[str, List[str]]: Knowledge graph for label
44
  """
45
- return await rag.get_knowledge_graph(node_label=label, max_depth=max_depth)
 
 
 
 
 
46
 
47
  return router
 
3
  """
4
 
5
  from typing import Optional
 
6
  from fastapi import APIRouter, Depends
7
 
8
  from ..utils_api import get_api_key_dependency, get_auth_dependency
 
24
  return await rag.get_graph_labels()
25
 
26
  @router.get("/graphs", dependencies=[Depends(optional_api_key)])
27
+ async def get_knowledge_graph(
28
+ label: str, max_depth: int = 3, min_degree: int = 0, inclusive: bool = False
29
+ ):
30
  """
31
  Retrieve a connected subgraph of nodes where the label includes the specified label.
32
  Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
33
  When reducing the number of nodes, the prioritization criteria are as follows:
34
+ 1. min_degree does not affect nodes directly connected to the matching nodes
35
+ 2. Label matching nodes take precedence
36
+ 3. Followed by nodes directly connected to the matching nodes
37
+ 4. Finally, the degree of the nodes
38
  Maximum number of nodes is limited to env MAX_GRAPH_NODES(default: 1000)
39
 
40
  Args:
41
  label (str): Label to get knowledge graph for
42
  max_depth (int, optional): Maximum depth of graph. Defaults to 3.
43
+ inclusive_search (bool, optional): If True, search for nodes that include the label. Defaults to False.
44
+ min_degree (int, optional): Minimum degree of nodes. Defaults to 0.
45
 
46
  Returns:
47
  Dict[str, List[str]]: Knowledge graph for label
48
  """
49
+ return await rag.get_knowledge_graph(
50
+ node_label=label,
51
+ max_depth=max_depth,
52
+ inclusive=inclusive,
53
+ min_degree=min_degree,
54
+ )
55
 
56
  return router
lightrag/api/webui/assets/{index-rP-YlyR1.css → index-CH-3l4_Z.css} RENAMED
Binary files a/lightrag/api/webui/assets/index-rP-YlyR1.css and b/lightrag/api/webui/assets/index-CH-3l4_Z.css differ
 
lightrag/api/webui/assets/{index-DbuMPJAD.js → index-CJz72b6Q.js} RENAMED
Binary files a/lightrag/api/webui/assets/index-DbuMPJAD.js and b/lightrag/api/webui/assets/index-CJz72b6Q.js differ
 
lightrag/api/webui/index.html CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
 
lightrag/base.py CHANGED
@@ -204,7 +204,7 @@ class BaseGraphStorage(StorageNameSpace, ABC):
204
 
205
  @abstractmethod
206
  async def get_knowledge_graph(
207
- self, node_label: str, max_depth: int = 5
208
  ) -> KnowledgeGraph:
209
  """Retrieve a subgraph of the knowledge graph starting from a given node."""
210
 
 
204
 
205
  @abstractmethod
206
  async def get_knowledge_graph(
207
+ self, node_label: str, max_depth: int = 3
208
  ) -> KnowledgeGraph:
209
  """Retrieve a subgraph of the knowledge graph starting from a given node."""
210
 
lightrag/kg/networkx_impl.py CHANGED
@@ -232,19 +232,26 @@ class NetworkXStorage(BaseGraphStorage):
232
  return sorted(list(labels))
233
 
234
  async def get_knowledge_graph(
235
- self, node_label: str, max_depth: int = 5
 
 
 
 
236
  ) -> KnowledgeGraph:
237
  """
238
  Retrieve a connected subgraph of nodes where the label includes the specified `node_label`.
239
  Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
240
  When reducing the number of nodes, the prioritization criteria are as follows:
241
- 1. Label matching nodes take precedence
242
- 2. Followed by nodes directly connected to the matching nodes
243
- 3. Finally, the degree of the nodes
 
244
 
245
  Args:
246
  node_label: Label of the starting node
247
  max_depth: Maximum depth of the subgraph
 
 
248
 
249
  Returns:
250
  KnowledgeGraph object containing nodes and edges
@@ -255,6 +262,10 @@ class NetworkXStorage(BaseGraphStorage):
255
 
256
  graph = await self._get_graph()
257
 
 
 
 
 
258
  # Handle special case for "*" label
259
  if node_label == "*":
260
  # For "*", return the entire graph including all nodes and edges
@@ -262,11 +273,16 @@ class NetworkXStorage(BaseGraphStorage):
262
  graph.copy()
263
  ) # Create a copy to avoid modifying the original graph
264
  else:
265
- # Find nodes with matching node id (partial match)
266
  nodes_to_explore = []
267
  for n, attr in graph.nodes(data=True):
268
- if node_label in str(n): # Use partial matching
269
- nodes_to_explore.append(n)
 
 
 
 
 
270
 
271
  if not nodes_to_explore:
272
  logger.warning(f"No nodes found with label {node_label}")
@@ -277,26 +293,37 @@ class NetworkXStorage(BaseGraphStorage):
277
  for start_node in nodes_to_explore:
278
  node_subgraph = nx.ego_graph(graph, start_node, radius=max_depth)
279
  combined_subgraph = nx.compose(combined_subgraph, node_subgraph)
280
- subgraph = combined_subgraph
281
-
282
- # Check if number of nodes exceeds max_graph_nodes
283
- if len(subgraph.nodes()) > MAX_GRAPH_NODES:
284
- origin_nodes = len(subgraph.nodes())
285
-
286
- node_degrees = dict(subgraph.degree())
287
-
288
- start_nodes = set()
289
- direct_connected_nodes = set()
290
 
291
- if node_label != "*" and nodes_to_explore:
 
292
  start_nodes = set(nodes_to_explore)
293
  # Get nodes directly connected to all start nodes
294
  for start_node in start_nodes:
295
- direct_connected_nodes.update(subgraph.neighbors(start_node))
 
 
296
 
297
  # Remove start nodes from directly connected nodes (avoid duplicates)
298
  direct_connected_nodes -= start_nodes
299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  def priority_key(node_item):
301
  node, degree = node_item
302
  # Priority order: start(2) > directly connected(1) > other nodes(0)
@@ -356,7 +383,7 @@ class NetworkXStorage(BaseGraphStorage):
356
  result.edges.append(
357
  KnowledgeGraphEdge(
358
  id=edge_id,
359
- type="RELATED",
360
  source=str(source),
361
  target=str(target),
362
  properties=edge_data,
 
232
  return sorted(list(labels))
233
 
234
  async def get_knowledge_graph(
235
+ self,
236
+ node_label: str,
237
+ max_depth: int = 3,
238
+ min_degree: int = 0,
239
+ inclusive: bool = False,
240
  ) -> KnowledgeGraph:
241
  """
242
  Retrieve a connected subgraph of nodes where the label includes the specified `node_label`.
243
  Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
244
  When reducing the number of nodes, the prioritization criteria are as follows:
245
+ 1. min_degree does not affect nodes directly connected to the matching nodes
246
+ 2. Label matching nodes take precedence
247
+ 3. Followed by nodes directly connected to the matching nodes
248
+ 4. Finally, the degree of the nodes
249
 
250
  Args:
251
  node_label: Label of the starting node
252
  max_depth: Maximum depth of the subgraph
253
+ min_degree: Minimum degree of nodes to include. Defaults to 0
254
+ inclusive: Do an inclusive search if true
255
 
256
  Returns:
257
  KnowledgeGraph object containing nodes and edges
 
262
 
263
  graph = await self._get_graph()
264
 
265
+ # Initialize sets for start nodes and direct connected nodes
266
+ start_nodes = set()
267
+ direct_connected_nodes = set()
268
+
269
  # Handle special case for "*" label
270
  if node_label == "*":
271
  # For "*", return the entire graph including all nodes and edges
 
273
  graph.copy()
274
  ) # Create a copy to avoid modifying the original graph
275
  else:
276
+ # Find nodes with matching node id based on search_mode
277
  nodes_to_explore = []
278
  for n, attr in graph.nodes(data=True):
279
+ node_str = str(n)
280
+ if not inclusive:
281
+ if node_label == node_str: # Use exact matching
282
+ nodes_to_explore.append(n)
283
+ else: # inclusive mode
284
+ if node_label in node_str: # Use partial matching
285
+ nodes_to_explore.append(n)
286
 
287
  if not nodes_to_explore:
288
  logger.warning(f"No nodes found with label {node_label}")
 
293
  for start_node in nodes_to_explore:
294
  node_subgraph = nx.ego_graph(graph, start_node, radius=max_depth)
295
  combined_subgraph = nx.compose(combined_subgraph, node_subgraph)
 
 
 
 
 
 
 
 
 
 
296
 
297
+ # Get start nodes and direct connected nodes
298
+ if nodes_to_explore:
299
  start_nodes = set(nodes_to_explore)
300
  # Get nodes directly connected to all start nodes
301
  for start_node in start_nodes:
302
+ direct_connected_nodes.update(
303
+ combined_subgraph.neighbors(start_node)
304
+ )
305
 
306
  # Remove start nodes from directly connected nodes (avoid duplicates)
307
  direct_connected_nodes -= start_nodes
308
 
309
+ subgraph = combined_subgraph
310
+
311
+ # Filter nodes based on min_degree, but keep start nodes and direct connected nodes
312
+ if min_degree > 0:
313
+ nodes_to_keep = [
314
+ node
315
+ for node, degree in subgraph.degree()
316
+ if node in start_nodes
317
+ or node in direct_connected_nodes
318
+ or degree >= min_degree
319
+ ]
320
+ subgraph = subgraph.subgraph(nodes_to_keep)
321
+
322
+ # Check if number of nodes exceeds max_graph_nodes
323
+ if len(subgraph.nodes()) > MAX_GRAPH_NODES:
324
+ origin_nodes = len(subgraph.nodes())
325
+ node_degrees = dict(subgraph.degree())
326
+
327
  def priority_key(node_item):
328
  node, degree = node_item
329
  # Priority order: start(2) > directly connected(1) > other nodes(0)
 
383
  result.edges.append(
384
  KnowledgeGraphEdge(
385
  id=edge_id,
386
+ type="DIRECTED",
387
  source=str(source),
388
  target=str(target),
389
  properties=edge_data,
lightrag/lightrag.py CHANGED
@@ -504,11 +504,39 @@ class LightRAG:
504
  return text
505
 
506
  async def get_knowledge_graph(
507
- self, node_label: str, max_depth: int
 
 
 
 
508
  ) -> KnowledgeGraph:
509
- return await self.chunk_entity_relation_graph.get_knowledge_graph(
510
- node_label=node_label, max_depth=max_depth
511
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
  def _get_storage_class(self, storage_name: str) -> Callable[..., Any]:
514
  import_path = STORAGES[storage_name]
 
504
  return text
505
 
506
  async def get_knowledge_graph(
507
+ self,
508
+ node_label: str,
509
+ max_depth: int = 3,
510
+ min_degree: int = 0,
511
+ inclusive: bool = False,
512
  ) -> KnowledgeGraph:
513
+ """Get knowledge graph for a given label
514
+
515
+ Args:
516
+ node_label (str): Label to get knowledge graph for
517
+ max_depth (int): Maximum depth of graph
518
+ min_degree (int, optional): Minimum degree of nodes to include. Defaults to 0.
519
+ inclusive (bool, optional): Whether to use inclusive search mode. Defaults to False.
520
+
521
+ Returns:
522
+ KnowledgeGraph: Knowledge graph containing nodes and edges
523
+ """
524
+ # get params supported by get_knowledge_graph of specified storage
525
+ import inspect
526
+
527
+ storage_params = inspect.signature(
528
+ self.chunk_entity_relation_graph.get_knowledge_graph
529
+ ).parameters
530
+
531
+ kwargs = {"node_label": node_label, "max_depth": max_depth}
532
+
533
+ if "min_degree" in storage_params and min_degree > 0:
534
+ kwargs["min_degree"] = min_degree
535
+
536
+ if "inclusive" in storage_params:
537
+ kwargs["inclusive"] = inclusive
538
+
539
+ return await self.chunk_entity_relation_graph.get_knowledge_graph(**kwargs)
540
 
541
  def _get_storage_class(self, storage_name: str) -> Callable[..., Any]:
542
  import_path = STORAGES[storage_name]
lightrag_webui/src/api/lightrag.ts CHANGED
@@ -161,8 +161,12 @@ axiosInstance.interceptors.response.use(
161
  )
162
 
163
  // API methods
164
- export const queryGraphs = async (label: string, maxDepth: number): Promise<LightragGraphType> => {
165
- const response = await axiosInstance.get(`/graphs?label=${label}&max_depth=${maxDepth}`)
 
 
 
 
166
  return response.data
167
  }
168
 
 
161
  )
162
 
163
  // API methods
164
+ export const queryGraphs = async (
165
+ label: string,
166
+ maxDepth: number,
167
+ minDegree: number
168
+ ): Promise<LightragGraphType> => {
169
+ const response = await axiosInstance.get(`/graphs?label=${encodeURIComponent(label)}&max_depth=${maxDepth}&min_degree=${minDegree}`)
170
  return response.data
171
  }
172
 
lightrag_webui/src/components/graph/GraphControl.tsx CHANGED
@@ -40,18 +40,21 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
40
  const focusedEdge = useGraphStore.use.focusedEdge()
41
 
42
  /**
43
- * When component mount
44
- * => load the graph
45
  */
46
  useEffect(() => {
47
  // Create & load the graph
48
  const graph = lightrageGraph()
49
  loadGraph(graph)
50
- if (!(graph as any).__force_applied) {
51
- assignLayout()
52
- Object.assign(graph, { __force_applied: true })
53
- }
54
 
 
 
 
 
 
55
  const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
56
  useGraphStore.getState()
57
 
@@ -87,7 +90,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
87
  },
88
  clickStage: () => clearSelection()
89
  })
90
- }, [assignLayout, loadGraph, registerEvents, lightrageGraph])
91
 
92
  /**
93
  * When component mount or hovered node change
 
40
  const focusedEdge = useGraphStore.use.focusedEdge()
41
 
42
  /**
43
+ * When component mount or maxIterations changes
44
+ * => load the graph and apply layout
45
  */
46
  useEffect(() => {
47
  // Create & load the graph
48
  const graph = lightrageGraph()
49
  loadGraph(graph)
50
+ assignLayout()
51
+ }, [assignLayout, loadGraph, lightrageGraph, maxIterations])
 
 
52
 
53
+ /**
54
+ * When component mount
55
+ * => register events
56
+ */
57
+ useEffect(() => {
58
  const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
59
  useGraphStore.getState()
60
 
 
90
  },
91
  clickStage: () => clearSelection()
92
  })
93
+ }, [registerEvents])
94
 
95
  /**
96
  * When component mount or hovered node change
lightrag_webui/src/components/graph/Settings.tsx CHANGED
@@ -90,9 +90,12 @@ const LabeledNumberInput = ({
90
  {label}
91
  </label>
92
  <Input
93
- value={currentValue || ''}
 
94
  onChange={onValueChange}
95
- className="h-6 w-full min-w-0"
 
 
96
  onBlur={onBlur}
97
  onKeyDown={(e) => {
98
  if (e.key === 'Enter') {
@@ -119,6 +122,7 @@ export default function Settings() {
119
  const enableHideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
120
  const showEdgeLabel = useSettingsStore.use.showEdgeLabel()
121
  const graphQueryMaxDepth = useSettingsStore.use.graphQueryMaxDepth()
 
122
  const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
123
 
124
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
@@ -177,6 +181,11 @@ export default function Settings() {
177
  useSettingsStore.setState({ graphQueryMaxDepth: depth })
178
  }, [])
179
 
 
 
 
 
 
180
  const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
181
  if (iterations < 1) return
182
  useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
@@ -266,6 +275,12 @@ export default function Settings() {
266
  value={graphQueryMaxDepth}
267
  onEditFinished={setGraphQueryMaxDepth}
268
  />
 
 
 
 
 
 
269
  <LabeledNumberInput
270
  label="Max Layout Iterations"
271
  min={1}
 
90
  {label}
91
  </label>
92
  <Input
93
+ type="number"
94
+ value={currentValue === null ? '' : currentValue}
95
  onChange={onValueChange}
96
+ className="h-6 w-full min-w-0 pr-1"
97
+ min={min}
98
+ max={max}
99
  onBlur={onBlur}
100
  onKeyDown={(e) => {
101
  if (e.key === 'Enter') {
 
122
  const enableHideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
123
  const showEdgeLabel = useSettingsStore.use.showEdgeLabel()
124
  const graphQueryMaxDepth = useSettingsStore.use.graphQueryMaxDepth()
125
+ const graphMinDegree = useSettingsStore.use.graphMinDegree()
126
  const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
127
 
128
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
 
181
  useSettingsStore.setState({ graphQueryMaxDepth: depth })
182
  }, [])
183
 
184
+ const setGraphMinDegree = useCallback((degree: number) => {
185
+ if (degree < 0) return
186
+ useSettingsStore.setState({ graphMinDegree: degree })
187
+ }, [])
188
+
189
  const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
190
  if (iterations < 1) return
191
  useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
 
275
  value={graphQueryMaxDepth}
276
  onEditFinished={setGraphQueryMaxDepth}
277
  />
278
+ <LabeledNumberInput
279
+ label="Minimum Degree"
280
+ min={0}
281
+ value={graphMinDegree}
282
+ onEditFinished={setGraphMinDegree}
283
+ />
284
  <LabeledNumberInput
285
  label="Max Layout Iterations"
286
  min={1}
lightrag_webui/src/components/ui/Input.tsx CHANGED
@@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
7
  <input
8
  type={type}
9
  className={cn(
10
- 'border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
11
  className
12
  )}
13
  ref={ref}
 
7
  <input
8
  type={type}
9
  className={cn(
10
+ 'border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm [&::-webkit-inner-spin-button]:opacity-100 [&::-webkit-outer-spin-button]:opacity-100',
11
  className
12
  )}
13
  ref={ref}
lightrag_webui/src/hooks/useLightragGraph.tsx CHANGED
@@ -50,11 +50,11 @@ export type NodeType = {
50
  }
51
  export type EdgeType = { label: string }
52
 
53
- const fetchGraph = async (label: string, maxDepth: number) => {
54
  let rawData: any = null
55
 
56
  try {
57
- rawData = await queryGraphs(label, maxDepth)
58
  } catch (e) {
59
  useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!')
60
  return null
@@ -161,13 +161,14 @@ const createSigmaGraph = (rawGraph: RawGraph | null) => {
161
  return graph
162
  }
163
 
164
- const lastQueryLabel = { label: '', maxQueryDepth: 0 }
165
 
166
  const useLightrangeGraph = () => {
167
  const queryLabel = useSettingsStore.use.queryLabel()
168
  const rawGraph = useGraphStore.use.rawGraph()
169
  const sigmaGraph = useGraphStore.use.sigmaGraph()
170
  const maxQueryDepth = useSettingsStore.use.graphQueryMaxDepth()
 
171
 
172
  const getNode = useCallback(
173
  (nodeId: string) => {
@@ -185,13 +186,16 @@ const useLightrangeGraph = () => {
185
 
186
  useEffect(() => {
187
  if (queryLabel) {
188
- if (lastQueryLabel.label !== queryLabel || lastQueryLabel.maxQueryDepth !== maxQueryDepth) {
 
 
189
  lastQueryLabel.label = queryLabel
190
  lastQueryLabel.maxQueryDepth = maxQueryDepth
 
191
 
192
  const state = useGraphStore.getState()
193
  state.reset()
194
- fetchGraph(queryLabel, maxQueryDepth).then((data) => {
195
  // console.debug('Query label: ' + queryLabel)
196
  state.setSigmaGraph(createSigmaGraph(data))
197
  data?.buildDynamicMap()
@@ -203,7 +207,7 @@ const useLightrangeGraph = () => {
203
  state.reset()
204
  state.setSigmaGraph(new DirectedGraph())
205
  }
206
- }, [queryLabel, maxQueryDepth])
207
 
208
  const lightrageGraph = useCallback(() => {
209
  if (sigmaGraph) {
 
50
  }
51
  export type EdgeType = { label: string }
52
 
53
+ const fetchGraph = async (label: string, maxDepth: number, minDegree: number) => {
54
  let rawData: any = null
55
 
56
  try {
57
+ rawData = await queryGraphs(label, maxDepth, minDegree)
58
  } catch (e) {
59
  useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!')
60
  return null
 
161
  return graph
162
  }
163
 
164
+ const lastQueryLabel = { label: '', maxQueryDepth: 0, minDegree: 0 }
165
 
166
  const useLightrangeGraph = () => {
167
  const queryLabel = useSettingsStore.use.queryLabel()
168
  const rawGraph = useGraphStore.use.rawGraph()
169
  const sigmaGraph = useGraphStore.use.sigmaGraph()
170
  const maxQueryDepth = useSettingsStore.use.graphQueryMaxDepth()
171
+ const minDegree = useSettingsStore.use.graphMinDegree()
172
 
173
  const getNode = useCallback(
174
  (nodeId: string) => {
 
186
 
187
  useEffect(() => {
188
  if (queryLabel) {
189
+ if (lastQueryLabel.label !== queryLabel ||
190
+ lastQueryLabel.maxQueryDepth !== maxQueryDepth ||
191
+ lastQueryLabel.minDegree !== minDegree) {
192
  lastQueryLabel.label = queryLabel
193
  lastQueryLabel.maxQueryDepth = maxQueryDepth
194
+ lastQueryLabel.minDegree = minDegree
195
 
196
  const state = useGraphStore.getState()
197
  state.reset()
198
+ fetchGraph(queryLabel, maxQueryDepth, minDegree).then((data) => {
199
  // console.debug('Query label: ' + queryLabel)
200
  state.setSigmaGraph(createSigmaGraph(data))
201
  data?.buildDynamicMap()
 
207
  state.reset()
208
  state.setSigmaGraph(new DirectedGraph())
209
  }
210
+ }, [queryLabel, maxQueryDepth, minDegree])
211
 
212
  const lightrageGraph = useCallback(() => {
213
  if (sigmaGraph) {
lightrag_webui/src/stores/settings.ts CHANGED
@@ -22,6 +22,9 @@ interface SettingsState {
22
  graphQueryMaxDepth: number
23
  setGraphQueryMaxDepth: (depth: number) => void
24
 
 
 
 
25
  graphLayoutMaxIterations: number
26
  setGraphLayoutMaxIterations: (iterations: number) => void
27
 
@@ -66,6 +69,7 @@ const useSettingsStoreBase = create<SettingsState>()(
66
  enableEdgeEvents: false,
67
 
68
  graphQueryMaxDepth: 3,
 
69
  graphLayoutMaxIterations: 10,
70
 
71
  queryLabel: defaultQueryLabel,
@@ -107,6 +111,8 @@ const useSettingsStoreBase = create<SettingsState>()(
107
 
108
  setGraphQueryMaxDepth: (depth: number) => set({ graphQueryMaxDepth: depth }),
109
 
 
 
110
  setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
111
 
112
  setApiKey: (apiKey: string | null) => set({ apiKey }),
 
22
  graphQueryMaxDepth: number
23
  setGraphQueryMaxDepth: (depth: number) => void
24
 
25
+ graphMinDegree: number
26
+ setGraphMinDegree: (degree: number) => void
27
+
28
  graphLayoutMaxIterations: number
29
  setGraphLayoutMaxIterations: (iterations: number) => void
30
 
 
69
  enableEdgeEvents: false,
70
 
71
  graphQueryMaxDepth: 3,
72
+ graphMinDegree: 0,
73
  graphLayoutMaxIterations: 10,
74
 
75
  queryLabel: defaultQueryLabel,
 
111
 
112
  setGraphQueryMaxDepth: (depth: number) => set({ graphQueryMaxDepth: depth }),
113
 
114
+ setGraphMinDegree: (degree: number) => set({ graphMinDegree: degree }),
115
+
116
  setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
117
 
118
  setApiKey: (apiKey: string | null) => set({ apiKey }),
lightrag_webui/src/vite-env.d.ts CHANGED
@@ -1 +1,11 @@
1
  /// <reference types="vite/client" />
 
 
 
 
 
 
 
 
 
 
 
1
  /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_API_PROXY: string
5
+ readonly VITE_API_ENDPOINTS: string
6
+ readonly VITE_BACKEND_URL: string
7
+ }
8
+
9
+ interface ImportMeta {
10
+ readonly env: ImportMetaEnv
11
+ }
lightrag_webui/tsconfig.json CHANGED
@@ -26,5 +26,5 @@
26
  "@/*": ["./src/*"]
27
  }
28
  },
29
- "include": ["src"]
30
  }
 
26
  "@/*": ["./src/*"]
27
  }
28
  },
29
+ "include": ["src", "vite.config.ts"]
30
  }
lightrag_webui/vite.config.ts CHANGED
@@ -14,6 +14,21 @@ export default defineConfig({
14
  },
15
  base: './',
16
  build: {
17
- outDir: path.resolve(__dirname, '../lightrag/api/webui')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
  })
 
14
  },
15
  base: './',
16
  build: {
17
+ outDir: path.resolve(__dirname, '../lightrag/api/webui'),
18
+ emptyOutDir: true
19
+ },
20
+ server: {
21
+ proxy: import.meta.env.VITE_API_PROXY === 'true' && import.meta.env.VITE_API_ENDPOINTS ?
22
+ Object.fromEntries(
23
+ import.meta.env.VITE_API_ENDPOINTS.split(',').map(endpoint => [
24
+ endpoint,
25
+ {
26
+ target: import.meta.env.VITE_BACKEND_URL || 'http://localhost:9621',
27
+ changeOrigin: true,
28
+ rewrite: endpoint === '/api' ?
29
+ (path) => path.replace(/^\/api/, '') : undefined
30
+ }
31
+ ])
32
+ ) : {}
33
  }
34
  })