ArnoChen commited on
Commit
07eaad0
·
1 Parent(s): a19b3c4

add graph depth and layout iteration settings

Browse files
lightrag/api/routers/graph_routes.py CHANGED
@@ -20,8 +20,8 @@ def create_graph_routes(rag, api_key: Optional[str] = None):
20
  return await rag.get_graph_labels()
21
 
22
  @router.get("/graphs", dependencies=[Depends(optional_api_key)])
23
- async def get_knowledge_graph(label: str):
24
  """Get knowledge graph for a specific label"""
25
- return await rag.get_knowledge_graph(node_label=label, max_depth=3)
26
 
27
  return router
 
20
  return await rag.get_graph_labels()
21
 
22
  @router.get("/graphs", dependencies=[Depends(optional_api_key)])
23
+ async def get_knowledge_graph(label: str, max_depth: int = 3):
24
  """Get knowledge graph for a specific label"""
25
+ return await rag.get_knowledge_graph(node_label=label, max_depth=max_depth)
26
 
27
  return router
lightrag_webui/src/api/lightrag.ts CHANGED
@@ -161,8 +161,8 @@ axiosInstance.interceptors.response.use(
161
  )
162
 
163
  // API methods
164
- export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
165
- const response = await axiosInstance.get(`/graphs?label=${label}`)
166
  return response.data
167
  }
168
 
 
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
 
lightrag_webui/src/components/graph/GraphControl.tsx CHANGED
@@ -26,8 +26,10 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
26
  const registerEvents = useRegisterEvents<NodeType, EdgeType>()
27
  const setSettings = useSetSettings<NodeType, EdgeType>()
28
  const loadGraph = useLoadGraph<NodeType, EdgeType>()
 
 
29
  const { assign: assignLayout } = useLayoutForceAtlas2({
30
- iterations: 20
31
  })
32
 
33
  const { theme } = useTheme()
 
26
  const registerEvents = useRegisterEvents<NodeType, EdgeType>()
27
  const setSettings = useSetSettings<NodeType, EdgeType>()
28
  const loadGraph = useLoadGraph<NodeType, EdgeType>()
29
+
30
+ const maxIterations = useSettingsStore.use.graphLayoutMaxIterations()
31
  const { assign: assignLayout } = useLayoutForceAtlas2({
32
+ iterations: maxIterations
33
  })
34
 
35
  const { theme } = useTheme()
lightrag_webui/src/components/graph/GraphLabels.tsx CHANGED
@@ -1,67 +1,81 @@
1
- import { useCallback, useState } from 'react'
2
  import { AsyncSelect } from '@/components/ui/AsyncSelect'
3
  import { getGraphLabels } from '@/api/lightrag'
4
  import { useSettingsStore } from '@/stores/settings'
 
 
5
  import MiniSearch from 'minisearch'
6
 
 
 
 
 
 
 
