ArnoChen
commited on
Commit
·
07eaad0
1
Parent(s):
a19b3c4
add graph depth and layout iteration settings
Browse files- lightrag/api/routers/graph_routes.py +2 -2
- lightrag_webui/src/api/lightrag.ts +2 -2
- lightrag_webui/src/components/graph/GraphControl.tsx +3 -1
- lightrag_webui/src/components/graph/GraphLabels.tsx +58 -44
- lightrag_webui/src/components/graph/GraphSearch.tsx +5 -5
- lightrag_webui/src/components/graph/LayoutsControl.tsx +5 -2
- lightrag_webui/src/components/graph/Settings.tsx +102 -7
- lightrag_webui/src/components/retrieval/ChatMessage.tsx +5 -1
- lightrag_webui/src/hooks/useLightragGraph.tsx +9 -6
- lightrag_webui/src/lib/constants.ts +1 -0
- lightrag_webui/src/stores/settings.ts +37 -12
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=
|
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:
|
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
|
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
|
10 |
-
labels: string[]
|
11 |
-
searchEngine: MiniSearch | null
|
12 |
-
}>({
|
13 |
-
labels: [],
|
14 |
-
searchEngine: null
|
15 |
-
})
|
16 |
-
const [fetched, setFetched] = useState(false)
|
17 |
|
18 |
-
const
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
22 |
|
23 |
-
|
24 |
-
|
|
|
|
|
25 |
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
}
|
|
|
|
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
fields: ['value'],
|
35 |
-
searchOptions: {
|
36 |
-
prefix: true,
|
37 |
-
fuzzy: 0.2,
|
38 |
-
boost: {
|
39 |
-
label: 2
|
40 |
-
}
|
41 |
-
}
|
42 |
-
})
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
}
|
57 |
|
58 |
-
|
59 |
-
|
|
|
60 |
},
|
61 |
-
[
|
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
|
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 || !
|
87 |
-
const result: OptionItem[] =
|
88 |
-
id:
|
89 |
type: 'nodes'
|
90 |
}))
|
91 |
|
@@ -101,7 +101,7 @@ export const GraphSearchInput = ({
|
|
101 |
}
|
102 |
]
|
103 |
},
|
104 |
-
[
|
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:
|
84 |
-
const layoutForceAtlas2 = useLayoutForceAtlas2({ iterations:
|
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 |
-
|
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 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
28 |
-
|
|
|
|
|
|
|
29 |
|
|
|
30 |
apiKey: string | null
|
31 |
setApiKey: (key: string | null) => void
|
32 |
|
33 |
-
|
34 |
-
|
|
|
35 |
|
36 |
-
|
37 |
-
|
38 |
|
39 |
-
|
40 |
-
|
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:
|
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 |
}
|