Merge pull request #1003 from danielaskdd/add-graph-search-mode
Feat: Added minimum degree filter for graph queries
This commit is contained in:
@@ -3,7 +3,6 @@ This module contains all graph-related routes for the LightRAG API.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from ..utils_api import get_api_key_dependency, get_auth_dependency
|
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):
|
|||||||
return await rag.get_graph_labels()
|
return await rag.get_graph_labels()
|
||||||
|
|
||||||
@router.get("/graphs", dependencies=[Depends(optional_api_key)])
|
@router.get("/graphs", dependencies=[Depends(optional_api_key)])
|
||||||
async def get_knowledge_graph(label: str, max_depth: int = 3):
|
async def get_knowledge_graph(
|
||||||
|
label: str, max_depth: int = 3, min_degree: int = 0, inclusive: bool = False
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Retrieve a connected subgraph of nodes where the label includes the specified label.
|
Retrieve a connected subgraph of nodes where the label includes the specified label.
|
||||||
Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
|
Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
|
||||||
When reducing the number of nodes, the prioritization criteria are as follows:
|
When reducing the number of nodes, the prioritization criteria are as follows:
|
||||||
1. Label matching nodes take precedence
|
1. min_degree does not affect nodes directly connected to the matching nodes
|
||||||
2. Followed by nodes directly connected to the matching nodes
|
2. Label matching nodes take precedence
|
||||||
3. Finally, the degree of the nodes
|
3. Followed by nodes directly connected to the matching nodes
|
||||||
|
4. Finally, the degree of the nodes
|
||||||
Maximum number of nodes is limited to env MAX_GRAPH_NODES(default: 1000)
|
Maximum number of nodes is limited to env MAX_GRAPH_NODES(default: 1000)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label (str): Label to get knowledge graph for
|
label (str): Label to get knowledge graph for
|
||||||
max_depth (int, optional): Maximum depth of graph. Defaults to 3.
|
max_depth (int, optional): Maximum depth of graph. Defaults to 3.
|
||||||
|
inclusive_search (bool, optional): If True, search for nodes that include the label. Defaults to False.
|
||||||
|
min_degree (int, optional): Minimum degree of nodes. Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, List[str]]: Knowledge graph for label
|
Dict[str, List[str]]: Knowledge graph for label
|
||||||
"""
|
"""
|
||||||
return await rag.get_knowledge_graph(node_label=label, max_depth=max_depth)
|
return await rag.get_knowledge_graph(
|
||||||
|
node_label=label,
|
||||||
|
max_depth=max_depth,
|
||||||
|
inclusive=inclusive,
|
||||||
|
min_degree=min_degree,
|
||||||
|
)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
|
1
lightrag/api/webui/assets/index-CH-3l4_Z.css
Normal file
1
lightrag/api/webui/assets/index-CH-3l4_Z.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="./logo.png" />
|
<link rel="icon" type="image/svg+xml" href="./logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Lightrag</title>
|
<title>Lightrag</title>
|
||||||
<script type="module" crossorigin src="./assets/index-DbuMPJAD.js"></script>
|
<script type="module" crossorigin src="./assets/index-CJz72b6Q.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-rP-YlyR1.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-CH-3l4_Z.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@@ -204,7 +204,7 @@ class BaseGraphStorage(StorageNameSpace, ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_knowledge_graph(
|
async def get_knowledge_graph(
|
||||||
self, node_label: str, max_depth: int = 5
|
self, node_label: str, max_depth: int = 3
|
||||||
) -> KnowledgeGraph:
|
) -> KnowledgeGraph:
|
||||||
"""Retrieve a subgraph of the knowledge graph starting from a given node."""
|
"""Retrieve a subgraph of the knowledge graph starting from a given node."""
|
||||||
|
|
||||||
|
@@ -232,19 +232,26 @@ class NetworkXStorage(BaseGraphStorage):
|
|||||||
return sorted(list(labels))
|
return sorted(list(labels))
|
||||||
|
|
||||||
async def get_knowledge_graph(
|
async def get_knowledge_graph(
|
||||||
self, node_label: str, max_depth: int = 5
|
self,
|
||||||
|
node_label: str,
|
||||||
|
max_depth: int = 3,
|
||||||
|
min_degree: int = 0,
|
||||||
|
inclusive: bool = False,
|
||||||
) -> KnowledgeGraph:
|
) -> KnowledgeGraph:
|
||||||
"""
|
"""
|
||||||
Retrieve a connected subgraph of nodes where the label includes the specified `node_label`.
|
Retrieve a connected subgraph of nodes where the label includes the specified `node_label`.
|
||||||
Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
|
Maximum number of nodes is constrained by the environment variable `MAX_GRAPH_NODES` (default: 1000).
|
||||||
When reducing the number of nodes, the prioritization criteria are as follows:
|
When reducing the number of nodes, the prioritization criteria are as follows:
|
||||||
1. Label matching nodes take precedence
|
1. min_degree does not affect nodes directly connected to the matching nodes
|
||||||
2. Followed by nodes directly connected to the matching nodes
|
2. Label matching nodes take precedence
|
||||||
3. Finally, the degree of the nodes
|
3. Followed by nodes directly connected to the matching nodes
|
||||||
|
4. Finally, the degree of the nodes
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
node_label: Label of the starting node
|
node_label: Label of the starting node
|
||||||
max_depth: Maximum depth of the subgraph
|
max_depth: Maximum depth of the subgraph
|
||||||
|
min_degree: Minimum degree of nodes to include. Defaults to 0
|
||||||
|
inclusive: Do an inclusive search if true
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
KnowledgeGraph object containing nodes and edges
|
KnowledgeGraph object containing nodes and edges
|
||||||
@@ -255,6 +262,10 @@ class NetworkXStorage(BaseGraphStorage):
|
|||||||
|
|
||||||
graph = await self._get_graph()
|
graph = await self._get_graph()
|
||||||
|
|
||||||
|
# Initialize sets for start nodes and direct connected nodes
|
||||||
|
start_nodes = set()
|
||||||
|
direct_connected_nodes = set()
|
||||||
|
|
||||||
# Handle special case for "*" label
|
# Handle special case for "*" label
|
||||||
if node_label == "*":
|
if node_label == "*":
|
||||||
# For "*", return the entire graph including all nodes and edges
|
# For "*", return the entire graph including all nodes and edges
|
||||||
@@ -262,10 +273,15 @@ class NetworkXStorage(BaseGraphStorage):
|
|||||||
graph.copy()
|
graph.copy()
|
||||||
) # Create a copy to avoid modifying the original graph
|
) # Create a copy to avoid modifying the original graph
|
||||||
else:
|
else:
|
||||||
# Find nodes with matching node id (partial match)
|
# Find nodes with matching node id based on search_mode
|
||||||
nodes_to_explore = []
|
nodes_to_explore = []
|
||||||
for n, attr in graph.nodes(data=True):
|
for n, attr in graph.nodes(data=True):
|
||||||
if node_label in str(n): # Use partial matching
|
node_str = str(n)
|
||||||
|
if not inclusive:
|
||||||
|
if node_label == node_str: # Use exact matching
|
||||||
|
nodes_to_explore.append(n)
|
||||||
|
else: # inclusive mode
|
||||||
|
if node_label in node_str: # Use partial matching
|
||||||
nodes_to_explore.append(n)
|
nodes_to_explore.append(n)
|
||||||
|
|
||||||
if not nodes_to_explore:
|
if not nodes_to_explore:
|
||||||
@@ -277,26 +293,37 @@ class NetworkXStorage(BaseGraphStorage):
|
|||||||
for start_node in nodes_to_explore:
|
for start_node in nodes_to_explore:
|
||||||
node_subgraph = nx.ego_graph(graph, start_node, radius=max_depth)
|
node_subgraph = nx.ego_graph(graph, start_node, radius=max_depth)
|
||||||
combined_subgraph = nx.compose(combined_subgraph, node_subgraph)
|
combined_subgraph = nx.compose(combined_subgraph, node_subgraph)
|
||||||
|
|
||||||
|
# Get start nodes and direct connected nodes
|
||||||
|
if nodes_to_explore:
|
||||||
|
start_nodes = set(nodes_to_explore)
|
||||||
|
# Get nodes directly connected to all start nodes
|
||||||
|
for start_node in start_nodes:
|
||||||
|
direct_connected_nodes.update(
|
||||||
|
combined_subgraph.neighbors(start_node)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove start nodes from directly connected nodes (avoid duplicates)
|
||||||
|
direct_connected_nodes -= start_nodes
|
||||||
|
|
||||||
subgraph = combined_subgraph
|
subgraph = combined_subgraph
|
||||||
|
|
||||||
|
# Filter nodes based on min_degree, but keep start nodes and direct connected nodes
|
||||||
|
if min_degree > 0:
|
||||||
|
nodes_to_keep = [
|
||||||
|
node
|
||||||
|
for node, degree in subgraph.degree()
|
||||||
|
if node in start_nodes
|
||||||
|
or node in direct_connected_nodes
|
||||||
|
or degree >= min_degree
|
||||||
|
]
|
||||||
|
subgraph = subgraph.subgraph(nodes_to_keep)
|
||||||
|
|
||||||
# Check if number of nodes exceeds max_graph_nodes
|
# Check if number of nodes exceeds max_graph_nodes
|
||||||
if len(subgraph.nodes()) > MAX_GRAPH_NODES:
|
if len(subgraph.nodes()) > MAX_GRAPH_NODES:
|
||||||
origin_nodes = len(subgraph.nodes())
|
origin_nodes = len(subgraph.nodes())
|
||||||
|
|
||||||
node_degrees = dict(subgraph.degree())
|
node_degrees = dict(subgraph.degree())
|
||||||
|
|
||||||
start_nodes = set()
|
|
||||||
direct_connected_nodes = set()
|
|
||||||
|
|
||||||
if node_label != "*" and nodes_to_explore:
|
|
||||||
start_nodes = set(nodes_to_explore)
|
|
||||||
# Get nodes directly connected to all start nodes
|
|
||||||
for start_node in start_nodes:
|
|
||||||
direct_connected_nodes.update(subgraph.neighbors(start_node))
|
|
||||||
|
|
||||||
# Remove start nodes from directly connected nodes (avoid duplicates)
|
|
||||||
direct_connected_nodes -= start_nodes
|
|
||||||
|
|
||||||
def priority_key(node_item):
|
def priority_key(node_item):
|
||||||
node, degree = node_item
|
node, degree = node_item
|
||||||
# Priority order: start(2) > directly connected(1) > other nodes(0)
|
# Priority order: start(2) > directly connected(1) > other nodes(0)
|
||||||
@@ -356,7 +383,7 @@ class NetworkXStorage(BaseGraphStorage):
|
|||||||
result.edges.append(
|
result.edges.append(
|
||||||
KnowledgeGraphEdge(
|
KnowledgeGraphEdge(
|
||||||
id=edge_id,
|
id=edge_id,
|
||||||
type="RELATED",
|
type="DIRECTED",
|
||||||
source=str(source),
|
source=str(source),
|
||||||
target=str(target),
|
target=str(target),
|
||||||
properties=edge_data,
|
properties=edge_data,
|
||||||
|
@@ -504,11 +504,39 @@ class LightRAG:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
async def get_knowledge_graph(
|
async def get_knowledge_graph(
|
||||||
self, node_label: str, max_depth: int
|
self,
|
||||||
|
node_label: str,
|
||||||
|
max_depth: int = 3,
|
||||||
|
min_degree: int = 0,
|
||||||
|
inclusive: bool = False,
|
||||||
) -> KnowledgeGraph:
|
) -> KnowledgeGraph:
|
||||||
return await self.chunk_entity_relation_graph.get_knowledge_graph(
|
"""Get knowledge graph for a given label
|
||||||
node_label=node_label, max_depth=max_depth
|
|
||||||
)
|
Args:
|
||||||
|
node_label (str): Label to get knowledge graph for
|
||||||
|
max_depth (int): Maximum depth of graph
|
||||||
|
min_degree (int, optional): Minimum degree of nodes to include. Defaults to 0.
|
||||||
|
inclusive (bool, optional): Whether to use inclusive search mode. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
KnowledgeGraph: Knowledge graph containing nodes and edges
|
||||||
|
"""
|
||||||
|
# get params supported by get_knowledge_graph of specified storage
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
storage_params = inspect.signature(
|
||||||
|
self.chunk_entity_relation_graph.get_knowledge_graph
|
||||||
|
).parameters
|
||||||
|
|
||||||
|
kwargs = {"node_label": node_label, "max_depth": max_depth}
|
||||||
|
|
||||||
|
if "min_degree" in storage_params and min_degree > 0:
|
||||||
|
kwargs["min_degree"] = min_degree
|
||||||
|
|
||||||
|
if "inclusive" in storage_params:
|
||||||
|
kwargs["inclusive"] = inclusive
|
||||||
|
|
||||||
|
return await self.chunk_entity_relation_graph.get_knowledge_graph(**kwargs)
|
||||||
|
|
||||||
def _get_storage_class(self, storage_name: str) -> Callable[..., Any]:
|
def _get_storage_class(self, storage_name: str) -> Callable[..., Any]:
|
||||||
import_path = STORAGES[storage_name]
|
import_path = STORAGES[storage_name]
|
||||||
|
@@ -161,8 +161,12 @@ axiosInstance.interceptors.response.use(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// API methods
|
// API methods
|
||||||
export const queryGraphs = async (label: string, maxDepth: number): Promise<LightragGraphType> => {
|
export const queryGraphs = async (
|
||||||
const response = await axiosInstance.get(`/graphs?label=${label}&max_depth=${maxDepth}`)
|
label: string,
|
||||||
|
maxDepth: number,
|
||||||
|
minDegree: number
|
||||||
|
): Promise<LightragGraphType> => {
|
||||||
|
const response = await axiosInstance.get(`/graphs?label=${encodeURIComponent(label)}&max_depth=${maxDepth}&min_degree=${minDegree}`)
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,18 +40,21 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
|
|||||||
const focusedEdge = useGraphStore.use.focusedEdge()
|
const focusedEdge = useGraphStore.use.focusedEdge()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When component mount
|
* When component mount or maxIterations changes
|
||||||
* => load the graph
|
* => load the graph and apply layout
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Create & load the graph
|
// Create & load the graph
|
||||||
const graph = lightrageGraph()
|
const graph = lightrageGraph()
|
||||||
loadGraph(graph)
|
loadGraph(graph)
|
||||||
if (!(graph as any).__force_applied) {
|
|
||||||
assignLayout()
|
assignLayout()
|
||||||
Object.assign(graph, { __force_applied: true })
|
}, [assignLayout, loadGraph, lightrageGraph, maxIterations])
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When component mount
|
||||||
|
* => register events
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
|
const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
|
||||||
useGraphStore.getState()
|
useGraphStore.getState()
|
||||||
|
|
||||||
@@ -87,7 +90,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
|
|||||||
},
|
},
|
||||||
clickStage: () => clearSelection()
|
clickStage: () => clearSelection()
|
||||||
})
|
})
|
||||||
}, [assignLayout, loadGraph, registerEvents, lightrageGraph])
|
}, [registerEvents])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When component mount or hovered node change
|
* When component mount or hovered node change
|
||||||
|
@@ -90,9 +90,12 @@ const LabeledNumberInput = ({
|
|||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={currentValue || ''}
|
type="number"
|
||||||
|
value={currentValue === null ? '' : currentValue}
|
||||||
onChange={onValueChange}
|
onChange={onValueChange}
|
||||||
className="h-6 w-full min-w-0"
|
className="h-6 w-full min-w-0 pr-1"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
@@ -119,6 +122,7 @@ export default function Settings() {
|
|||||||
const enableHideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
|
const enableHideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
|
||||||
const showEdgeLabel = useSettingsStore.use.showEdgeLabel()
|
const showEdgeLabel = useSettingsStore.use.showEdgeLabel()
|
||||||
const graphQueryMaxDepth = useSettingsStore.use.graphQueryMaxDepth()
|
const graphQueryMaxDepth = useSettingsStore.use.graphQueryMaxDepth()
|
||||||
|
const graphMinDegree = useSettingsStore.use.graphMinDegree()
|
||||||
const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
|
const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
|
||||||
|
|
||||||
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
||||||
@@ -177,6 +181,11 @@ export default function Settings() {
|
|||||||
useSettingsStore.setState({ graphQueryMaxDepth: depth })
|
useSettingsStore.setState({ graphQueryMaxDepth: depth })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const setGraphMinDegree = useCallback((degree: number) => {
|
||||||
|
if (degree < 0) return
|
||||||
|
useSettingsStore.setState({ graphMinDegree: degree })
|
||||||
|
}, [])
|
||||||
|
|
||||||
const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
|
const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
|
||||||
if (iterations < 1) return
|
if (iterations < 1) return
|
||||||
useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
|
useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
|
||||||
@@ -266,6 +275,12 @@ export default function Settings() {
|
|||||||
value={graphQueryMaxDepth}
|
value={graphQueryMaxDepth}
|
||||||
onEditFinished={setGraphQueryMaxDepth}
|
onEditFinished={setGraphQueryMaxDepth}
|
||||||
/>
|
/>
|
||||||
|
<LabeledNumberInput
|
||||||
|
label="Minimum Degree"
|
||||||
|
min={0}
|
||||||
|
value={graphMinDegree}
|
||||||
|
onEditFinished={setGraphMinDegree}
|
||||||
|
/>
|
||||||
<LabeledNumberInput
|
<LabeledNumberInput
|
||||||
label="Max Layout Iterations"
|
label="Max Layout Iterations"
|
||||||
min={1}
|
min={1}
|
||||||
|
@@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
'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',
|
'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',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@@ -50,11 +50,11 @@ export type NodeType = {
|
|||||||
}
|
}
|
||||||
export type EdgeType = { label: string }
|
export type EdgeType = { label: string }
|
||||||
|
|
||||||
const fetchGraph = async (label: string, maxDepth: number) => {
|
const fetchGraph = async (label: string, maxDepth: number, minDegree: number) => {
|
||||||
let rawData: any = null
|
let rawData: any = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rawData = await queryGraphs(label, maxDepth)
|
rawData = await queryGraphs(label, maxDepth, minDegree)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!')
|
useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!')
|
||||||
return null
|
return null
|
||||||
@@ -161,13 +161,14 @@ const createSigmaGraph = (rawGraph: RawGraph | null) => {
|
|||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastQueryLabel = { label: '', maxQueryDepth: 0 }
|
const lastQueryLabel = { label: '', maxQueryDepth: 0, minDegree: 0 }
|
||||||
|
|
||||||
const useLightrangeGraph = () => {
|
const useLightrangeGraph = () => {
|
||||||
const queryLabel = useSettingsStore.use.queryLabel()
|
const queryLabel = useSettingsStore.use.queryLabel()
|
||||||
const rawGraph = useGraphStore.use.rawGraph()
|
const rawGraph = useGraphStore.use.rawGraph()
|
||||||
const sigmaGraph = useGraphStore.use.sigmaGraph()
|
const sigmaGraph = useGraphStore.use.sigmaGraph()
|
||||||
const maxQueryDepth = useSettingsStore.use.graphQueryMaxDepth()
|
const maxQueryDepth = useSettingsStore.use.graphQueryMaxDepth()
|
||||||
|
const minDegree = useSettingsStore.use.graphMinDegree()
|
||||||
|
|
||||||
const getNode = useCallback(
|
const getNode = useCallback(
|
||||||
(nodeId: string) => {
|
(nodeId: string) => {
|
||||||
@@ -185,13 +186,16 @@ const useLightrangeGraph = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryLabel) {
|
if (queryLabel) {
|
||||||
if (lastQueryLabel.label !== queryLabel || lastQueryLabel.maxQueryDepth !== maxQueryDepth) {
|
if (lastQueryLabel.label !== queryLabel ||
|
||||||
|
lastQueryLabel.maxQueryDepth !== maxQueryDepth ||
|
||||||
|
lastQueryLabel.minDegree !== minDegree) {
|
||||||
lastQueryLabel.label = queryLabel
|
lastQueryLabel.label = queryLabel
|
||||||
lastQueryLabel.maxQueryDepth = maxQueryDepth
|
lastQueryLabel.maxQueryDepth = maxQueryDepth
|
||||||
|
lastQueryLabel.minDegree = minDegree
|
||||||
|
|
||||||
const state = useGraphStore.getState()
|
const state = useGraphStore.getState()
|
||||||
state.reset()
|
state.reset()
|
||||||
fetchGraph(queryLabel, maxQueryDepth).then((data) => {
|
fetchGraph(queryLabel, maxQueryDepth, minDegree).then((data) => {
|
||||||
// console.debug('Query label: ' + queryLabel)
|
// console.debug('Query label: ' + queryLabel)
|
||||||
state.setSigmaGraph(createSigmaGraph(data))
|
state.setSigmaGraph(createSigmaGraph(data))
|
||||||
data?.buildDynamicMap()
|
data?.buildDynamicMap()
|
||||||
@@ -203,7 +207,7 @@ const useLightrangeGraph = () => {
|
|||||||
state.reset()
|
state.reset()
|
||||||
state.setSigmaGraph(new DirectedGraph())
|
state.setSigmaGraph(new DirectedGraph())
|
||||||
}
|
}
|
||||||
}, [queryLabel, maxQueryDepth])
|
}, [queryLabel, maxQueryDepth, minDegree])
|
||||||
|
|
||||||
const lightrageGraph = useCallback(() => {
|
const lightrageGraph = useCallback(() => {
|
||||||
if (sigmaGraph) {
|
if (sigmaGraph) {
|
||||||
|
@@ -22,6 +22,9 @@ interface SettingsState {
|
|||||||
graphQueryMaxDepth: number
|
graphQueryMaxDepth: number
|
||||||
setGraphQueryMaxDepth: (depth: number) => void
|
setGraphQueryMaxDepth: (depth: number) => void
|
||||||
|
|
||||||
|
graphMinDegree: number
|
||||||
|
setGraphMinDegree: (degree: number) => void
|
||||||
|
|
||||||
graphLayoutMaxIterations: number
|
graphLayoutMaxIterations: number
|
||||||
setGraphLayoutMaxIterations: (iterations: number) => void
|
setGraphLayoutMaxIterations: (iterations: number) => void
|
||||||
|
|
||||||
@@ -66,6 +69,7 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|||||||
enableEdgeEvents: false,
|
enableEdgeEvents: false,
|
||||||
|
|
||||||
graphQueryMaxDepth: 3,
|
graphQueryMaxDepth: 3,
|
||||||
|
graphMinDegree: 0,
|
||||||
graphLayoutMaxIterations: 10,
|
graphLayoutMaxIterations: 10,
|
||||||
|
|
||||||
queryLabel: defaultQueryLabel,
|
queryLabel: defaultQueryLabel,
|
||||||
@@ -107,6 +111,8 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|||||||
|
|
||||||
setGraphQueryMaxDepth: (depth: number) => set({ graphQueryMaxDepth: depth }),
|
setGraphQueryMaxDepth: (depth: number) => set({ graphQueryMaxDepth: depth }),
|
||||||
|
|
||||||
|
setGraphMinDegree: (degree: number) => set({ graphMinDegree: degree }),
|
||||||
|
|
||||||
setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
|
setEnableHealthCheck: (enable: boolean) => set({ enableHealthCheck: enable }),
|
||||||
|
|
||||||
setApiKey: (apiKey: string | null) => set({ apiKey }),
|
setApiKey: (apiKey: string | null) => set({ apiKey }),
|
||||||
|
10
lightrag_webui/src/vite-env.d.ts
vendored
10
lightrag_webui/src/vite-env.d.ts
vendored
@@ -1 +1,11 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_PROXY: string
|
||||||
|
readonly VITE_API_ENDPOINTS: string
|
||||||
|
readonly VITE_BACKEND_URL: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
|
@@ -26,5 +26,5 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,21 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
base: './',
|
base: './',
|
||||||
build: {
|
build: {
|
||||||
outDir: path.resolve(__dirname, '../lightrag/api/webui')
|
outDir: path.resolve(__dirname, '../lightrag/api/webui'),
|
||||||
|
emptyOutDir: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: import.meta.env.VITE_API_PROXY === 'true' && import.meta.env.VITE_API_ENDPOINTS ?
|
||||||
|
Object.fromEntries(
|
||||||
|
import.meta.env.VITE_API_ENDPOINTS.split(',').map(endpoint => [
|
||||||
|
endpoint,
|
||||||
|
{
|
||||||
|
target: import.meta.env.VITE_BACKEND_URL || 'http://localhost:9621',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: endpoint === '/api' ?
|
||||||
|
(path) => path.replace(/^\/api/, '') : undefined
|
||||||
|
}
|
||||||
|
])
|
||||||
|
) : {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user