7
  const GraphLabels = () => {
8
  const label = useSettingsStore.use.queryLabel()
9
- const [labels, setLabels] = useState<{
10
- labels: string[]
11
- searchEngine: MiniSearch | null
12
- }>({
13
- labels: [],
14
- searchEngine: null
15
- })
16
- const [fetched, setFetched] = useState(false)
17
 
18
- const fetchData = useCallback(
19
- async (query?: string): Promise<string[]> => {
20
- let _labels = labels.labels
21
- let _searchEngine = labels.searchEngine
 
 
 
 
22
 
23
- if (!fetched || !_searchEngine) {
24
- _labels = ['*'].concat(await getGraphLabels())
 
 
25
 
26
- // Ensure query label exists
27
- if (!_labels.includes(useSettingsStore.getState().queryLabel)) {
28
- useSettingsStore.getState().setQueryLabel(_labels[0])
 
 
 
 
 
 
29
  }
 
 
30
 
31
- // Create search engine
32
- _searchEngine = new MiniSearch({
33
- idField: 'id',
34
- fields: ['value'],
35
- searchOptions: {
36
- prefix: true,
37
- fuzzy: 0.2,
38
- boost: {
39
- label: 2
40
- }
41
- }
42
- })
43
 
44
- // Add documents
45
- const documents = _labels.map((str, index) => ({ id: index, value: str }))
46
- _searchEngine.addAll(documents)
47
 
48
- setLabels({
49
- labels: _labels,
50
- searchEngine: _searchEngine
51
- })
52
- setFetched(true)
53
- }
54
- if (!query) {
55
- return _labels
 
 
 
 
 
 
56
  }
57
 
58
- // Search labels
59
- return _searchEngine.search(query).map((result) => _labels[result.id])
 
60
  },
61
- [labels, fetched, setLabels, setFetched]
62
  )
63
 
64
  const setQueryLabel = useCallback((label: string) => {
 
65
  useSettingsStore.getState().setQueryLabel(label)
66
  }, [])
67
 
 
1
+ import { useCallback } from 'react'
2
  import { AsyncSelect } from '@/components/ui/AsyncSelect'
3
  import { getGraphLabels } from '@/api/lightrag'
4
  import { useSettingsStore } from '@/stores/settings'
5
+ import { useGraphStore } from '@/stores/graph'
6
+ import { labelListLimit } from '@/lib/constants'
7
  import MiniSearch from 'minisearch'
8
 
9
+ const lastGraph: any = {
10
+ graph: null,
11
+ searchEngine: null,
12
+ labels: []
13
+ }
14
+
15
  const GraphLabels = () => {
16
  const label = useSettingsStore.use.queryLabel()
17
+ const graph = useGraphStore.use.sigmaGraph()
 
 
 
 
 
 
 
18
 
19
+ const getSearchEngine = useCallback(async () => {
20
+ if (lastGraph.graph == graph) {
21
+ return {
22
+ labels: lastGraph.labels,
23
+ searchEngine: lastGraph.searchEngine
24
+ }
25
+ }
26
+ const labels = ['*'].concat(await getGraphLabels())
27
 
28
+ // Ensure query label exists
29
+ if (!labels.includes(useSettingsStore.getState().queryLabel)) {
30
+ useSettingsStore.getState().setQueryLabel(labels[0])
31
+ }
32
 
33
+ // Create search engine
34
+ const searchEngine = new MiniSearch({
35
+ idField: 'id',
36
+ fields: ['value'],
37
+ searchOptions: {
38
+ prefix: true,
39
+ fuzzy: 0.2,
40
+ boost: {
41
+ label: 2
42
  }
43
+ }
44
+ })
45
 
46
+ // Add documents
47
+ const documents = labels.map((str, index) => ({ id: index, value: str }))
48
+ searchEngine.addAll(documents)
 
 
 
 
 
 
 
 
 
49
 
50
+ lastGraph.graph = graph
51
+ lastGraph.searchEngine = searchEngine
52
+ lastGraph.labels = labels
53
 
54
+ return {
55
+ labels,
56
+ searchEngine
57
+ }
58
+ }, [graph])
59
+
60
+ const fetchData = useCallback(
61
+ async (query?: string): Promise<string[]> => {
62
+ const { labels, searchEngine } = await getSearchEngine()
63
+
64
+ let result: string[] = labels
65
+ if (query) {
66
+ // Search labels
67
+ result = searchEngine.search(query).map((r) => labels[r.id])
68
  }
69
 
70
+ return result.length <= labelListLimit
71
+ ? result
72
+ : [...result.slice(0, labelListLimit), `And ${result.length - labelListLimit} others`]
73
  },
74
+ [getSearchEngine]
75
  )
76
 
77
  const setQueryLabel = useCallback((label: string) => {
78
+ if (label.startsWith('And ') && label.endsWith(' others')) return
79
  useSettingsStore.getState().setQueryLabel(label)
80
  }, [])
81
 
lightrag_webui/src/components/graph/GraphSearch.tsx CHANGED
@@ -46,7 +46,7 @@ export const GraphSearchInput = ({
46
  }) => {
47
  const graph = useGraphStore.use.sigmaGraph()
48
 
49
- const search = useMemo(() => {
50
  if (lastGraph.graph == graph) {
51
  return lastGraph.searchEngine
52
  }
@@ -83,9 +83,9 @@ export const GraphSearchInput = ({
83
  const loadOptions = useCallback(
84
  async (query?: string): Promise<OptionItem[]> => {
85
  if (onFocus) onFocus(null)
86
- if (!query || !search) return []
87
- const result: OptionItem[] = search.search(query).map((result) => ({
88
- id: result.id,
89
  type: 'nodes'
90
  }))
91
 
@@ -101,7 +101,7 @@ export const GraphSearchInput = ({
101
  }
102
  ]
103
  },
104
- [search, onFocus]
105
  )
106
 
107
  return (
 
46
  }) => {
47
  const graph = useGraphStore.use.sigmaGraph()
48
 
49
+ const searchEngine = useMemo(() => {
50
  if (lastGraph.graph == graph) {
51
  return lastGraph.searchEngine
52
  }
 
83
  const loadOptions = useCallback(
84
  async (query?: string): Promise<OptionItem[]> => {
85
  if (onFocus) onFocus(null)
86
+ if (!query || !searchEngine) return []
87
+ const result: OptionItem[] = searchEngine.search(query).map((r) => ({
88
+ id: r.id,
89
  type: 'nodes'
90
  }))
91
 
 
101
  }
102
  ]
103
  },
104
+ [searchEngine, onFocus]
105
  )
106
 
107
  return (
lightrag_webui/src/components/graph/LayoutsControl.tsx CHANGED
@@ -13,6 +13,7 @@ import Button from '@/components/ui/Button'
13
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
14
  import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/Command'
15
  import { controlButtonVariant } from '@/lib/constants'
 
16
 
17
  import { GripIcon, PlayIcon, PauseIcon } from 'lucide-react'
18
 
@@ -75,13 +76,15 @@ const LayoutsControl = () => {
75
  const sigma = useSigma()
76
  const [layout, setLayout] = useState<LayoutName>('Circular')
77
  const [opened, setOpened] = useState<boolean>(false)
 
 
78
 
79
  const layoutCircular = useLayoutCircular()
80
  const layoutCirclepack = useLayoutCirclepack()
81
  const layoutRandom = useLayoutRandom()
82
  const layoutNoverlap = useLayoutNoverlap({ settings: { margin: 1 } })
83
- const layoutForce = useLayoutForce({ maxIterations: 20 })
84
- const layoutForceAtlas2 = useLayoutForceAtlas2({ iterations: 20 })
85
  const workerNoverlap = useWorkerLayoutNoverlap()
86
  const workerForce = useWorkerLayoutForce()
87
  const workerForceAtlas2 = useWorkerLayoutForceAtlas2()
 
13
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
14
  import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/Command'
15
  import { controlButtonVariant } from '@/lib/constants'
16
+ import { useSettingsStore } from '@/stores/settings'
17
 
18
  import { GripIcon, PlayIcon, PauseIcon } from 'lucide-react'
19
 
 
76
  const sigma = useSigma()
77
  const [layout, setLayout] = useState<LayoutName>('Circular')
78
  const [opened, setOpened] = useState<boolean>(false)
79
+
80
+ const maxIterations = useSettingsStore.use.graphLayoutMaxIterations()
81
 
82
  const layoutCircular = useLayoutCircular()
83
  const layoutCirclepack = useLayoutCirclepack()
84
  const layoutRandom = useLayoutRandom()
85
  const layoutNoverlap = useLayoutNoverlap({ settings: { margin: 1 } })
86
+ const layoutForce = useLayoutForce({ maxIterations: maxIterations })
87
+ const layoutForceAtlas2 = useLayoutForceAtlas2({ iterations: maxIterations })
88
  const workerNoverlap = useWorkerLayoutNoverlap()
89
  const workerForce = useWorkerLayoutForce()
90
  const workerForceAtlas2 = useWorkerLayoutForceAtlas2()
lightrag_webui/src/components/graph/Settings.tsx CHANGED
@@ -1,9 +1,10 @@
 
1
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
2
  import Checkbox from '@/components/ui/Checkbox'
3
  import Button from '@/components/ui/Button'
4
  import Separator from '@/components/ui/Separator'
5
  import Input from '@/components/ui/Input'
6
- import { useState, useCallback, useEffect } from 'react'
7
  import { controlButtonVariant } from '@/lib/constants'
8
  import { useSettingsStore } from '@/stores/settings'
9
  import { useBackendState } from '@/stores/state'
@@ -35,6 +36,74 @@ const LabeledCheckBox = ({
35
  )
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Component that displays a popover with settings options.
40
  */
@@ -45,11 +114,12 @@ export default function Settings() {
45
  const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
46
  const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
47
  const showNodeLabel = useSettingsStore.use.showNodeLabel()
48
-
49
  const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
50
  const enableNodeDrag = useSettingsStore.use.enableNodeDrag()
51
  const enableHideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
52
  const showEdgeLabel = useSettingsStore.use.showEdgeLabel()
 
 
53
 
54
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
55
  const apiKey = useSettingsStore.use.apiKey()
@@ -102,6 +172,16 @@ export default function Settings() {
102
  []
103
  )
104
 
 
 
 
 
 
 
 
 
 
 
105
  const setApiKey = useCallback(async () => {
106
  useSettingsStore.setState({ apiKey: tempApiKey || null })
107
  await useBackendState.getState().check()
@@ -129,6 +209,14 @@ export default function Settings() {
129
  onCloseAutoFocus={(e) => e.preventDefault()}
130
  >
131
  <div className="flex flex-col gap-2">
 
 
 
 
 
 
 
 
132
  <LabeledCheckBox
133
  checked={showPropertyPanel}
134
  onCheckedChange={setShowPropertyPanel}
@@ -172,11 +260,18 @@ export default function Settings() {
172
  />
173
 
174
  <Separator />
175
-
176
- <LabeledCheckBox
177
- checked={enableHealthCheck}
178
- onCheckedChange={setEnableHealthCheck}
179
- label="Health Check"
 
 
 
 
 
 
 
180
  />
181
 
182
  <Separator />
 
1
+ import { useState, useCallback, useEffect } from 'react'
2
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
3
  import Checkbox from '@/components/ui/Checkbox'
4
  import Button from '@/components/ui/Button'
5
  import Separator from '@/components/ui/Separator'
6
  import Input from '@/components/ui/Input'
7
+
8
  import { controlButtonVariant } from '@/lib/constants'
9
  import { useSettingsStore } from '@/stores/settings'
10
  import { useBackendState } from '@/stores/state'
 
36
  )
37
  }
38
 
39
+ /**
40
+ * Component that displays a number input with a label.
41
+ */
42
+ const LabeledNumberInput = ({
43
+ value,
44
+ onEditFinished,
45
+ label,
46
+ min,
47
+ max
48
+ }: {
49
+ value: number
50
+ onEditFinished: (value: number) => void
51
+ label: string
52
+ min: number
53
+ max?: number
54
+ }) => {
55
+ const [currentValue, setCurrentValue] = useState<number | null>(value)
56
+
57
+ const onValueChange = useCallback(
58
+ (e: React.ChangeEvent<HTMLInputElement>) => {
59
+ const text = e.target.value.trim()
60
+ if (text.length === 0) {
61
+ setCurrentValue(null)
62
+ return
63
+ }
64
+ const newValue = Number.parseInt(text)
65
+ if (!isNaN(newValue) && newValue !== currentValue) {
66
+ if (min !== undefined && newValue < min) {
67
+ return
68
+ }
69
+ if (max !== undefined && newValue > max) {
70
+ return
71
+ }
72
+ setCurrentValue(newValue)
73
+ }
74
+ },
75
+ [currentValue, min, max]
76
+ )
77
+
78
+ const onBlur = useCallback(() => {
79
+ if (currentValue !== null && value !== currentValue) {
80
+ onEditFinished(currentValue)
81
+ }
82
+ }, [value, currentValue, onEditFinished])
83
+
84
+ return (
85
+ <div className="flex flex-col gap-2">
86
+ <label
87
+ htmlFor="terms"
88
+ className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
89
+ >
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') {
99
+ onBlur()
100
+ }
101
+ }}
102
+ />
103
+ </div>
104
+ )
105
+ }
106
+
107
  /**
108
  * Component that displays a popover with settings options.
109
  */
 
114
  const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
115
  const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
116
  const showNodeLabel = useSettingsStore.use.showNodeLabel()
 
117
  const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
118
  const enableNodeDrag = useSettingsStore.use.enableNodeDrag()
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()
125
  const apiKey = useSettingsStore.use.apiKey()
 
172
  []
173
  )
174
 
175
+ const setGraphQueryMaxDepth = useCallback((depth: number) => {
176
+ if (depth < 1) return
177
+ useSettingsStore.setState({ graphQueryMaxDepth: depth })
178
+ }, [])
179
+
180
+ const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
181
+ if (iterations < 1) return
182
+ useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
183
+ }, [])
184
+
185
  const setApiKey = useCallback(async () => {
186
  useSettingsStore.setState({ apiKey: tempApiKey || null })
187
  await useBackendState.getState().check()
 
209
  onCloseAutoFocus={(e) => e.preventDefault()}
210
  >
211
  <div className="flex flex-col gap-2">
212
+ <LabeledCheckBox
213
+ checked={enableHealthCheck}
214
+ onCheckedChange={setEnableHealthCheck}
215
+ label="Health Check"
216
+ />
217
+
218
+ <Separator />
219
+
220
  <LabeledCheckBox
221
  checked={showPropertyPanel}
222
  onCheckedChange={setShowPropertyPanel}
 
260
  />
261
 
262
  <Separator />
263
+ <LabeledNumberInput
264
+ label="Max Query Depth"
265
+ min={1}
266
+ value={graphQueryMaxDepth}
267
+ onEditFinished={setGraphQueryMaxDepth}
268
+ />
269
+ <LabeledNumberInput
270
+ label="Max Layout Iterations"
271
+ min={1}
272
+ max={20}
273
+ value={graphLayoutMaxIterations}
274
+ onEditFinished={setGraphLayoutMaxIterations}
275
  />
276
 
277
  <Separator />
lightrag_webui/src/components/retrieval/ChatMessage.tsx CHANGED
@@ -2,6 +2,7 @@ import { ReactNode, useCallback } from 'react'
2
  import { Message } from '@/api/lightrag'
3
  import useTheme from '@/hooks/useTheme'
4
  import Button from '@/components/ui/Button'
 
5
 
6
  import ReactMarkdown from 'react-markdown'
7
  import remarkGfm from 'remark-gfm'
@@ -101,7 +102,10 @@ const CodeHighlight = ({ className, children, node, ...props }: CodeHighlightPro
101
  {String(children).replace(/\n$/, '')}
102
  </SyntaxHighlighter>
103
  ) : (
104
- <code className={className} {...props}>
 
 
 
105
  {children}
106
  </code>
107
  )
 
2
  import { Message } from '@/api/lightrag'
3
  import useTheme from '@/hooks/useTheme'
4
  import Button from '@/components/ui/Button'
5
+ import { cn } from '@/lib/utils'
6
 
7
  import ReactMarkdown from 'react-markdown'
8
  import remarkGfm from 'remark-gfm'
 
102
  {String(children).replace(/\n$/, '')}
103
  </SyntaxHighlighter>
104
  ) : (
105
+ <code
106
+ className={cn(className, 'mx-1 rounded-xs bg-black/10 px-1 dark:bg-gray-100/20')}
107
+ {...props}
108
+ >
109
  {children}
110
  </code>
111
  )
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) => {
54
  let rawData: any = null
55
 
56
  try {
57
- rawData = await queryGraphs(label)
58
  } catch (e) {
59
  useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!')
60
  return null
@@ -161,12 +161,13 @@ const createSigmaGraph = (rawGraph: RawGraph | null) => {
161
  return graph
162
  }
163
 
164
- const lastQueryLabel = { label: '' }
165
 
166
  const useLightrangeGraph = () => {
167
  const queryLabel = useSettingsStore.use.queryLabel()
168
  const rawGraph = useGraphStore.use.rawGraph()
169
  const sigmaGraph = useGraphStore.use.sigmaGraph()
 
170
 
171
  const getNode = useCallback(
172
  (nodeId: string) => {
@@ -184,11 +185,13 @@ const useLightrangeGraph = () => {
184
 
185
  useEffect(() => {
186
  if (queryLabel) {
187
- if (lastQueryLabel.label !== queryLabel) {
188
  lastQueryLabel.label = queryLabel
 
 
189
  const state = useGraphStore.getState()
190
  state.reset()
191
- fetchGraph(queryLabel).then((data) => {
192
  // console.debug('Query label: ' + queryLabel)
193
  state.setSigmaGraph(createSigmaGraph(data))
194
  data?.buildDynamicMap()
@@ -200,7 +203,7 @@ const useLightrangeGraph = () => {
200
  state.reset()
201
  state.setSigmaGraph(new DirectedGraph())
202
  }
203
- }, [queryLabel])
204
 
205
  const lightrageGraph = useCallback(() => {
206
  if (sigmaGraph) {
 
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
  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
 
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
  state.reset()
204
  state.setSigmaGraph(new DirectedGraph())
205
  }
206
+ }, [queryLabel, maxQueryDepth])
207
 
208
  const lightrageGraph = useCallback(() => {
209
  if (sigmaGraph) {
lightrag_webui/src/lib/constants.ts CHANGED
@@ -16,6 +16,7 @@ export const edgeColorSelected = '#F57F17'
16
  export const edgeColorHighlighted = '#B2EBF2'
17
 
18
  export const searchResultLimit = 20
 
19
 
20
  export const minNodeSize = 4
21
  export const maxNodeSize = 20
 
16
  export const edgeColorHighlighted = '#B2EBF2'
17
 
18
  export const searchResultLimit = 20
19
+ export const labelListLimit = 40
20
 
21
  export const minNodeSize = 4
22
  export const maxNodeSize = 20
lightrag_webui/src/stores/settings.ts CHANGED
@@ -8,9 +8,7 @@ type Theme = 'dark' | 'light' | 'system'
8
  type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
9
 
10
  interface SettingsState {
11
- theme: Theme
12
- setTheme: (theme: Theme) => void
13
-
14
  showPropertyPanel: boolean
15
  showNodeSearchBar: boolean
16
 
@@ -21,23 +19,35 @@ interface SettingsState {
21
  enableHideUnselectedEdges: boolean
22
  enableEdgeEvents: boolean
23
 
 
 
 
 
 
 
 
24
  queryLabel: string
25
  setQueryLabel: (queryLabel: string) => void
26
 
27
- enableHealthCheck: boolean
28
- setEnableHealthCheck: (enable: boolean) => void
 
 
 
29
 
 
30
  apiKey: string | null
31
  setApiKey: (key: string | null) => void
32
 
33
- currentTab: Tab
34
- setCurrentTab: (tab: Tab) => void
 
35
 
36
- retrievalHistory: Message[]
37
- setRetrievalHistory: (history: Message[]) => void
38
 
39
- querySettings: Omit<QueryRequest, 'query'>
40
- updateQuerySettings: (settings: Partial<QueryRequest>) => void
41
  }
42
 
43
  const useSettingsStoreBase = create<SettingsState>()(
@@ -55,7 +65,11 @@ const useSettingsStoreBase = create<SettingsState>()(
55
  enableHideUnselectedEdges: true,
56
  enableEdgeEvents: false,
57
 
 
 
 
58
  queryLabel: defaultQueryLabel,
 
59
  enableHealthCheck: true,
60
 
61
  apiKey: null,
@@ -81,11 +95,18 @@ const useSettingsStoreBase = create<SettingsState>()(
81
 
82
  setTheme: (theme: Theme) => set({ theme }),
83
 
 
 
 
 
 
84
  setQueryLabel: (queryLabel: string) =>
85
  set({
86
  queryLabel
87
  }),
88
 
 
 
89
  setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
90
 
91
  setApiKey: (apiKey: string | null) => set({ apiKey }),
@@ -102,7 +123,7 @@ const useSettingsStoreBase = create<SettingsState>()(
102
  {
103
  name: 'settings-storage',
104
  storage: createJSONStorage(() => localStorage),
105
- version: 6,
106
  migrate: (state: any, version: number) => {
107
  if (version < 2) {
108
  state.showEdgeLabel = false
@@ -137,6 +158,10 @@ const useSettingsStoreBase = create<SettingsState>()(
137
  }
138
  state.retrievalHistory = []
139
  }
 
 
 
 
140
  return state
141
  }
142
  }
 
8
  type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
9
 
10
  interface SettingsState {
11
+ // Graph viewer settings
 
 
12
  showPropertyPanel: boolean
13
  showNodeSearchBar: boolean
14
 
 
19
  enableHideUnselectedEdges: boolean
20
  enableEdgeEvents: boolean
21
 
22
+ graphQueryMaxDepth: number
23
+ setGraphQueryMaxDepth: (depth: number) => void
24
+
25
+ graphLayoutMaxIterations: number
26
+ setGraphLayoutMaxIterations: (iterations: number) => void
27
+
28
+ // Retrieval settings
29
  queryLabel: string
30
  setQueryLabel: (queryLabel: string) => void
31
 
32
+ retrievalHistory: Message[]
33
+ setRetrievalHistory: (history: Message[]) => void
34
+
35
+ querySettings: Omit<QueryRequest, 'query'>
36
+ updateQuerySettings: (settings: Partial<QueryRequest>) => void
37
 
38
+ // Auth settings
39
  apiKey: string | null
40
  setApiKey: (key: string | null) => void
41
 
42
+ // App settings
43
+ theme: Theme
44
+ setTheme: (theme: Theme) => void
45
 
46
+ enableHealthCheck: boolean
47
+ setEnableHealthCheck: (enable: boolean) => void
48
 
49
+ currentTab: Tab
50
+ setCurrentTab: (tab: Tab) => void
51
  }
52
 
53
  const useSettingsStoreBase = create<SettingsState>()(
 
65
  enableHideUnselectedEdges: true,
66
  enableEdgeEvents: false,
67
 
68
+ graphQueryMaxDepth: 3,
69
+ graphLayoutMaxIterations: 10,
70
+
71
  queryLabel: defaultQueryLabel,
72
+
73
  enableHealthCheck: true,
74
 
75
  apiKey: null,
 
95
 
96
  setTheme: (theme: Theme) => set({ theme }),
97
 
98
+ setGraphLayoutMaxIterations: (iterations: number) =>
99
+ set({
100
+ graphLayoutMaxIterations: iterations
101
+ }),
102
+
103
  setQueryLabel: (queryLabel: string) =>
104
  set({
105
  queryLabel
106
  }),
107
 
108
+ setGraphQueryMaxDepth: (depth: number) => set({ graphQueryMaxDepth: depth }),
109
+
110
  setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
111
 
112
  setApiKey: (apiKey: string | null) => set({ apiKey }),
 
123
  {
124
  name: 'settings-storage',
125
  storage: createJSONStorage(() => localStorage),
126
+ version: 7,
127
  migrate: (state: any, version: number) => {
128
  if (version < 2) {
129
  state.showEdgeLabel = false
 
158
  }
159
  state.retrievalHistory = []
160
  }
161
+ if (version < 7) {
162
+ state.graphQueryMaxDepth = 3
163
+ state.graphLayoutMaxIterations = 10
164
+ }
165
  return state
166
  }
167
  }